Bug: Sequential tool calls fail with Google Gemini - UNEXPECTED_TOOL_CALL
Description
When using Google Gemini models (2.5+) with agents that require sequential tool calls, the second tool call fails with FinishReason.UNEXPECTED_TOOL_CALL and returns empty content.
Environment
- Library: 10xscale-agentflow
- Provider: Google Gemini (
provider="google")
- Model: gemini-2.5-flash (also affects other Gemini 2.5+ models)
- Python: 3.10+
Steps to Reproduce
- Create an agent with multiple tools:
from agentflow.core.graph import StateGraph, Agent
from agentflow.core import ToolNode
def tool_a(param: str) -> str:
"""First tool - retrieves data"""
return "data from tool A"
def tool_b(param: str) -> str:
"""Second tool - processes data"""
return "processed by tool B"
tool_node = ToolNode([tool_a, tool_b])
agent = Agent(
model="gemini-2.5-flash",
provider="google",
output_type="text",
tool_node=tool_node
)
-
Design a prompt that requires the agent to:
- First call
tool_a to retrieve data
- Then call
tool_b to process/submit that data
-
Run the agent
Expected Behavior
Both tool calls should execute successfully:
tool_a → returns result
tool_b → returns result
- Agent returns final response
Actual Behavior
First tool call works, second fails:
[First LLM call] - Tools passed ✓
[tool_a called] - Returns successfully ✓
[Second LLM call] - Tools NOT passed ✗
Response: {
finish_reason: 'FinishReason.UNEXPECTED_TOOL_CALL',
tools_calls: None,
content: []
}
Root Cause
In agentflow/core/graph/agent_internal/execution.py around line 331:
if state.context and state.context[-1].role == "tool":
response = await self._call_llm_with_retry(messages=messages, stream=is_stream)
else:
tools = await self._resolve_tools(container)
response = await self._call_llm_with_retry(
messages=messages,
tools=tools if tools else None,
stream=is_stream,
)
When the last message is a tool result (role == "tool"), the code skips passing tools to the LLM. This was likely an optimization assuming the model would just process the result and respond.
However, Gemini 2.5+ models with reasoning/thinking often want to make additional tool calls after receiving tool results. When tools aren't passed, Gemini returns UNEXPECTED_TOOL_CALL.
From Google's API documentation:
UNEXPECTED_TOOL_CALL - "The model generated a tool call, but no tools were enabled in the request."
Proposed Fix
Always pass tools to the LLM, regardless of the previous message type:
# Always resolve tools - even after tool results, the model may want to call
# additional tools (e.g., Gemini 2.5+ with sequential tool calls)
tools = await self._resolve_tools(container)
response = await self._call_llm_with_retry(
messages=messages,
tools=tools if tools else None,
stream=is_stream,
)
Impact
Affected workflows:
- Any agent requiring sequential tool calls (A → B → C)
- Data retrieval followed by data submission
- Multi-step validation processes
- Any "read then write" pattern with tools
Not affected:
- Single tool call workflows
- Agents without tools
- Non-Google providers (OpenAI, Anthropic) - not verified
Workaround
Manually patch the file in your venv until an official fix is released.
Additional Context
I discovered this while building a Sprint Update Validator that needs to:
- Call
get_sprint_updates_tool to retrieve history
- Call
submit_update_tool to save the validated update
- Return JSON response
The first tool worked, but the second always failed with UNEXPECTED_TOOL_CALL.
Bug: Sequential tool calls fail with Google Gemini -
UNEXPECTED_TOOL_CALLDescription
When using Google Gemini models (2.5+) with agents that require sequential tool calls, the second tool call fails with
FinishReason.UNEXPECTED_TOOL_CALLand returns empty content.Environment
provider="google")Steps to Reproduce
Design a prompt that requires the agent to:
tool_ato retrieve datatool_bto process/submit that dataRun the agent
Expected Behavior
Both tool calls should execute successfully:
tool_a→ returns resulttool_b→ returns resultActual Behavior
First tool call works, second fails:
Root Cause
In
agentflow/core/graph/agent_internal/execution.pyaround line 331:When the last message is a tool result (
role == "tool"), the code skips passing tools to the LLM. This was likely an optimization assuming the model would just process the result and respond.However, Gemini 2.5+ models with reasoning/thinking often want to make additional tool calls after receiving tool results. When tools aren't passed, Gemini returns
UNEXPECTED_TOOL_CALL.From Google's API documentation:
Proposed Fix
Always pass tools to the LLM, regardless of the previous message type:
Impact
Affected workflows:
Not affected:
Workaround
Manually patch the file in your venv until an official fix is released.
Additional Context
I discovered this while building a Sprint Update Validator that needs to:
get_sprint_updates_toolto retrieve historysubmit_update_toolto save the validated updateThe first tool worked, but the second always failed with
UNEXPECTED_TOOL_CALL.