fix(cost): keep a $0 stream bill in hidden response_cost so the egress shows it
Some checks are pending
LiteLLM Rust DB / db-queries (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run

A free or fully discounted stream used to leave the upstream usage.cost on the client frame while the spend row read 0, because the builder skipped stamping a zero bill

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
kerry 2026-10-28 03:10:24 +00:00
parent e2033586a4
commit 856c22a1cd
3 changed files with 41 additions and 8 deletions

View file

@ -1415,7 +1415,7 @@ def _stamp_streaming_response_cost(response: ModelResponse, logging_obj: Optiona
if logging_obj is None:
return
computed_cost: Final = logging_obj.response_cost_calculator(result=response)
if not isinstance(computed_cost, (int, float)) or computed_cost <= 0:
if isinstance(computed_cost, bool) or not isinstance(computed_cost, (int, float)) or computed_cost < 0:
return
_set_hidden_response_cost(response, float(computed_cost))

View file

@ -885,16 +885,29 @@ def test_normalize_responses_usage_never_writes_a_cost_the_provider_did_not_repo
def test_normalize_responses_usage_stamps_the_logging_objects_bill_as_hidden_response_cost():
from litellm.responses.streaming_iterator import normalize_responses_usage
response = _responses_api_response_with_usage()
logging_obj = MagicMock()
logging_obj.response_cost_calculator.return_value = 0.00032
from litellm.litellm_core_utils.litellm_logging import Logging
response: Final = _responses_api_response_with_usage()
logging_obj: Final = Logging(
model="mantle-claude",
messages=[{"role": "user", "content": "q"}],
stream=True,
call_type="aresponses",
start_time=None,
litellm_call_id="call",
function_id="fn",
)
logging_obj.update_environment_variables(
litellm_params={"custom_llm_provider": "openai", "input_cost_per_token": 2e-6, "output_cost_per_token": 6e-6},
optional_params={},
)
logging_obj.custom_cost_per_token = {"input_cost_per_token": 2e-6, "output_cost_per_token": 6e-6}
normalize_responses_usage(response, logging_obj)
hidden: Final = get_hidden_params(response)
assert hidden is not None and hidden["response_cost"] == 0.00032
assert hidden is not None and hidden["response_cost"] == pytest.approx(20 * 2e-6 + 60 * 6e-6)
assert getattr(response.usage, "cost", None) is None, "the client field is written at the proxy egress, not here"
assert logging_obj.response_cost_calculator.call_args.kwargs["result"] is response
def _capture_dispatch(logged: list):

View file

@ -4907,6 +4907,26 @@ def test_stream_chunk_builder_bills_a_stream_without_writing_into_its_usage(monk
assert response._hidden_params["response_cost"] > 0
def test_stream_chunk_builder_keeps_a_zero_bill_as_hidden_response_cost() -> None:
logging_obj: Final = _stream_builder_logging_obj()
logging_obj.update_environment_variables(
litellm_params={"custom_llm_provider": "openai", "input_cost_per_token": 0.0, "output_cost_per_token": 0.0},
optional_params={},
)
logging_obj.custom_cost_per_token = {"input_cost_per_token": 0.0, "output_cost_per_token": 0.0}
chunks: Final = [
_stream_builder_text_chunk("gpt-4o", "Hello "),
_stream_builder_text_chunk("gpt-4o", "world.", finish_reason="stop"),
]
response: Final = litellm.stream_chunk_builder(
chunks=chunks, messages=[{"role": "user", "content": "hi"}], logging_obj=logging_obj
)
assert response is not None
assert response._hidden_params["response_cost"] == 0.0, "a free stream still tells the egress what was billed"
def test_stream_chunk_builder_stamped_cost_is_not_re_read_as_xai_reported_cost():
import time as time_module
@ -4949,7 +4969,7 @@ def test_stream_chunk_builder_stamped_cost_is_not_re_read_as_xai_reported_cost()
assert spend_row_cost == pytest.approx(usage_cost)
def test_stream_chunk_builder_skips_stamp_when_cost_is_unpriceable():
def test_stream_chunk_builder_stamps_the_zero_bill_when_cost_is_unpriceable():
import time as time_module
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
@ -4978,7 +4998,7 @@ def test_stream_chunk_builder_skips_stamp_when_cost_is_unpriceable():
assert response is not None
assert getattr(response.usage, "cost", None) is None
assert response._hidden_params.get("response_cost") is None
assert response._hidden_params.get("response_cost") == 0.0, "the spend row reads 0, so the egress must too"
def test_stream_chunk_builder_prices_alias_response_at_its_deployment_model():