Skip to content

[fix] parse_tool_calls: replace shared dict refs from list multiplication with independent dicts#6932

Open
NIK-TIGER-BILL wants to merge 1 commit intoagno-agi:mainfrom
NIK-TIGER-BILL:fix/parse-tool-calls-shared-dict-refs
Open

[fix] parse_tool_calls: replace shared dict refs from list multiplication with independent dicts#6932
NIK-TIGER-BILL wants to merge 1 commit intoagno-agi:mainfrom
NIK-TIGER-BILL:fix/parse-tool-calls-shared-dict-refs

Conversation

@NIK-TIGER-BILL
Copy link
Contributor

Problem

parse_tool_calls in libs/agno/agno/models/openai/chat.py uses:

tool_calls.extend([{}] * (_index - len(tool_calls) + 1))

This is a classic Python pitfall — [{}] * N creates N references to the same dict object, not N independent dicts. When any element is mutated (e.g. tool_call_entry["id"] = ...), all N elements change simultaneously through the shared reference.

This causes every tool call to be executed twice (or more) when the model returns a non-zero-based tool_call.index (e.g., Claude via OpenRouter), because multiple slots in the list end up sharing the same dict and all get updated together.

Fix

Replace [{}] * N with a list comprehension:

# Before (buggy)
tool_calls.extend([{}] * (_index - len(tool_calls) + 1))

# After (fixed)
tool_calls.extend([{} for _ in range(_index - len(tool_calls) + 1)])

Each slot now gets its own independent empty dict, so mutations to one slot do not affect others.

Verification

# Demonstrates the bug
shared = [{}] * 3
shared[0]['key'] = 'value'
assert shared[1]['key'] == 'value'  # all 3 are mutated!

# Correct
independent = [{} for _ in range(3)]
independent[0]['key'] = 'value'
assert 'key' not in independent[1]  # only index 0 is mutated ✅

Closes #6542

…tion with independent dicts

`[{}] * N` creates N references to the **same** dict object — a classic
Python pitfall. When any element is mutated (e.g. tool_call_entry["id"] = ...),
all N elements change simultaneously because they share the same reference.

This caused every tool call to be executed twice (or more) when the model
returned a non-zero-based `tool_call.index`, because multiple slots in the
list pointed to the same dict and all got updated together.

Fix: replace `[{}] * N` with `[{} for _ in range(N)]` so each slot gets
its own independent empty dict.

Closes agno-agi#6542
@NIK-TIGER-BILL NIK-TIGER-BILL requested a review from a team as a code owner March 10, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] parse_tool_calls uses [{}] * N creating shared dict references, causing tool double-execution

1 participant