fix(messages): price a native /v1/messages body as served unless its provider reports its own cost (#49356)

* fix(messages): price a native /v1/messages body as served unless its provider reports its own cost

* fix(messages): bill the requested speed when a native body as served reports none

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Kerry Lu 2026-10-31 09:52:39 -07:00 • committed by GitHub
parent 7b4bdde96e
commit bfef2e1e56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 92 additions and 3 deletions

View file

@ -1074,9 +1074,14 @@ def billed_usage_cost(response: object, custom_llm_provider: str | None) -> floa
_MESSAGES_REPORTED_COST_PROVIDERS: Final = frozenset({"opencode_go"})
def reports_native_messages_cost(custom_llm_provider: object) -> bool:
"""Whether a Messages upstream is one whose own reported cost litellm bills from instead of the price map."""
return isinstance(custom_llm_provider, str) and custom_llm_provider in _MESSAGES_REPORTED_COST_PROVIDERS
def native_messages_reported_cost(usage: object, custom_llm_provider: object) -> float | None:
"""Only a Messages upstream known to price its own responses is trusted, every other one keeps the price map."""
if not isinstance(custom_llm_provider, str) or custom_llm_provider not in _MESSAGES_REPORTED_COST_PROVIDERS:
if not isinstance(custom_llm_provider, str) or not reports_native_messages_cost(custom_llm_provider):
return None
return provider_reported_usage_cost(usage, custom_llm_provider)

View file

@ -86,6 +86,7 @@ from litellm.litellm_core_utils.core_helpers import (
proxy_stamped_kubernetes_pod_routing,
proxy_stamped_used_client_oauth_token,
reconstruct_model_name,
reports_native_messages_cost,
set_response_cost_in_hidden_params,
single_log_line,
)
@ -5188,16 +5189,34 @@ class Logging(LiteLLMLoggingBaseClass):
def billable_result(self, result: object) -> object:
"""The response the cost calculator prices: a /v1/messages body served through the chat
bridge is replaced by the ModelResponse its spend row is built from, anything else is
bridge is replaced by the ModelResponse its spend row is built from, a native one is only
converted when its provider reports the cost the bill is read from, anything else is
priced as is."""
served: Final[object] = result
call_type: Final[object] = getattr(self, "call_type", None)
if not isinstance(result, dict):
return served
if self.bridged_model_response is None and call_type != CallTypes.anthropic_messages.value:
if self.bridged_model_response is not None:
return self._anthropic_messages_logged_response(result=served)
if call_type != CallTypes.anthropic_messages.value:
return served
if not reports_native_messages_cost(self.model_call_details.get("custom_llm_provider")):
return self._served_with_requested_speed(result=served)
return self._anthropic_messages_logged_response(result=served)
def _served_with_requested_speed(self, result: object) -> object:
"""A native /v1/messages body priced as served bills the speed the client asked for when its
usage block reports none, the same speed its spend row is priced at."""
body: Final = _SERVED_ANTHROPIC_BODY.validate_python(result)
usage: Final = body.get("usage")
requested: Final = _LITELLM_PARAMS.validate_python(getattr(self, "optional_params", None) or {}).get("speed")
if not isinstance(requested, str) or not isinstance(usage, dict):
return result
reported: Final = _SERVED_ANTHROPIC_BODY.validate_python(usage)
if isinstance(reported.get("speed"), str):
return result
return {**body, "usage": {**reported, "speed": requested}}
def _anthropic_messages_logged_response(self, result: object) -> ModelResponse:
"""
The ModelResponse a /v1/messages spend_logs row is built from.

View file

@ -14231,6 +14231,71 @@ def test_native_messages_body_carries_the_reported_list_cost_into_the_priced_res
assert headers.get("llm_provider-x-litellm-response-cost") == expected_header
_NATIVE_PRICED_MESSAGE: Final = {
"id": "msg_native",
"type": "message",
"role": "assistant",
"model": "claude-haiku-4-5",
"content": [{"type": "text", "text": "answer"}],
"stop_reason": "end_turn",
"stop_sequence": None,
"usage": {"input_tokens": 1200, "output_tokens": 300},
}
def _native_anthropic_messages_logging_obj() -> LitellmLogging:
logging_obj: Final = _anthropic_messages_logging_obj()
logging_obj.stream = False
logging_obj.optional_params = {}
logging_obj.model = "claude-haiku-4-5"
logging_obj.model_call_details["custom_llm_provider"] = "anthropic"
return logging_obj
@pytest.mark.parametrize(
"served_body",
(
{**_NATIVE_PRICED_MESSAGE, "content": None},
{key: value for key, value in _NATIVE_PRICED_MESSAGE.items() if key != "content"},
),
ids=("content-null", "content-missing"),
)
def test_a_native_messages_body_without_content_is_priced_from_its_usage(served_body: dict[str, object]) -> None:
"""Regression: #48254 converted every native /v1/messages body before pricing it, and the conversion
raised on a 200 whose ``content`` was null or missing, turning the served 200 into a 500."""
with_content: Final = _native_anthropic_messages_logging_obj().response_cost_calculator(
result=_NATIVE_PRICED_MESSAGE
)
assert with_content is not None and with_content > 0
without_content: Final = _native_anthropic_messages_logging_obj().response_cost_calculator(result=served_body)
assert without_content == with_content
def _fast_mode_native_logging_obj(requested_speed: str | None) -> LitellmLogging:
logging_obj: Final = _native_anthropic_messages_logging_obj()
logging_obj.model = "claude-opus-5"
logging_obj.model_call_details["model"] = "claude-opus-5"
logging_obj.optional_params = {"speed": requested_speed} if requested_speed else {}
return logging_obj
def test_a_native_messages_body_without_a_reported_speed_bills_the_requested_speed() -> None:
"""A fast-mode request whose upstream usage block omits ``speed`` is still billed at the fast rate,
the same rate the spend row applies from the request's ``speed``."""
body: Final = {**_NATIVE_PRICED_MESSAGE, "model": "claude-opus-5"}
standard: Final = _fast_mode_native_logging_obj(None).response_cost_calculator(result=body)
reported_fast: Final = _fast_mode_native_logging_obj(None).response_cost_calculator(
result={**body, "usage": {**_NATIVE_PRICED_MESSAGE["usage"], "speed": "fast"}}
)
assert standard is not None and reported_fast is not None and reported_fast > standard
requested_fast: Final = _fast_mode_native_logging_obj("fast").response_cost_calculator(result=body)
assert requested_fast == reported_fast
def test_record_native_bedrock_guardrail_bills_when_the_metadata_is_none() -> None:
import datetime