This commit is contained in:
安妮的心动录 2026-07-14 01:15:07 +00:00 • committed by GitHub
commit 2eae216c37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 59 additions and 1 deletions

View file

@ -2310,7 +2310,17 @@ class Logging(LiteLLMLoggingBaseClass):
isinstance(callback, CustomLogger)
and is_sync_request
and self.call_type
!= CallTypes.pass_through.value # pass-through endpoints call async_log_success_event
not in (
# both endpoints are always dispatched through the
# async wrapper, which already calls
# async_log_success_event. anthropic_messages
# (/v1/messages) sets no acompletion-style flag, so
# is_sync_request is True here and the sync pass would
# otherwise re-dispatch every CustomLogger, doubling
# OTEL spans + cost/success callbacks.
CallTypes.pass_through.value,
CallTypes.anthropic_messages.value,
)
): # custom logger class
if self.stream and complete_streaming_response is None:
callback.log_stream_event(

View file

@ -3707,3 +3707,51 @@ def test_set_cost_breakdown_stores_reasoning_cost():
cost_for_built_in_tools_cost_usd_dollar=0.0,
)
assert "reasoning_cost" not in no_reasoning.cost_breakdown
def test_success_handler_skips_sync_callbacks_for_anthropic_messages(logging_obj):
"""anthropic_messages (/v1/messages) is always dispatched through the async
wrapper, so the sync success_handler pass must not re-dispatch CustomLogger
callbacks -- the async handler is the canonical path.
Regression for the duplicate ``litellm_request`` OTEL spans + double
success/cost callbacks on non-streaming /v1/messages. Unlike acompletion,
anthropic_messages sets no async flag in litellm_params, so
_is_sync_litellm_request is True and the sync pass would otherwise fire.
"""
from litellm.integrations.custom_logger import CustomLogger
class DummyLogger(CustomLogger):
pass
logging_obj.stream = False
logging_obj.call_type = "anthropic_messages"
logging_obj.model_call_details["litellm_params"] = {}
logging_obj.litellm_params = {}
dummy_logger = DummyLogger()
dummy_logger.log_success_event = MagicMock()
dummy_logger.log_stream_event = MagicMock()
model_response = ModelResponse(
id="resp-123",
model="gpt-4o-mini",
choices=[
{
"message": {"role": "assistant", "content": "hello"},
"finish_reason": "stop",
"index": 0,
}
],
usage={"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
)
with patch.object(
logging_obj,
"get_combined_callback_list",
return_value=[dummy_logger],
):
logging_obj.success_handler(result=model_response)
dummy_logger.log_success_event.assert_not_called()
dummy_logger.log_stream_event.assert_not_called()