Repository navigation
feat(logging): add opt-in session_id and trace_id correlation to JSON log records via contextvars - #34418
Conversation
|
Deepanshu seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
Greptile SummaryAdds opt-in request correlation for async logging
Confidence Score: 5/5The PR appears safe to merge No blocking failures remain
|
| Filename | Overview |
|---|---|
| litellm/_logging.py | Adds sanitized context-variable storage, log-record filtering, reserved JSON correlation fields, and plaintext correlation formatting |
| litellm/litellm_core_utils/litellm_logging.py | Binds and restores per-call correlation state and populates independent trace and session fields in standard logging payloads |
| litellm/litellm_core_utils/streaming_handler.py | Restores consumer correlation state across stream exhaustion, failures, explicit closure, and guarded finalization |
| litellm/utils.py | Scopes correlation to asynchronous calls and restores caller context after setup failures and completed non-streaming calls |
| litellm/proxy/litellm_pre_call_utils.py | Adds lower-precedence W3C traceparent and baggage extraction for proxy request correlation |
| litellm/types/utils.py | Extends the standard logging payload contract with an independent session identifier |
Reviews (20): Last reviewed commit: "fix(logging): restore correlation contex..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
Ran a live local verification of this PR against a real Anthropic-backed proxy instance Bootstrapped the worktree with With To probe the contextvar isolation claim specifically, fired 8 concurrent requests, each with a distinct The underlying Anthropic calls themselves returned 401 in this sandbox since no live key was configured locally. That's fine for this verification because Two environment notes unrelated to this diff but worth flagging: the dev proxy fails startup entirely without a reachable Postgres rather than degrading gracefully, and |
|
Follow-up verification for the plaintext-log and StandardLoggingPayload additions in the latest commit Ran the same local setup as before (isolated throwaway Postgres, real With For One thing worth flagging: even with the flag on, if only |
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 7 · PR risk: 0/10 |
|
Verification evidence for the two review findings, both addressed in 313e170 Correlation context restoration (Greptile). Simulated the exact scenario flagged: a CustomLogger.log_failure_event registered via litellm.callbacks makes its own nested litellm.completion() call (a stand-in for a guardrail's LLM-as-judge call or an MCP sampling call), both against real Anthropic auth failures so the full failure_handler path runs Also ran the earlier 8-concurrent-request isolation check again (asyncio Task-per-request, the primary use case this PR targets) to confirm the restructure didn't regress it; still zero cross-contamination, matching the original verification comment above Note this went through two iterations before landing correctly. The first attempt (6c70f42) put the reset at the very top of each handler, before callback dispatch runs - live-testing caught that this resets the outer context too early, so a nested call's own token ends up capturing the wrong baseline. The fix in 313e170 wraps each handler's body in try/finally instead, so the reset only fires once the handler's own callback dispatch - including anything nested it triggers - has actually completed. Leaving both commits in the history since they show the debugging process, but 313e170 is the one that actually holds up Log injection via unsanitized correlation ids (veria-ai). >>> set_trace_id('evil\r\n{"level": "CRITICAL", "message": "forged"}')
>>> trace_id_var.get()
'evil{"level": "CRITICAL", "message": "forged"}' # \r\n stripped, can't forge a fake log line
>>> set_session_id('a' * 1000)
>>> len(session_id_var.get())
256 # boundedAll 225 tests across test_logging.py, test_standard_logging_payload.py, and the litellm_core_utils logging test files pass, and make pre-commit (ruff, ruff-strict-budget, type-discipline-budget, basedpyright-budget, format) is clean |
|
@greptileai please re-review, latest commit fixes the correlation-context-restore placement bug you flagged plus adds coverage for the async_failure_handler path |
|
Round summary: cross-context restoration issue (flagged independently by both veria-ai and Greptile's re-review) fixed in 43c164a Both reviewers correctly identified that the token-based reset from the previous round (6c70f42/313e170f) breaks whenever async_success_handler or a streaming failure path gets dispatched onto a different asyncio Task than Logging.init ran in - which litellm's real async success path does, via asyncio.create_task -> GLOBAL_LOGGING_WORKER. contextvars.Token.reset() only works in the exact Context it was created in; calling it from a different Task raises ValueError, which the old reset_trace_id/reset_session_id helpers silently swallowed, meaning the restore was a no-op for exactly that path Verified independently before fixing (not just trusting the report): reproduced the raw contextvars behavior in isolation, then traced litellm's actual dispatch code (litellm/utils.py, litellm/litellm_core_utils/logging_worker.py) to confirm it really does cross a Context boundary Fix: capture the pre-call value in Logging.init instead of a Token, restore via a plain set_trace_id()/set_session_id() call in 43c164a, which works regardless of which Task calls it - nothing to swallow. reset_trace_id/reset_session_id removed as dead code. Added test_restore_correlation_context_works_across_asyncio_task_boundary, which runs init and the restore in separate asyncio Tasks; confirmed it fails against the prior Token-based commit with the exact silent-swallow failure mode described, and passes against 43c164a 227 tests pass across test_logging.py, test_standard_logging_payload.py, and the litellm_core_utils logging test files. make pre-commit clean @greptileai please re-review |
|
Round summary: Greptile's third finding ("Restoration targets the wrong context") fixed in 15271c9 The prior fix (43c164a) correctly made the restore work across Task/Context boundaries without raising, but only restored the handler's own (detached, throwaway) Task - never the originating request Task that Logging.init actually ran in. For a successful acompletion(), that meant the caller's own Task kept the just-completed call's trace_id/session_id set indefinitely (e.g. visible to a subsequent nested call made in the same Task) wrapper()/wrapper_async() in litellm/utils.py now restore the originating Task's context in a finally block once the whole call is done. This required a small mutable holder to carry the constructed Logging instance out to the outer wrapper, since the wrapped body rebinds its own Also removed the idempotency guard on _restore_correlation_context(): with value-based restoration, every distinct Task involved in an attempt needs its own call to actually take effect in that Task's own view of the contextvars, so repeat calls are required now, not just tolerated Added a regression test that exercises the real success dispatch path via @greptileai please re-review |
|
Round summary: Greptile's fourth finding ("Setup failures leak correlation context") fixed in 343cb7a If function_setup() constructs Logging() (mutating trace_id_var/session_id_var in init) and then raises before returning - e.g. update_environment_variables() throws - neither wrapper() nor wrapper_async() ever receives a logging_obj reference, so the finally-block restore added last round never fires, leaking the failed call's ids into every subsequent log line on that thread/task function_setup()'s own except block now restores the context itself, using whatever logging_obj it managed to construct before failing. Since function_setup() is shared by both the sync and async wrapper bodies, this one fix covers both paths Greptile's comment mentioned Added a regression test that monkeypatches Logging.update_environment_variables to raise after construction. Confirmed it fails without this fix (the leaked ids show up directly in the raised exception's own log line - concrete, not inferred) and passes with it Broader regression sweep again given the touched surface: 722 tests across test_utils.py, test_router.py, test_main_module_header.py, streaming handler tests, and all logging-specific tests, all passing. make pre-commit clean @greptileai please re-review |
|
Fixed a CI regression from the prior commit (343cb7a) in 07d47bf The "All Other Providers" job caught it: tests/test_litellm/llms/xai/test_xai_key_fallback.py injects a minimal FakeLogging stand-in (only implementing update_from_kwargs) as litellm_logging_obj for a narrow unit test, bypassing the real Logging class. The prior commit's wrapper()/wrapper_async()/function_setup() code unconditionally called _restore_correlation_context() on whatever ended up in the holder, which doesn't exist on that stand-in - 3 tests failed with AttributeError Added _restore_correlation_context_if_supported(), a getattr-guarded helper, and used it at all three call sites, since this method is new plumbing specific to this feature and no pre-existing stand-in object is expected to implement it. Confirmed the previously-failing tests/test_litellm/llms/xai/test_xai_key_fallback.py now passes (20/20), plus re-ran the full 826-test broad sweep from the last round, still clean. make pre-commit clean |
|
Round summary: two more findings from the same review round, both fixed in 705e47e Setup failure logged before cleanup. function_setup()'s except block restored correlation context after logging the exception, so that diagnostic log line was stamped with the doomed call's ids rather than the outer ones. Restore now happens first. Verified with a test capturing the actual emitted log record (not just contextvar state afterward) - fails against the prior commit, passes now Streaming context restored too early. wrapper()/wrapper_async() restored the originating task's context as soon as a streaming call returned the lazy CustomStreamWrapper, before the caller starts iterating it - so log lines emitted during iteration incorrectly showed the pre-call ids. The finally block now detects a CustomStreamWrapper return value and skips the restore, deferring to the terminal handler that already fires once the stream is actually assembled. Verified with sync + async tests (mock_response + stream=True, no live provider needed) - both fail against the prior commit, pass now Broader sweep: 829 tests passing across all the same suites as prior rounds. make pre-commit clean @greptileai please re-review |
|
Round summary: abandoned-stream correlation cleanup, 13a7577 This one is different from the prior six: it doesn't have a clean fix, only a best-effort mitigation, and I said so directly on the thread rather than overclaiming If a caller returns a streaming response and never fully consumes it (stops early, drops the reference, cancels it), the terminal handler that normally restores the originating task's context never fires. CustomStreamWrapper has no close()/context-manager convention today, so there's no reliable signal for this. Added a del that runs the same restore logic, documented as best-effort: GC timing is unpredictable (cyclic-collection delay, no guarantee at interpreter shutdown, can run on a different thread), so this shortens the leak window in the common case rather than eliminating it Testing note: real garbage collection turned out to be unreliable to test against directly (per-chunk logging holds a transient executor-thread reference to the wrapper that doesn't clear on a fixed schedule), so the tests call del directly to validate the logic deterministically, plus confirm a broken logging_obj can never make it raise Asked on the thread whether there's a better idea for a complete fix here; my best alternative (requiring context-manager usage) is a bigger API change than seems warranted for this PR @greptileai please re-review |
|
Round summary: two more streaming-related findings, both fixed in df6ac7b Veria AI: even a fully consumed stream never restored the actual consuming thread/task's context - the terminal success dispatch (create_task for async, the shared executor for sync) only ever fixes up whatever detached context it runs in Greptile: explicit aclose() (client disconnect, router fallback aborting a partial stream) closed the underlying stream without restoring context either Added CustomStreamWrapper._restore_consumer_correlation_context(), called at every point control genuinely returns to the consumer: the final raise StopIteration/StopAsyncIteration on natural exhaustion (both sync and async paths), _handle_stream_fallback_error (the single shared choke point for all three failure-raising call sites, per its own docstring), and aclose(). del now delegates to the same helper instead of duplicating it Extended the existing streaming-exhaustion tests to assert the consuming context is restored after the loop completes (not just preserved during iteration), and added a dedicated aclose() test - both confirmed to fail against the prior commit and pass now. Broader sweep: 832 passing, make pre-commit clean @greptileai please re-review |
|
@greptileai please re-review |
|
@greptileai please re-review |
…th only Blocking review finding: wrapper() (the sync entry point) used the same skip-restore-on-stream design as wrapper_async(), but a plain OS thread has no per-call context isolation the way an asyncio Task does, and a thread pool's worker threads are recycled across unrelated requests - an abandoned sync stream could leave its ids stuck on a thread a pool later hands to a completely different request, misattributing that request's logs. A fix existed and was tested (restore unconditionally in wrapper()'s own finally), but it doesn't benefit this feature's primary consumer - the proxy only ever calls the async entry point - and carries sync-specific complexity this PR doesn't need. Scope the feature to async only instead: Logging.__init__() takes a new supports_correlation_logging parameter (default True), threaded down from a new function_setup(..., is_async_call: bool = True) parameter. wrapper() is the one caller that passes is_async_call=False; every other function_setup() call site (wrapper_async(), the router, and proxy/MCP-internal call sites) is already async and keeps the default. With supports_correlation_logging=False, Logging.__init__() never calls set_trace_id()/set_session_id() at all, so a sync call has nothing to leak in the first place. wrapper() reverts to its pre-review shape with no correlation-specific code at all. StandardLoggingPayload's own trace_id/session_id fields are unaffected either way - they're a deterministic per-call read of self.litellm_trace_id/self.litellm_session_id, not ambient contextvar state, so they were never exposed to the cross-request bug. Full sync/direct-SDK support (stamping + its own safe-restore mechanism) is deferred to a follow-up PR; the fix and its regression test already exist in this branch's history at commit 9f3a20f and can be resurrected there. Tests: replaced the two wrapper()-level tests with ones proving the new invariant (sync calls, streaming and non-streaming, never touch trace_id_var/session_id_var even when the caller explicitly passes litellm_trace_id/litellm_session_id), and added a direct unit test for the supports_correlation_logging=False gate on Logging.__init__ itself. Verified live: a real proxy (Postgres-backed, real OpenAI calls) shows clean trace_id/session_id isolation across two concurrent sessions with no cross-contamination; a standalone script confirms real sync SDK calls against a real model never touch the correlation contextvars.
…ion_id request_correlation_in_logs previously only resolved trace_id/session_id from litellm-specific sources: x-litellm-trace-id/x-litellm-session-id headers, a generic x-<vendor>-session-id header, or Anthropic-style metadata.user_id. If none were present, trace_id fell back to an auto-generated UUID unrelated to anything else, and session_id stayed empty - even when the caller already had real distributed-tracing instrumentation sending the actual industry-standard headers for this. Add a fallback to the W3C Trace Context traceparent header (trace-id component) and W3C Baggage header (session.id entry), so a request already carrying real OpenTelemetry trace context correlates litellm's own logs with the same trace in the caller's observability backend (Datadog, Honeycomb, Tempo, etc.) instead of getting an unrelated generated id. Precedence is unchanged for existing sources: explicit litellm headers and the Anthropic metadata path both still win over this new fallback, which only fires when neither found anything. trace_id and session_id are resolved independently here (unlike the existing chain_id mechanism, which uses one shared value for both), since traceparent and baggage are semantically distinct W3C concepts. New helpers _trace_id_from_traceparent/_session_id_from_baggage in litellm_pre_call_utils.py parse the header formats directly (no new dependency - both are simple fixed-width/delimited strings), wired into LiteLLMProxyRequestSetup.add_litellm_metadata_from_request_headers() only when the corresponding litellm_trace_id/litellm_session_id key isn't already set by the existing paths. Verified live against a real proxy: a bare traceparent header produces a log trace_id exactly matching its trace-id component; a traceparent alongside an explicit x-litellm-trace-id header (different value) produces a log showing the explicit header's value, proving precedence.
…ssage-content spoofing JsonFormatter merges keys parsed from the message body before applying extra record attributes, and the extra-attributes loop skips a key that's already present. A caller-controlled log message that happens to parse as JSON/dict with a "trace_id"/"session_id" key (e.g. the proxy logging a raw request-header dict) could therefore make the JSON record carry the attacker-supplied value instead of the real correlation context set via CorrelationContextFilter. trace_id/session_id are now applied from the LogRecord's own attributes after message-content parsing, unconditionally overwriting anything the message body claimed for those two keys.
…lm_logging.py - _logging.py: import litellm belongs after the stdlib from-imports, grouped with the other litellm.* imports, not before them. - litellm_logging.py: the refactor to Mapping introduced a second, separate `from collections.abc import Mapping` instead of merging it into the existing `from collections.abc import Callable` import. Caught by the strict-rule budget gate (ruff-strict-budget.json caps I001 at 0 new violations); both auto-fixed with `ruff check --fix --select I001`.
…T002
Five sites in this PR's diff built a mutable list/dict literal instead of a
frozen value: a plain list of optional strings in CorrelationPlainFormatter,
a `kwargs or {}` fallback, a `metadata or {}` fallback, two `[...]` candidate
orderings, and a `dict(headers)` copy feeding a dict comprehension. Each is
build-once/read-only, so this rewrites them as tuples, MappingProxyType, or a
plain conditional `.get()` instead of seeding then reading a fresh mutable
collection - no behavior change, confirmed by the existing test suite.
Caught by the type-discipline budget gate (LIT002 capped at 0 new
violations).
…ntext is active Live-proxy verification surfaced a gap in the earlier message-content-spoofing fix (7f390a5): that fix only overwrites trace_id/session_id from the LogRecord's own attribute, so it does nothing for a log line emitted before CorrelationContextFilter has stamped anything on this record (e.g. the "Request Headers" debug line, which fires before Logging.__init__() runs for the request). On such a record, a caller-supplied header literally named trace_id/session_id still got promoted into the JSON output via the embedded JSON/dict-repr parser, since there was no genuine value to protect. Fixed at the source: trace_id/session_id are now excluded unconditionally from the message-content-parsing promotion step, not just superseded afterward. Verified live against a real proxy - the exact adversarial request (headers literally named trace_id/session_id) no longer leaks into any JSON log record. Added a regression test for this no-active-context variant specifically, confirmed it fails against the prior commit and passes now. Also fixes an unrelated basedpyright regression from an earlier rebase's conflict resolution: litellm/utils.py's `logging_obj` was incorrectly re-annotated `Final` at its second assignment in function_setup() (it's first declared `None` a few lines earlier), which basedpyright correctly rejects.
_session_id_from_baggage() extracts the caller-controlled session.id entry verbatim - it isn't sanitized until set_session_id() runs later in Logging.__init__(). The debug log line for this extraction interpolated the raw value directly, so a caller could embed terminal control characters or ANSI escape sequences that forge/alter plaintext log output for anyone tailing the proxy's logs. Verified live: a baggage header with an embedded ANSI escape reached the terminal as a real, unescaped control sequence before this fix. Drops the value from the log line entirely (the extraction succeeding is enough signal on its own) rather than sanitizing-then-logging, matching veria-ai's suggestion. Added a regression test using caplog that fails against the prior commit and passes now.
…ption mapping _map_anthropic_exception/_map_aleph_alpha_exception synchronously log a debug diagnostic (the raw status code) as part of exception_type()'s mapping. _handle_stream_fallback_error restored the consumer's outer correlation context before calling exception_type(), so that diagnostic log line carried the outer (or empty) trace_id/session_id instead of the failing stream's own - flagged by Greptile. Moved the restore to run after mapping completes, matching the same restore-after-not-before pattern already applied elsewhere in this file for success/finish_reason handling. Added a regression test that captures the correlation context live during a mocked exception_type() call; fails against the prior commit, passes now.
…lose completes aclose() restored the consumer's outer correlation context as its first statement, before awaiting the underlying provider stream's own aclose()/ close(). If that close attempt raises, the except branch's debug diagnostic ran under the already-restored outer context instead of the closing stream's own trace_id/session_id - flagged by Greptile, same restore-too-early pattern as the stream-failure fix in f1cf958. Moved the restore to the end of aclose(), after the close attempt (and its diagnostic logging) completes. Added a regression test with a fake stream whose aclose() raises, capturing the correlation context live during the diagnostic log call; fails against the prior commit, passes now.
…Final, ANN401, S110, TRY300, kwargs typing) Rebasing onto litellm_internal_staging pulled in 116 upstream commits that introduced/tightened several lint gates this PR's own code now trips: - LIT010 (every local/module-level variable must be Final): added Final annotations across _logging.py, litellm_logging.py, streaming_handler.py, litellm_pre_call_utils.py, and utils.py. Where a name is genuinely reassigned (logging_obj: starts None, later set to the real object) or branch-assigned, either restructured into a single ternary expression (ordered_candidates) or suppressed with `# rebind-ok: <reason>` matching this repo's documented escape hatch. - LIT011 (parameter mutation): suppressed the two new `data[key] = value` writes in litellm_pre_call_utils.py with `# rebind-ok`, matching the unsuppressed precedent already used for every other `data[...]` write in the same function - `data` is an intentional out-param there. - ANN001/ANN003/ANN202 (missing parameter/return type annotations): fully typed success_handler/_success_handler_body, their async twins, and failure_handler/_failure_handler_body/async variants in litellm_logging.py, plus function_setup in utils.py (added Rules to its existing TYPE_CHECKING block for the rules_obj: Rules annotation). - ANN401 (explicit Any disallowed): suppressed with `# noqa: ANN401` on the handful of genuinely-heterogeneous result/*args/**kwargs parameters, since ordinary suppression is this repo's documented path. - S110 (try/except/pass): added to the existing BLE001 noqa on the one best-effort correlation-cleanup try/except this PR added. - TRY300 (return inside try): moved two `return result` statements into `else:` blocks in the retry-fallback paths this PR's own diff touched. - reportPrivateUsage (basedpyright): renamed the two new StandardLoggingPayloadSetup static methods (get_standard_logging_payload_ trace_id/session_id) to drop their leading underscore, since they're genuinely called from a sibling module-level function in the same file. No behavior change - confirmed by the full existing test suite (819 passed) plus all four lint gates (ruff format, ruff-strict, type-discipline, basedpyright) passing clean.
CI's plain "ruff check" job uses the default ruff.toml, a narrower config than ruff-strict.toml (used only by the strict-rule budget gate). ANN401 and S110 aren't enabled in the default config, so RUF100 (unused-noqa) flagged the `# noqa: ANN401`/`# noqa: ...,S110` suppressions this PR added as pointless under that config, even though they're genuinely needed under ruff-strict.toml. - ANN401: added to ruff.toml's existing `lint.external` list (same mechanism already used for C901/TID251, enforced by the strict gate but not by this config) - these Any usages are genuinely dynamic/forwarded, so the suppression itself is correct and just needed registering. - S110: fixed the underlying code instead of registering another external code - the try/except/pass in CustomStreamWrapper._restore_consumer_correlation_context now logs at debug level on failure (matching the existing best-effort-cleanup pattern in _record_partial_usage_for_failure elsewhere in this file), which satisfies S110's own suggestion directly and needs no suppression at all. Verified against both ruff.toml and ruff-strict.toml directly, plus all three other gates (ruff format, type-discipline, basedpyright) and the full test suite (821 passed).
…-wide noqa Ruff has no per-line-scoped way to register a noqa code across configs (that requires the default ruff.toml's lint.external list, which is repo-wide in scope even though the noqa itself is per-line). Since ruff does support file-level exemptions via per-file-ignores, and ANN401 only needed exempting in exactly two files, moved the exemption there instead: - ruff-strict.toml: added [lint.per-file-ignores] disabling ANN401 for litellm_logging.py and utils.py specifically, with a comment explaining why (heterogeneous response/forwarded-args parameters with no fitting concrete type - already verified by trying CostResponseTypes and hitting a real basedpyright mismatch). - ruff.toml: reverted the ANN401 entry from lint.external - no longer needed, since there's no `# noqa: ANN401` left anywhere for RUF100 to second-guess. - Removed the now-redundant `# noqa: ANN401` from the 10 affected parameters in both files, keeping the existing kwargs-ok reasons and adding a short inline comment on the `result`/`*args` lines pointing at the ruff-strict.toml exemption for context. Verified against both configs directly (ANN401 clean under ruff-strict.toml for these files, RUF100 clean under the default config), all four gates (ruff format, ruff-strict, type-discipline, basedpyright), and the full test suite (821 passed).
45b7584 to
389ffad
Compare
…mping log records CorrelationContextFilter stamps trace_id/session_id onto a LogRecord after SecretRedactionFilter has already run, so a caller-controlled value (e.g. via x-litellm-trace-id or a W3C baggage header) that happens to look like a real credential reached JSON and plaintext logs unredacted. Apply the same credential redaction already used elsewhere in this module at _sanitize_correlation_id(), the single choke point both set_trace_id() and set_session_id() route through, so every caller-facing entry point is covered without depending on filter ordering.
|
Round summary: one veria-ai finding addressed in 4f6ac4c. Low: Correlation identifiers bypass credential redaction - CorrelationContextFilter stamps trace_id/session_id onto the log record after SecretRedactionFilter has already run, so a caller-controlled value (via x-litellm-trace-id or a W3C baggage header) shaped like a real credential reached JSON and plaintext logs unredacted. Verified live with a poisoned sk-ant-api03-... trace_id: unredacted before the fix, REDACTED after. Fixed at the single choke point (_sanitize_correlation_id, called by both set_trace_id() and set_session_id()) rather than depending on filter ordering. Added a regression test confirmed to fail on the pre-fix code. This round also rebased onto the latest litellm_internal_staging (56 more commits) and re-verified all lint/type/test gates. CI is fully green on the rebase push (lint, code-quality, codecov/patch, and every test shard all passed). |
|
@greptileai please re-review - the credential-redaction fix for correlation identifiers is in 4f6ac4c. |
|
@veria-ai please re-review |
…n timeout fires CustomStreamWrapper.__anext__() called _check_max_streaming_duration() before entering its try block, so the litellm.Timeout it raises bypassed the except Exception -> _handle_stream_fallback_error path entirely, leaking the timed-out stream's own trace_id/session_id into whatever the consumer's task logs next. Move the check inside the try so it flows through the same restoration path every other stream failure already uses.
|
Round summary: one more veria-ai finding addressed in dc7e40f. Low: Streaming timeout bypasses context restoration - CustomStreamWrapper.anext() called _check_max_streaming_duration() before entering its try block, so the litellm.Timeout it raises bypassed the except Exception -> _handle_stream_fallback_error restoration path entirely, leaking the timed-out stream's own trace_id/session_id into whatever the consumer's task logs next. Verified live: forced a stream past LITELLM_MAX_STREAMING_DURATION_SECONDS and confirmed the outer context stayed stuck on the stream's own ids before the fix, restored after. Fixed by moving the check inside the try block. Added a regression test confirmed to fail on the pre-fix code. CI is fully green on this push as well (lint, code-quality, codecov/patch, all test shards). |
|
@greptileai please re-review - the streaming max-duration timeout context-restoration fix is in dc7e40f. |
|
@veria-ai please re-review |
…e async max-duration test Moving _check_max_streaming_duration() inside __anext__()'s try block (prior commit) means a max-duration Timeout now dispatches failure handlers through the same path every other stream failure already uses, instead of bypassing it entirely. dispatch_failure_handlers is async on the real Logging class; the test's plain MagicMock logging_obj made asyncio.create_task() choke on a non-coroutine return value once that path actually got exercised.
|
Round summary: fixed a real CI failure this push (f07b916), not a review finding. The prior commit (dc7e40f) moved _check_max_streaming_duration() inside anext()'s try block, which correctly routes a max-duration Timeout through the same dispatch_failure_handlers/failure-logging path every other stream failure already uses. tests/test_litellm/litellm_core_utils/test_max_streaming_duration.py::test_should_raise_on_async_anext_when_exceeded used a plain MagicMock for logging_obj, which broke once that async dispatch path was genuinely exercised for the first time. Made the mock's dispatch_failure_handlers awaitable to match the real Logging class. |
9ce96c2
into
BerriAI:litellm_internal_staging
…8.0) (#393)
This PR contains the following updates:
| Package | Update | Change |
|---|---|---|
| [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | minor | `v1.97.0` → `v1.98.0` |
---
### Release Notes
<details>
<summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary>
### [`v1.98.0`](https://github.com/BerriAI/litellm/releases/tag/v1.98.0)
[Compare Source](https://github.com/BerriAI/litellm/compare/v1.98.0...v1.98.0)
##### Verify Docker Image Signature
All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).
**Verify using the pinned commit hash (recommended):**
A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:
```bash
cosign verify \
--key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
ghcr.io/berriai/litellm:v1.98.0
```
**Verify using the release tag (convenience):**
Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:
```bash
cosign verify \
--key https://raw.githubusercontent.com/BerriAI/litellm/v1.98.0/cosign.pub \
ghcr.io/berriai/litellm:v1.98.0
```
Expected output:
```
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
```
***
##### What's Changed
- fix(bedrock): drop toolSpec.strict for Claude Sonnet 5 on Converse by [@​kr0k](https://github.com/kr0k) in [#​33196](https://github.com/BerriAI/litellm/pull/33196)
- fix(batches): attribute Vertex passthrough batch cost to key/team/tags by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​34456](https://github.com/BerriAI/litellm/pull/34456)
- docs: rewrite the CLAUDE.md comment rule with explicit exceptions by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36301](https://github.com/BerriAI/litellm/pull/36301)
- fix(proxy): scope file list pagination cursors to the caller by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36093](https://github.com/BerriAI/litellm/pull/36093)
- fix(proxy): skip prisma-dependent hooks when no database is attached by [@​mateo-berri](https://github.com/mateo-berri) in [#​36273](https://github.com/BerriAI/litellm/pull/36273)
- fix(proxy): report has\_more false on caller-scoped file list pages by [@​mateo-berri](https://github.com/mateo-berri) in [#​36326](https://github.com/BerriAI/litellm/pull/36326)
- fix(proxy): restore management\_v1 query-param validation under fastapi>=0.140.7 by [@​HuanQian571](https://github.com/HuanQian571) in [#​35773](https://github.com/BerriAI/litellm/pull/35773)
- fix(proxy): stop /{provider}/v1/files from capturing /openai\_passthrough by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36092](https://github.com/BerriAI/litellm/pull/36092)
- chore(typing): remove 914 basedpyright Any errors across 16 hotspot files by [@​mateo-berri](https://github.com/mateo-berri) in [#​36386](https://github.com/BerriAI/litellm/pull/36386)
- fix(router): keep batch fallbacks inside the model group that owns the file by [@​mateo-berri](https://github.com/mateo-berri) in [#​36181](https://github.com/BerriAI/litellm/pull/36181)
- feat(ptu): configure provisioned-throughput flat cost on a model deployment by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​35341](https://github.com/BerriAI/litellm/pull/35341)
- docs: clarify the CLAUDE.md comment exceptions are any-of by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36421](https://github.com/BerriAI/litellm/pull/36421)
- docs: replace the Changes PR template section with Caveats by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36423](https://github.com/BerriAI/litellm/pull/36423)
- fix(bedrock): enable native structured output for GLM 5 and DeepSeek V3.2 by [@​alexshtf](https://github.com/alexshtf) in [#​35669](https://github.com/BerriAI/litellm/pull/35669)
- feat(ptu): daily rollup writes per-model PTU flat cost by active hour by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​35343](https://github.com/BerriAI/litellm/pull/35343)
- feat(logging): add opt-in session\_id and trace\_id correlation to JSON log records via contextvars by [@​deepanshululla](https://github.com/deepanshululla) in [#​34418](https://github.com/BerriAI/litellm/pull/34418)
- feat(ptu): surface PTU flat cost on the daily activity read path by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​35391](https://github.com/BerriAI/litellm/pull/35391)
- feat(router): add per-deployment allowed\_fails\_policy and cooldown\_time override support by [@​deepanshululla](https://github.com/deepanshululla) in [#​34416](https://github.com/BerriAI/litellm/pull/34416)
- feat(ptu): add PTU inputs to the model form and flat cost to the Usage page by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​35393](https://github.com/BerriAI/litellm/pull/35393)
- fix(cost): price dict-shaped image input token details at the image rate by [@​vairodp](https://github.com/vairodp) in [#​33490](https://github.com/BerriAI/litellm/pull/33490)
- fix(model\_prices): refresh deprecation dates, correct xAI pricing and add missing provider models by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36403](https://github.com/BerriAI/litellm/pull/36403)
- feat(ptu): gate PTU flat-cost attribution behind an opt-in env var by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36138](https://github.com/BerriAI/litellm/pull/36138)
- ci: cache Prisma CLI and engine binaries, split test timeout from setup by [@​mateo-berri](https://github.com/mateo-berri) in [#​36417](https://github.com/BerriAI/litellm/pull/36417)
- feat(rate limiting): configurable estimated output tokens per key, team and model by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36143](https://github.com/BerriAI/litellm/pull/36143)
- fix(ui): hide admin-only Logs tabs from roles that cannot call their endpoints by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36333](https://github.com/BerriAI/litellm/pull/36333)
- test(proxy): guard management\_v1 against fastapi names removed in supported releases by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36336](https://github.com/BerriAI/litellm/pull/36336)
- fix(ui): gate policy and prompt lookups on an admin capability by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36335](https://github.com/BerriAI/litellm/pull/36335)
- build(deps): bump pypdf to 6.15.0 to clear osv-scan by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36350](https://github.com/BerriAI/litellm/pull/36350)
- fix(proxy): isolate guardrail load failures per row by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36432](https://github.com/BerriAI/litellm/pull/36432)
- fix(ui): gate organization and agent usage views behind capabilities by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36334](https://github.com/BerriAI/litellm/pull/36334)
- fix(reset\_budget\_job): atomic budget cascade with chunked reset scans by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36287](https://github.com/BerriAI/litellm/pull/36287)
- feat(proxy): add GET /v1/indexes to list vector store indexes by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36289](https://github.com/BerriAI/litellm/pull/36289)
- feat(ui): show vector store indexes on the Vector Stores page by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36306](https://github.com/BerriAI/litellm/pull/36306)
- fix(proxy): treat SAML as configured in UI SSO detection by [@​fancybear-dev](https://github.com/fancybear-dev) in [#​36196](https://github.com/BerriAI/litellm/pull/36196)
- fix(bedrock): reject Anthropic server-side web\_search tool with actionable error by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36473](https://github.com/BerriAI/litellm/pull/36473)
- fix(ui): open the classifier prompt editor above the edit auto-router form by [@​tin-berri](https://github.com/tin-berri) in [#​36438](https://github.com/BerriAI/litellm/pull/36438)
- fix(arize): trace MCP tool calls instead of crashing on CallToolResult by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36453](https://github.com/BerriAI/litellm/pull/36453)
- refactor(ui): make illegal DataTable prop combinations unrepresentable by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36470](https://github.com/BerriAI/litellm/pull/36470)
- fix(ui): scope Virtual Keys and Logs team lists to the caller by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36472](https://github.com/BerriAI/litellm/pull/36472)
- fix(ui): gate the Old Usage page behind a proxy-admin capability by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36469](https://github.com/BerriAI/litellm/pull/36469)
- docs(terraform): describe the provider release as automatic by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36467](https://github.com/BerriAI/litellm/pull/36467)
- feat(proxy): add per-deployment keepalive\_seconds SSE heartbeat to prevent load-balancer timeout on long streams by [@​deepanshululla](https://github.com/deepanshululla) in [#​34423](https://github.com/BerriAI/litellm/pull/34423)
- fix(router): cool down failed fallback deployments and correct cooldown TTL after Redis backfill by [@​deepanshululla](https://github.com/deepanshululla) in [#​35104](https://github.com/BerriAI/litellm/pull/35104)
- perf(spend): write each daily spend batch in one upsert statement by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36448](https://github.com/BerriAI/litellm/pull/36448)
- fix(ui): gate four sidebar pages on the roles their endpoints allow by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36475](https://github.com/BerriAI/litellm/pull/36475)
- fix(ui): restore the Logs Deleted Teams tab for organization admins by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36478](https://github.com/BerriAI/litellm/pull/36478)
- fix(websearch): stop leaking interception control fields to providers by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36480](https://github.com/BerriAI/litellm/pull/36480)
- test(e2e): cover the Anthropic web\_search server tool on Bedrock by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36443](https://github.com/BerriAI/litellm/pull/36443)
- fix(router): warn when a deployment's credentials contradict its provider by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36486](https://github.com/BerriAI/litellm/pull/36486)
- fix: net prompt-caching savings against the cache-write premium by [@​tin-berri](https://github.com/tin-berri) in [#​36452](https://github.com/BerriAI/litellm/pull/36452)
- feat(ui): deployment affinity toggle for the auto-router by [@​tin-berri](https://github.com/tin-berri) in [#​36302](https://github.com/BerriAI/litellm/pull/36302)
- fix(bedrock): use deployment credentials for AWS requests by [@​daleselaji-dev](https://github.com/daleselaji-dev) in [#​36160](https://github.com/BerriAI/litellm/pull/36160)
- fix(anthropic): preserve midturn system corrections by [@​eugene-yao-zocdoc](https://github.com/eugene-yao-zocdoc) in [#​34290](https://github.com/BerriAI/litellm/pull/34290)
- fix(email): stop duplicate legacy invitation email and fix its onboarding link by [@​mubashir1osmani](https://github.com/mubashir1osmani) in [#​36455](https://github.com/BerriAI/litellm/pull/36455)
- feat(ui): show models under each tier in routing benchmark chart by [@​tin-berri](https://github.com/tin-berri) in [#​36291](https://github.com/BerriAI/litellm/pull/36291)
- fix(proxy): inject streaming usage cost on openai passthrough streams by [@​mateo-berri](https://github.com/mateo-berri) in [#​36503](https://github.com/BerriAI/litellm/pull/36503)
- docs: require a user flow and live-proxy proof in bug reports by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36498](https://github.com/BerriAI/litellm/pull/36498)
- fix(proxy): add config\_updated\_at audit timestamp for virtual keys by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36488](https://github.com/BerriAI/litellm/pull/36488)
- docs: require a user flow and a stuck-at proof in feature requests by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36500](https://github.com/BerriAI/litellm/pull/36500)
- feat(router): add required-AND (&) tag prefix and allow\_fail\_open flag by [@​deepanshululla](https://github.com/deepanshululla) in [#​36193](https://github.com/BerriAI/litellm/pull/36193)
- feat(proxy): per-key prompt caching toggle via enable\_prompt\_caching by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36466](https://github.com/BerriAI/litellm/pull/36466)
- fix(bedrock): send tool-search beta header for Haiku 4.5 on Invoke /v1/messages by [@​mateo-berri](https://github.com/mateo-berri) in [#​36502](https://github.com/BerriAI/litellm/pull/36502)
- fix(bedrock): preserve adaptive thinking effort through the /v1/messages bridge by [@​mateo-berri](https://github.com/mateo-berri) in [#​36507](https://github.com/BerriAI/litellm/pull/36507)
- ci: retry transient network fetch failures in lint workflow by [@​mateo-berri](https://github.com/mateo-berri) in [#​36563](https://github.com/BerriAI/litellm/pull/36563)
- fix(ui): stub useIsOrgAdmin in UsageTab tests so useCan needs no QueryClient by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36565](https://github.com/BerriAI/litellm/pull/36565)
- fix(alerting): dedupe scheduled Slack spend reports across pods by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36489](https://github.com/BerriAI/litellm/pull/36489)
- chore(typing): clear 1.6k basedpyright Any errors across 56 files by [@​mateo-berri](https://github.com/mateo-berri) in [#​36543](https://github.com/BerriAI/litellm/pull/36543)
- fix(bedrock): add text block to converse user messages carrying documents by [@​mateo-berri](https://github.com/mateo-berri) in [#​36499](https://github.com/BerriAI/litellm/pull/36499)
- fix(deps): ship boto3 with the base SDK so bedrock works out of the box by [@​mubashir1osmani](https://github.com/mubashir1osmani) in [#​36568](https://github.com/BerriAI/litellm/pull/36568)
- fix(model\_prices): add provider-announced deprecation dates for Bedrock, Mistral, Cohere and Gemini models by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36538](https://github.com/BerriAI/litellm/pull/36538)
- chore: bump litellm-enterprise 0.1.54 -> 0.1.55, litellm-proxy-extras 0.4.84 -> 0.4.85, litellm 1.97.0 -> 1.98.0 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36577](https://github.com/BerriAI/litellm/pull/36577)
- fix(bedrock\_guardrails): skip ApplyGuardrail when there is no content to scan by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36441](https://github.com/BerriAI/litellm/pull/36441)
- fix(e2e): assert on the gen-AI span that served the stream, not the span count by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36582](https://github.com/BerriAI/litellm/pull/36582)
- test(e2e): harden vendor API coverage by [@​mubashir1osmani](https://github.com/mubashir1osmani) in [#​34557](https://github.com/BerriAI/litellm/pull/34557)
- test(e2e): add reproducers for passthrough and model budget gaps by [@​mubashir1osmani](https://github.com/mubashir1osmani) in [#​34657](https://github.com/BerriAI/litellm/pull/34657)
- test(e2e): cover google-native generateContent framing and prometheus queue time by [@​mubashir1osmani](https://github.com/mubashir1osmani) in [#​34650](https://github.com/BerriAI/litellm/pull/34650)
- chore(ci): promote internal staging to main by [@​tin-berri](https://github.com/tin-berri) in [#​36560](https://github.com/BerriAI/litellm/pull/36560)
- feat(router): make routing groups callable as virtual models and list them in /v1/models by [@​tin-berri](https://github.com/tin-berri) in [#​36519](https://github.com/BerriAI/litellm/pull/36519)
- fix(xai): bill web\_search from server\_side\_tool\_usage\_details by [@​geraint0923](https://github.com/geraint0923) in [#​30817](https://github.com/BerriAI/litellm/pull/30817)
- fix(responses): init completed\_response on bridge streaming iterator ([#​35411](https://github.com/BerriAI/litellm/issues/35411)) by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​35413](https://github.com/BerriAI/litellm/pull/35413)
- fix(batches): attribute Anthropic passthrough batch cost to the creating key, team and tags by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36468](https://github.com/BerriAI/litellm/pull/36468)
- feat(dashscope): add latest Model Studio models to the cost map by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36496](https://github.com/BerriAI/litellm/pull/36496)
- fix(proxy): track streamed passthrough Responses cost by [@​william-xue](https://github.com/william-xue) in [#​36529](https://github.com/BerriAI/litellm/pull/36529)
- fix(model\_prices): advertise native structured output on every Bedrock DeepSeek V3.2 and GLM 5 id by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36597](https://github.com/BerriAI/litellm/pull/36597)
- test(bedrock): repoint live Claude tests off the retired Claude 3 Sonnet by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36600](https://github.com/BerriAI/litellm/pull/36600)
- fix(anthropic): preserve speed=fast in usage for /v1/messages and pass-through by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36447](https://github.com/BerriAI/litellm/pull/36447)
- fix(proxy): forward resolved provider and deployment pricing in /cost/estimate by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​35880](https://github.com/BerriAI/litellm/pull/35880)
- feat(proxy): global SSE keepalive ping interval for OpenAI-shaped streaming routes by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36154](https://github.com/BerriAI/litellm/pull/36154)
- fix(responses): preserve Codex namespace tool calls by [@​dcadenas](https://github.com/dcadenas) in [#​32536](https://github.com/BerriAI/litellm/pull/32536)
- fix(nvidia\_nim): preserve image passages and stop sending top\_k to /v1/ranking by [@​atomic](https://github.com/atomic) in [#​34177](https://github.com/BerriAI/litellm/pull/34177)
- fix: refactor HTTP handler initialization with client support by [@​Praveen11558](https://github.com/Praveen11558) in [#​30952](https://github.com/BerriAI/litellm/pull/30952)
- feat(lint): gate writable TypedDict fields with LIT012 by [@​mateo-berri](https://github.com/mateo-berri) in [#​36590](https://github.com/BerriAI/litellm/pull/36590)
- perf(proxy): stagger scheduled background jobs across jobs and pods by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36589](https://github.com/BerriAI/litellm/pull/36589)
- test: remove four mirror test files that exercise none of their module by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​34635](https://github.com/BerriAI/litellm/pull/34635)
- fix(router): stop re-applying router-selecting request tags to the routed tier's deployments by [@​mateo-berri](https://github.com/mateo-berri) in [#​36628](https://github.com/BerriAI/litellm/pull/36628)
- test: remove tests that never execute by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36681](https://github.com/BerriAI/litellm/pull/36681)
- fix(ui): align spend and budget columns by [@​daniel-meismer-zocdoc](https://github.com/daniel-meismer-zocdoc) in [#​35176](https://github.com/BerriAI/litellm/pull/35176)
- test: rename tests that a later definition shadowed by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36685](https://github.com/BerriAI/litellm/pull/36685)
- fix(passthrough): carry the budget reservation into request metadata by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36592](https://github.com/BerriAI/litellm/pull/36592)
- fix(mcp): bound MCP client requests with a session read timeout by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36675](https://github.com/BerriAI/litellm/pull/36675)
- fix(proxy): log requests rejected for an unparsable body in spend logs by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36673](https://github.com/BerriAI/litellm/pull/36673)
- refactor(ui): migrate cost-optimization to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36629](https://github.com/BerriAI/litellm/pull/36629)
- refactor(ui): migrate cost-tracking to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36631](https://github.com/BerriAI/litellm/pull/36631)
- refactor(ui): migrate admin-panel to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36635](https://github.com/BerriAI/litellm/pull/36635)
- refactor(ui): migrate users dashboard to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36642](https://github.com/BerriAI/litellm/pull/36642)
- refactor(ui): migrate prompts to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36643](https://github.com/BerriAI/litellm/pull/36643)
- refactor(ui): migrate team settings to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36641](https://github.com/BerriAI/litellm/pull/36641)
- refactor(ui): migrate models-and-endpoints to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36648](https://github.com/BerriAI/litellm/pull/36648)
- refactor(ui): migrate policy impact popover to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36653](https://github.com/BerriAI/litellm/pull/36653)
- fix(proxy): expand config-defined model access groups when resolving team models for /v2/model/info by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​34211](https://github.com/BerriAI/litellm/pull/34211)
- fix(batches): strip NUL bytes from passthrough batch tags before the managed object write by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36688](https://github.com/BerriAI/litellm/pull/36688)
- test(e2e-ui): verify UI mutations against the API instead of trusting the toast by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36632](https://github.com/BerriAI/litellm/pull/36632)
- fix(proxy): serialize model reconciles so concurrent model writes stop evicting each other by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36687](https://github.com/BerriAI/litellm/pull/36687)
- chore(e2e): port the compat-matrix cron publisher to tests/e2e/claude\_code by [@​mateo-berri](https://github.com/mateo-berri) in [#​36465](https://github.com/BerriAI/litellm/pull/36465)
- fix(router): never price a strategy-router alias by [@​tin-berri](https://github.com/tin-berri) in [#​36691](https://github.com/BerriAI/litellm/pull/36691)
- feat(model\_prices): add NVIDIA Nemotron 3.5 Lightning on OpenRouter and DeepInfra by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36696](https://github.com/BerriAI/litellm/pull/36696)
- feat(terraform/aws): make VPC, Aurora, and Redis optional by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36676](https://github.com/BerriAI/litellm/pull/36676)
- feat(ui): warn in the Admin UI when no Redis is configured by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36495](https://github.com/BerriAI/litellm/pull/36495)
- fix(ui): show and edit key-level router settings on a virtual key by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36674](https://github.com/BerriAI/litellm/pull/36674)
- fix(router): forward auto-router alias params from the marker entry, not the first same-name deployment by [@​mateo-berri](https://github.com/mateo-berri) in [#​36626](https://github.com/BerriAI/litellm/pull/36626)
- fix(bedrock\_mantle): 1M context window and long-context pricing for GPT-5.6 Sol/Terra/Luna by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36698](https://github.com/BerriAI/litellm/pull/36698)
- fix(model\_prices): sync the Groq registry with Groq's docs by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36664](https://github.com/BerriAI/litellm/pull/36664)
- fix(router): let untagged requests bypass a tagged pre-routing strategy on shared model names by [@​mateo-berri](https://github.com/mateo-berri) in [#​36627](https://github.com/BerriAI/litellm/pull/36627)
- fix(spend): stop losing spend log rows when a flush is cancelled by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​34826](https://github.com/BerriAI/litellm/pull/34826)
- docs(claude): drop the @​ prefix from the PR template path by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36726](https://github.com/BerriAI/litellm/pull/36726)
- fix(langfuse): emit otel trace version and release on the keys langfuse v4 reads by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36702](https://github.com/BerriAI/litellm/pull/36702)
- test(interactions): follow Google spec drift replacing Turn with typed steps by [@​mateo-berri](https://github.com/mateo-berri) in [#​36730](https://github.com/BerriAI/litellm/pull/36730)
- refactor(ui): migrate team detail controls to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36695](https://github.com/BerriAI/litellm/pull/36695)
- refactor(ui): migrate guardrail and duration controls to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36693](https://github.com/BerriAI/litellm/pull/36693)
- refactor(ui): migrate guardrails-monitor, projects, logs to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​34606](https://github.com/BerriAI/litellm/pull/34606)
- refactor(ui): migrate search and user controls to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36694](https://github.com/BerriAI/litellm/pull/36694)
- fix(guardrails): scan and re-emit raw Anthropic SSE streams in the bedrock post-call hook by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36598](https://github.com/BerriAI/litellm/pull/36598)
- fix(helm): render nodeSelector on the migrations job by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36747](https://github.com/BerriAI/litellm/pull/36747)
- fix(langfuse): coerce header-sourced mask and trace-update steering values by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36740](https://github.com/BerriAI/litellm/pull/36740)
- refactor(ui): migrate usage tables to shared DataTable by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36707](https://github.com/BerriAI/litellm/pull/36707)
- refactor(ui): migrate guardrails monitor table to shared DataTable by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36709](https://github.com/BerriAI/litellm/pull/36709)
- refactor(ui): migrate guardrails content tables to shared DataTable by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36708](https://github.com/BerriAI/litellm/pull/36708)
- feat(gemini): day-0 pricing for gemini-3.7-flash by [@​mateo-berri](https://github.com/mateo-berri) in [#​36792](https://github.com/BerriAI/litellm/pull/36792)
- ci: promote staging to main by [@​mateo-berri](https://github.com/mateo-berri) in [#​36725](https://github.com/BerriAI/litellm/pull/36725)
- build(deps): bump nanoid to 3.3.18 to clear osv-scan by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36787](https://github.com/BerriAI/litellm/pull/36787)
- fix(router): stop scoring system prompt text for code/technical complexity by [@​tin-berri](https://github.com/tin-berri) in [#​36721](https://github.com/BerriAI/litellm/pull/36721)
- feat(complexity\_router): calibrate the classifier rubric with worked examples, selectable per router by [@​tin-berri](https://github.com/tin-berri) in [#​36578](https://github.com/BerriAI/litellm/pull/36578)
- fix(interactions): map step and turn history to Responses API roles and content types by [@​mateo-berri](https://github.com/mateo-berri) in [#​36733](https://github.com/BerriAI/litellm/pull/36733)
- fix(ui): restore playground model filtering by endpoint by [@​mubashir1osmani](https://github.com/mubashir1osmani) in [#​36130](https://github.com/BerriAI/litellm/pull/36130)
- fix(proxy/batches): stop forwarding custom\_llm\_provider twice in list and cancel by [@​anxkhn](https://github.com/anxkhn) in [#​32813](https://github.com/BerriAI/litellm/pull/32813)
- refactor(ui): migrate TokenFlow and JsonViewer to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36735](https://github.com/BerriAI/litellm/pull/36735)
- feat: pre-adoption shadow eval for the auto-router (blind pairwise judge, derived state) by [@​tin-berri](https://github.com/tin-berri) in [#​36587](https://github.com/BerriAI/litellm/pull/36587)
- refactor(ui): migrate SimpleMessageBlock and SimpleToolCallBlock to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36737](https://github.com/BerriAI/litellm/pull/36737)
- refactor(ui): migrate HistoryTree and CollapsibleMessage to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36738](https://github.com/BerriAI/litellm/pull/36738)
- refactor: replace Any with precise types across responses, proxy, and llms modules by [@​mateo-berri](https://github.com/mateo-berri) in [#​36763](https://github.com/BerriAI/litellm/pull/36763)
- refactor(ui): migrate TruncatedValue and OutputCard to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36739](https://github.com/BerriAI/litellm/pull/36739)
- refactor(ui): migrate SectionHeader and ToolsSection to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36793](https://github.com/BerriAI/litellm/pull/36793)
- feat(ui): migrate playground chat controls to shadcn by [@​mubashir1osmani](https://github.com/mubashir1osmani) in [#​36129](https://github.com/BerriAI/litellm/pull/36129)
- feat(xai): day-0 pricing for grok-4.6 by [@​mateo-berri](https://github.com/mateo-berri) in [#​36805](https://github.com/BerriAI/litellm/pull/36805)
- feat(ui): highlight Auto Router in the navbar announcement by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36315](https://github.com/BerriAI/litellm/pull/36315)
- test(e2e): assert the model allow-list permits, not only denies by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36823](https://github.com/BerriAI/litellm/pull/36823)
- fix(proxy): tolerate a concurrent creator when creating spend views by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36824](https://github.com/BerriAI/litellm/pull/36824)
- fix(proxy): honor explicit null budget\_duration on team and key create + clearable UI dropdowns by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​36699](https://github.com/BerriAI/litellm/pull/36699)
- feat(model\_prices): add meta/muse-spark-1.2 and its contributor tier by [@​mateo-berri](https://github.com/mateo-berri) in [#​36717](https://github.com/BerriAI/litellm/pull/36717)
- fix(auth): carry team grants in lite login session tokens by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36826](https://github.com/BerriAI/litellm/pull/36826)
- feat(ui): show provider prompt cache tokens in chat response metrics by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36827](https://github.com/BerriAI/litellm/pull/36827)
- fix(auth): stop the team fallback from widening model access by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36837](https://github.com/BerriAI/litellm/pull/36837)
- fix(proxy/team): resolve member\_delete cleanup by user id, not the addressed email by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36839](https://github.com/BerriAI/litellm/pull/36839)
- fix(cli): launch agents as a child process on Windows by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36822](https://github.com/BerriAI/litellm/pull/36822)
- feat(ui): shadow evals tab beside auto-router usage by [@​tin-berri](https://github.com/tin-berri) in [#​36588](https://github.com/BerriAI/litellm/pull/36588)
- feat(cli): make the hidden `lite` command list configurable by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36816](https://github.com/BerriAI/litellm/pull/36816)
- feat(azure\_ai): add Fireworks FW model pricing on Azure AI Foundry by [@​emerzon](https://github.com/emerzon) in [#​35613](https://github.com/BerriAI/litellm/pull/35613)
- fix: enable xhigh reasoning support for gpt-5.4-mini models by [@​emerzon](https://github.com/emerzon) in [#​26909](https://github.com/BerriAI/litellm/pull/26909)
- feat(azure-ai): add Grok 4.3 model metadata by [@​emerzon](https://github.com/emerzon) in [#​27932](https://github.com/BerriAI/litellm/pull/27932)
- feat(ui): render request metrics on the /ui/chat surface by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36845](https://github.com/BerriAI/litellm/pull/36845)
- fix(ui): stop a deselected MCP server keeping its grant on a virtual key by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36840](https://github.com/BerriAI/litellm/pull/36840)
- fix(team): sweep dangling team references and cache on team delete by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36819](https://github.com/BerriAI/litellm/pull/36819)
- fix(mcp): resolve admin OAuth sessions from any worker via DB-backed drafts by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36844](https://github.com/BerriAI/litellm/pull/36844)
- refactor(ui): migrate usage to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36834](https://github.com/BerriAI/litellm/pull/36834)
- refactor(ui): migrate guardrails-monitor to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36838](https://github.com/BerriAI/litellm/pull/36838)
- refactor(ui): migrate playground to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36847](https://github.com/BerriAI/litellm/pull/36847)
- refactor(ui): migrate guardrails to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36832](https://github.com/BerriAI/litellm/pull/36832)
- fix(batches): stop uncostable batches from starving the cost poll page by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36714](https://github.com/BerriAI/litellm/pull/36714)
- perf(spend-logs): bound retention cleanup so one run cannot saturate the database by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36594](https://github.com/BerriAI/litellm/pull/36594)
- fix(proxy): fail config load when a callbacks entry is not dispatchable by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36858](https://github.com/BerriAI/litellm/pull/36858)
- fix(bedrock): hoist custom.defer\_loading before dropping custom on invoke tools by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36855](https://github.com/BerriAI/litellm/pull/36855)
- fix(access groups): sync assigned\_key\_ids from the key write paths by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36843](https://github.com/BerriAI/litellm/pull/36843)
- fix(mcp): expose client HTTP headers to logging callbacks and hooks by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36724](https://github.com/BerriAI/litellm/pull/36724)
- fix(ptu): stop per-token billing on a PTU-configured deployment by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36829](https://github.com/BerriAI/litellm/pull/36829)
- fix(ui): add nvidia riva to the model provider list by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36769](https://github.com/BerriAI/litellm/pull/36769)
- fix(scripts): end make check with a ran/skipped summary and verdict by [@​mateo-berri](https://github.com/mateo-berri) in [#​36864](https://github.com/BerriAI/litellm/pull/36864)
- fix(proxy): track spend for OpenAI passthrough /v1/embeddings by [@​lostmartian](https://github.com/lostmartian) in [#​36660](https://github.com/BerriAI/litellm/pull/36660)
- test(proxy): stop monkeypatch.undo re-planting fixture-mocked prisma\_client by [@​mateo-berri](https://github.com/mateo-berri) in [#​36872](https://github.com/BerriAI/litellm/pull/36872)
- fix(access groups): sync assigned\_team\_ids from the team write paths by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36825](https://github.com/BerriAI/litellm/pull/36825)
- ci: drop the CircleCI ui\_build and ui\_unit\_tests jobs by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36893](https://github.com/BerriAI/litellm/pull/36893)
- fix(langfuse)!: source the emitted metadata blob from StandardLoggingPayload by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36744](https://github.com/BerriAI/litellm/pull/36744)
- refactor(ui): migrate Navbar off antd to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36902](https://github.com/BerriAI/litellm/pull/36902)
- refactor(ui): migrate log details drawer off antd to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36904](https://github.com/BerriAI/litellm/pull/36904)
- refactor(ui): migrate AI Hub off antd and tremor to shadcn by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36908](https://github.com/BerriAI/litellm/pull/36908)
- refactor(ui): move the shared dropdowns and selectors onto shadcn primitives by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36924](https://github.com/BerriAI/litellm/pull/36924)
- refactor(ui): move the root-level dashboard components onto shadcn primitives by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36927](https://github.com/BerriAI/litellm/pull/36927)
- refactor(ui): move the settings page and bulk user invite onto shadcn primitives by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36936](https://github.com/BerriAI/litellm/pull/36936)
- refactor(ui): move the cost tracking components onto shadcn primitives by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36955](https://github.com/BerriAI/litellm/pull/36955)
- ci: drop the duplicate proxy\_unit\_tests letter-shard workflow by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36866](https://github.com/BerriAI/litellm/pull/36866)
- refactor(ui): migrate shared common\_components off antd and tremor by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36910](https://github.com/BerriAI/litellm/pull/36910)
- refactor(ui): migrate key info and permissions views off antd and tremor by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36913](https://github.com/BerriAI/litellm/pull/36913)
- feat(proxy): serve Anthropic-native /v1/models for Claude Code gateway discovery by [@​Ar-maan05](https://github.com/Ar-maan05) in [#​35455](https://github.com/BerriAI/litellm/pull/35455)
- refactor(ui): migrate router settings and shared badges off antd and tremor by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36915](https://github.com/BerriAI/litellm/pull/36915)
- refactor(ui): move the model hub and model select onto shadcn primitives by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36918](https://github.com/BerriAI/litellm/pull/36918)
- fix(ui): keep the cost tracking removal confirmation open until it settles by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36960](https://github.com/BerriAI/litellm/pull/36960)
- refactor(ui): declare DateRangePickerValue locally instead of importing it from tremor by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36962](https://github.com/BerriAI/litellm/pull/36962)
- fix(main): an explicit provider outranks a known OpenAI model name by [@​FahimaGold](https://github.com/FahimaGold) in [#​36800](https://github.com/BerriAI/litellm/pull/36800)
- fix(exception\_mapping): bare 429 in an error body no longer outranks the status code by [@​FahimaGold](https://github.com/FahimaGold) in [#​36705](https://github.com/BerriAI/litellm/pull/36705)
- refactor(ui): move MCP permission panels onto shadcn primitives by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36964](https://github.com/BerriAI/litellm/pull/36964)
- refactor(ui): migrate ten small dashboard files off antd and tremor by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36966](https://github.com/BerriAI/litellm/pull/36966)
- fix(proxy): force prisma recreate on postgres cached-plan error by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36428](https://github.com/BerriAI/litellm/pull/36428)
- fix(transcription): stop a zero output rate from zeroing transcription cost by [@​hMED22](https://github.com/hMED22) in [#​36914](https://github.com/BerriAI/litellm/pull/36914)
- fix(langfuse): restrict trace steering keys to real langfuse trace fields by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36862](https://github.com/BerriAI/litellm/pull/36862)
- Revert "fix(auth): stop the team fallback from widening model access" ([#​36837](https://github.com/BerriAI/litellm/issues/36837)) by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36982](https://github.com/BerriAI/litellm/pull/36982)
- fix(ui): show zeroed auto-router usage stats when a window has no sessions by [@​tin-berri](https://github.com/tin-berri) in [#​36868](https://github.com/BerriAI/litellm/pull/36868)
- fix(mcp): keep admin-entered oauth endpoints in management reads by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36888](https://github.com/BerriAI/litellm/pull/36888)
- fix(ui): distinguish hosted and local vLLM in the provider dropdown by [@​mateo-berri](https://github.com/mateo-berri) in [#​36974](https://github.com/BerriAI/litellm/pull/36974)
- fix(openai,azure): return a length-truncated 200 when the output budget fits no token by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36859](https://github.com/BerriAI/litellm/pull/36859)
- fix(proxy): always emit the Anthropic /v1/models token limits, null when unknown by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36961](https://github.com/BerriAI/litellm/pull/36961)
- feat(helm): add startupProbe and hpa.behavior to the componentized chart by [@​Louis-Vauterin](https://github.com/Louis-Vauterin) in [#​36382](https://github.com/BerriAI/litellm/pull/36382)
- fix(proxy): serve aggregate MCP endpoint on bare /mcp instead of 307-redirecting by [@​tin-berri](https://github.com/tin-berri) in [#​34845](https://github.com/BerriAI/litellm/pull/34845)
- feat(shadow\_eval): add reverse-direction shadow eval jobs by [@​tin-berri](https://github.com/tin-berri) in [#​36865](https://github.com/BerriAI/litellm/pull/36865)
- fix(proxy): requeue Redis spend buffer transactions when the DB commit fails by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​33881](https://github.com/BerriAI/litellm/pull/33881)
- feat(search): add Nimble as a search provider by [@​ilchemla](https://github.com/ilchemla) in [#​36347](https://github.com/BerriAI/litellm/pull/36347)
- fix(mcp): drop caller host and configured upstream headers from logged metadata by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​36901](https://github.com/BerriAI/litellm/pull/36901)
- fix(azure\_ai): recognize real Search doc endpoints so teams can read/write via passthrough by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36798](https://github.com/BerriAI/litellm/pull/36798)
- fix(anthropic): aggregate 5m/1h cache-write split across iterations path by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​34860](https://github.com/BerriAI/litellm/pull/34860)
- fix(anthropic cost): apply regional geo uplift to cached tokens by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​34850](https://github.com/BerriAI/litellm/pull/34850)
- fix(ui): match the MCP servers count badge to its sibling permission badges by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36984](https://github.com/BerriAI/litellm/pull/36984)
- fix(batches): mark terminal batch with no output file as processed in CheckBatchCost by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​35360](https://github.com/BerriAI/litellm/pull/35360)
- fix(caching): cache anthropic /v1/messages responses, including streaming by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​34581](https://github.com/BerriAI/litellm/pull/34581)
- fix(anthropic\_messages): make tool\_result images visible to OpenAI-compatible providers by [@​hMED22](https://github.com/hMED22) in [#​34462](https://github.com/BerriAI/litellm/pull/34462)
- feat(fireworks\_ai): translate NIM/vLLM extra params to Fireworks-native args by [@​milesadkins](https://github.com/milesadkins) in [#​35969](https://github.com/BerriAI/litellm/pull/35969)
- fix(ui): stop the models tab strip from scrolling vertically by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36993](https://github.com/BerriAI/litellm/pull/36993)
- fix(ui): anchor chips-combobox popups to the field instead of the inner input by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36995](https://github.com/BerriAI/litellm/pull/36995)
- feat(proxy): per-component response cost headers by [@​erensh27](https://github.com/erensh27) in [#​36965](https://github.com/BerriAI/litellm/pull/36965)
- fix(cost): track OpenAI/Azure web search tool cost per call by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​35286](https://github.com/BerriAI/litellm/pull/35286)
- fix(bedrock): resolve aliases in batch file records by [@​daleselaji-dev](https://github.com/daleselaji-dev) in [#​36159](https://github.com/BerriAI/litellm/pull/36159)
- fix: report real token usage on guardrail-blocked /v1/responses replies by [@​guptaishaan](https://github.com/guptaishaan) in [#​36907](https://github.com/BerriAI/litellm/pull/36907)
- fix(proxy): requeue spend logs when the DB write fails with a transport error by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36716](https://github.com/BerriAI/litellm/pull/36716)
- fix(cost): tiered pricing supports cache creation cost and is all-or-nothing by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36720](https://github.com/BerriAI/litellm/pull/36720)
- fix(vertex\_ai): translate /v1/embeddings batch rows to the Gemini embedding shape by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​35092](https://github.com/BerriAI/litellm/pull/35092)
- docs(claude): require ReadOnly on every TypedDict field (LIT012) by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​37005](https://github.com/BerriAI/litellm/pull/37005)
- refactor(ui): migrate access group create modal to RHF + zod + shadcn by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​37033](https://github.com/BerriAI/litellm/pull/37033)
- refactor(ui): re-sync badge and skeleton onto the base-vega shadcn style by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​36991](https://github.com/BerriAI/litellm/pull/36991)
- feat(ui): link user detail team names to team pages by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​37022](https://github.com/BerriAI/litellm/pull/37022)
- fix(model\_prices): correct DeepSeek V4 max output tokens by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36925](https://github.com/BerriAI/litellm/pull/36925)
- fix(ui): rename models table Status column to Source by [@​ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#​37021](https://github.com/BerriAI/litellm/pull/37021)
- chore: bump litellm-enterprise 0.1.55 -> 0.1.56, litellm-proxy-extras 0.4.85 -> 0.4.86 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​37045](https://github.com/BerriAI/litellm/pull/37045)
- feat(proxy): gate the Global Control Plane worker registry on an enterprise license by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​36996](https://github.com/BerriAI/litellm/pull/36996)
- fix(model\_prices): add gemini 3.1 flash tts preview and legacy OpenAI shutdown dates by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36788](https://github.com/BerriAI/litellm/pull/36788)
- fix(panw\_prisma\_airs): surface scan\_id on allowed requests by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​37037](https://github.com/BerriAI/litellm/pull/37037)
- fix(model\_map): flag native structured outputs on Anthropic-direct claude-sonnet-5 and claude-haiku-4-5 by [@​anmolg1997](https://github.com/anmolg1997) in [#​35930](https://github.com/BerriAI/litellm/pull/35930)
- fix(router): stop get\_router\_model\_info from wiping cached pricing by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36985](https://github.com/BerriAI/litellm/pull/36985)
- fix(redis): unwrap decorated \_\_init\_\_s when deriving the from\_url kwargs allowlist by [@​anmolg1997](https://github.com/anmolg1997) in [#​36654](https://github.com/BerriAI/litellm/pull/36654)
- fix(proxy): reserve the larger declared output budget for TPM limits by [@​yassin-berriai](https://github.com/yassin-berriai) in [#​37001](https://github.com/BerriAI/litellm/pull/37001)
- fix(databricks): surface provider usage, including prompt-cache counts, in streaming chunks by [@​pokepoke81](https://github.com/pokepoke81) in [#​36943](https://github.com/BerriAI/litellm/pull/36943)
- fix(spend): give a batch's cost row a primary key of its own by [@​marty-sullivan](https://github.com/marty-sullivan) in [#​36876](https://github.com/BerriAI/litellm/pull/36876)
- feat: shadow eval samples /v1/messages and /v1/responses traffic by [@​tin-berri](https://github.com/tin-berri) in [#​36830](https://github.com/BerriAI/litellm/pull/36830)
- fix(ptu): stop a PTU deployment billing for grounded search by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​37043](https://github.com/BerriAI/litellm/pull/37043)
- fix(fireworks\_ai): support router slugs via routers/ prefix by [@​heathriel](https://github.com/heathriel) in [#​34257](https://github.com/BerriAI/litellm/pull/34257)
- fix(bedrock): register managed-batch litellm\_params so they stop leaking to the provider (internal copy of [#​36633](https://github.com/BerriAI/litellm/issues/36633)) by [@​mateo-berri](https://github.com/mateo-berri) in [#​37048](https://github.com/BerriAI/litellm/pull/37048)
- fix(bedrock): resolve the managed-batch output bucket on every path that reads it by [@​mateo-berri](https://github.com/mateo-berri) in [#​37047](https://github.com/BerriAI/litellm/pull/37047)
- fix(bedrock): resolve the managed-batch output bucket on every path that reads it by [@​marty-sullivan](https://github.com/marty-sullivan) in [#​36634](https://github.com/BerriAI/litellm/pull/36634)
- feat(scripts): queue heavy gates behind a machine-wide slot lock by [@​mateo-berri](https://github.com/mateo-berri) in [#​36988](https://github.com/BerriAI/litellm/pull/36988)
- feat(mcp): scope gateway session bearers to the RFC 8707 resource by [@​tin-berri](https://github.com/tin-berri) in [#​35045](https://github.com/BerriAI/litellm/pull/35045)
- feat(ui): direction picker and reverse-mode display for shadow evals by [@​tin-berri](https://github.com/tin-berri) in [#​36994](https://github.com/BerriAI/litellm/pull/36994)
- fix(guardrails): return the full PANW AIRS scan response on blocked requests by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​37036](https://github.com/BerriAI/litellm/pull/37036)
- fix(passthrough): stop forwarding client Accept-Encoding upstream by [@​mateo-berri](https://github.com/mateo-berri) in [#​37058](https://github.com/BerriAI/litellm/pull/37058)
- fix(batches): account a managed batch's cost exactly once by [@​mateo-berri](https://github.com/mateo-berri) in [#​37050](https://github.com/BerriAI/litellm/pull/37050)
- fix(panw\_prisma\_airs): scan tool call args as plain text, not a tool\_event by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​37038](https://github.com/BerriAI/litellm/pull/37038)
- feat(lint): exempt TypedDict-annotated dict literals from LIT002 by [@​mateo-berri](https://github.com/mateo-berri) in [#​36869](https://github.com/BerriAI/litellm/pull/36869)
- docs(claude): tell agents to let heavy gates queue for machine-wide slots by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​37057](https://github.com/BerriAI/litellm/pull/37057)
- test: unstick the suites CircleCI is failing on by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​37059](https://github.com/BerriAI/litellm/pull/37059)
- docs(github): proof-of-fix section shows only the latest run as Before/After with nested cases by [@​mateo-berri](https://github.com/mateo-berri) in [#​37063](https://github.com/BerriAI/litellm/pull/37063)
- test(e2e): assert provider error shape instead of pinned prose by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​37065](https://github.com/BerriAI/litellm/pull/37065)
- fix(ui): de-duplicate the reset budget option and polish shadcn surfaces by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​37010](https://github.com/BerriAI/litellm/pull/37010)
- chore: rebuild Admin UI bundle from litellm\_internal\_staging by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​37066](https://github.com/BerriAI/litellm/pull/37066)
- test(e2e/ui): assert the log drawer chevrons by their lucide classes by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​37069](https://github.com/BerriAI/litellm/pull/37069)
- chore(ci): promote internal staging to main by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​37042](https://github.com/BerriAI/litellm/pull/37042)
- fix(ui): keep completion-mode models in the playground chat dropdown (backport to rc/1.98.0) by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​37955](https://github.com/BerriAI/litellm/pull/37955)
##### New Contributors
- [@​kr0k](https://github.com/kr0k) made their first contribution in [#​33196](https://github.com/BerriAI/litellm/pull/33196)
- [@​HuanQian571](https://github.com/HuanQian571) made their first contribution in [#​35773](https://github.com/BerriAI/litellm/pull/35773)
- [@​alexshtf](https://github.com/alexshtf) made their first contribution in [#​35669](https://github.com/BerriAI/litellm/pull/35669)
- [@​vairodp](https://github.com/vairodp) made their first contribution in [#​33490](https://github.com/BerriAI/litellm/pull/33490)
- [@​fancybear-dev](https://github.com/fancybear-dev) made their first contribution in [#​36196](https://github.com/BerriAI/litellm/pull/36196)
- [@​daleselaji-dev](https://github.com/daleselaji-dev) made their first contribution in [#​36160](https://github.com/BerriAI/litellm/pull/36160)
- [@​eugene-yao-zocdoc](https://github.com/eugene-yao-zocdoc) made their first contribution in [#​34290](https://github.com/BerriAI/litellm/pull/34290)
- [@​geraint0923](https://github.com/geraint0923) made their first contribution in [#​30817](https://github.com/BerriAI/litellm/pull/30817)
- [@​william-xue](https://github.com/william-xue) made their first contribution in [#​36529](https://github.com/BerriAI/litellm/pull/36529)
- [@​dcadenas](https://github.com/dcadenas) made their first contribution in [#​32536](https://github.com/BerriAI/litellm/pull/32536)
- [@​atomic](https://github.com/atomic) made their first contribution in [#​34177](https://github.com/BerriAI/litellm/pull/34177)
- [@​Praveen11558](https://github.com/Praveen11558) made their first contribution in [#​30952](https://github.com/BerriAI/litellm/pull/30952)
- [@​anxkhn](https://github.com/anxkhn) made their first contribution in [#​32813](https://github.com/BerriAI/litellm/pull/32813)
- [@​lostmartian](https://github.com/lostmartian) made their first contribution in [#​36660](https://github.com/BerriAI/litellm/pull/36660)
- [@​FahimaGold](https://github.com/FahimaGold) made their first contribution in [#​36800](https://github.com/BerriAI/litellm/pull/36800)
- [@​Louis-Vauterin](https://github.com/Louis-Vauterin) made their first contribution in [#​36382](https://github.com/BerriAI/litellm/pull/36382)
- [@​ilchemla](https://github.com/ilchemla) made their first contribution in [#​36347](https://github.com/BerriAI/litellm/pull/36347)
- [@​milesadkins](https://github.com/milesadkins) made their first contribution in [#​35969](https://github.com/BerriAI/litellm/pull/35969)
- [@​erensh27](https://github.com/erensh27) made their first contribution in [#​36965](https://github.com/BerriAI/litellm/pull/36965)
- [@​guptaishaan](https://github.com/guptaishaan) made their first contribution in [#​36907](https://github.com/BerriAI/litellm/pull/36907)
- [@​pokepoke81](https://github.com/pokepoke81) made their first contribution in [#​36943](https://github.com/BerriAI/litellm/pull/36943)
- [@​heathriel](https://github.com/heathriel) made their first contribution in [#​34257](https://github.com/BerriAI/litellm/pull/34257)
**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.97.0...v1.98.0>
### [`v1.98.0`](https://github.com/BerriAI/litellm/releases/tag/v1.98.0)
[Compare Source](https://github.com/BerriAI/litellm/compare/v1.97.0...v1.98.0)
##### Verify Docker Image Signature
All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).
**Verify using the pinned commit hash (recommended):**
A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:
```bash
cosign verify \
--key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
ghcr.io/berriai/litellm:v1.98.0
```
**Verify using the release tag (convenience):**
Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:
```bash
cosign verify \
--key https://raw.githubusercontent.com/BerriAI/litellm/v1.98.0/cosign.pub \
ghcr.io/berriai/litellm:v1.98.0
```
Expected output:
```
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
```
***
##### What's Changed
- fix(bedrock): drop toolSpec.strict for Claude Sonnet 5 on Converse by [@​kr0k](https://github.com/kr0k) in [#​33196](https://github.com/BerriAI/litellm/pull/33196)
- fix(batches): attribute Vertex passthrough batch cost to key/team/tags by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​34456](https://github.com/BerriAI/litellm/pull/34456)
- docs: rewrite the CLAUDE.md comment rule with explicit exceptions by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36301](https://github.com/BerriAI/litellm/pull/36301)
- fix(proxy): scope file list pagination cursors to the caller by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36093](https://github.com/BerriAI/litellm/pull/36093)
- fix(proxy): skip prisma-dependent hooks when no database is attached by [@​mateo-berri](https://github.com/mateo-berri) in [#​36273](https://github.com/BerriAI/litellm/pull/36273)
- fix(proxy): report has\_more false on caller-scoped file list pages by [@​mateo-berri](https://github.com/mateo-berri) in [#​36326](https://github.com/BerriAI/litellm/pull/36326)
- fix(proxy): restore management\_v1 query-param validation under fastapi>=0.140.7 by [@​HuanQian571](https://github.com/HuanQian571) in [#​35773](https://github.com/BerriAI/litellm/pull/35773)
- fix(proxy): stop /{provider}/v1/files from capturing /openai\_passthrough by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36092](https://github.com/BerriAI/litellm/pull/36092)
- chore(typing): remove 914 basedpyright Any errors across 16 hotspot files by [@​mateo-berri](https://github.com/mateo-berri) in [#​36386](https://github.com/BerriAI/litellm/pull/36386)
- fix(router): keep batch fallbacks inside the model group that owns the file by [@​mateo-berri](https://github.com/mateo-berri) in [#​36181](https://github.com/BerriAI/litellm/pull/36181)
- feat(ptu): configure provisioned-throughput flat cost on a model deployment by [@​yucheng-berri](https://github.com/yucheng-berri) in [#​35341](https://github.com/BerriAI/litellm/pull/35341)
- docs: clarify the CLAUDE.md comment exceptions are any-of by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​36421](https://github.com/BerriAI/litellm/pull/36421)
- docs: replace the Changes …
Relevant issues
Closes #31873
Supersedes #33260 (rebuilt on top of
litellm_log_correlation/litellm_internal_staginginstead oflitellm_oss_staging)Linear ticket
None
Docs
BerriAI/litellm-docs#645
Pre-Submission checklist
Screenshots / Proof of Fix
This PR adds structured logging fields and does not change LLM provider behavior; proof of fix is via the test suite (see Changes) plus live runs against real provider APIs, no mocks (see verification comments on this PR): a live local proxy (real Postgres, real OpenAI-backed deployment) confirming
trace_id/session_idare correctly and consistently stamped across an entire request's logging lifecycle including async spend-tracking/callback dispatch, with no cross-request bleed between two concurrent sessions; and a standalone script confirming direct sync SDK calls (litellm.completion(), streaming and non-streaming, against a real model) never touch the correlation contextvars at all, per this PR's async/proxy-only scopingType
Refactoring / New Feature
Changes
Adds opt-in request correlation to litellm's logging, gated entirely behind a single flag,
falseby default:With the flag on, every log line emitted through the standard litellm loggers automatically includes
trace_idandsession_id, sourced from Python contextvars, andStandardLoggingPayload(sent to logging integrations like S3 and Langfuse) gets an independentsession_idfield. With the flag off (default), nothing about existing logging behavior orStandardLoggingPayload's shape changesThe implementation follows the standard Python logging pattern: a
CorrelationContextFilter(logging.Filter)subclass stamps eachLogRecordwith the contextvar values before the formatter runs; the filter itself no-ops unless the flag is on.JsonFormatteralready has a loop that promotes arbitraryrecord.__dict__entries to first-class JSON fields, so no formatter changes were needed there. This mirrors the existingSecretRedactionFilterpattern in the same module. A newCorrelationPlainFormattergives plaintext logs (json_logsoff) the same[trace_id=... session_id=...]suffix, so the flag has a visible effect regardless of log formatset_trace_id()andset_session_id()are the public API for callers to bind correlation IDs.litellm.litellm_core_utils.litellm_logging.Logging.__init__()already calls both at request start, so every log line emitted during a litellm call inherits the correct IDs with no per-call-site wiring. Contextvars provide natural async isolation: concurrent tasks each see only their own IDs (verified live against 8 concurrent proxy requests, see verification comment)StandardLoggingPayloadgains an independentsession_idfield, populated fromlitellm_session_id. This field, and the reordering oftrace_id's fallback precedence to favor an explicitlitellm_trace_idoverlitellm_session_id, are both gated behindrequest_correlation_in_logs: off (default),trace_id's existing session-id-first fallback is preserved exactly as before andsession_idstays empty; on, the two fields are genuinely independentTwo issues raised in review are also fixed here: correlation context is now restored after a nested call (e.g. a guardrail's own LLM-as-judge call sharing the same asyncio Task as the outer request) completes, via a captured pre-call value restored in
Logging's terminal handlers (contextvars.Token.reset()isn't usable here since a token can only be reset in the exact Context it was created in, and these handlers can run in a different asyncio Task than__init__did), so it no longer leaks into the outer request's subsequent log lines; andset_trace_id/set_session_idnow strip control characters and bound length before storing a caller-controlled id, since a caller could otherwise embed\r/\n/escape sequences to forge fake log entries in plaintext output_correlation_filteris registered on the module-level stream handler, the JSON exception handler created by_setup_json_exception_handlers(), and any handler passed to_initialize_loggers_with_handler(), so coverage is complete regardless of which code path activates loggingThe test helper
_JsonCaptureis updated to registerCorrelationContextFilteron itself so that unit tests exercise the same filter path production code usesStreaming responses needed separate handling since
wrapper_async()deliberately skips restoring context when it returns a stream (so log lines emitted while the caller iterates it still carry the call's ids), but the terminal success/failure handlers a stream dispatches to run on a different Task than the one actually consuming it.CustomStreamWrapperrestores the consuming context at every genuine exit point on the async side: natural exhaustion, a raised failure, and explicitaclose(). For a stream the caller abandons without ever exhausting or closing it, a best-effort__del__hook is the last resort, since real cleanup can't be guaranteed there:__del__timing is unpredictable (delayed by cyclic GC, not guaranteed at interpreter shutdown). To keep that best-effort hook from doing harm if it fires late, it only restores when the contextvars still hold the ids this call itself set, so it never clobbers a different, still-active call that has since taken over the same Task's contextScope: async/proxy only in this PR
Review surfaced a genuine bug in the sync path:
wrapper()(the sync entry point,litellm.completion()) used the same skip-restore-on-stream behavior aswrapper_async(), but a plain OS thread has no per-call context isolation the way an asyncio Task does, and a thread pool's worker threads are recycled across unrelated requests - so an abandoned sync stream could leave its ids "stuck" on a thread that a pool later hands to a completely different request, misattributing that request's logs. A fix existed and was tested (restore unconditionally inwrapper()'s ownfinally, before ever handing a stream back), but it does not benefit this feature's primary consumer (the proxy, which only ever calls the async entry point) and adds sync-specific complexity this PR doesn't need to carry.Instead,
request_correlation_in_logsis scoped to the async path only here:Logging.__init__()takes asupports_correlation_loggingparameter (defaultTrue), threaded down from a newfunction_setup(..., is_async_call: bool = True)parameter.wrapper()is the one caller that passesis_async_call=False- every otherfunction_setup()call site (wrapper_async(), the router, and proxy/MCP-internal call sites) is already async and keeps the default. Withsupports_correlation_logging=False,Logging.__init__()never callsset_trace_id()/set_session_id()at all, so a sync call has nothing to leak in the first place -wrapper()is back to its original, pre-review shape with no correlation-specific code.StandardLoggingPayload's owntrace_id/session_idfields are unaffected by this scoping either way, since they're a deterministic per-call read ofself.litellm_trace_id/self.litellm_session_id, not ambient contextvar state.Full sync/direct-SDK support (stamping + its own safe-restore mechanism) is deferred to a follow-up PR; the fix and its regression test already exist in this branch's history and can be resurrected there.
Fallback to W3C traceparent/baggage headers
trace_id/session_idpreviously only resolved from litellm-specific sources:x-litellm-trace-id/x-litellm-session-idheaders, a genericx-<vendor>-session-idheader, or Anthropic-stylemetadata.user_id. If none were present,trace_idfell back to an unrelated auto-generated UUID even when the caller already had real distributed-tracing instrumentation sending the actual industry-standard headers for this.Added a fallback to the W3C Trace Context
traceparentheader (its trace-id component becomestrace_id) and W3C Baggagebaggageheader (asession.identry becomessession_id), so a request already carrying real OpenTelemetry trace context correlates litellm's logs with the same trace in the caller's observability backend instead of an unrelated generated id. Precedence is unchanged for existing sources - explicit litellm headers and the Anthropic metadata path both still win; this fallback only fires when neither found anything.trace_idandsession_idare resolved independently here (unlike the existingchain_idmechanism, which uses one shared value for both), sincetraceparentandbaggageare semantically distinct W3C concepts. Parsed directly with no new dependency - both header formats are simple fixed-width/delimited strings.Verified live against a real proxy: a bare
traceparentheader produces a logtrace_idexactly matching its trace-id component; atraceparentalongside an explicitx-litellm-trace-idheader (different value) produces a log showing the explicit header's value, proving precedence (see verification comment).