mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(logging): handle ResponseCompletedEvent in anthropic_messages spend_logs path
This commit is contained in:
parent
ef36e89638
commit
e4b6859c68
2 changed files with 111 additions and 1 deletions
|
|
@ -3424,7 +3424,9 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
else:
|
||||
return None
|
||||
|
||||
def _handle_anthropic_messages_response_logging(self, result: Any) -> ModelResponse:
|
||||
def _handle_anthropic_messages_response_logging(
|
||||
self, result: Any
|
||||
) -> Union[ModelResponse, ResponsesAPIResponse]:
|
||||
"""
|
||||
Handles logging for Anthropic messages responses.
|
||||
|
||||
|
|
@ -3444,6 +3446,18 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
elif isinstance(result, ModelResponse):
|
||||
return result
|
||||
|
||||
# Responses API bridge: streaming delivers a ResponseCompletedEvent (or its
|
||||
# incomplete/failed variants) rather than an AnthropicResponse. Extract the
|
||||
# nested ResponsesAPIResponse so downstream logging (_transform_usage_objects,
|
||||
# spend_logs) can process it without attempting AnthropicResponse.model_validate.
|
||||
if isinstance(
|
||||
result,
|
||||
(ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent),
|
||||
):
|
||||
return result.response
|
||||
elif isinstance(result, ResponsesAPIResponse):
|
||||
return result
|
||||
|
||||
httpx_response = self.model_call_details.get("httpx_response", None)
|
||||
if httpx_response and isinstance(httpx_response, httpx.Response):
|
||||
result = litellm.AnthropicConfig().transform_response(
|
||||
|
|
|
|||
|
|
@ -2872,3 +2872,99 @@ class TestFirstApiCallStartTimeSetOnce:
|
|||
assert obj.model_call_details["api_call_start_time"] > first
|
||||
assert obj.model_call_details["first_api_call_start_time"] == first
|
||||
assert user_meta == {}
|
||||
|
||||
|
||||
class TestHandleAnthropicMessagesResponseLoggingResponsesBridge:
|
||||
"""
|
||||
Regression tests for _handle_anthropic_messages_response_logging when the
|
||||
Responses API bridge is active (non-Anthropic backend behind /v1/messages).
|
||||
|
||||
Before the fix, result arrived as a ResponseCompletedEvent and
|
||||
AnthropicResponse.model_validate(result) raised a ValidationError that was
|
||||
swallowed as Non-Blocking, silently dropping the spend_logs row.
|
||||
"""
|
||||
|
||||
def _make_logging_obj(self, stream: bool) -> LitellmLogging:
|
||||
return LitellmLogging(
|
||||
model="openai/gpt-4o",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
stream=stream,
|
||||
call_type="anthropic_messages",
|
||||
start_time=time.time(),
|
||||
litellm_call_id="test-responses-bridge-123",
|
||||
function_id="test-fn",
|
||||
)
|
||||
|
||||
def _make_responses_api_response(self) -> "ResponsesAPIResponse":
|
||||
from litellm.types.llms.openai import ResponsesAPIResponse
|
||||
|
||||
return ResponsesAPIResponse(
|
||||
id="resp_abc123",
|
||||
created_at=1700000000,
|
||||
model="gpt-4o",
|
||||
output=[],
|
||||
usage={
|
||||
"input_tokens": 20,
|
||||
"output_tokens": 10,
|
||||
"total_tokens": 30,
|
||||
},
|
||||
)
|
||||
|
||||
def test_handle_anthropic_messages_response_logging_streaming_response_completed_event_returns_responses_api_response(
|
||||
self,
|
||||
):
|
||||
from litellm.types.llms.openai import (
|
||||
ResponseCompletedEvent,
|
||||
ResponsesAPIResponse,
|
||||
)
|
||||
|
||||
logging_obj = self._make_logging_obj(stream=True)
|
||||
inner_response = self._make_responses_api_response()
|
||||
completed_event = ResponseCompletedEvent(
|
||||
type="response.completed",
|
||||
response=inner_response,
|
||||
)
|
||||
|
||||
result = logging_obj._handle_anthropic_messages_response_logging(
|
||||
result=completed_event
|
||||
)
|
||||
|
||||
assert isinstance(result, ResponsesAPIResponse)
|
||||
assert result.id == "resp_abc123"
|
||||
|
||||
def test_handle_anthropic_messages_response_logging_response_incomplete_event_returns_responses_api_response(
|
||||
self,
|
||||
):
|
||||
from litellm.types.llms.openai import (
|
||||
ResponseIncompleteEvent,
|
||||
ResponsesAPIResponse,
|
||||
)
|
||||
|
||||
logging_obj = self._make_logging_obj(stream=True)
|
||||
inner_response = self._make_responses_api_response()
|
||||
incomplete_event = ResponseIncompleteEvent(
|
||||
type="response.incomplete",
|
||||
response=inner_response,
|
||||
)
|
||||
|
||||
result = logging_obj._handle_anthropic_messages_response_logging(
|
||||
result=incomplete_event
|
||||
)
|
||||
|
||||
assert isinstance(result, ResponsesAPIResponse)
|
||||
assert result.id == "resp_abc123"
|
||||
|
||||
def test_handle_anthropic_messages_response_logging_responses_api_response_passthrough(
|
||||
self,
|
||||
):
|
||||
from litellm.types.llms.openai import ResponsesAPIResponse
|
||||
|
||||
logging_obj = self._make_logging_obj(stream=False)
|
||||
responses_api_response = self._make_responses_api_response()
|
||||
|
||||
result = logging_obj._handle_anthropic_messages_response_logging(
|
||||
result=responses_api_response
|
||||
)
|
||||
|
||||
assert isinstance(result, ResponsesAPIResponse)
|
||||
assert result.id == "resp_abc123"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue