How to Fix 'duplicate tool calls' in AutoGen (Python)

By Cyprian AaronsUpdated 2026-04-21
duplicate-tool-callsautogenpython

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 patternFixed pattern
You manually inspect tool_calls and run the function yourselfLet AutoGen handle tool execution once
You append assistant messages with tool calls back into history incorrectlyReturn only the tool result message expected by AutoGen
You call agent.run() after already processing the same turnKeep 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

  1. Inspect raw messages

    • Print every assistant message and look for repeated tool_calls.
    • In many cases you’ll see identical payloads back-to-back.
  2. 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.
  3. 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.
  4. 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

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

Related Guides