Skip to content

fix(logging): dedupe success logging for non-streaming anthropic_messages (/v1/messages) - #31172

Open
anneheartrecord wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
anneheartrecord:fix/anthropic-messages-duplicate-success-logging
Open

fix(logging): dedupe success logging for non-streaming anthropic_messages (/v1/messages)#31172
anneheartrecord wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
anneheartrecord:fix/anthropic-messages-duplicate-success-logging

Conversation

@anneheartrecord

Copy link
Copy Markdown
Contributor

Summary

A single non-streaming POST /v1/messages (anthropic_messages) emitted two litellm_request OTEL spans and fired the success/cost callbacks twice. Plain /chat/completions was unaffected.

Closes #31121

Root cause

/v1/messages is always dispatched through the async wrapper, so logging runs via async_success_handler. The async path also schedules the sync success_handler (via handle_sync_success_callbacks_for_async_calls) for legacy sync callbacks. That sync pass is supposed to skip CustomLogger dispatch when async already handled it — but the guard only excludes call_type == pass_through.

Unlike acompletion, anthropic_messages sets no async flag in litellm_params, so _is_sync_litellm_request() returns True and is_sync_request is True in the sync handler. With anthropic_messages not excluded, the sync pass re-dispatches every CustomLogger (OpenTelemetry, _ProxyDBLogger cost tracking, …) on top of the async one → two litellm_request spans and double cost/success callbacks. This is the non-streaming counterpart of #29550 / #29598, whose fix is gated on stream is True.

Change

Extend the existing pass_through skip in success_handler to also cover anthropic_messages, so the sync pass no longer re-dispatches CustomLoggers — logging happens once via the canonical async path, matching acompletion.

Verifying it doesn't drop logging

End-to-end with litellm.anthropic_messages(..., mock_response=...) and a counting CustomLogger:

sync log_success_event async async_log_success_event
before 1 1
after 0 1

Callbacks that reach the async path (otel via success_callback: ["otel"], _ProxyDBLogger, any logger in litellm.callbacks) keep their single async invocation and lose only the duplicate. A CustomLogger reachable only via the sync success_callback list is already not invoked on acompletion either, so this change makes anthropic_messages consistent with the other async paths rather than regressing them.

Tests

Added test_success_handler_skips_sync_callbacks_for_anthropic_messages, mirroring the existing sync/async callback tests. It fails before the change (sync callback fires) and passes after. Full tests/test_litellm/litellm_core_utils/test_litellm_logging.py passes (104).

@codspeed-hq

codspeed-hq Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Congrats! CodSpeed is installed 🎉

🆕 16 new benchmarks were detected.

You will start to see performance impacts in the reports once the benchmarks are run from your default branch.

Detected benchmarks


Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes double-invocation of CustomLogger success callbacks (OTEL spans, cost tracking, etc.) for non-streaming POST /v1/messages (anthropic_messages) requests. The anthropic_messages endpoint is always dispatched through the async wrapper, but — unlike acompletion — it does not set an async flag in litellm_params, so _is_sync_litellm_request() returns True and the sync pass in success_handler re-dispatches every CustomLogger on top of the async dispatch.

  • Extends the call_type not in (...) guard in success_handler to also include CallTypes.anthropic_messages.value alongside the existing pass_through exclusion, matching the behaviour established for other async-only routes.
  • Adds test_success_handler_skips_sync_callbacks_for_anthropic_messages which directly verifies that log_success_event is not called from the sync path when call_type == "anthropic_messages".

Confidence Score: 5/5

Safe to merge — the change is a minimal, targeted guard extension in the sync callback dispatcher with a direct regression test.

The one-line condition extension is well-understood: CallTypes.anthropic_messages is a valid enum value (confirmed at litellm/types/utils.py:351), the async failure path has no equivalent handle_sync_failure_callbacks_for_async_calls so the failure handler is not affected by the same double-dispatch pattern, no existing tests were weakened, and the new test directly exercises the fixed condition.

No files require special attention.

Important Files Changed

Filename Overview
litellm/litellm_core_utils/litellm_logging.py Extends the CustomLogger re-dispatch exclusion in success_handler to cover anthropic_messages in addition to pass_through; change is surgical and well-scoped.
tests/test_litellm/litellm_core_utils/test_litellm_logging.py Adds a new test verifying the sync CustomLogger path is skipped for anthropic_messages; no existing tests modified.

Reviews (1): Last reviewed commit: "fix(logging): dedupe success logging for..." | Re-trigger Greptile

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@anneheartrecord
anneheartrecord force-pushed the fix/anthropic-messages-duplicate-success-logging branch from 724216e to e6d3d8a Compare June 24, 2026 04:27
@anneheartrecord
anneheartrecord changed the base branch from main to litellm_oss_branch June 24, 2026 04:27
…ages

The /v1/messages (anthropic_messages) route is always dispatched through
the async wrapper, which logs via async_success_handler. But unlike
acompletion, anthropic_messages sets no async flag in litellm_params, so
_is_sync_litellm_request returns True in the sync success_handler and it
re-dispatches every CustomLogger. The result for a single non-streaming
request was two litellm_request OTEL spans and double cost/success
callbacks.

Skip the sync CustomLogger pass for anthropic_messages, mirroring how
pass_through is already handled, so logging runs once via the canonical
async path -- matching acompletion behaviour.
@anneheartrecord
anneheartrecord changed the base branch from litellm_oss_branch to litellm_internal_staging July 1, 2026 12:29
@anneheartrecord
anneheartrecord force-pushed the fix/anthropic-messages-duplicate-success-logging branch from e6d3d8a to 2776dec Compare July 1, 2026 12:33
@anneheartrecord

Copy link
Copy Markdown
Contributor Author

Rebased onto the current litellm_internal_staging and retargeted the base to it (the branch was previously cut from an older integration branch). The change is unchanged in scope — 2 files, +59/-1: anthropic_messages (/v1/messages) is always dispatched through the async success path, so the sync success_handler pass now skips it to avoid double-firing CustomLogger callbacks (duplicate OTEL litellm_request spans + double cost/success), matching how pass_through is already handled. Regression test included and green locally (test_success_handler_skips_sync_callbacks_for_anthropic_messages). Ready for review 🙏 — resolves #31121.

@anneheartrecord

Copy link
Copy Markdown
Contributor Author

@Sameerlite could you take a look at this one when you get a chance? It dedupes the double success logging (and double OTEL span) for non-streaming /v1/messages by extending the existing pass_through skip to anthropic_messages. Rebased onto current litellm_internal_staging; the two red checks are the fork-base guard and a pre-existing openapi_compliance failure on base, both unrelated.

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.

[Bug]: Non-streaming /v1/messages (anthropic_messages) emits duplicate litellm_request OTEL spans + double success/cost callbacks

1 participant