From 5bde81a5f9c1e4de14f950637a2f504604447e17 Mon Sep 17 00:00:00 2001 From: Yucheng Zhu Date: Wed, 26 Aug 2026 17:13:26 -0700 Subject: [PATCH] fix(proxy): report a zero cost split only under a zero cost total The component headers were filled from call-type membership alone, while the total they sit beside keeps its real value when the read priced normally, so a breakdown that had not landed by the time headers were built could advertise a real total next to an all-zero split. The split is now reported as zero only when the total agrees with it, and is otherwise left absent. --- litellm/proxy/common_request_processing.py | 24 ++++++++++++++++--- .../proxy/test_common_request_processing.py | 21 ++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 0699f28cf75..1ab5a2a2bc6 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -1308,21 +1308,37 @@ _ZERO_COST_BREAKDOWN: Final = CostBreakdownHeaderValues( read or management route still finds the whole family rather than a partially populated one.""" +def _totals_to_zero(response_cost: float | str | None) -> bool: + """Whether the total these headers carry is zero, counting a total no route ever priced as one. + + A component split is only reported as zero alongside a total that agrees with it, so a read + that did price normally never advertises a real total beside an all-zero split. + """ + if response_cost is None or response_cost == "": + return True + try: + return float(response_cost) == 0.0 + except (TypeError, ValueError): + return False + + def _get_cost_breakdown_from_logging_obj( litellm_logging_obj: LiteLLMLoggingObj | None, + response_cost: float | str | None = None, ) -> CostBreakdownHeaderValues: """Extract discount, margin, and per-component cost information from logging object's cost breakdown. A non-inference call that priced at zero never records a breakdown, so its components are reported as zero here. Any such call that did price normally (retrieving a background response, - and the cost poller's read of one) has a stored breakdown and takes the branch below instead. + and the cost poller's read of one) reports the breakdown it stored, or nothing at all when the + breakdown has not landed yet. """ if not litellm_logging_obj or not hasattr(litellm_logging_obj, "cost_breakdown"): return CostBreakdownHeaderValues() cost_breakdown: Final = litellm_logging_obj.cost_breakdown if not cost_breakdown: - if litellm_logging_obj.call_type in NON_INFERENCE_CALL_TYPES: + if litellm_logging_obj.call_type in NON_INFERENCE_CALL_TYPES and _totals_to_zero(response_cost): return _ZERO_COST_BREAKDOWN return CostBreakdownHeaderValues() @@ -1467,7 +1483,9 @@ class ProxyBaseLLMRequestProcessing: exclude_values: Final = {"", None, "None"} hidden_params = hidden_params or {} - cost_breakdown: Final = _get_cost_breakdown_from_logging_obj(litellm_logging_obj=litellm_logging_obj) + cost_breakdown: Final = _get_cost_breakdown_from_logging_obj( + litellm_logging_obj=litellm_logging_obj, response_cost=response_cost + ) # Calculate updated spend for header (include current response_cost) current_spend: Final = user_api_key_dict.spend or 0.0 diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index c42c4d29ed7..8b909adcda2 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -5116,6 +5116,27 @@ class TestCostHeadersForCallsPricedAtZero: assert breakdown == CostBreakdownHeaderValues() + def test_cost_breakdown_never_zeroes_the_split_under_a_real_total(self): + """Reading a background response prices normally, so a breakdown that has not landed by the + time headers are built is reported as absent rather than as a zero split contradicting the + real total alongside it.""" + breakdown = _get_cost_breakdown_from_logging_obj( + litellm_logging_obj=self._logging_obj(call_type="aget_responses"), + response_cost=1.96e-05, + ) + + assert breakdown == CostBreakdownHeaderValues() + + def test_cost_breakdown_reports_zero_components_under_a_zero_total(self): + breakdown = _get_cost_breakdown_from_logging_obj( + litellm_logging_obj=self._logging_obj(call_type="aget_responses"), + response_cost=0.0, + ) + + assert breakdown.original_cost == 0.0 + assert breakdown.input_cost == 0.0 + assert breakdown.output_cost == 0.0 + class TestPreCallWithFallbacksOnLocalRateLimit: