How to Fix 'duplicate tool calls' in AutoGen (Python)
What the error means
duplicate tool calls in AutoGen usually means the model tried to invoke the same tool more than once in a single turn, or AutoGen saw the same tool invocation twice in the message flow. In practice, this shows up when you wire tool execution incorrectly, retry a request without clearing state, or let both the LLM and your code path trigger the same function call.
You’ll typically hit this when using AssistantAgent, UserProxyAgent, GroupChat, or any custom tool-execution loop built on top of OpenAI function/tool calling.
The Most Common Cause
The #1 cause is executing tools manually while AutoGen is already configured to execute them automatically.
That creates a double-submit pattern:
- •the model emits a tool call
- •AutoGen executes it
- •your code sees the same call and executes it again
Broken vs fixed pattern
| Broken pattern | Fixed pattern |
|---|---|
You manually inspect tool_calls and run the function yourself | Let AutoGen handle tool execution once |
| You append assistant messages with tool calls back into history incorrectly | Return only the tool result message expected by AutoGen |
You call agent.run() after already processing the same turn | Keep one execution path per turn |
# BROKEN: manual execution + AutoGen execution = duplicate tool calls
from autogen.agentchat import AssistantAgent, UserProxyAgent
def get_balance(account_id: str) -> str:
return f"Balance for {account_id}: $1,250"
assistant = AssistantAgent(
name="assistant",
llm_config={"config_list": [{"model": "gpt-4o-mini", "api_key": "YOUR_KEY"}]},
)
user = UserProxyAgent(
name="user",
human_input_mode="NEVER",
code_execution_config=False,
)
# Tool registered in AutoGen
assistant.register_for_llm(name="get_balance", description="Get account balance")(get_balance)
user.register_for_execution(name="get_balance")(get_balance)
# BAD: also manually execute tool calls from messages
result = user.initiate_chat(assistant, message="Check balance for account 123")
for msg in result.chat_history:
if msg.get("tool_calls"):
for tc in msg["tool_calls"]:
# This repeats what AutoGen already did
print(get_balance(tc["function"]["arguments"]))
# FIXED: let AutoGen handle the tool execution path once
from autogen.agentchat import AssistantAgent, UserProxyAgent
def get_balance(account_id: str) -> str:
return f"Balance for {account_id}: $1,250"
assistant = AssistantAgent(
name="assistant",
llm_config={"config_list": [{"model": "gpt-4o-mini", "api_key": "YOUR_KEY"}]},
)
user = UserProxyAgent(
name="user",
human_input_mode="NEVER",
code_execution_config=False,
)
assistant.register_for_llm(name="get_balance", description="Get account balance")(get_balance)
user.register_for_execution(name="get_balance")(get_balance)
# GOOD: one chat loop, one tool execution path
user.initiate_chat(assistant, message="Check balance for account 123")
If you’re using newer AutoGen APIs with Tool objects or FunctionTool, the rule is the same: don’t execute the same function both inside AutoGen and outside it.
Other Possible Causes
1) Retrying a request with stale conversation state
If you retry after an exception but reuse the same message history, the model may see the prior tool_calls again and emit them again.
# BAD: reusing stale history after a failed turn
history = []
try:
chat_result = user.initiate_chat(assistant, message="Run reconciliation")
history.extend(chat_result.chat_history)
except Exception:
pass
# retry uses old state and can duplicate previous tool calls
user.initiate_chat(assistant, message=history[-1]["content"])
Fix by starting a fresh conversation or trimming assistant/tool messages before retrying.
2) Registering the same function twice
This happens when you decorate and register the same callable on multiple agents, or register it repeatedly during app startup.
# BAD: duplicate registration of same callable
assistant.register_for_llm(name="lookup_policy")(lookup_policy)
assistant.register_for_llm(name="lookup_policy")(lookup_policy)
Use one registration path only. If you need shared tools across agents, centralize registration in one setup function.
3) GroupChat speaker loops causing repeated tool emission
In GroupChat, an agent can be selected twice in a row and re-issue the same call if your speaker selection doesn’t advance state correctly.
groupchat = GroupChat(
agents=[planner, executor],
messages=[],
max_round=10,
)
manager = GroupChatManager(groupchat=groupchat, llm_config=llm_config)
If planner keeps getting selected after emitting a tool call, tighten your speaker transition rules or add a termination condition after successful execution.
4) Tool schema mismatch between model and executor
If your function signature doesn’t match what AutoGen advertised to the model, you can get repeated attempts because each call fails validation and gets retried.
# BAD: signature mismatch / unexpected args
def create_ticket(customer_id: str):
return {"ticket_id": "T-100"}
# model may send {"customerId": "..."} if schema wasn't aligned well
Make sure parameter names are explicit and stable. For bank/insurance workflows, avoid ambiguous names like id; use policy_number, claim_id, or account_id.
How to Debug It
- •
Inspect raw messages
- •Print every assistant message and look for repeated
tool_calls. - •In many cases you’ll see identical payloads back-to-back.
- •Print every assistant message and look for repeated
- •
Confirm who executes tools
- •Search your code for direct calls to the underlying Python function.
- •If AutoGen is already wired with
register_for_execution, remove manual execution paths.
- •
Check retries and state reuse
- •Look at exception handlers that rerun
initiate_chat()or resend previous messages. - •If you’re reusing
chat_history, reset it before retrying.
- •Look at exception handlers that rerun
- •
Verify registrations
- •Log all registered tools at startup.
- •Make sure each callable is registered once per agent lifecycle.
A quick debug print helps:
for i, msg in enumerate(chat_result.chat_history):
print(i, msg.get("role"), msg.get("name"), msg.get("tool_calls"))
If you see identical tool_calls repeated with no new user input, your issue is almost always state reuse or double execution.
Prevention
- •Keep one owner for tool execution: either AutoGen executes it, or your orchestration layer does. Not both.
- •Reset conversation state on retries unless you intentionally want replay behavior.
- •Use explicit function names and arguments that match your domain objects exactly.
- •Add startup checks that fail fast if a tool is registered more than once.
If you’re building agent workflows for regulated systems like claims intake or customer support triage, treat tool calls like database writes: idempotent where possible, and never executed twice by accident.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit