-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
Summary
When using AgentTool to wrap a SequentialAgent as a tool for a PlanReActPlanner, the input_schema defined on the first sub-agent is not exposed in the tool's function declaration. This causes the planner to send unstructured input (a simple request string) instead of the expected structured schema.
Root Cause
In agent_tool.py, the _get_declaration() method only uses input_schema when the wrapped agent is an LlmAgent:
if isinstance(self.agent, LlmAgent) and self.agent.input_schema:
result = _automatic_function_calling_util.build_function_declaration(
func=self.agent.input_schema, variant=self._api_variant
)
else:
# Falls back to simple 'request' string parameter
result = types.FunctionDeclaration(
parameters=types.Schema(
type=types.Type.OBJECT,
properties={
'request': types.Schema(type=types.Type.STRING),
},
required=['request'],
),
...
)
Since SequentialAgent extends BaseAgent (not LlmAgent), it always falls into the else branch, exposing only a generic request string parameter.
Additionally, SequentialAgent itself doesn't support input_schema as a parameter (its Pydantic model has extra='forbid'), so there's no way to define an input schema directly on the sequence.
Expected Behavior
When wrapping a SequentialAgent with AgentTool, the tool should expose the input_schema from the first sub-agent (if available), since that's where the input will be consumed.
Reproduction
from google.adk.agents import LlmAgent, SequentialAgent
from google.adk.planners import PlanReActPlanner
from google.adk.tools import agent_tool
from pydantic import BaseModel, Field
class MyInputSchema(BaseModel):
query: str = Field(..., description="The user's question")
language: str = Field(..., description="The user's language")
agent_1 = LlmAgent(
name="agent_1",
model="gemini-2.5-flash",
input_schema=MyInputSchema, # This schema should be exposed
disallow_transfer_to_peers=True,
)
agent_2 = LlmAgent(
name="agent_2",
model="gemini-2.5-flash",
disallow_transfer_to_peers=True,
)
sequence = SequentialAgent(
name="sequence",
description="Process the query through multiple steps",
sub_agents=[agent_1, agent_2],
)
# AgentTool falls back to 'request' string instead of MyInputSchema
tool_sequence = agent_tool.AgentTool(agent=sequence)
root_agent = LlmAgent(
name="router",
model="gemini-2.5-flash",
planner=PlanReActPlanner(),
tools=[tool_sequence],
disallow_transfer_to_parent=True,
)
Actual behavior: The planner sees tool_sequence with a single request: str parameter.
Expected behavior: The planner sees tool_sequence with query: str and language: str parameters from MyInputSchema.
Suggested Fix
Modify AgentTool._get_declaration() to check for SequentialAgent and use the first sub-agent's input_schema:
def _get_declaration(self) -> types.FunctionDeclaration:
input_schema = None
if isinstance(self.agent, LlmAgent) and self.agent.input_schema:
input_schema = self.agent.input_schema
elif isinstance(self.agent, SequentialAgent) and self.agent.sub_agents:
first_sub = self.agent.sub_agents[0]
if isinstance(first_sub, LlmAgent) and first_sub.input_schema:
input_schema = first_sub.input_schema
if input_schema:
result = _automatic_function_calling_util.build_function_declaration(
func=input_schema, variant=self._api_variant
)
result.description = self.agent.description
else:
# Fall back to 'request' string
...
The same logic should be applied in run_async() to properly validate and serialize the structured input.
Environment
ADK version: 1.22.0
Python: 3.11