Repository navigation
feat(otel): promote nested request metadata keys to litellm.metadata.* span attributes - #41462
Conversation
…* span attributes baggage_metadata_keys entries such as requester_metadata.trace_id now resolve the caller's nested metadata.trace_id and stamp it on the LLM-call span as litellm.metadata.trace_id, in both the OTEL v2 logger and the legacy OpenTelemetry callback. Nested metadata mappings are flattened to dotted paths, only allowlisted leaves are promoted, and the requester_metadata blob itself is never promoted Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
Greptile SummaryThis PR allows explicitly configured nested caller metadata to be promoted into OpenTelemetry span attributes while preserving dotted paths and excluding the enclosing metadata blob
Confidence Score: 5/5The PR appears safe to merge, with no outstanding correctness or repository-rule findings The metadata promotion remains explicitly allowlisted, preserves dotted paths, excludes the requester wrapper, and is covered across both OTEL implementations. The earlier collision and unnecessary-docstring threads were manually resolved without explanation. The immutable merge finding was fixed as claimed by devin-ai-integration[bot] and its thread was resolved Important Files Changed
Reviews (3): Last reviewed commit: "test(otel): describe which request metad..." | Re-trigger Greptile |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Strip only the proxy's requester_metadata. wrapper from an allowlisted key so requester_metadata.trace_id lands as litellm.metadata.trace_id while other dotted keys keep their full path and cannot collide on a shared leaf name Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…v2 pre-call hook The pre-call hook passed the proxy's whole per-request metadata dict into the request identity, so proxy-owned siblings such as requester_ip_address were promoted alongside the caller's keys. Only the requester_metadata mapping is read now, keyed under its wrapper, which keeps the default allowlist behaviour unchanged Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8de51df. Configure here.
…sted_request_metadata_keys feat(otel): promote nested request metadata keys to litellm.metadata.* span attributes (cherry picked from commit 79fc515)
…02.1) (#736) 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)) | patch | `v1.102.0` → `v1.102.1` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.102.1`](https://github.com/BerriAI/litellm/releases/tag/v1.102.1) [Compare Source](BerriAI/litellm@v1.102.0...v1.102.1) ##### 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`](BerriAI/litellm@0112e53). **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.102.1 ``` **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.102.1/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` 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(anthropic): backport [#​42152](BerriAI/litellm#42152) and [#​42288](BerriAI/litellm#42288) to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42538](BerriAI/litellm#42538) - feat(typesafe): backport the jev change set to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42595](BerriAI/litellm#42595) - chore(release): backport [#​42388](BerriAI/litellm#42388) and [#​41462](BerriAI/litellm#41462) to stable/1.102.x by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42618](BerriAI/litellm#42618) **Full Changelog**: <BerriAI/litellm@v1.102.0...v1.102.1> </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/London) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDguMiIsInVwZGF0ZWRJblZlciI6IjQ0LjEwOC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.hayden.moe/hayden/phoebe/pulls/736
…02.1) (#2200) 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)) | patch | `v1.102.0` → `v1.102.1` | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/6) for more information. --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.102.1`](https://github.com/BerriAI/litellm/releases/tag/v1.102.1) [Compare Source](BerriAI/litellm@v1.102.0...v1.102.1) #### 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`](BerriAI/litellm@0112e53). **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.102.1 ``` **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.102.1/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` 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(anthropic): backport [#​42152](BerriAI/litellm#42152) and [#​42288](BerriAI/litellm#42288) to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42538](BerriAI/litellm#42538) - feat(typesafe): backport the jev change set to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42595](BerriAI/litellm#42595) - chore(release): backport [#​42388](BerriAI/litellm#42388) and [#​41462](BerriAI/litellm#41462) to stable/1.102.x by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42618](BerriAI/litellm#42618) **Full Changelog**: <BerriAI/litellm@v1.102.0...v1.102.1> </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Rome) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDQuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjEwNC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.aresu.eu/GiorgioAresu/home-ops/pulls/2200
…02.1) (#267) 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)) | patch | `v1.102.0` → `v1.102.1` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.102.1`](https://github.com/BerriAI/litellm/releases/tag/v1.102.1) [Compare Source](BerriAI/litellm@v1.102.0...v1.102.1) #### 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`](BerriAI/litellm@0112e53). **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.102.1 ``` **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.102.1/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` 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(anthropic): backport [#​42152](BerriAI/litellm#42152) and [#​42288](BerriAI/litellm#42288) to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42538](BerriAI/litellm#42538) - feat(typesafe): backport the jev change set to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42595](BerriAI/litellm#42595) - chore(release): backport [#​42388](BerriAI/litellm#42388) and [#​41462](BerriAI/litellm#41462) to stable/1.102.x by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42618](BerriAI/litellm#42618) **Full Changelog**: <BerriAI/litellm@v1.102.0...v1.102.1> </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDUuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjEwNS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.greyrock.io/todd/home-ops/pulls/267
TLDR
Problem this solves:
baggage_metadata_keys: [requester_metadata.trace_id]promoted nothingmetadata.trace_idnever reached the LLM-call span as its own attributeHow it solves it:
requester_metadata.<path>lands aslitellm.metadata.<path>; only the proxy'srequester_metadata.wrapper is stripped, every other key keeps its full dotted namerequester_metadataas a whole is still never promotedUser Flow
Before: a platform team wants to correlate their own trace id with the gateway's LLM span, but the span never carries it
callback_settings.otel.baggage_metadata_keys: [requester_metadata.trace_id]and restarts the proxy"metadata": {"trace_id": "abc"}in the bodychat gpt-4o-minion OTEL v2,litellm_requeston OTEL v1) has nolitellm.metadata.trace_idattribute, so they cannot join it to their own traceAfter: the same request stamps the caller's trace id on the request's spans
callback_settings.otel.baggage_metadata_keys: [requester_metadata.trace_id]and restarts the proxy"metadata": {"trace_id": "abc"}in the bodylitellm.metadata.trace_id=abc, whilemetadata.nested.deepand the rest of the caller's metadata stay out because they were not allowlisted. Allowlistingrequester_metadata.nested.deepas well addslitellm.metadata.nested.deep=xRelevant issues
Customer request via Pylon #8571
Affected release
Linear ticket
Resolves LIT-7760
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup. Four proxies, each started with
--num_workers 2against real OpenAI and a real PostgreSQL, all exporting over OTLP/HTTP to one local Jaeger (jaegertracing/all-in-one:1.62.0). The Before pair runs from the merge-base worktree, the After pair from the PR tip, on both OTEL v2 (LITELLM_OTEL_V2=true) and the legacy OTEL v1 callback. Each arm reports under its ownservice_nameso the traces can be read back per arm from Jaeger's query API, which is what a user opening the Jaeger UI sees. The request bodies are byte-identical across every leg and every armcat chat.json {"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Reply with exactly: pong"}], "max_tokens": 5, "metadata": {"trace_id": "abc", "nested": {"deep": "x"}, "empty": ""}} cat responses.json {"model": "gpt-4o-mini", "input": "Reply with exactly: pong", "max_output_tokens": 16, "metadata": {"trace_id": "abc", "nested": {"deep": "x"}, "empty": ""}} cat messages.json {"model": "gpt-4o-mini", "max_tokens": 5, "messages": [{"role": "user", "content": "Reply with exactly: pong"}], "metadata": {"trace_id": "abc", "nested": {"deep": "x"}, "empty": ""}}Each case below runs the same three curls against the arm's port, three times each so both uvicorn workers serve requests, then reads the traces back. The v1 arms take the exporter from
OTEL_EXPORTER=otlp_http OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:47791/v1/traces OTEL_SERVICE_NAME=lit7760-<arm>because the legacy callback reads exporter settings from the environmentBefore (4e99640)
/v1/chat/completions
PYTHONPATH="$PWD:$PWD/enterprise" python -c "import litellm; print(litellm.__file__)"->/home/ubuntu/lit7760/base/litellm/__init__.pyon both Before arms200 object=chat.completion model=gpt-4o-mini text='pong'on v1 (port 47780) and v2 (port 47782)lit7760-base_v2,litellm.metadata.*tags per span of the three traces:lit7760-base_v1:/v1/responses
200 object=response model=gpt-4o-mini text='pong'on v1 and v2/v1/messages
200 object=message model=gpt-4o-mini text='pong'on v1 and v2Every litellm.metadata.* tag Jaeger holds for the Before arms
lit7760-base_v1->{}lit7760-base_v2->{'litellm.metadata.table_name': 7}(the proxy's own PostgreSQL spans, present on both arms and unrelated to caller metadata)After (8de51df)
/v1/chat/completions
PYTHONPATH="$PWD:$PWD/enterprise" python -c "import litellm; print(litellm.__file__)"->/home/ubuntu/repos/litellm/litellm/__init__.pyon both After arms,git rev-parse --short HEAD->8de51dfaab200 object=chat.completion model=gpt-4o-mini text='pong'on v1 (port 47781) and v2 (port 47783)lit7760-head_v2:lit7760-head_v1:/v1/responses
200 object=response model=gpt-4o-mini text='pong'on v1 and v2/v1/messages
200 object=message model=gpt-4o-mini text='pong'on v1 and v2Every litellm.metadata.* tag Jaeger holds for the After arms
lit7760-head_v1->{'litellm.metadata.nested.deep': 9, 'litellm.metadata.trace_id': 9}(onelitellm_requestspan per request, 9 requests)lit7760-head_v2->{'litellm.metadata.nested.deep': 27, 'litellm.metadata.trace_id': 27, 'litellm.metadata.table_name': 20}(server span, LLM span and the cost-tracking DB span of each request;table_nameis the same pre-existing PostgreSQL tag as on Before)litellm.metadata.requester_metadata,litellm.metadata.deep,litellm.metadata.emptyorlitellm.metadata.nested.skipped: the wrapper is not promoted as a blob, the leaf keeps its dotted parent, the empty string is dropped and unlisted siblings stay outDefault allowlist unchanged (no baggage_metadata_keys, console exporter, request with "user": "enduser-7760")
30f02aa6da; the only commit after it,8de51dfaab, changes a test docstring and no runtime code{'litellm.metadata.user_api_key_user_id': 'default_user_id', 'litellm.metadata.user_api_key_end_user_id': 'enduser-7760'}on the server, LLM and DB spans, and nothing from the caller'smetadatametadata.user_api_key_*blob attributes onlitellm_requestand nolitellm.metadata.*keyType
🆕 New Feature
Caveats (if any)
Medium
baggage_*_keyssetting today, so this key (like the existingbaggage_promoted_keysandbaggage_team_metadata_keys) is config.yaml and env var only. Adding a UI field for the wholebaggage_*family is a separate PRbaggage_metadata_keysdotted paths and the newLITELLM_OTEL_BAGGAGE_METADATA_KEYSenv var on v1 belong in litellm-docs and are not part of this PRLow
a.bstill lands aslitellm.metadata.a.b, only therequester_metadata.wrapper is dropped. The one collision left is a caller key and a proxy key with the same name (requester_metadata.trace_idand a top-leveltrace_id); the later allowlist entry wins0andFalseare stringified and keptlitellm_requestspan only, where every otherlitellm.*attribute of the legacy callback already lives; the v1 server span carries no per-request metadata before or after this PRset_attributes(Arize, Arize Phoenix) never reach the legacy stamping path, sobaggage_metadata_keysis inert there; the other v1 subclasses inherit it and are unchanged while the key is unsetTaxonomy audit of the diff. F3: v1 and v2 both covered, sibling endpoints share the same metadata snapshot (see Low above). C5: precedence unchanged, auth-derived
user_api_key_*keys still win over request metadata of the same name, and the v1 config-then-env order mirrorsbaggage_team_metadata_keys. W1:flatten_metadata,metadata_from_request_dataandpromoted_metadataonly read and return new read-only mappings; the pre-call hook test assertsdatais unchanged. X1:is not Nonefor the request metadata, empty mapping handled explicitly,0covered by a test. Y4:metadatavslitellm_metadatapicks the one carryingrequester_metadata. H2/H4: no new comments, noOptional, no baredict, noAny, lines at 120. T1-T5: the new tests fail on the merge base (KeyError: 'litellm.metadata.trace_id',unexpected keyword argument 'baggage_metadata_keys') and pass with the fix; env vars are patched withpatch.dict. B, D, E, G, M, N, O, P, R, V, Z: not applicable, no cache, auth, spend, streaming, guardrail, provider or migration code touchedFinal Attestation
ran /live-pr-risk and found no regressions/backward incompatible risks
Link to Devin session: https://app.devin.ai/sessions/c3ea5e13360e4d5391e80461d27f551f
Open in Devin Desktop: https://app.devin.ai/desktop/session/c3ea5e13360e4d5391e80461d27f551f?variant=devin
Requested by: @yassin-berriai
Note
Low Risk
Observability-only allowlist changes; default configs unchanged and unlisted metadata is not exported, with minor attribute-name collision risk if allowlists overlap.
Overview
Enables allowlisted caller metadata (e.g.
requester_metadata.trace_idfrom requestmetadata.trace_id) to appear on traces aslitellm.metadata.*span attributes on both OTEL v2 and legacy v1.Nested metadata is flattened to dotted paths before lookup; allowlisted
requester_metadata.<path>keys are promoted with therequester_metadata.wrapper stripped (other dotted keys keep their full path). The fullrequester_metadatablob is never promoted—only explicitly listed leaves.OTEL v2 seeds baggage in the pre-call hook using a
requester_metadata-only snapshot from the request body (proxy-owned metadata siblings are not read from there). Legacy v1 addsbaggage_metadata_keys/LITELLM_OTEL_BAGGAGE_METADATA_KEYSand stamps the same promoted attributes on inference spans when configured (empty by default, unlike v2’s default auth keys).Reviewed by Cursor Bugbot for commit 8de51df. Bugbot is set up for automated code reviews on this repo. Configure here.