Skip to content

BUG: ToolCallingAgent silently drops tool calls with missing or duplicate IDs#2683

Description

@chrischen-coder

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

["second"]
1
second

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

  • I have searched the existing issues and have not found a similar bug report.
  • I have provided a minimal, reproducible example.
  • No traceback is produced; the failure is silent and the actual output is included above.
  • I have provided my environment details.
  • I am willing to work on this issue and submit a pull request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions