@@ -91,6 +91,7 @@ def traced_method(wrapped, instance, args, kwargs):
9191 result ,
9292 span_attributes ,
9393 error_type ,
94+ GenAIAttributes .GenAiOperationNameValues .CHAT .value ,
9495 )
9596
9697 return traced_method
@@ -149,6 +150,137 @@ async def traced_method(wrapped, instance, args, kwargs):
149150 result ,
150151 span_attributes ,
151152 error_type ,
153+ GenAIAttributes .GenAiOperationNameValues .CHAT .value ,
154+ )
155+
156+ return traced_method
157+
158+
159+ def embeddings_create (
160+ tracer : Tracer ,
161+ event_logger : EventLogger ,
162+ instruments : Instruments ,
163+ capture_content : bool ,
164+ ):
165+ """Wrap the `create` method of the `Embeddings` class to trace it."""
166+
167+ def traced_method (wrapped , instance , args , kwargs ):
168+ span_attributes = {
169+ ** get_llm_request_attributes (
170+ kwargs ,
171+ instance ,
172+ GenAIAttributes .GenAiOperationNameValues .EMBEDDINGS .value ,
173+ )
174+ }
175+
176+ # Set embeddings dimensions if specified in the request
177+ if "dimensions" in kwargs and kwargs ["dimensions" ] is not None :
178+ span_attributes ["gen_ai.embeddings.dimensions" ] = kwargs [
179+ "dimensions"
180+ ]
181+
182+ span_name = f"{ span_attributes [GenAIAttributes .GEN_AI_OPERATION_NAME ]} { span_attributes [GenAIAttributes .GEN_AI_REQUEST_MODEL ]} "
183+ with tracer .start_as_current_span (
184+ name = span_name ,
185+ kind = SpanKind .CLIENT ,
186+ attributes = span_attributes ,
187+ end_on_exit = False ,
188+ ) as span :
189+ # Store the input for later use in the response attributes
190+ input_text = kwargs .get ("input" , "" )
191+
192+ start = default_timer ()
193+ result = None
194+ error_type = None
195+ try :
196+ result = wrapped (* args , ** kwargs )
197+
198+ if span .is_recording ():
199+ _set_embeddings_response_attributes (
200+ span , result , event_logger , capture_content , input_text
201+ )
202+
203+ span .end ()
204+ return result
205+
206+ except Exception as error :
207+ error_type = type (error ).__qualname__
208+ handle_span_exception (span , error )
209+ raise
210+ finally :
211+ duration = max ((default_timer () - start ), 0 )
212+ _record_metrics (
213+ instruments ,
214+ duration ,
215+ result ,
216+ span_attributes ,
217+ error_type ,
218+ GenAIAttributes .GenAiOperationNameValues .EMBEDDINGS .value ,
219+ )
220+
221+ return traced_method
222+
223+
224+ def async_embeddings_create (
225+ tracer : Tracer ,
226+ event_logger : EventLogger ,
227+ instruments : Instruments ,
228+ capture_content : bool ,
229+ ):
230+ """Wrap the `create` method of the `AsyncEmbeddings` class to trace it."""
231+
232+ async def traced_method (wrapped , instance , args , kwargs ):
233+ span_attributes = {
234+ ** get_llm_request_attributes (
235+ kwargs ,
236+ instance ,
237+ GenAIAttributes .GenAiOperationNameValues .EMBEDDINGS .value ,
238+ )
239+ }
240+
241+ # Set embeddings dimensions if specified in the request
242+ if "dimensions" in kwargs and kwargs ["dimensions" ] is not None :
243+ span_attributes ["gen_ai.embeddings.dimensions" ] = kwargs [
244+ "dimensions"
245+ ]
246+
247+ span_name = f"{ span_attributes [GenAIAttributes .GEN_AI_OPERATION_NAME ]} { span_attributes [GenAIAttributes .GEN_AI_REQUEST_MODEL ]} "
248+ with tracer .start_as_current_span (
249+ name = span_name ,
250+ kind = SpanKind .CLIENT ,
251+ attributes = span_attributes ,
252+ end_on_exit = False ,
253+ ) as span :
254+ # Store the input for later use in the response attributes
255+ input_text = kwargs .get ("input" , "" )
256+
257+ start = default_timer ()
258+ result = None
259+ error_type = None
260+ try :
261+ result = await wrapped (* args , ** kwargs )
262+
263+ if span .is_recording ():
264+ _set_embeddings_response_attributes (
265+ span , result , event_logger , capture_content , input_text
266+ )
267+
268+ span .end ()
269+ return result
270+
271+ except Exception as error :
272+ error_type = type (error ).__qualname__
273+ handle_span_exception (span , error )
274+ raise
275+ finally :
276+ duration = max ((default_timer () - start ), 0 )
277+ _record_metrics (
278+ instruments ,
279+ duration ,
280+ result ,
281+ span_attributes ,
282+ error_type ,
283+ GenAIAttributes .GenAiOperationNameValues .EMBEDDINGS .value ,
152284 )
153285
154286 return traced_method
@@ -160,15 +292,22 @@ def _record_metrics(
160292 result ,
161293 span_attributes : dict ,
162294 error_type : Optional [str ],
295+ operation_name : str ,
163296):
297+ """Generalized function to record metrics for both chat and embeddings operations."""
164298 common_attributes = {
165- GenAIAttributes .GEN_AI_OPERATION_NAME : GenAIAttributes . GenAiOperationNameValues . CHAT . value ,
299+ GenAIAttributes .GEN_AI_OPERATION_NAME : operation_name ,
166300 GenAIAttributes .GEN_AI_SYSTEM : GenAIAttributes .GenAiSystemValues .OPENAI .value ,
167301 GenAIAttributes .GEN_AI_REQUEST_MODEL : span_attributes [
168302 GenAIAttributes .GEN_AI_REQUEST_MODEL
169303 ],
170304 }
171305
306+ if "gen_ai.embeddings.dimensions" in span_attributes :
307+ common_attributes ["gen_ai.embeddings.dimensions" ] = span_attributes [
308+ "gen_ai.embeddings.dimensions"
309+ ]
310+
172311 if error_type :
173312 common_attributes ["error.type" ] = error_type
174313
@@ -210,13 +349,18 @@ def _record_metrics(
210349 attributes = input_attributes ,
211350 )
212351
213- completion_attributes = {
352+ token_count = (
353+ result .usage .total_tokens
354+ if operation_name
355+ == GenAIAttributes .GenAiOperationNameValues .EMBEDDINGS .value
356+ else result .usage .completion_tokens
357+ )
358+ attributes = {
214359 ** common_attributes ,
215360 GenAIAttributes .GEN_AI_TOKEN_TYPE : GenAIAttributes .GenAiTokenTypeValues .COMPLETION .value ,
216361 }
217362 instruments .token_usage_histogram .record (
218- result .usage .completion_tokens ,
219- attributes = completion_attributes ,
363+ token_count , attributes = attributes
220364 )
221365
222366
@@ -262,6 +406,54 @@ def _set_response_attributes(
262406 )
263407
264408
409+ def _set_embeddings_response_attributes (
410+ span ,
411+ result ,
412+ event_logger : EventLogger ,
413+ capture_content : bool ,
414+ input_text : str ,
415+ ):
416+ """Set attributes on the span based on the embeddings response."""
417+ # Set the model name if available
418+ set_span_attribute (
419+ span , GenAIAttributes .GEN_AI_RESPONSE_MODEL , result .model
420+ )
421+
422+ # Set embeddings dimensions if we can determine it from the response
423+ if getattr (result , "data" , None ) and len (result .data ) > 0 :
424+ first_embedding = result .data [0 ]
425+ if getattr (first_embedding , "embedding" , None ):
426+ set_span_attribute (
427+ span ,
428+ "gen_ai.embeddings.dimensions" ,
429+ len (first_embedding .embedding ),
430+ )
431+
432+ # Get the usage
433+ if getattr (result , "usage" , None ):
434+ set_span_attribute (
435+ span ,
436+ GenAIAttributes .GEN_AI_USAGE_INPUT_TOKENS ,
437+ result .usage .prompt_tokens ,
438+ )
439+ set_span_attribute (
440+ span ,
441+ GenAIAttributes .GEN_AI_USAGE_OUTPUT_TOKENS ,
442+ result .usage .total_tokens ,
443+ )
444+
445+ # Emit event for embeddings if content capture is enabled
446+ if capture_content :
447+ input_event = Event (
448+ name = "gen_ai.embeddings" ,
449+ attributes = {
450+ GenAIAttributes .GEN_AI_SYSTEM : GenAIAttributes .GenAiSystemValues .OPENAI .value
451+ },
452+ body = {"content" : input_text , "role" : "user" },
453+ )
454+ event_logger .emit (input_event )
455+
456+
265457class ToolCallBuffer :
266458 def __init__ (self , index , tool_call_id , function_name ):
267459 self .index = index
0 commit comments