Repository navigation
fix(otel v2): name Langfuse traces from the langfuse_trace_name header or metadata.trace_name - #40793
Conversation
…r or metadata.trace_name 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 restores caller-defined Langfuse trace names in OTel v2 while preserving existing fallback behavior.
Confidence Score: 5/5The PR appears safe to merge, with no outstanding correctness or repository-rule violations. No new actionable issues were found. The root-context concern was resolved after the implementation clarified that context is propagated into synchronous provider execution, and the untyped test-helper thread was manually resolved after the latest revision added explicit parameter, return, and local-variable types.
|
| Filename | Overview |
|---|---|
| litellm/integrations/otel/langfuse_logger.py | Adds Langfuse root-span trace naming and separates trace-only behavior from content stamping. |
| litellm/integrations/otel/logger.py | Propagates the resolved trace name into span data and selects the appropriate Langfuse logger regardless of content capture. |
| litellm/integrations/otel/mappers/langfuse.py | Maps the caller-selected name to the Langfuse trace-name attribute. |
| litellm/integrations/otel/model/metadata.py | Resolves trace names with header-first precedence and carries them through the call event. |
| litellm/integrations/otel/model/payloads.py | Adds the optional trace name to generation-span mapping data. |
| tests/test_litellm/integrations/otel/test_langfuse_logger.py | Tests root and generation naming across precedence and content-capture configurations; the helper is now fully typed. |
| tests/test_litellm/integrations/otel/test_otel_v2_sources_of_truth.py | Tests trace-name extraction, precedence, fallback, and payload propagation. |
| tests/test_litellm/integrations/otel/test_otel_v2_vendor_mappers.py | Verifies Langfuse emits the trace-name attribute only when a name is present. |
Reviews (2): Last reviewed commit: "test(otel v2): type the named-request he..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…ests 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 0b85ead. Configure here.
bf146e2
into
litellm_internal_staging
TLDR
Problem this solves:
langfuse_trace_nameheadermetadata.trace_namePOST /v1/chat/completionsHow it solves it:
metadata.trace_name(litellm_metadataon/v1/messages)langfuse.trace.nameon the generation spanUser Flow
Before: a developer tags requests with
langfuse_trace_nameso their Langfuse project groups traces by workflow, but every trace is named after the URLlangfuse_trace_name: nightly-evaland a normal chat bodyx-litellm-call-idheader backPOST /v1/chat/completions"metadata": {"trace_name": "nightly-eval"}in the body instead of the header, and the trace is still namedPOST /v1/chat/completionsAfter: the same requests produce traces named the way the developer asked
langfuse_trace_name: nightly-evaland a normal chat bodyx-litellm-call-idheader backnightly-eval"metadata": {"trace_name": "nightly-eval"}in the body instead of the header, and the trace is namednightly-evaltooPOST /v1/chat/completionsnameRelevant issues
Linear ticket
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 for both legs: real proxy on :17700 with a real Postgres database, real Anthropic calls,
LITELLM_OTEL_V2=true,LANGFUSE_HOSTpointing at a local Langfuse v3 on :3100 with real project keys,--num_workers 4on both legs (4Started server processlines in each proxy log). Config:Requests (
$Tis a per-run tag,r4basefor Before andr4fixedfor After):Readback, per
x-litellm-call-idprinted by the requests above, straight from the Langfuse public API:Before (09b6948)
/v1/chat/completions, header only
curl ... -H "langfuse_trace_name: r4base-hdr-only"->HTTP/1.1 200 OK,x-litellm-call-id: fbb61a69-84bd-44d6-b6de-d051f5a517e2call_id=fbb61a69-84bd-44d6-b6de-d051f5a517e2 trace=59a57d3d0e7ee520d7ce9fd8af6a135c name=POST /v1/chat/completions/v1/chat/completions, body metadata only
curl ... -d '{..., "metadata":{"trace_name":"r4base-body-only"}}'->HTTP/1.1 200 OK,x-litellm-call-id: 86f55c72-e7d6-4938-a269-3f442fbe0f95call_id=86f55c72-e7d6-4938-a269-3f442fbe0f95 trace=996080ce82da050cbaa3457234b0587c name=POST /v1/chat/completions/v1/chat/completions, header and body disagree
curl ... -H "langfuse_trace_name: r4base-hdr-wins" -d '{..., "metadata":{"trace_name":"r4base-body-loses"}}'->HTTP/1.1 200 OK,x-litellm-call-id: a72fcb18-025b-444d-aadb-79eb4f77d4f0call_id=a72fcb18-025b-444d-aadb-79eb4f77d4f0 trace=445b928ab83e7084e37ad93050de99bf name=POST /v1/chat/completions/v1/chat/completions, neither
curl ...->HTTP/1.1 200 OK,x-litellm-call-id: 330b1150-6351-4cf3-bc51-7643af3aba7ecall_id=330b1150-6351-4cf3-bc51-7643af3aba7e trace=9f91f85018bdb81fabc9169345a38546 name=POST /v1/chat/completions/v1/chat/completions streaming, header
curl ... -H "langfuse_trace_name: r4base-stream-hdr" -d '{..., "stream":true}'->HTTP/1.1 200 OK,x-litellm-call-id: ed9b634c-fb76-4406-9de9-c2ef8e87891fcall_id=ed9b634c-fb76-4406-9de9-c2ef8e87891f trace=f15b0014cf59aa543726b2e5b31c48ae name=POST /v1/chat/completions/v1/messages, header
curl ... /v1/messages -H "langfuse_trace_name: r4base-msgs-hdr"->HTTP/1.1 200 OK,x-litellm-call-id: 62235fec-6fce-49fe-915b-2115a4925209call_id=62235fec-6fce-49fe-915b-2115a4925209 trace=1deb9f023550099e258e05c870c5e2e8 name=POST /v1/messages/v1/messages, litellm_metadata body
curl ... /v1/messages -d '{..., "litellm_metadata":{"trace_name":"r4base-msgs-body"}}'->HTTP/1.1 200 OK,x-litellm-call-id: e1e2ae63-6e0e-4694-878d-138dc3209fe4call_id=e1e2ae63-6e0e-4694-878d-138dc3209fe4 trace=a2ffb12437cfc730ba472801dd8cf8f5 name=POST /v1/messages/v1/responses, header
curl ... /v1/responses -H "langfuse_trace_name: r4base-resp-hdr"->HTTP/1.1 200 OK,x-litellm-call-id: 9f5cc8a1-6e6d-400a-aaa1-49245f45b36ccall_id=9f5cc8a1-6e6d-400a-aaa1-49245f45b36c trace=3bd3075ea63c1bc72947bdbf88066f4a name=POST /v1/responses/v1/responses, body metadata
curl ... /v1/responses -d '{..., "metadata":{"trace_name":"r4base-resp-body"}}'->HTTP/1.1 200 OK,x-litellm-call-id: 66ac02a1-d8ea-4811-a50e-fc9baa0b651fcall_id=66ac02a1-d8ea-4811-a50e-fc9baa0b651f trace=6c9be0ca54888a5e6d7ddefde0e59f97 name=POST /v1/responsesLangfuse UI, trace list and the header-and-body-disagree trace
lpr-org, projectteam-a, Tracing, typer4basein the IDs / Names search ->No results., none of the 9 requests produced a trace named after the callerPOST /v1/chat/completions-> the 22:35:04 to 22:35:07 rows from this run are all namedPOST /v1/chat/completionsPOST /v1/chat/completions: 445b928ab83e7084e37ad93050de99bf, neitherr4base-hdr-winsnorr4base-body-losesappears anywhere on the pageAfter (0b85ead)
The same 9 requests against the PR tip merged into the current
litellm_internal_staginghead22c60ef9e7(merge commit4f883a7a65, 4 workers, real Anthropic and Langfuse) stored the same names, taggedm4-*/v1/chat/completions, header only
curl ... -H "langfuse_trace_name: r4fixed-hdr-only"->HTTP/1.1 200 OK,x-litellm-call-id: 0d56b55b-1c93-4b30-add9-8339fc2e9eafcall_id=0d56b55b-1c93-4b30-add9-8339fc2e9eaf trace=4366d979b22020e523d23e7522be32b0 name=r4fixed-hdr-only/v1/chat/completions, body metadata only
curl ... -d '{..., "metadata":{"trace_name":"r4fixed-body-only"}}'->HTTP/1.1 200 OK,x-litellm-call-id: 446d53b8-0cbb-4049-9b9e-2ea749c5be77call_id=446d53b8-0cbb-4049-9b9e-2ea749c5be77 trace=372eaa18f06befae2d28591f0c4697b6 name=r4fixed-body-only/v1/chat/completions, header and body disagree
curl ... -H "langfuse_trace_name: r4fixed-hdr-wins" -d '{..., "metadata":{"trace_name":"r4fixed-body-loses"}}'->HTTP/1.1 200 OK,x-litellm-call-id: 24170f99-064e-434a-a31c-6c7f237aa84acall_id=24170f99-064e-434a-a31c-6c7f237aa84a trace=38ae44246cc8cda528fb5f0e25ca348f name=r4fixed-hdr-wins/v1/chat/completions, neither
curl ...->HTTP/1.1 200 OK,x-litellm-call-id: 6507ae87-11ce-4e6f-944b-32c0f9d99c93call_id=6507ae87-11ce-4e6f-944b-32c0f9d99c93 trace=c02c6a732e58b08d44c38f3533cd24d7 name=POST /v1/chat/completions(unchanged fallback)/v1/chat/completions streaming, header
curl ... -H "langfuse_trace_name: r4fixed-stream-hdr" -d '{..., "stream":true}'->HTTP/1.1 200 OK,x-litellm-call-id: f739dce4-3633-4f99-9247-c5c616ffa2f8call_id=f739dce4-3633-4f99-9247-c5c616ffa2f8 trace=f3ebd5276e68cae3aaf5f7b0142387d7 name=r4fixed-stream-hdr/v1/messages, header
curl ... /v1/messages -H "langfuse_trace_name: r4fixed-msgs-hdr"->HTTP/1.1 200 OK,x-litellm-call-id: 9910a21d-5ace-4e95-aef0-c8424618a248call_id=9910a21d-5ace-4e95-aef0-c8424618a248 trace=6b7e3af42e249d3557116e0ee3f7a308 name=r4fixed-msgs-hdr/v1/messages, litellm_metadata body
curl ... /v1/messages -d '{..., "litellm_metadata":{"trace_name":"r4fixed-msgs-body"}}'->HTTP/1.1 200 OK,x-litellm-call-id: 6b8f7b5b-1210-41db-b1c8-cc16de032e1ecall_id=6b8f7b5b-1210-41db-b1c8-cc16de032e1e trace=bc4924115dd66bd935f866b503c9ed89 name=r4fixed-msgs-body/v1/responses, header
curl ... /v1/responses -H "langfuse_trace_name: r4fixed-resp-hdr"->HTTP/1.1 200 OK,x-litellm-call-id: cf60eae9-953e-4dbd-97ab-2cedc185edfacall_id=cf60eae9-953e-4dbd-97ab-2cedc185edfa trace=2b4de76249e939f966985cd4d380da9a name=r4fixed-resp-hdr/v1/responses, body metadata
curl ... /v1/responses -d '{..., "metadata":{"trace_name":"r4fixed-resp-body"}}'->HTTP/1.1 200 OK,x-litellm-call-id: fc9d0031-5f24-4fc7-b377-ece3bdd6e18bcall_id=fc9d0031-5f24-4fc7-b377-ece3bdd6e18b trace=3a5e5477c165fe8e4da41ae5c0942a85 name=r4fixed-resp-bodyLangfuse UI, trace list and the header-and-body-disagree trace
lpr-org, projectteam-a, Tracing, typer4fixedin the IDs / Names search -> 8 rows, one per named request:r4fixed-hdr-only,r4fixed-body-only,r4fixed-hdr-wins,r4fixed-stream-hdr,r4fixed-msgs-hdr,r4fixed-msgs-body,r4fixed-resp-hdr,r4fixed-resp-body(the unnamed request staysPOST /v1/chat/completions, 22:33:58 in the previous search)r4fixed-hdr-wins: 38ae44246cc8cda528fb5f0e25ca348f, the header won overmetadata.trace_name, and the child spans (POST /v1/chat/completions,auth,chat haiku,batch_write_to_db) are unchangedType
🐛 Bug Fix
Caveats (if any)
Low
LangfuseOpenTelemetryV2now runs for Langfuse without content capture toocapture_span_contente2e_openai_endpoints(not a required status check) reads failure on this tiptests/openai_endpoints_tests/**(OpenAI batches, files, fine-tuning, Responses) against a proxy that does not enable OTel v2, and nothing under that path importslitellm/integrations/oteld51a7af655onlitellm_internal_staging, while every required check and the other 43 CircleCI statuses pass hereFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/1d3bdadbd958454796014f11b7033854
Open in Devin Desktop: https://app.devin.ai/desktop/session/1d3bdadbd958454796014f11b7033854?variant=devin
Requested by: @yucheng-berri
Note
Low Risk
Telemetry-only labeling for Langfuse OTel v2; no auth, billing, or request-path logic changes beyond reading optional header/metadata.
Overview
OTel v2 Langfuse traces now use a caller-provided name instead of defaulting to the HTTP route (e.g.
POST /v1/chat/completions), restoring behavior that v1 already had.Resolution order is
langfuse_trace_nameheader first, thenmetadata.trace_nameorlitellm_metadata.trace_name(for/v1/messages). The name is emitted aslangfuse.trace.nameon the LLM generation span and stamped on the proxy root span duringlog_pre_api_callwhile it is still recording.The Langfuse logger is split:
LangfuseOpenTelemetryV2handles trace naming for any Langfuse mapper config;LangfuseContentOpenTelemetryV2keeps root input/output stamping behindcapture_span_content. The factory now selects the Langfuse base logger whenever the Langfuse mapper is enabled, not only when content capture is on.Reviewed by Cursor Bugbot for commit 0b85ead. Bugbot is set up for automated code reviews on this repo. Configure here.
ran /live-pr-risk and found no regressions/backward incompatible risks