Implement LLM-WP-0007: Kimi K3 default and EUR spend reporting
Add moonshotai/kimi-k3 as OpenRouter basemodel default after live smoke, USD→EUR cost conversion, append-only usage ledger, and CLI run/cost/spend week commands with token and euro reporting.
This commit is contained in:
parent
f3121c3f1f
commit
09aa1f3604
21 changed files with 1396 additions and 103 deletions
|
|
@ -5,17 +5,27 @@ from __future__ import annotations
|
|||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from llm_connect.fx import FxRate, resolve_fx_rate
|
||||
from llm_connect.rates import ModelRateRegistry
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CostEstimate:
|
||||
"""Cost estimate split by prompt and completion token spend."""
|
||||
"""Cost estimate split by prompt and completion token spend.
|
||||
|
||||
USD fields come from the rate table. EUR fields are derived via :class:`FxRate`
|
||||
when conversion is available; missing FX yields ``cost_eur=None`` with an
|
||||
explicit ``fx_source`` rather than silently treating spend as zero.
|
||||
"""
|
||||
|
||||
cost_usd: float | None
|
||||
cost_source: str
|
||||
prompt_cost_usd: float | None = None
|
||||
completion_cost_usd: float | None = None
|
||||
cost_eur: float | None = None
|
||||
prompt_cost_eur: float | None = None
|
||||
completion_cost_eur: float | None = None
|
||||
fx_source: str | None = None
|
||||
|
||||
|
||||
def estimate_cost(
|
||||
|
|
@ -24,11 +34,16 @@ def estimate_cost(
|
|||
completion_tokens: int = 0,
|
||||
*,
|
||||
registry: ModelRateRegistry | None = None,
|
||||
fx: FxRate | float | None = None,
|
||||
apply_fx: bool = True,
|
||||
) -> CostEstimate:
|
||||
"""Estimate USD cost for token counts using *registry*.
|
||||
"""Estimate USD (and optionally EUR) cost for token counts using *registry*.
|
||||
|
||||
Unknown models return ``CostEstimate(None, "unknown")`` so callers can
|
||||
record uncertainty explicitly instead of treating missing prices as zero.
|
||||
|
||||
When *apply_fx* is true (default), EUR fields are filled using *fx* or the
|
||||
resolved default FX rate. Pass ``apply_fx=False`` to skip conversion.
|
||||
"""
|
||||
prompt_count = _non_negative_int("prompt_tokens", prompt_tokens)
|
||||
completion_count = _non_negative_int("completion_tokens", completion_tokens)
|
||||
|
|
@ -39,11 +54,31 @@ def estimate_cost(
|
|||
|
||||
prompt_cost = (prompt_count / 1000.0) * rate.prompt_per_1k
|
||||
completion_cost = (completion_count / 1000.0) * rate.completion_per_1k
|
||||
cost_usd = prompt_cost + completion_cost
|
||||
|
||||
cost_eur = None
|
||||
prompt_cost_eur = None
|
||||
completion_cost_eur = None
|
||||
fx_source: str | None = None
|
||||
if apply_fx:
|
||||
resolved = resolve_fx_rate(fx)
|
||||
if resolved is None:
|
||||
fx_source = "unknown"
|
||||
else:
|
||||
fx_source = resolved.source
|
||||
cost_eur = resolved.usd_to_eur(cost_usd)
|
||||
prompt_cost_eur = resolved.usd_to_eur(prompt_cost)
|
||||
completion_cost_eur = resolved.usd_to_eur(completion_cost)
|
||||
|
||||
return CostEstimate(
|
||||
cost_usd=prompt_cost + completion_cost,
|
||||
cost_usd=cost_usd,
|
||||
cost_source=f"rate_table:{rate.model_id}",
|
||||
prompt_cost_usd=prompt_cost,
|
||||
completion_cost_usd=completion_cost,
|
||||
cost_eur=cost_eur,
|
||||
prompt_cost_eur=prompt_cost_eur,
|
||||
completion_cost_eur=completion_cost_eur,
|
||||
fx_source=fx_source,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -52,6 +87,8 @@ class CostModel:
|
|||
"""Small wrapper for callers that prefer an object over a free function."""
|
||||
|
||||
registry: ModelRateRegistry | None = None
|
||||
fx: FxRate | float | None = None
|
||||
apply_fx: bool = True
|
||||
|
||||
def estimate_cost(
|
||||
self,
|
||||
|
|
@ -59,12 +96,14 @@ class CostModel:
|
|||
prompt_tokens: int,
|
||||
completion_tokens: int = 0,
|
||||
) -> CostEstimate:
|
||||
"""Estimate cost using this model's registry."""
|
||||
"""Estimate cost using this model's registry and FX settings."""
|
||||
return estimate_cost(
|
||||
model_id,
|
||||
prompt_tokens,
|
||||
completion_tokens,
|
||||
registry=self.registry,
|
||||
fx=self.fx,
|
||||
apply_fx=self.apply_fx,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue