Skip to content

fix(langfuse): detach explicit traces from external parents - #34234

Closed
ShellWen wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
ShellWen:litellm_langfuse_explicit_trace_parent
Closed

fix(langfuse): detach explicit traces from external parents#34234
ShellWen wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
ShellWen:litellm_langfuse_explicit_trace_parent

Conversation

@ShellWen

Copy link
Copy Markdown

TLDR

Problem this solves:

  • Explicit Langfuse traces retain unrelated external parent span IDs
  • Resulting parent observations do not exist in the selected trace

How it solves it:

  • Start an independent OTEL context for explicit Langfuse trace IDs
  • Preserve normal traceparent propagation without an explicit trace ID

Relevant issues

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR’s scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

Live validation is pending an image build from this branch

Before this fix, an explicit Langfuse trace_id combined with an incoming traceparent produced a trace whose generation referenced a parent observation that could not exist in the selected trace

Type

🐛 Bug Fix

Changes

Langfuse OTEL now starts a root context when request metadata supplies an explicit trace_id

Requests without an explicit Langfuse Trace ID retain the existing OpenTelemetry parent context behavior

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Copilot AI review requested due to automatic review settings July 22, 2026 08:58
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR changes Langfuse OTEL parenting when request metadata provides an explicit trace ID. The main changes are:

  • Detach explicit Langfuse traces from propagated OpenTelemetry parents
  • Preserve existing propagation when no explicit trace ID is provided
  • Add unit tests for both context-selection branches

Confidence Score: 4/5

Explicit trace handling can break nested observation parenting and should be corrected before merging

  • The override drops caller-supplied internal parents along with external context
  • Empty trace IDs also trigger detachment
  • The new tests do not create spans or verify the resulting hierarchy

litellm/integrations/langfuse/langfuse_otel.py

Important Files Changed

Filename Overview
litellm/integrations/langfuse/langfuse_otel.py Adds context detachment for explicit trace IDs, but also removes internal parent spans and activates for empty IDs
tests/test_litellm/integrations/test_langfuse_otel.py Tests direct context selection but does not verify actual span hierarchy or malformed trace ID behavior

Reviews (1): Last reviewed commit: "fix(langfuse): detach explicit traces fr..." | Re-trigger Greptile

Comment on lines +414 to +415
if metadata.get("trace_id") is not None:
return None, None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Internal Parent Context Is Dropped

This override discards both the external context and any default_span supplied by an internal caller. Nested operations carrying the same explicit trace_id, including guardrail spans, can therefore become root spans instead of children of the request span, breaking the observation hierarchy within the selected trace

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +414 to +415
if metadata.get("trace_id") is not None:
return None, None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Empty Trace ID Detaches Context

An empty trace_id, such as one populated from an unset environment variable, satisfies this condition and discards valid parent propagation. The span then carries an empty langfuse.trace.id, which can leave it orphaned or rejected instead of preserving the existing OpenTelemetry trace

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts the Langfuse OpenTelemetry integration to prevent explicit Langfuse trace selection (via request metadata) from inheriting an unrelated external parent context (for example, an inbound traceparent header), which can produce traces whose parent observations do not exist within the selected Langfuse trace

Changes:

  • Override LangfuseOtelLogger._get_span_context to short-circuit context propagation when an explicit Langfuse trace ID is provided via metadata
  • Add regression tests to ensure explicit trace IDs do not call into the base OpenTelemetry context propagation logic, while requests without explicit trace IDs retain the prior propagation behavior

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
litellm/integrations/langfuse/langfuse_otel.py Overrides span context resolution to detach explicit Langfuse trace selection from external parent propagation
tests/test_litellm/integrations/test_langfuse_otel.py Adds unit tests validating the changed context-propagation behavior

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +412 to +416
def _get_span_context(self, kwargs, default_span: Optional[Span] = None):
metadata = self._extract_langfuse_metadata(kwargs)
if metadata.get("trace_id") is not None:
return None, None
return super()._get_span_context(kwargs, default_span)
Comment on lines +140 to +159
def test_explicit_trace_id_does_not_inherit_external_parent_context(self):
logger = object.__new__(LangfuseOtelLogger)
kwargs = {
"litellm_params": {
"metadata": {"trace_id": "0123456789abcdef0123456789abcdef"},
"proxy_server_request": {
"headers": {
"traceparent": "00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01"
}
},
}
}

with patch(
"litellm.integrations.opentelemetry.OpenTelemetry._get_span_context"
) as parent_context:
assert logger._get_span_context(kwargs) == (None, None)

parent_context.assert_not_called()

@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing ShellWen:litellm_langfuse_explicit_trace_parent (d62e2d2) with litellm_internal_staging (f1f0a0b)

Open in CodSpeed

@ShellWen

Copy link
Copy Markdown
Author

Closing this because the production issue is in the standard Langfuse v4 callback from #33391, not the langfuse_otel callback changed here. The current #33391 head now exports an explicit trace span, so the generation parent resolves within the trace. No change from this PR is needed.

@ShellWen ShellWen closed this Jul 22, 2026
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants