@@ -444,6 +444,63 @@ def _extract_llm_attributes(llm_request_dict: Dict[str, Any], llm_response: Opti
444444 return attributes
445445
446446
447+ def _extract_tool_info (tool : Any ) -> Optional [Dict [str , Any ]]:
448+ """Extract info from a single tool entry in an ADK agent's tools list.
449+
450+ ADK agents can have three kinds of tool entries:
451+ 1. Raw callables (Python functions) — have ``__name__`` but not ``name``
452+ 2. BaseTool subclass instances — have ``.name`` and ``.description``
453+ 3. AgentTool instances — BaseTool with an ``.agent`` attribute wrapping
454+ another agent (the "agent-as-a-tool" pattern)
455+
456+ For AgentTool, we recursively extract the wrapped agent's own tools
457+ so the trace shows the full tool hierarchy.
458+
459+ Args:
460+ tool: A tool entry from an ADK agent's ``tools`` list.
461+
462+ Returns:
463+ Dictionary with tool metadata, or None if the tool cannot be identified.
464+ """
465+ tool_info : Optional [Dict [str , Any ]] = None
466+
467+ # Case 1: AgentTool (must check before generic BaseTool)
468+ if hasattr (tool , "agent" ) and hasattr (tool , "name" ):
469+ tool_info = {
470+ "name" : tool .name ,
471+ "type" : "agent_tool" ,
472+ }
473+ if hasattr (tool , "description" ) and tool .description :
474+ tool_info ["description" ] = tool .description
475+
476+ # Recursively extract the wrapped agent's tools
477+ wrapped_agent = tool .agent
478+ if hasattr (wrapped_agent , "tools" ) and wrapped_agent .tools :
479+ agent_tools = []
480+ for inner_tool in wrapped_agent .tools :
481+ inner_info = _extract_tool_info (inner_tool )
482+ if inner_info :
483+ agent_tools .append (inner_info )
484+ if agent_tools :
485+ tool_info ["agent_tools" ] = agent_tools
486+
487+ # Case 2: BaseTool subclass (has .name attribute set by BaseTool.__init__)
488+ elif hasattr (tool , "name" ) and tool .name :
489+ tool_info = {"name" : tool .name }
490+ if hasattr (tool , "description" ) and tool .description :
491+ tool_info ["description" ] = tool .description
492+
493+ # Case 3: Raw callable (plain Python function or lambda)
494+ elif callable (tool ):
495+ name = getattr (tool , "__name__" , None ) or getattr (tool , "__qualname__" , "unknown_tool" )
496+ tool_info = {"name" : name }
497+ doc = getattr (tool , "__doc__" , None )
498+ if doc :
499+ tool_info ["description" ] = doc .strip ().split ("\n " )[0 ]
500+
501+ return tool_info
502+
503+
447504def extract_agent_attributes (instance : Any ) -> Dict [str , Any ]:
448505 """Extract agent metadata for tracing.
449506
@@ -465,13 +522,13 @@ def extract_agent_attributes(instance: Any) -> Dict[str, Any]:
465522 attributes ["instruction" ] = instance .instruction
466523
467524 # Extract tool information
525+ # ADK agents store tools as a mix of raw callables (functions),
526+ # BaseTool instances, and AgentTool wrappers. We need to handle all three.
468527 if hasattr (instance , "tools" ) and instance .tools :
469528 tools_info = []
470529 for tool in instance .tools :
471- if hasattr (tool , "name" ):
472- tool_info = {"name" : tool .name }
473- if hasattr (tool , "description" ):
474- tool_info ["description" ] = tool .description
530+ tool_info = _extract_tool_info (tool )
531+ if tool_info :
475532 tools_info .append (tool_info )
476533 if tools_info :
477534 attributes ["tools" ] = tools_info
0 commit comments