Source code for aeat.adapters.outbound.llm._pricing

"""Provider pricing helpers for cost estimation.

Holds the public per-million-token pricing table for each supported
:class:`adapters.outbound.llm.LLMProvider` and exposes
:func:`estimate_cost_usd` so :class:`adapters.outbound.llm.LLMClient`
can stamp every :class:`adapters.outbound.llm.LLMResponse` with a
deterministic USD cost estimate. The local provider always reports zero.
"""

from __future__ import annotations

from decimal import ROUND_HALF_UP, Decimal

from ._models import LLMProvider

_PRICING_PER_MILLION: tuple[tuple[LLMProvider, str, Decimal, Decimal], ...] = (
    (LLMProvider.ANTHROPIC, "claude-opus-4-6", Decimal("5.00"), Decimal("25.00")),
    (LLMProvider.ANTHROPIC, "claude-sonnet-4-6", Decimal("3.00"), Decimal("15.00")),
    (LLMProvider.ANTHROPIC, "claude-opus-4-5", Decimal("5.00"), Decimal("25.00")),
    (LLMProvider.ANTHROPIC, "claude-sonnet-4-5", Decimal("3.00"), Decimal("15.00")),
    (LLMProvider.OPENAI, "gpt-4.1", Decimal("2.00"), Decimal("8.00")),
    (LLMProvider.OPENAI, "gpt-4o", Decimal("2.50"), Decimal("10.00")),
    (LLMProvider.GEMINI, "gemini-2.5-pro", Decimal("1.25"), Decimal("10.00")),
    (LLMProvider.GEMINI, "gemini-2.5-flash", Decimal("0.30"), Decimal("2.50")),
)
_ZERO = Decimal("0")
_TOKEN_SCALE = Decimal("1000000")
_QUANTIZE = Decimal("0.000001")


[docs] def estimate_cost_usd(provider: LLMProvider, model: str, input_tokens: int, output_tokens: int) -> Decimal: """Estimate request cost in USD from known public pricing. Looks up the first pricing entry whose ``model`` field is a prefix of the lowercased ``model`` argument so model variants (versioned suffixes, dated minor releases) inherit their family's rates. Unknown ``provider`` / ``model`` combinations evaluate to zero rather than raising — the caller's downstream usage record always carries a well-typed :class:`decimal.Decimal`. Args: provider: The :class:`adapters.outbound.llm.LLMProvider` that produced the response. model: Provider-resolved model identifier (e.g. ``"claude-sonnet-4-6-20260101"``). input_tokens: Prompt-side token count. output_tokens: Completion-side token count. Returns: Estimated cost in USD, quantised to 6 decimal places using banker-rounded :data:`decimal.ROUND_HALF_UP`. Returns :data:`decimal.Decimal('0')` for the local provider and unknown models. """ if provider is LLMProvider.LOCAL: return _ZERO normalized_model = model.lower() for known_provider, prefix, input_rate, output_rate in _PRICING_PER_MILLION: if provider is known_provider and normalized_model.startswith(prefix): input_cost = (Decimal(input_tokens) / _TOKEN_SCALE) * input_rate output_cost = (Decimal(output_tokens) / _TOKEN_SCALE) * output_rate return (input_cost + output_cost).quantize(_QUANTIZE, rounding=ROUND_HALF_UP) return _ZERO