mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(interactions): price the settled background body against its own deployment
The poll fetches the terminal interaction through its own client call, which stamps a response cost computed by a throwaway logging object holding none of the original request's deployment context: no model_info, no router model_id, no deployment litellm_params. Carrying that cost into the settlement event billed custom-priced deployments at the wrong rate, and it also satisfied the "already calculated" shortcut in _response_cost_calculator's caller, so the settlement never repriced and never built a cost breakdown. The zeros stamped by the usage-less create survived into the spend log row and the OTEL span. Dropping the imported cost before re-emitting makes the settlement price the settled body itself, against the deployment that served the create.
This commit is contained in:
parent
8b566a7f0a
commit
b460254428
2 changed files with 56 additions and 0 deletions
|
|
@ -2202,7 +2202,20 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
``in_progress`` response (no usage, so no cost was tracked); clearing
|
||||
the dedup flags lets the completed result flow through cost calculation
|
||||
and spend tracking exactly once, spanning create to completion.
|
||||
|
||||
The poll fetched this body through its own client call, which priced it
|
||||
against a throwaway logging object holding none of this request's
|
||||
deployment context: no ``model_info``, no router ``model_id``, no
|
||||
deployment ``litellm_params``. Keeping that price would bill a
|
||||
custom-priced deployment at the wrong rate, and it would also satisfy
|
||||
the "already calculated" shortcut and skip repricing here, leaving the
|
||||
cost breakdown at the zeros the usage-less create stamped and writing
|
||||
those zeros to the spend log. Dropping it makes this event price the
|
||||
settled body itself, against the deployment that served the create.
|
||||
"""
|
||||
settled_hidden_params: Final = getattr(result, "_hidden_params", None)
|
||||
if isinstance(settled_hidden_params, dict):
|
||||
settled_hidden_params.pop("response_cost", None)
|
||||
self._reset_success_emission_dedupe()
|
||||
await self.async_success_handler(result=result)
|
||||
|
||||
|
|
|
|||
|
|
@ -4644,6 +4644,49 @@ async def test_background_interaction_completion_rebills_after_in_progress_succe
|
|||
assert logging_obj.model_call_details["standard_logging_object"]["total_tokens"] == 175
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_background_interaction_completion_prices_the_settled_body_itself():
|
||||
"""
|
||||
The poll fetches the settled body through its own client call, which
|
||||
prices it against a throwaway logging object holding none of this
|
||||
request's deployment context. Adopting that price would bill a
|
||||
custom-priced deployment at the wrong rate, and it would also satisfy the
|
||||
"already calculated" shortcut and skip repricing, leaving the breakdown at
|
||||
the zeros the usage-less create stamped and writing those to the spend log.
|
||||
"""
|
||||
import datetime as dt
|
||||
|
||||
from litellm.types.interactions import InteractionsAPIResponse
|
||||
|
||||
logging_obj = _interactions_logging_obj(stream=False)
|
||||
in_progress = InteractionsAPIResponse(id="interactions/abc", model="gemini-2.5-flash", status="in_progress")
|
||||
await logging_obj.async_success_handler(
|
||||
result=in_progress,
|
||||
start_time=dt.datetime.now(),
|
||||
end_time=dt.datetime.now(),
|
||||
)
|
||||
|
||||
completed = InteractionsAPIResponse(
|
||||
id="interactions/abc",
|
||||
model="gemini-2.5-flash",
|
||||
status="completed",
|
||||
steps=[],
|
||||
usage=dict(INTERACTIONS_USAGE_BLOCK),
|
||||
)
|
||||
completed._hidden_params = {"response_cost": 99.0}
|
||||
|
||||
await logging_obj.async_log_background_interaction_completion(result=completed)
|
||||
|
||||
response_cost = logging_obj.model_call_details["response_cost"]
|
||||
assert response_cost != 99.0
|
||||
assert response_cost > 0
|
||||
|
||||
cost_breakdown = logging_obj.model_call_details["standard_logging_object"]["cost_breakdown"]
|
||||
assert cost_breakdown["total_cost"] == response_cost
|
||||
assert cost_breakdown["input_cost"] > 0
|
||||
assert cost_breakdown["output_cost"] > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_background_interaction_completion_lets_otel_emit_the_cost_span():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue