Skip to content

fix(passthrough): run async success handler for SDK pass-through streaming - #29312

Merged
mateo-berri merged 1 commit into
litellm_litellm__duplicate_cc_trcefrom
claude/brave-tesla-msN3I
May 30, 2026
Merged

fix(passthrough): run async success handler for SDK pass-through streaming#29312
mateo-berri merged 1 commit into
litellm_litellm__duplicate_cc_trcefrom
claude/brave-tesla-msN3I

Conversation

@mateo-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Fixes two CircleCI failures on #29311 (internal copy of #29089). This PR targets that PR's branch so the fix lands directly on it.

What was failing and why

Two streaming-plus-logging tests were red on #29311:

  • pass_through_unit_testing: tests/pass_through_unit_tests/test_anthropic_messages_passthrough.py::TestAnthropicBedrockAPI::test_anthropic_messages_litellm_router_streaming_with_logging and the TestAnthropicDirectAPI variant, both with "Logging payload should not be None".
  • google_generate_content_endpoint_testing: tests/unified_google_tests/test_google_ai_studio.py::TestGoogleGenAIStudio::test_async_streaming_with_logging, with standard_logging_object is not None failing.

Both SDK streaming paths (aanthropic_messages, google agenerate_content) export their assembled stream through PassThroughStreamingHandler._route_streaming_logging_to_handler, which #29089 changed to call dispatch_success_handlers. That method treated these calls as synchronous SDK requests because their call_type is not pass_through_endpoint and their litellm_params carries no acompletion flag. As a result only the sync success_handler ran, so async-only loggers (CustomLogger.async_log_success_event) never fired and the standard logging payload stayed None.

The other two CircleCI reds on #29311 are not caused by this branch: local_testing_part1 fails on test_vertex_ai_llama_tool_calling with a Vertex ACCESS_TOKEN_EXPIRED error, and batches_testing fails on test_async_file_and_batch with a Bedrock "account is not authorized" error. Both are environment/credential issues.

The fix

_route_streaming_logging_to_handler now passes prefer_async_handlers=True. That is always valid because the function only runs from an async context (anthropic_messages, google_genai, and proxy pass-through stream tasks). It restores the pre-#29089 behavior of awaiting async_success_handler and conditionally submitting the sync handler for legacy string callbacks, while keeping the new has_dispatched_final_stream_success dedupe guard that #29089 added to stop the duplicate Claude Code traces.

Pre-Submission checklist

  • I have added meaningful tests
  • My PR's scope is as isolated as possible; it only solves 1 specific problem

Screenshots / Proof of Fix

The new regression test test_route_streaming_logging_runs_async_handler_for_sdk_passthrough constructs a real Logging object configured like an anthropic_messages streaming call, drives the production _route_streaming_logging_to_handler, and asserts the async handler is awaited once and the sync handler is not called. It fails before the change (async awaited 0 times) and passes after.

To confirm against the originally failing CircleCI cases with real provider keys set:

uv run --no-sync python -m pytest \
  "tests/pass_through_unit_tests/test_anthropic_messages_passthrough.py::TestAnthropicDirectAPI::test_anthropic_messages_litellm_router_streaming_with_logging" \
  "tests/unified_google_tests/test_google_ai_studio.py::TestGoogleGenAIStudio::test_async_streaming_with_logging" \
  -vv

Type

🐛 Bug Fix

Changes

litellm/proxy/pass_through_endpoints/streaming_handler.py: pass prefer_async_handlers=True when routing assembled-stream logging, so async loggers run for SDK pass-through streaming whose call_type is not pass_through_endpoint.

tests/pass_through_unit_tests/test_unit_test_streaming.py: add a regression test that drives _route_streaming_logging_to_handler for an anthropic_messages streaming call and asserts the async success handler runs.


Generated by Claude Code

…aming

dispatch_success_handlers classified anthropic_messages and google
generate_content streaming as sync SDK requests, since their call_type is
not pass_through_endpoint and their litellm_params carries no acompletion
flag. Only the sync success_handler ran, so async-only loggers
(CustomLogger.async_log_success_event) never recorded the assembled stream;
this broke test_anthropic_messages_litellm_router_streaming_with_logging and
test_async_streaming_with_logging.

_route_streaming_logging_to_handler now passes prefer_async_handlers=True,
which is always valid because it runs from an async context (anthropic_messages,
google_genai, and proxy pass-through stream tasks). This restores async
dispatch while keeping the final-stream dedupe guard intact.

Adds a regression test asserting the async handler runs and the sync handler
does not for an anthropic_messages streaming call.
@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@mateo-berri
mateo-berri marked this pull request as ready for review May 30, 2026 00:26
@mateo-berri
mateo-berri merged commit da57dac into litellm_litellm__duplicate_cc_trce May 30, 2026
45 checks passed
@mateo-berri
mateo-berri deleted the claude/brave-tesla-msN3I branch May 30, 2026 00:26
@greptile-apps

greptile-apps Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression where async-only loggers (e.g., CustomLogger.async_log_success_event) were silently skipped for SDK pass-through streaming calls (aanthropic_messages, agenerate_content) because dispatch_success_handlers classified them as synchronous SDK requests and took the sync-only early-return path.

  • streaming_handler.py: _route_streaming_logging_to_handler now passes prefer_async_handlers=True to dispatch_success_handlers, bypassing the sync-SDK shortcut. This is safe because the function is exclusively reachable from async contexts (anthropic_messages, google_genai, and proxy stream tasks).
  • test_unit_test_streaming.py: A new regression test constructs a real LiteLLMLoggingObj configured as an anthropic_messages call (without the acompletion flag), drives the production code path, and asserts async_success_handler is awaited once while success_handler is not called.

Confidence Score: 5/5

Safe to merge — the change is a one-liner addition of a flag to an already-existing dispatch_success_handlers call, backed by a targeted regression test.

The fix is minimal and well-scoped: _route_streaming_logging_to_handler is only ever reached from async tasks, so always passing prefer_async_handlers=True is correct. The existing has_dispatched_final_stream_success deduplication guard from the parent PR is left intact, so there is no risk of double-logging. The regression test uses real production objects (not just mocks of the handler itself) and accurately models the failure scenario described in the PR.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/pass_through_endpoints/streaming_handler.py Passes prefer_async_handlers=True to dispatch_success_handlers so async loggers run for SDK pass-through streaming (anthropic_messages, google_genai) whose call_type is not pass_through_endpoint
tests/pass_through_unit_tests/test_unit_test_streaming.py Adds regression test verifying that _route_streaming_logging_to_handler awaits the async success handler (and skips the sync handler) for an anthropic_messages streaming call; uses only mocks, no real network calls

Reviews (1): Last reviewed commit: "fix(passthrough): run async success hand..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant