mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(spend_tracking): derive failure call_type from the requested route
An upstream failure never reaches the SDK, so nothing stamps call_type on the request data the failure hook logs from; use the route the caller hit, falling back to the request's Logging object for routes outside the map.
This commit is contained in:
parent
fe42ca68a2
commit
4119cd67a1
2 changed files with 46 additions and 15 deletions
|
|
@ -6,6 +6,9 @@ from typing import Any, List, Optional, Union, cast
|
|||
import litellm
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.litellm_core_utils.api_route_to_call_types import (
|
||||
get_primary_call_type_for_route,
|
||||
)
|
||||
from litellm.litellm_core_utils.core_helpers import (
|
||||
_get_parent_otel_span_from_kwargs,
|
||||
get_litellm_metadata_from_kwargs,
|
||||
|
|
@ -148,6 +151,12 @@ class _ProxyDBLogger(CustomLogger):
|
|||
request_data["litellm_params"]["proxy_server_request"] = (
|
||||
request_data.get("proxy_server_request") or existing_litellm_params.get("proxy_server_request") or {}
|
||||
)
|
||||
# Failures that never reached the SDK carry no call_type, so the row lands
|
||||
# without one and can't be attributed to the route the caller used.
|
||||
route_call_type = get_primary_call_type_for_route(request_route)
|
||||
if not request_data.get("call_type") and route_call_type is not None:
|
||||
request_data["call_type"] = route_call_type.value
|
||||
|
||||
request_data[metadata_key] = spend_metadata
|
||||
request_data["litellm_params"]["metadata"] = spend_metadata
|
||||
if "litellm_metadata" in existing_litellm_params:
|
||||
|
|
@ -174,8 +183,6 @@ class _ProxyDBLogger(CustomLogger):
|
|||
)
|
||||
if request_data.get("litellm_trace_id") is None:
|
||||
request_data["litellm_trace_id"] = getattr(_litellm_logging_obj, "litellm_trace_id", None)
|
||||
# Without this the spend log for a failure has a blank call_type, so the
|
||||
# row can't be attributed to the route the caller actually used.
|
||||
if not request_data.get("call_type"):
|
||||
logged_call_type = _known_call_type(getattr(_litellm_logging_obj, "call_type", None))
|
||||
if logged_call_type is not None:
|
||||
|
|
|
|||
|
|
@ -1401,27 +1401,51 @@ async def test_failure_spend_log_drops_caller_supplied_key_identity():
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"logged_call_type, expected_call_type",
|
||||
"request_route, expected_call_type",
|
||||
[
|
||||
("aresponses", "aresponses"),
|
||||
("anthropic_messages", "anthropic_messages"),
|
||||
("/v1/responses", ""),
|
||||
("/v1/responses", "aresponses"),
|
||||
("/v1/messages", "anthropic_messages"),
|
||||
("/v1/chat/completions", "acompletion"),
|
||||
],
|
||||
)
|
||||
async def test_failure_spend_log_call_type_comes_from_logging_object(
|
||||
logged_call_type, expected_call_type
|
||||
):
|
||||
"""Regression for #35068: failure rows were written with a blank call_type, so a
|
||||
failed Responses request was indistinguishable from a failed chat completion. Route
|
||||
strings left on the Logging object by proxy-only errors must not leak into the
|
||||
column."""
|
||||
async def test_failure_spend_log_call_type_comes_from_the_requested_route(request_route, expected_call_type):
|
||||
"""Regression for #35068: an upstream failure never reaches the SDK, so nothing sets
|
||||
call_type and the row was written blank; a failed Responses request was then
|
||||
indistinguishable from a failed chat completion."""
|
||||
payload, _ = await _failure_spend_log_payload(
|
||||
request_data={"model": "test-model", "litellm_metadata": {}},
|
||||
user_api_key_dict=UserAPIKeyAuth(
|
||||
api_key="sk-test",
|
||||
user_id="real-user",
|
||||
team_id="real-team",
|
||||
request_route=request_route,
|
||||
),
|
||||
)
|
||||
|
||||
assert payload["call_type"] == expected_call_type
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_failure_spend_log_call_type_falls_back_to_logging_object():
|
||||
"""Routes outside the route map (e.g. pass-through) still know their call type from
|
||||
the request's Logging object. Route strings left there by proxy-only errors must not
|
||||
leak into the column."""
|
||||
payload, _ = await _failure_spend_log_payload(
|
||||
request_data={
|
||||
"model": "test-model",
|
||||
"litellm_metadata": {},
|
||||
"litellm_logging_obj": _StubLoggingObj(call_type=logged_call_type),
|
||||
"litellm_logging_obj": _StubLoggingObj(call_type="aresponses"),
|
||||
},
|
||||
user_api_key_dict=UserAPIKeyAuth(api_key="sk-test", user_id="real-user", team_id="real-team"),
|
||||
)
|
||||
assert payload["call_type"] == "aresponses"
|
||||
|
||||
assert payload["call_type"] == expected_call_type
|
||||
payload, _ = await _failure_spend_log_payload(
|
||||
request_data={
|
||||
"model": "test-model",
|
||||
"litellm_metadata": {},
|
||||
"litellm_logging_obj": _StubLoggingObj(call_type="/v1/responses"),
|
||||
},
|
||||
user_api_key_dict=UserAPIKeyAuth(api_key="sk-test", user_id="real-user", team_id="real-team"),
|
||||
)
|
||||
assert payload["call_type"] == ""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue