diff --git a/litellm/integrations/langfuse/langfuse_otel.py b/litellm/integrations/langfuse/langfuse_otel.py index e564766eabb..70734b8ba96 100644 --- a/litellm/integrations/langfuse/langfuse_otel.py +++ b/litellm/integrations/langfuse/langfuse_otel.py @@ -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: diff --git a/tests/test_litellm/integrations/test_langfuse_otel.py b/tests/test_litellm/integrations/test_langfuse_otel.py index 57600e2ebd2..b4f5e6d96dd 100644 --- a/tests/test_litellm/integrations/test_langfuse_otel.py +++ b/tests/test_litellm/integrations/test_langfuse_otel.py @@ -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