diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index d97708efd..35a1769f2 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -1548,6 +1548,7 @@ def update_current_span( langfuse_client=self, environment=self._environment, release=self._release, + set_observation_type=False, ) if name: @@ -1604,6 +1605,7 @@ def set_current_trace_io( langfuse_client=self, environment=self._environment, release=self._release, + set_observation_type=False, ) span.set_trace_io( @@ -1638,6 +1640,7 @@ def set_current_trace_as_public(self) -> None: otel_span=current_otel_span, langfuse_client=self, environment=self._environment, + set_observation_type=False, ) span.set_trace_as_public() diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py index 71f8d68c7..b6131cbcc 100644 --- a/langfuse/_client/span.py +++ b/langfuse/_client/span.py @@ -96,6 +96,7 @@ def __init__( usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Optional[PromptClient] = None, + set_observation_type: bool = True, ): """Initialize a new Langfuse span wrapper. @@ -120,11 +121,15 @@ def __init__( usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) cost_details: Cost information for the model call prompt: Associated prompt template from Langfuse prompt management + set_observation_type: Whether to write the Langfuse observation type to + the wrapped OpenTelemetry span """ self._otel_span = otel_span - self._otel_span.set_attribute( - LangfuseOtelSpanAttributes.OBSERVATION_TYPE, as_type - ) + self._set_observation_type = set_observation_type + if self._set_observation_type: + self._otel_span.set_attribute( + LangfuseOtelSpanAttributes.OBSERVATION_TYPE, as_type + ) self._langfuse_client = langfuse_client self._observation_type = as_type @@ -727,6 +732,9 @@ def update( ), ) + if not self._set_observation_type: + attributes.pop(LangfuseOtelSpanAttributes.OBSERVATION_TYPE, None) + self._otel_span.set_attributes(attributes=attributes) # Set OTEL span status if level is ERROR self._set_otel_span_status_if_error(level=level, status_message=status_message) @@ -1286,6 +1294,7 @@ def __init__( version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, + set_observation_type: bool = True, ): """Initialize a new LangfuseSpan. @@ -1300,6 +1309,8 @@ def __init__( version: Version identifier for the code or component level: Importance level of the span (info, warning, error) status_message: Optional status message for the span + set_observation_type: Whether to write the Langfuse observation type to + the wrapped OpenTelemetry span """ super().__init__( otel_span=otel_span, @@ -1313,6 +1324,7 @@ def __init__( version=version, level=level, status_message=status_message, + set_observation_type=set_observation_type, ) @@ -1343,6 +1355,7 @@ def __init__( usage_details: Optional[Dict[str, int]] = None, cost_details: Optional[Dict[str, float]] = None, prompt: Optional[PromptClient] = None, + set_observation_type: bool = True, ): """Initialize a new LangfuseGeneration span. @@ -1363,6 +1376,8 @@ def __init__( usage_details: Token usage information (e.g., prompt_tokens, completion_tokens) cost_details: Cost information for the model call prompt: Associated prompt template from Langfuse prompt management + set_observation_type: Whether to write the Langfuse observation type to + the wrapped OpenTelemetry span """ super().__init__( as_type="generation", @@ -1382,6 +1397,7 @@ def __init__( usage_details=usage_details, cost_details=cost_details, prompt=prompt, + set_observation_type=set_observation_type, ) @@ -1401,6 +1417,7 @@ def __init__( version: Optional[str] = None, level: Optional[SpanLevel] = None, status_message: Optional[str] = None, + set_observation_type: bool = True, ): """Initialize a new LangfuseEvent span. @@ -1415,6 +1432,8 @@ def __init__( version: Version identifier for the model or component level: Importance level of the generation (info, warning, error) status_message: Optional status message for the generation + set_observation_type: Whether to write the Langfuse observation type to + the wrapped OpenTelemetry span """ super().__init__( otel_span=otel_span, @@ -1428,6 +1447,7 @@ def __init__( version=version, level=level, status_message=status_message, + set_observation_type=set_observation_type, ) def update( diff --git a/tests/unit/test_otel.py b/tests/unit/test_otel.py index 46a085a71..49613c965 100644 --- a/tests/unit/test_otel.py +++ b/tests/unit/test_otel.py @@ -374,6 +374,19 @@ def test_update_current_span_name(self, langfuse_client, memory_exporter): ) assert len(original_spans) == 0, "Expected no spans with original name" + def test_update_current_span_preserves_third_party_observation_type( + self, langfuse_client, tracer_provider + ): + tracer = tracer_provider.get_tracer("openinference.instrumentation.agno") + + with tracer.start_as_current_span("third-party-agent") as span: + span.set_attribute("openinference.span.kind", "AGENT") + + langfuse_client.update_current_span(metadata={"user_id": "u1"}) + + assert LangfuseOtelSpanAttributes.OBSERVATION_TYPE not in span.attributes + assert span.attributes["langfuse.observation.metadata.user_id"] == "u1" + def test_span_attributes(self, langfuse_client, memory_exporter): """Test that span attributes are correctly set and updated.""" # Create a span with attributes