-
Notifications
You must be signed in to change notification settings - Fork 37
Description
Prerequisites
- I've searched the current open issues
- I've updated to the latest version of Toolbox
- I've updated to the latest version of the SDK
Toolbox version
0.26.0
Environment
- OS type and version: Darwin Kernel
- How are you running Toolbox:
- As a downloaded binary from curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
- Python version: 3.13.11
- pip version: 26.0.1
Client
- Client: toolbox-adk (installed via google-adk[toolbox])
- Version: (
pip show <package-name>)?
- toolbox-adk 0.5.8
- toolbox-core 0.5.8
- google-adk 1.24.1
- google-genai 1.61.0
- Example: If possible, please include your code of configuration:
from google.adk.agents import LlmAgent
from google.adk.tools.toolbox_toolset import ToolboxToolset
from toolbox_adk import CredentialStrategy
from toolbox_core.protocol import Protocol
toolset = ToolboxToolset(
server_url="http://127.0.0.1:5000",
toolset_name="my-toolset",
credentials=CredentialStrategy.toolbox_identity(),
protocol=Protocol.MCP_v20251125,
)
root_agent = LlmAgent(
model="gemini-2.5-flash",
name="my_agent",
description="An agent that uses Toolbox tools.",
instruction="You are a helpful agent. Use the available tools to answer questions.",
tools=[toolset],
)```
### Expected Behavior
When ToolboxToolset resolves tools from the Toolbox server and passes them to LlmAgent, each tool should produce a valid FunctionDeclaration so the Gemini API knows which tools are available. The model should then make proper function calls to invoke those tools.
### Current Behavior
The model returns finish_reason: UNEXPECTED_TOOL_CALL with adk_error_code: "UNEXPECTED_TOOL_CALL". The event looks like:
```yaml
kind: "message"
metadata:
adk_error_code: "UNEXPECTED_TOOL_CALL"
parts:
- kind: "text"
text: "Unexpected tool call: print(default_api.get_tool_name(param='value'))"
role: "agent"The model hallucinates Python code like print(default_api.<tool_name>(...)) instead of making proper function calls, because it has no tool definitions to work with.
Root cause: toolbox_adk.ToolboxTool (in toolbox_adk/tool.py) extends google.adk.tools.base_tool.BaseTool but does not override _get_declaration(). The base class implementation returns None:
# google/adk/tools/base_tool.py line 81-94
def _get_declaration(self) -> Optional[types.FunctionDeclaration]:
return NoneWhen the ADK builds the LLM request via LlmRequest.append_tools(), it calls _get_declaration() on each tool:
# google/adk/models/llm_request.py line 254-258
for tool in tools:
declaration = tool._get_declaration()
if declaration: # <-- always None for ToolboxTool
declarations.append(declaration)
self.tools_dict[tool.name] = toolSince every ToolboxTool returns None, zero FunctionDeclaration objects are added to the request. The model receives no tool schema, tries to call a tool anyway, and the Gemini API rejects it with UNEXPECTED_TOOL_CALL.
Steps to reproduce?
- Start a Toolbox server with any toolset containing one or more tools:
- Create a minimal agent using ToolboxToolset:
from google.adk.agents import LlmAgent
from google.adk.tools.toolbox_toolset import ToolboxToolset
from toolbox_adk import CredentialStrategy
from toolbox_core.protocol import Protocol
toolset = ToolboxToolset(
server_url="http://127.0.0.1:5000",
toolset_name="my-toolset",
credentials=CredentialStrategy.toolbox_identity(),
protocol=Protocol.MCP_v20251125,
)
root_agent = LlmAgent(
model="gemini-2.5-flash",
name="my_agent",
description="Test agent",
instruction="Use the available tools to answer questions.",
tools=[toolset],
)- Run the agent via adk web or programmatically and send any prompt that should trigger a tool call.
- Observe the response event contains adk_error_code: "UNEXPECTED_TOOL_CALL" and the model generates code like print(default_api.<tool_name>(...)) instead of a proper function call.
Additional Details
Workaround / Proposed Fix
Add a _get_declaration() override to toolbox_adk.ToolboxTool in toolbox_adk/tool.py. The core tool (toolbox_core.ToolboxTool) already exposes _params — a list of ParameterSchema objects with name, type, description, and required fields. This method converts them into a proper FunctionDeclaration:
@override
def _get_declaration(self):
"""Build a FunctionDeclaration from the core tool's parameter metadata."""
from google.genai import types as genai_types
_TYPE_MAP = {
"string": "STRING",
"integer": "INTEGER",
"float": "NUMBER",
"number": "NUMBER",
"boolean": "BOOLEAN",
"array": "ARRAY",
"object": "OBJECT",
}
core_params = getattr(self._core_tool, "_params", [])
if not core_params:
return genai_types.FunctionDeclaration(
name=self.name,
description=self.description,
)
properties = {}
required = []
for p in core_params:
schema_type = _TYPE_MAP.get(p.type, "STRING")
properties[p.name] = genai_types.Schema(
type=schema_type,
description=p.description,
)
if p.required:
required.append(p.name)
return genai_types.FunctionDeclaration(
name=self.name,
description=self.description,
parameters=genai_types.Schema(
type="OBJECT",
properties=properties,
required=required if required else None,
),
)This method should be placed in the ToolboxTool class in toolbox_adk/tool.py, between init and run_async.
Verification
After applying this patch locally to site-packages/toolbox_adk/tool.py, the tools produce valid FunctionDeclaration objects, the model receives proper tool schemas, and makes correct function calls without hallucinating code.
Note on default_api
The default_api prefix in the hallucinated code (print(default_api.<tool_name>(...))) is an OpenAPI/Swagger convention. When the model has no FunctionDeclaration but sees tool-like context in the system prompt, it falls back to generating OpenAPI-style Python client code instead of making proper function calls.