17
17
OpenInferenceSpanKindValues ,
18
18
SpanAttributes ,
19
19
ToolAttributes ,
20
+ ToolCallAttributes ,
20
21
)
21
22
22
23
__all__ = ("_RequestAttributesExtractor" ,)
@@ -322,7 +323,7 @@ def _get_attributes_from_message_param(
322
323
elif isinstance (input_contents , Content ) or isinstance (input_contents , UserContent ):
323
324
yield from self ._get_attributes_from_content (input_contents )
324
325
elif isinstance (input_contents , Part ):
325
- yield from self ._get_attributes_from_part (input_contents )
326
+ yield from self ._get_attributes_from_part (input_contents , 0 )
326
327
else :
327
328
# TODO: Implement for File, PIL_Image
328
329
logger .exception (f"Unexpected input contents type: { type (input_contents )} " )
@@ -345,28 +346,54 @@ def _get_attributes_from_content(
345
346
yield from self ._flatten_parts (parts )
346
347
347
348
def _get_attributes_from_function_call (
348
- self , function_call : FunctionCall
349
+ self , function_call : FunctionCall , tool_call_index : int
349
350
) -> Iterator [Tuple [str , AttributeValue ]]:
350
351
if name := get_attribute (function_call , "name" ):
351
352
if isinstance (name , str ):
352
- yield (MessageAttributes .MESSAGE_FUNCTION_CALL_NAME , name )
353
+ yield (
354
+ MessageAttributes .MESSAGE_TOOL_CALLS
355
+ + f".{ tool_call_index } ."
356
+ + ToolCallAttributes .TOOL_CALL_FUNCTION_NAME ,
357
+ name ,
358
+ )
353
359
if args := get_attribute (function_call , "args" ):
354
- yield (MessageAttributes .MESSAGE_FUNCTION_CALL_ARGUMENTS_JSON , safe_json_dumps (args ))
360
+ yield (
361
+ MessageAttributes .MESSAGE_TOOL_CALLS
362
+ + f".{ tool_call_index } ."
363
+ + ToolCallAttributes .TOOL_CALL_FUNCTION_ARGUMENTS_JSON ,
364
+ safe_json_dumps (args ),
365
+ )
366
+
367
+ if id := get_attribute (function_call , "id" ):
368
+ yield (
369
+ MessageAttributes .MESSAGE_TOOL_CALLS
370
+ + f".{ tool_call_index } ."
371
+ + ToolCallAttributes .TOOL_CALL_ID ,
372
+ id ,
373
+ )
355
374
356
375
def _get_attributes_from_function_response (
357
376
self , function_response : FunctionResponse
358
377
) -> Iterator [Tuple [str , AttributeValue ]]:
359
378
if response := get_attribute (function_response , "response" ):
360
379
yield (MessageAttributes .MESSAGE_CONTENT , safe_json_dumps (response ))
380
+ if id := get_attribute (function_response , "id" ):
381
+ yield (
382
+ MessageAttributes .MESSAGE_TOOL_CALL_ID ,
383
+ id ,
384
+ )
361
385
362
386
def _flatten_parts (self , parts : list [Part ]) -> Iterator [Tuple [str , AttributeValue ]]:
363
387
content_values = []
388
+ tool_call_index = 0
364
389
for part in parts :
365
- for attr , value in self ._get_attributes_from_part (part ):
366
- if attr in [
367
- MessageAttributes .MESSAGE_FUNCTION_CALL_NAME ,
368
- MessageAttributes .MESSAGE_FUNCTION_CALL_ARGUMENTS_JSON ,
369
- ]:
390
+ for attr , value in self ._get_attributes_from_part (part , tool_call_index ):
391
+ if attr .startswith (MessageAttributes .MESSAGE_TOOL_CALLS ):
392
+ # Increment tool call index if there happens to be multiple tool calls
393
+ # across parts
394
+ tool_call_index = self ._extract_tool_call_index (attr ) + 1
395
+ yield (attr , value )
396
+ elif attr == MessageAttributes .MESSAGE_TOOL_CALL_ID :
370
397
yield (attr , value )
371
398
elif isinstance (value , str ):
372
399
# Flatten all other string values into a single message content
@@ -377,15 +404,27 @@ def _flatten_parts(self, parts: list[Part]) -> Iterator[Tuple[str, AttributeValu
377
404
if content_values :
378
405
yield (MessageAttributes .MESSAGE_CONTENT , "\n \n " .join (content_values ))
379
406
380
- def _get_attributes_from_part (self , part : Part ) -> Iterator [Tuple [str , AttributeValue ]]:
407
+ def _extract_tool_call_index (self , attr : str ) -> int :
408
+ """Extract tool call index from message tool call attribute key.
409
+
410
+ Example: 'message.tool_calls.0.function_name' -> 0
411
+ """
412
+ parts = attr .split ("." )
413
+ if len (parts ) >= 3 and parts [2 ].isdigit ():
414
+ return int (parts [2 ])
415
+ return 0
416
+
417
+ def _get_attributes_from_part (
418
+ self , part : Part , tool_call_index : int
419
+ ) -> Iterator [Tuple [str , AttributeValue ]]:
381
420
# https://github.com/googleapis/python-genai/blob/main/google/genai/types.py#L566
382
421
if text := get_attribute (part , "text" ):
383
422
yield (
384
423
MessageAttributes .MESSAGE_CONTENT ,
385
424
text ,
386
425
)
387
426
elif function_call := get_attribute (part , "function_call" ):
388
- yield from self ._get_attributes_from_function_call (function_call )
427
+ yield from self ._get_attributes_from_function_call (function_call , tool_call_index )
389
428
elif function_response := get_attribute (part , "function_response" ):
390
429
yield from self ._get_attributes_from_function_response (function_response )
391
430
else :
0 commit comments