fix(langfuse_otel): keep trace tags off joined traces entirely

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-23 08:18:49 +00:00
parent 8f0788486f
commit ee059cb13c
2 changed files with 15 additions and 18 deletions

View file

@ -154,9 +154,7 @@ class LangfuseOtelLogger(OpenTelemetry):
return _extract_output_items(response_obj) or _extract_choices_output(response_obj)
@staticmethod
def _trace_tags(
kwargs: Mapping[str, object], metadata: Mapping[str, object], derive_defaults: bool = True
) -> tuple[str, ...]:
def _trace_tags(kwargs: Mapping[str, object], metadata: Mapping[str, object]) -> tuple[str, ...]:
"""Order-preserving dedupe of caller tags, request tags and langfuse_default_tags expansions."""
import litellm
@ -184,16 +182,15 @@ class LangfuseOtelLogger(OpenTelemetry):
expanded: Final = tuple(_default_tag(key) for key in default_tags) if isinstance(default_tags, list) else ()
candidates: Final = (
(caller_tags,)
if isinstance(caller_tags, str)
else tuple(tag for tag in caller_tags if isinstance(tag, str))
if isinstance(caller_tags, list)
else ()
) + (
(tuple(tag for tag in request_tags if isinstance(tag, str)) if isinstance(request_tags, list) else ())
(
(caller_tags,)
if isinstance(caller_tags, str)
else tuple(tag for tag in caller_tags if isinstance(tag, str))
if isinstance(caller_tags, list)
else ()
)
+ (tuple(tag for tag in request_tags if isinstance(tag, str)) if isinstance(request_tags, list) else ())
+ tuple(tag for tag in expanded if tag is not None)
if derive_defaults
else ()
)
return tuple(dict.fromkeys(candidates))
@ -232,9 +229,10 @@ class LangfuseOtelLogger(OpenTelemetry):
f"litellm-{kwargs.get('call_type') or 'completion'}",
)
tags: Final = LangfuseOtelLogger._trace_tags(kwargs, metadata, derive_defaults=not joins_existing_trace)
if tags:
safe_set_attribute(span, LangfuseSpanAttributes.TAGS.value, json.dumps(list(tags)))
if not joins_existing_trace:
tags: Final = LangfuseOtelLogger._trace_tags(kwargs, metadata)
if tags:
safe_set_attribute(span, LangfuseSpanAttributes.TAGS.value, json.dumps(list(tags)))
input_json: Final = safe_dumps(kwargs.get("messages")) if kwargs.get("messages") else None
if input_json is not None:

View file

@ -219,7 +219,6 @@ class TestLangfuseOtelIntegration:
LangfuseSpanAttributes.TRACE_USER_ID.value: "user-123",
LangfuseSpanAttributes.SESSION_ID.value: "sess-456",
# Lists / dicts should be JSON strings
LangfuseSpanAttributes.TAGS.value: json.dumps(["tagA", "tagB"]),
LangfuseSpanAttributes.TRACE_NAME.value: "trace-name",
LangfuseSpanAttributes.TRACE_ID.value: "traceid", # stripped dashes
LangfuseSpanAttributes.TRACE_METADATA.value: json.dumps({"k": "v"}),
@ -1033,7 +1032,7 @@ class TestDerivedTraceFields:
"messages": [{"role": "user", "content": "hi"}],
"cache_hit": True,
"litellm_params": {
"metadata": {"existing_trace_id": "abc123", "tags": ["mine"]},
"metadata": {"existing_trace_id": "abc123", "tags": ["experiment"]},
},
"standard_logging_object": {"request_tags": ["injected"]},
},
@ -1043,9 +1042,9 @@ class TestDerivedTraceFields:
assert attributes["langfuse.trace.existing_id"] == "abc123"
assert "langfuse.trace.input" not in attributes
assert "langfuse.trace.output" not in attributes
assert "langfuse.trace.tags" not in attributes
assert "langfuse.observation.input" in attributes
assert "langfuse.observation.output" in attributes
assert json.loads(attributes["langfuse.trace.tags"]) == ["mine"]
def test_responses_api_output_mirrors_observation_output_items(self):
from openai.types.responses import ResponseFunctionToolCall