1717from uuid import UUID
1818
1919from opentelemetry import trace
20- from opentelemetry ._events import Event
21- from opentelemetry ._logs import LogRecord
20+ from opentelemetry ._logs import Logger , LogRecord
2221from opentelemetry .context import Context , get_current
2322from opentelemetry .metrics import Meter
2423from opentelemetry .semconv ._incubating .attributes import (
3736from opentelemetry .trace .status import Status , StatusCode
3837from opentelemetry .util .types import Attributes
3938
40- from .data import Error
39+ from .data import ChatGeneration , Error , Message
4140from .instruments import Instruments
4241from .types import LLMInvocation
4342
@@ -49,7 +48,6 @@ class _SpanState:
4948 start_time : float
5049 request_model : Optional [str ] = None
5150 system : Optional [str ] = None
52- db_system : Optional [str ] = None
5351 children : List [UUID ] = field (default_factory = list )
5452
5553
@@ -60,93 +58,54 @@ def _get_property_value(obj, property_name) -> object:
6058 return getattr (obj , property_name , None )
6159
6260
63- def _message_to_event (message , provider_name , framework ) -> Optional [Event ]:
64- content = _get_property_value (message , "content" )
65- # TODO: check if content is not None and should_collect_content()
66- if content :
67- # update this to event.gen_ai.client.inference.operation.details: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-events.md
68- message_type = _get_property_value (message , "type" )
69- message_type = "user" if message_type == "human" else message_type
70- body = {"content" : content }
71- attributes = {
72- # TODO: add below to opentelemetry.semconv._incubating.attributes.gen_ai_attributes
73- "gen_ai.provider.name" : provider_name , # Added in 1.37 - https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/gen-ai.md#gen-ai-provider-name
74- "gen_ai.framework" : framework ,
75- GenAI .GEN_AI_SYSTEM : provider_name , # Deprecated: Removed in 1.37
76- }
77-
78- return Event (
79- name = f"gen_ai.{ message_type } .message" ,
80- attributes = attributes ,
81- body = body or None ,
82- )
83-
84-
8561def _message_to_log_record (
86- message , provider_name , framework
62+ message : Message , provider_name , framework , capture_content : bool
8763) -> Optional [LogRecord ]:
8864 content = _get_property_value (message , "content" )
89- # check if content is not None and should_collect_content()
9065 message_type = _get_property_value (message , "type" )
91- body = {"content" : content }
66+
67+ body = {}
68+ if content and capture_content :
69+ body = {"type" : message_type , "content" : content }
9270
9371 attributes = {
9472 # TODO: add below to opentelemetry.semconv._incubating.attributes.gen_ai_attributes
9573 "gen_ai.framework" : framework ,
74+ # TODO: Convert below to constant once opentelemetry.semconv._incubating.attributes.gen_ai_attributes is available
9675 "gen_ai.provider.name" : provider_name ,
97- GenAI .GEN_AI_SYSTEM : provider_name , # Deprecated: use "gen_ai.provider.name"
9876 }
9977
78+ if capture_content :
79+ attributes ["gen_ai.input.messages" ] = [message ._to_part_dict ()]
80+
10081 return LogRecord (
101- event_name = f "gen_ai.{ message_type } .message " ,
82+ event_name = "gen_ai.client.inference.operation.details " ,
10283 attributes = attributes ,
10384 body = body or None ,
10485 )
10586
10687
107- def _chat_generation_to_event (
108- chat_generation , index , provider_name , framework
109- ) -> Optional [Event ]:
110- if chat_generation .content :
111- attributes = {
112- # TODO: add below to opentelemetry.semconv._incubating.attributes.gen_ai_attributes
113- "gen_ai.provider.name" : provider_name , # added in 1.37 - https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/gen-ai.md#gen-ai-provider-name
114- "gen_ai.framework" : framework ,
115- GenAI .GEN_AI_SYSTEM : provider_name , # Deprecated: removed in 1.37
116- }
117-
118- message = {
119- "content" : chat_generation .content ,
120- "type" : chat_generation .type ,
121- }
122- body = {
123- "index" : index ,
124- "finish_reason" : chat_generation .finish_reason or "error" ,
125- "message" : message ,
126- }
127-
128- return Event (
129- name = "gen_ai.choice" ,
130- attributes = attributes ,
131- body = body or None ,
132- )
133-
134-
13588def _chat_generation_to_log_record (
136- chat_generation , index , prefix , provider_name , framework
89+ chat_generation : ChatGeneration ,
90+ index ,
91+ provider_name ,
92+ framework ,
93+ capture_content : bool ,
13794) -> Optional [LogRecord ]:
13895 if chat_generation :
13996 attributes = {
14097 # TODO: add below to opentelemetry.semconv._incubating.attributes.gen_ai_attributes
14198 "gen_ai.framework" : framework ,
99+ # TODO: Convert below to constant once opentelemetry.semconv._incubating.attributes.gen_ai_attributes is available
142100 "gen_ai.provider.name" : provider_name ,
143- GenAI .GEN_AI_SYSTEM : provider_name , # Deprecated: removed in 1.37
144101 }
145102
146103 message = {
147- "content" : chat_generation .content ,
148104 "type" : chat_generation .type ,
149105 }
106+ if capture_content and chat_generation .content :
107+ message ["content" ] = chat_generation .content
108+
150109 body = {
151110 "index" : index ,
152111 "finish_reason" : chat_generation .finish_reason or "error" ,
@@ -204,13 +163,18 @@ class SpanMetricEventEmitter(BaseEmitter):
204163 """
205164
206165 def __init__ (
207- self , event_logger , tracer : Tracer = None , meter : Meter = None
166+ self ,
167+ logger : Logger = None ,
168+ tracer : Tracer = None ,
169+ meter : Meter = None ,
170+ capture_content : bool = False ,
208171 ):
209172 self ._tracer = tracer or trace .get_tracer (__name__ )
210173 instruments = Instruments (meter )
211174 self ._duration_histogram = instruments .operation_duration_histogram
212175 self ._token_histogram = instruments .token_usage_histogram
213- self ._event_logger = event_logger
176+ self ._logger = logger
177+ self ._capture_content = capture_content
214178
215179 # Map from run_id -> _SpanState, to keep track of spans and parent/child relationships
216180 self .spans : Dict [UUID , _SpanState ] = {}
@@ -250,13 +214,25 @@ def init(self, invocation: LLMInvocation):
250214
251215 for message in invocation .messages :
252216 system = invocation .attributes .get ("system" )
253- self ._event_logger .emit (
254- _message_to_event (
255- message = message ,
256- provider_name = system ,
257- framework = invocation .attributes .get ("framework" ),
258- )
217+ # Event API is deprecated, use structured logs instead
218+ # event = _message_to_event(
219+ # message=message,
220+ # provider_name=system,
221+ # framework=invocation.attributes.get("framework"),
222+ # )
223+ # if event and self._event_logger:
224+ # self._event_logger.emit(
225+ # event
226+ # )
227+
228+ log = _message_to_log_record (
229+ message = message ,
230+ provider_name = system ,
231+ framework = invocation .attributes .get ("framework" ),
232+ capture_content = self ._capture_content ,
259233 )
234+ if log and self ._logger :
235+ self ._logger .emit (log )
260236
261237 def emit (self , invocation : LLMInvocation ):
262238 system = invocation .attributes .get ("system" )
@@ -304,11 +280,24 @@ def emit(self, invocation: LLMInvocation):
304280 for index , chat_generation in enumerate (
305281 invocation .chat_generations
306282 ):
307- self ._event_logger .emit (
308- _chat_generation_to_event (
309- chat_generation , index , system , framework
310- )
283+ # Event API is deprecated. Use structured logs instead
284+ # event = _chat_generation_to_event(
285+ # chat_generation, index, system, framework
286+ # )
287+ # if event and self._event_logger:
288+ # self._event_logger.emit(
289+ # event
290+ # )
291+
292+ log = _chat_generation_to_log_record (
293+ chat_generation ,
294+ index ,
295+ system ,
296+ framework ,
297+ capture_content = self ._capture_content ,
311298 )
299+ if log and self ._logger :
300+ self ._logger .emit (log )
312301 finish_reasons .append (chat_generation .finish_reason )
313302
314303 if finish_reasons is not None and len (finish_reasons ) > 0 :
@@ -426,11 +415,17 @@ class SpanMetricEmitter(BaseEmitter):
426415 Emits only spans and metrics (no events).
427416 """
428417
429- def __init__ (self , tracer : Tracer = None , meter : Meter = None ):
418+ def __init__ (
419+ self ,
420+ tracer : Tracer = None ,
421+ meter : Meter = None ,
422+ capture_content : bool = False ,
423+ ):
430424 self ._tracer = tracer or trace .get_tracer (__name__ )
431425 instruments = Instruments (meter )
432426 self ._duration_histogram = instruments .operation_duration_histogram
433427 self ._token_histogram = instruments .token_usage_histogram
428+ self ._capture_content = capture_content
434429
435430 # Map from run_id -> _SpanState, to keep track of spans and parent/child relationships
436431 self .spans : Dict [UUID , _SpanState ] = {}
@@ -502,21 +497,19 @@ def emit(self, invocation: LLMInvocation):
502497 # TODO: add below to opentelemetry.semconv._incubating.attributes.gen_ai_attributes
503498 framework = invocation .attributes .get ("framework" )
504499 if framework is not None :
505- span .set_attribute (
506- "gen_ai.framework" , invocation .attributes .get ("framework" )
507- )
500+ span .set_attribute ("gen_ai.framework" , framework )
508501 span .set_attribute (
509502 GenAI .GEN_AI_SYSTEM , system
510503 ) # Deprecated: use "gen_ai.provider.name"
511504 # TODO: add below to opentelemetry.semconv._incubating.attributes.gen_ai_attributes
512505 span .set_attribute ("gen_ai.provider.name" , system )
513506
514- finish_reasons = []
507+ finish_reasons : list [ str ] = []
515508 for index , chat_generation in enumerate (
516509 invocation .chat_generations
517510 ):
518511 finish_reasons .append (chat_generation .finish_reason )
519- if finish_reasons is not None and len (finish_reasons ) > 0 :
512+ if finish_reasons and len (finish_reasons ) > 0 :
520513 span .set_attribute (
521514 GenAI .GEN_AI_RESPONSE_FINISH_REASONS , finish_reasons
522515 )
0 commit comments