feat(langfuse): upgrade SDK and OTel ingestion to v4#33391
feat(langfuse): upgrade SDK and OTel ingestion to v4#33391devin-ai-integration[bot] wants to merge 13 commits into
Conversation
🤖 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:
|
|
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR migrates the Langfuse integration from the v2
Confidence Score: 5/5The migration is mechanically correct; all known regressions (tag isolation for existing traces, UUID-format parent observation IDs) were already fixed in follow-up commits before this review. The logic changes are faithful translations of the v2 API to v4 context-manager semantics. tests/logging_callback_tests/test_langfuse_unit_tests.py — two new tests instantiate a real Langfuse SDK client against a live host URL; worth verifying they pass in an offline CI environment.
|
| Filename | Overview |
|---|---|
| litellm/integrations/langfuse/langfuse.py | Core migration from Langfuse v2 trace/generation API to v4 start_as_current_observation / propagate_attributes; logic is sound but _supports_completion_start_time is now dead code |
| litellm/integrations/langfuse/langfuse_otel.py | Adds build_langfuse_otel_headers helper; flattens trace_metadata dict into per-key OTEL attributes matching v4 langfuse.trace.metadata.<key> shape; maps both user_id and trace_user_id to TRACE_USER_ID |
| litellm/integrations/otel/presets/langfuse.py | Dynamic header builder now includes the x-langfuse-ingestion-version: 4 header via the shared build_langfuse_otel_headers helper |
| litellm/types/integrations/langfuse_otel.py | Enum values updated to v4 attribute names (langfuse.version, langfuse.release); TRACE_VERSION and GENERATION_VERSION now share the same attribute key as intended by v4 spec |
| pyproject.toml | Bumps langfuse to >=4.7,<5.0, OTel stack to 1.33.1, and traceloop-sdk to 0.34.0; breaking for users pinned to langfuse 2.x but this is the intended v4 migration |
| tests/logging_callback_tests/test_langfuse_unit_tests.py | Replaces v2 mock-HTTP test with real Langfuse SDK + InMemorySpanExporter tests; folder rule restricts to mock-only tests — potential network call during SDK init is unverified |
| tests/test_litellm/integrations/test_langfuse.py | Mock chain updated from trace.generation() to start_as_current_observation context manager; adds tests for trace-context normalization and tag-isolation for existing traces; dead _is_langfuse_v2 mock setup remains |
| litellm/integrations/SlackAlerting/utils.py | Fixes Langfuse base URL lookup from removed Langfuse.base_url attribute to the LangFuseLogger.langfuse_host instance attribute which is still set in __init__ |
Reviews (6): Last reviewed commit: "fix(langfuse): keep telemetry dependency..." | Re-trigger Greptile
| "opentelemetry-sdk==1.28.0", | ||
| "opentelemetry-exporter-otlp==1.28.0", | ||
| "opentelemetry-instrumentation-fastapi==0.49b0", | ||
| "langfuse>=4.7,<5.0", |
There was a problem hiding this comment.
Hard breaking change for users on langfuse < 4.7
The minimum is raised from >=2.59.7,<3.0 to >=4.7,<5.0, skipping v3 entirely (which was never supported) and jumping two major versions. Any deployment that has langfuse pinned to v2.x or v3.x will encounter an import-time AttributeError or resolution conflict when they next update LiteLLM. Per the team's policy on backwards-incompatible changes, a feature flag or explicit opt-in mechanism should gate the v4 behaviour, giving users a migration window.
Rule Used: What: avoid backwards-incompatible changes without... (source)
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit aaeb263. Configure here.
| ) | ||
| generation_client = trace.start_observation(**generation_params) | ||
| generation_client.end() | ||
| trace.end() |
There was a problem hiding this comment.
Parent ID nests wrong observation
High Severity
The parent_observation_id is now applied to the root trace observation via trace_context, rather than directly to the generation. This introduces an unintended intermediate span, breaking the expected direct parent-child relationship for generations and distorting the trace hierarchy, particularly for agent and tool nesting.
Reviewed by Cursor Bugbot for commit aaeb263. Configure here.
There was a problem hiding this comment.
Fixed in 2c1dfdb4f0e. The generation is now the root v4 observation created with trace_context, so parent_observation_id remains its direct parent without an intermediate span. Provider and guardrail observations remain children of the generation
| TRACE_METADATA = "langfuse.trace.metadata" | ||
| TRACE_VERSION = "langfuse.trace.version" | ||
| TRACE_RELEASE = "langfuse.trace.release" | ||
| TRACE_VERSION = "langfuse.version" |
There was a problem hiding this comment.
Version attributes collide on OTel
Medium Severity
The GENERATION_VERSION and TRACE_VERSION attributes now map to the same langfuse.version key. This causes _set_metadata_attributes to overwrite one value when both are present, preventing distinct generation and trace versions from being recorded for Langfuse OTel spans. The SDK callback path still treats these fields as distinct.
Reviewed by Cursor Bugbot for commit aaeb263. Configure here.
There was a problem hiding this comment.
Fixed in 2c1dfdb4f0e. Langfuse v4 uses the single langfuse.version semantic attribute; observation version now takes precedence over propagated trace_version, matching SDK behavior, with trace version used as the fallback
| version=propagated_version, | ||
| level=level, | ||
| status_message=trace_params.get("status_message"), | ||
| ) |
There was a problem hiding this comment.
Trace release attribute dropped
Medium Severity
trace_release is still copied into trace_params as release, but neither propagate_attributes nor start_observation receives it. Per-request release metadata is therefore discarded on the SDK callback path, while the OTel path still emits langfuse.release.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit aaeb263. Configure here.
There was a problem hiding this comment.
Fixed in 2c1dfdb4f0e. Per-request trace_release is applied as langfuse.release to the generation and its provider or guardrail child observations, with exported-span regression coverage
|
A downstream release that cherry-picked this PR exposed a timing regression in the v4 callback. |


Relevant issues
Fixes #33383
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@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
Live Langfuse ingestion proof was not captured because this environment has no Langfuse project credentials
Type
New Feature
Changes
Upgrades the runtime and development Langfuse dependency to v4.7 and raises the compatible OpenTelemetry and Traceloop pins. OpenTelemetry remains optional when LiteLLM is imported without proxy runtime dependencies
Legacy callback correlation now normalizes valid trace IDs, seeds deterministic trace and parent observation IDs for arbitrary external identifiers, and applies the parent directly to the generation observation without an intermediate span. Requested generation IDs remain available in metadata
User, session, trace name, metadata, version, and release attributes are attached to exported generation, provider, and guardrail observations. Observation version takes precedence over propagated trace version on the shared Langfuse v4
langfuse.versionattribute. Requests continuing anexisting_trace_iddo not propagate tags that could replace the trace's existing tag setLangfuse v4 does not accept historical observation start timestamps. The callback closes observations using SDK timestamps instead of applying historical end and first-token timestamps that would predate the SDK-created span start
Langfuse OTLP configuration sends
Authorizationandx-langfuse-ingestion-version: 4for static, dynamic, and OTel v2 preset exporters. Trace metadata is emitted with the v4langfuse.trace.metadata.<key>shape, and bothuser_idandtrace_user_idmap touser.idLegacy tests now query v4 observations and traces through
client.api. The obsolete v2/api/public/ingestionpayload snapshots were removed in favor of v4 exported-span coverageFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/55ba7054762d48cc9fd95922bd7f6506