Problem
ToolCallingAgent.process_tool_calls() stores pending calls in a dictionary keyed by the model-provided tool_call.id. Some OpenAI-compatible providers and streaming adapters can return an empty ID, and malformed provider responses can repeat an ID. Each later call then overwrites the earlier call before execution.
This silently violates action completeness: the generator yields every requested ToolCall, but only the last call for each ID is executed and recorded. There is no error or warning.
Steps to reproduce
from unittest.mock import MagicMock
from smolagents.agents import ToolCallingAgent, ToolOutput
from smolagents.memory import ActionStep
from smolagents.models import ChatMessage, ChatMessageToolCall, ChatMessageToolCallFunction, MessageRole
from smolagents.tools import Tool
class EchoTool(Tool):
name = "echo"
description = "Echo a value."
inputs = {"value": {"type": "string", "description": "Value to echo."}}
output_type = "string"
def forward(self, value: str) -> str:
return value
calls = [
ChatMessageToolCall(ChatMessageToolCallFunction({"value": "first"}, "echo"), "", "function"),
ChatMessageToolCall(ChatMessageToolCallFunction({"value": "second"}, "echo"), "", "function"),
]
agent = ToolCallingAgent(tools=[EchoTool()], model=MagicMock(), verbosity_level=0)
step = ActionStep(step_number=1, timing=MagicMock(), model_output="")
events = list(agent.process_tool_calls(ChatMessage(MessageRole.ASSISTANT, "", calls), step))
print([event.output for event in events if isinstance(event, ToolOutput)])
print(len(step.tool_calls or []))
print(step.observations)
Actual behavior and error logs
The first call is silently discarded. Replacing both empty IDs with the same non-empty ID has the same result.
Expected behavior
Both calls should execute and both results should be recorded. Missing or duplicate provider IDs should be normalized to unique fallback IDs before calls are indexed, so tool request/response correlation remains unambiguous.
Environment:
- OS: macOS
- Python version: 3.12.12
- Package version:
1.27.0.dev0, main at 30bb1161
Additional context
The loss happens at the provider boundary and is especially hard to diagnose because yielded call events and actual execution disagree. Normalizing IDs also protects later memory serialization from ambiguous tool response correlation.
Checklist
Problem
ToolCallingAgent.process_tool_calls()stores pending calls in a dictionary keyed by the model-providedtool_call.id. Some OpenAI-compatible providers and streaming adapters can return an empty ID, and malformed provider responses can repeat an ID. Each later call then overwrites the earlier call before execution.This silently violates action completeness: the generator yields every requested
ToolCall, but only the last call for each ID is executed and recorded. There is no error or warning.Steps to reproduce
Actual behavior and error logs
["second"] 1 secondThe first call is silently discarded. Replacing both empty IDs with the same non-empty ID has the same result.
Expected behavior
Both calls should execute and both results should be recorded. Missing or duplicate provider IDs should be normalized to unique fallback IDs before calls are indexed, so tool request/response correlation remains unambiguous.
Environment:
1.27.0.dev0,mainat30bb1161Additional context
The loss happens at the provider boundary and is especially hard to diagnose because yielded call events and actual execution disagree. Normalizing IDs also protects later memory serialization from ambiguous tool response correlation.
Checklist