Skip to content

toolbox_adk.ToolboxTool does not override _get_declaration() — Gemini API returns UNEXPECTED_TOOL_CALL because zero FunctionDeclaration objects are sent #540

@aarthi-a13

Description

@aarthi-a13

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

  1. OS type and version: Darwin Kernel
  2. How are you running Toolbox:
  1. Python version: 3.13.11
  2. pip version: 26.0.1

Client

  1. Client: toolbox-adk (installed via google-adk[toolbox])
  2. 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
  1. 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 None

When 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] = tool

Since 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?

  1. Start a Toolbox server with any toolset containing one or more tools:
  2. 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],
)
  1. Run the agent via adk web or programmatically and send any prompt that should trigger a tool call.
  2. 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.

Metadata

Metadata

Assignees

Labels

type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions