@@ -110,6 +110,17 @@ def _parse_semconv_opt_in(self) -> set[str]:
110110 opt_in_env = os .getenv ("OTEL_SEMCONV_STABILITY_OPT_IN" , "" )
111111 return {value .strip () for value in opt_in_env .split ("," )}
112112
113+ @property
114+ def is_langfuse (self ) -> bool :
115+ """Check if Langfuse is configured as the OTLP endpoint.
116+
117+ Returns:
118+ True if Langfuse is the OTLP endpoint, False otherwise.
119+ """
120+ return "langfuse" in os .getenv ("OTEL_EXPORTER_OTLP_ENDPOINT" , "" ) or "langfuse" in os .getenv (
121+ "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" , ""
122+ )
123+
113124 def _start_span (
114125 self ,
115126 span_name : str ,
@@ -142,23 +153,10 @@ def _start_span(
142153
143154 # Add all provided attributes
144155 if attributes :
145- self . _set_attributes ( span , attributes )
156+ span . set_attributes ( attributes )
146157
147158 return span
148159
149- def _set_attributes (self , span : Span , attributes : dict [str , AttributeValue ]) -> None :
150- """Set attributes on a span, handling different value types appropriately.
151-
152- Args:
153- span: The span to set attributes on
154- attributes: Dictionary of attributes to set
155- """
156- if not span :
157- return
158-
159- for key , value in attributes .items ():
160- span .set_attribute (key , value )
161-
162160 def _add_optional_usage_and_metrics_attributes (
163161 self , attributes : dict [str , AttributeValue ], usage : Usage , metrics : Metrics
164162 ) -> None :
@@ -203,7 +201,7 @@ def _end_span(
203201
204202 # Add any additional attributes
205203 if attributes :
206- self . _set_attributes ( span , attributes )
204+ span . set_attributes ( attributes )
207205
208206 # Handle error if present
209207 if error :
@@ -236,17 +234,24 @@ def end_span_with_error(self, span: Span, error_message: str, exception: Excepti
236234 error = exception or Exception (error_message )
237235 self ._end_span (span , error = error )
238236
239- def _add_event (self , span : Span | None , event_name : str , event_attributes : Attributes ) -> None :
237+ def _add_event (
238+ self , span : Span | None , event_name : str , event_attributes : Attributes , to_span_attributes : bool = False
239+ ) -> None :
240240 """Add an event with attributes to a span.
241241
242242 Args:
243243 span: The span to add the event to
244244 event_name: Name of the event
245245 event_attributes: Dictionary of attributes to set on the event
246+ to_span_attributes: Add the attributes to span attributes
246247 """
247248 if not span :
248249 return
249250
251+ # Add to span attribute since some backend can't read the events
252+ if to_span_attributes and event_attributes :
253+ span .set_attributes (event_attributes )
254+
250255 span .add_event (event_name , attributes = event_attributes )
251256
252257 def _get_event_name_for_message (self , message : Message ) -> str :
@@ -358,6 +363,7 @@ def end_model_invoke_span(
358363 ]
359364 ),
360365 },
366+ to_span_attributes = self .is_langfuse ,
361367 )
362368 else :
363369 self ._add_event (
@@ -366,7 +372,7 @@ def end_model_invoke_span(
366372 event_attributes = {"finish_reason" : str (stop_reason ), "message" : serialize (message ["content" ])},
367373 )
368374
369- self . _set_attributes ( span , attributes )
375+ span . set_attributes ( attributes )
370376
371377 def start_tool_call_span (
372378 self ,
@@ -423,6 +429,7 @@ def start_tool_call_span(
423429 ]
424430 )
425431 },
432+ to_span_attributes = self .is_langfuse ,
426433 )
427434 else :
428435 self ._add_event (
@@ -476,6 +483,7 @@ def end_tool_call_span(self, span: Span, tool_result: ToolResult | None, error:
476483 ]
477484 )
478485 },
486+ to_span_attributes = self .is_langfuse ,
479487 )
480488 else :
481489 self ._add_event (
@@ -572,6 +580,7 @@ def end_event_loop_cycle_span(
572580 ]
573581 )
574582 },
583+ to_span_attributes = self .is_langfuse ,
575584 )
576585 else :
577586 self ._add_event (span , "gen_ai.choice" , event_attributes = event_attributes )
@@ -666,6 +675,7 @@ def end_agent_span(
666675 ]
667676 )
668677 },
678+ to_span_attributes = self .is_langfuse ,
669679 )
670680 else :
671681 self ._add_event (
@@ -675,9 +685,7 @@ def end_agent_span(
675685 )
676686
677687 if hasattr (response , "metrics" ) and hasattr (response .metrics , "accumulated_usage" ):
678- if "langfuse" in os .getenv ("OTEL_EXPORTER_OTLP_ENDPOINT" , "" ) or "langfuse" in os .getenv (
679- "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" , ""
680- ):
688+ if self .is_langfuse :
681689 attributes .update ({"langfuse.observation.type" : "span" })
682690 accumulated_usage = response .metrics .accumulated_usage
683691 attributes .update (
@@ -736,6 +744,7 @@ def start_multiagent_span(
736744 span ,
737745 "gen_ai.client.inference.operation.details" ,
738746 {"gen_ai.input.messages" : serialize ([{"role" : "user" , "parts" : parts }])},
747+ to_span_attributes = self .is_langfuse ,
739748 )
740749 else :
741750 self ._add_event (
@@ -767,6 +776,7 @@ def end_swarm_span(
767776 ]
768777 )
769778 },
779+ to_span_attributes = self .is_langfuse ,
770780 )
771781 else :
772782 self ._add_event (
@@ -816,7 +826,10 @@ def _add_event_messages(self, span: Span, messages: Messages) -> None:
816826 {"role" : message ["role" ], "parts" : self ._map_content_blocks_to_otel_parts (message ["content" ])}
817827 )
818828 self ._add_event (
819- span , "gen_ai.client.inference.operation.details" , {"gen_ai.input.messages" : serialize (input_messages )}
829+ span ,
830+ "gen_ai.client.inference.operation.details" ,
831+ {"gen_ai.input.messages" : serialize (input_messages )},
832+ to_span_attributes = self .is_langfuse ,
820833 )
821834 else :
822835 for message in messages :
0 commit comments