mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-05 07:37:50 +00:00
fix(cost): stamp the bill on a plain reply only where the upstream reported usage.cost
A reply whose upstream sent no usage.cost keeps its usage untouched, as on main, so SDK callers comparing the usage dict to the upstream's see no added key and logging payloads gain no additional_usage_values.cost
This commit is contained in:
parent
a20609a6f2
commit
e4b9584ea6
2 changed files with 30 additions and 2 deletions
|
|
@ -185,11 +185,12 @@ class ResponseMetadata:
|
|||
self._update_hidden_params(new_params)
|
||||
|
||||
def _stamp_billed_usage_cost(self, response_cost: object) -> None:
|
||||
"""A client reads what litellm billed from usage.cost, streamed or not, priced deployment or not."""
|
||||
"""A usage.cost the upstream reported becomes what litellm billed, so a client and the spend log agree on a
|
||||
priced deployment; a reply without one keeps its usage as the upstream sent it."""
|
||||
if not isinstance(self.result, (ModelResponse, ResponsesAPIResponse)):
|
||||
return
|
||||
usage: Final = getattr(self.result, "usage", None)
|
||||
if not isinstance(usage, (Usage, ResponseAPIUsage)):
|
||||
if not isinstance(usage, (Usage, ResponseAPIUsage)) or getattr(usage, "cost", None) is None:
|
||||
return
|
||||
stamp_billable_usage_cost(self.result, usage, response_cost)
|
||||
|
||||
|
|
|
|||
|
|
@ -10787,6 +10787,33 @@ def test_the_client_reads_what_litellm_billed_from_usage_cost_on_every_route(
|
|||
assert logging_obj.response_cost_calculator(result=response, ignore_stamped_cost=True) == pytest.approx(bill)
|
||||
|
||||
|
||||
def test_a_plain_reply_without_an_upstream_usage_cost_keeps_none_after_pricing(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Clients comparing the usage dict to the upstream's byte for byte must not find a cost litellm added."""
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost,
|
||||
_PRICED_DEPLOYMENT_ID,
|
||||
{"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": 2e-6, "output_cost_per_token": 6e-6},
|
||||
)
|
||||
response: Final = ModelResponse(
|
||||
id="chatcmpl-unreported",
|
||||
model="reported-cost-fixture",
|
||||
choices=[Choices(index=0, message=Message(role="assistant", content="hi"), finish_reason="stop")],
|
||||
usage=Usage(prompt_tokens=100, completion_tokens=20, total_tokens=120),
|
||||
)
|
||||
|
||||
update_response_metadata(
|
||||
result=response,
|
||||
logging_obj=_priced_logging(False, "completion"),
|
||||
model=_REPORTED_COST_FIXTURE_MODEL,
|
||||
kwargs={},
|
||||
start_time=datetime.datetime(2026, 1, 1),
|
||||
end_time=datetime.datetime(2026, 1, 1, 0, 0, 1),
|
||||
)
|
||||
|
||||
assert response.usage is not None and getattr(response.usage, "cost", None) is None
|
||||
assert response._hidden_params["response_cost"] == pytest.approx(100 * 2e-6 + 20 * 6e-6)
|
||||
|
||||
|
||||
def test_provider_reported_usage_cost_splits_input_and_output_by_the_token_math_ratio(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue