Skip to content

Commit 0ee64fe

Browse files
viniciusdsmelloclaude
authored andcommitted
fix(open-8974): detect all tool types in Google ADK agent traces
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent da42435 commit 0ee64fe

File tree

1 file changed

+61
-4
lines changed

1 file changed

+61
-4
lines changed

src/openlayer/lib/integrations/google_adk_tracer.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
447504
def 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

Comments
 (0)