fix(cost): keep the provider's usage.cost behind a stamped bill and read it on every pricing pass

stamp_billed_usage_cost records the provider's own usage.cost in hidden params before writing litellm's bill over it, and completion_cost prices from that recorded figure, so a second pass (uncached_price, MCP rounds) bills the same number as the first instead of re-billing the stamped total or losing the report on an unmapped model. cost_per_token reads the reported cost per provider, so OpenCode Go's balance-hit usage.cost of 0 no longer zeroes a row.
This commit is contained in:
devin-ai-integration[bot] 2026-10-25 03:07:22 +00:00
parent e0ee962054
commit 2c0e47ba7e
3 changed files with 35 additions and 17 deletions

View file

@ -25,7 +25,7 @@ from litellm.constants import (
from litellm.litellm_core_utils.core_helpers import as_str_mapping, provider_reported_usage_cost
from litellm.litellm_core_utils.cost_discount import resolve_cost_discount
from litellm.litellm_core_utils.decisions_pricing import decisions_pricing_fields, is_decisions_call_type
from litellm.litellm_core_utils.hidden_params import HIDDEN_PARAMS_ATTR, usage_cost_stamped_by_litellm
from litellm.litellm_core_utils.hidden_params import HIDDEN_PARAMS_ATTR, provider_stated_usage_cost
from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import (
StandardBuiltInToolCostTracking,
)
@ -408,8 +408,8 @@ def _per_second_pricing_cost(
return resolved_cost_per_second * seconds, 0.0
def _billable_reported_cost(usage_object: Usage | None) -> float | None:
return _billable_header_cost(provider_reported_usage_cost(usage_object, None))
def _billable_reported_cost(usage_object: Usage | None, custom_llm_provider: str | None) -> float | None:
return _billable_header_cost(provider_reported_usage_cost(usage_object, custom_llm_provider))
def _split_reported_cost(reported_cost: float, priced: tuple[float, float] | None) -> tuple[float, float]:
@ -465,7 +465,7 @@ def cost_per_token(
Without a reported cost the token math is the bill.
"""
priced_by_caller: Final = custom_cost_per_token is not None or custom_cost_per_second is not None
reported_cost: Final = None if priced_by_caller else _billable_reported_cost(usage_object)
reported_cost: Final = None if priced_by_caller else _billable_reported_cost(usage_object, custom_llm_provider)
rated: Final = partial(
_cost_per_token_from_rates,
model=model,
@ -1560,6 +1560,16 @@ def _store_cost_breakdown_in_logging_obj(
# Don't fail the main cost calculation if breakdown storage fails
def _with_provider_stated_cost(completion_response: object, usage: Usage | None) -> Usage | None:
if usage is None:
return usage
stamped_cost: Final = getattr(usage, "cost", None)
stated_cost: Final = provider_stated_usage_cost(completion_response, stamped_cost)
if stated_cost is stamped_cost:
return usage
return usage.model_copy(update=MappingProxyType({"cost": stated_cost}))
def _without_provider_stated_cost(usage: Usage | None) -> Usage | None:
if usage is None or getattr(usage, "cost", None) is None:
return usage
@ -1702,10 +1712,12 @@ def completion_cost(
cache_creation_input_tokens: int | None = None
cache_read_input_tokens: int | None = None
audio_transcription_file_duration: float = 0.0
provider_usage_object: Final = get_usage_object(completion_response=completion_response)
provider_usage_object: Final = _with_provider_stated_cost(
completion_response, get_usage_object(completion_response=completion_response)
)
cost_per_token_usage_object: Final[Usage | None] = (
_without_provider_stated_cost(provider_usage_object)
if custom_pricing or base_model is not None or usage_cost_stamped_by_litellm(completion_response)
if custom_pricing or base_model is not None
else provider_usage_object
)
rerank_billed_units: RerankBilledUnits | None = None

View file

@ -136,15 +136,20 @@ def get_or_create_hidden_params(obj: object) -> MutableMapping[str, object]:
raise TypeError(f"unsupported hidden params storage: {type(hidden_params).__name__}")
USAGE_COST_STAMPED_BY_LITELLM: Final = "usage_cost_stamped_by_litellm"
USAGE_COST_BEFORE_LITELLM_STAMP: Final = "usage_cost_before_litellm_stamp"
def stamp_billed_usage_cost(obj: object, usage: object, cost: float) -> None:
"""usage.cost becomes what litellm billed, marked so the calculator never re-bills it as a provider's report."""
setattr(usage, "cost", cost)
set_hidden_param(obj, USAGE_COST_STAMPED_BY_LITELLM, True)
def usage_cost_stamped_by_litellm(obj: object) -> bool:
"""usage.cost becomes what litellm billed; the provider's own figure is kept so a later pricing pass bills the same."""
hidden_params: Final = get_hidden_params(obj)
return hidden_params is not None and hidden_params.get(USAGE_COST_STAMPED_BY_LITELLM) is True
if hidden_params is None or USAGE_COST_BEFORE_LITELLM_STAMP not in hidden_params:
set_hidden_param(obj, USAGE_COST_BEFORE_LITELLM_STAMP, getattr(usage, "cost", None))
setattr(usage, "cost", cost)
def provider_stated_usage_cost(obj: object, usage_cost: object) -> object:
"""What the provider itself put in usage.cost, looking past a bill litellm stamped there."""
hidden_params: Final = get_hidden_params(obj)
if hidden_params is None or USAGE_COST_BEFORE_LITELLM_STAMP not in hidden_params:
return usage_cost
return hidden_params.get(USAGE_COST_BEFORE_LITELLM_STAMP)

View file

@ -5062,7 +5062,7 @@ def test_stream_chunk_builder_prices_alias_from_openai_sdk_usage_chunk():
assert response._hidden_params["response_cost"] == pytest.approx(0.000704)
def test_stream_chunk_builder_leaves_xai_reported_cost_to_the_calculator(monkeypatch: pytest.MonkeyPatch):
def test_stream_chunk_builder_stamps_the_margin_priced_bill_over_the_xai_reported_cost(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(litellm, "cost_margin_config", {"xai": 0.5})
usage_chunk: Final = _stream_builder_text_chunk("grok-4", "")
usage_chunk.usage = Usage(prompt_tokens=5, completion_tokens=2, total_tokens=7, cost=0.42)
@ -5078,9 +5078,10 @@ def test_stream_chunk_builder_leaves_xai_reported_cost_to_the_calculator(monkeyp
)
assert response is not None
assert getattr(response.usage, "cost", None) == pytest.approx(0.42)
assert response._hidden_params.get("response_cost") is None
assert getattr(response.usage, "cost", None) == pytest.approx(0.63)
assert response._hidden_params.get("response_cost") == pytest.approx(0.63)
assert logging_obj.response_cost_calculator(result=response) == pytest.approx(0.63)
assert logging_obj.response_cost_calculator(result=response, ignore_stamped_cost=True) == pytest.approx(0.63)
def test_speech_mistral_dispatches_and_decodes_audio(respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch):