Repository navigation
Python: [Bug]: Streamed parallel tool calls get merged into the wrong call when their argument deltas interleave #8336
Copy link
Copy link
Labels
agentsUsage: [Issues, PRs], Target: Single agentUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflowUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
Description
Activity
- addedpythonUsage: [Issues, PRs], Target: PythonUsage: [Issues, PRs], Target: PythontriageUsage: [Issues], Target: All issues that still need to be triagedUsage: [Issues], Target: All issues that still need to be triaged
on Sep 12, 2026 - addedreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflowUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
on Sep 12, 2026 github-actions commented
on Sep 12, 2026 on Sep 12, 2026 – with GitHub ActionsContributorMore actions🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
The bug reproduces in
python/packages/core/agent_framework/_types.py::_process_update, specifically the trailing-item merge branch at lines 2036-2040, when function-call argument chunks with distinctcall_idvalues interleave. Minimal repro: aggregate six alternatingChatResponseUpdatechunks for two calls and assert thatChatResponse.from_updatesreturns two complete function calls; current behavior returns six fragments.- Failing test:
python/packages/core/tests/core/test_types.py::test_interleaved_function_call_chunks_merge_by_call_id - Files examined: python/AGENTS.md, python/packages/core/AGENTS.md, python/packages/core/agent_framework/_types.py, python/packages/core/tests/core/test_types.py, python/packages/core/pyproject.toml
- Tests run: test_function_call_merge_in_process_update_and_usage_aggregation, test_function_call_incompatible_ids_are_not_merged, test_interleaved_function_call_chunks_merge_by_call_id
- Reported version:
1.18.0 - Current version:
1.18.0
- Failing test:
- addedagentsUsage: [Issues, PRs], Target: Single agentUsage: [Issues, PRs], Target: Single agentand removedtriageUsage: [Issues], Target: All issues that still need to be triagedUsage: [Issues], Target: All issues that still need to be triaged
on Sep 12, 2026 - added a commit that references this issue
on Sep 14, 2026
Metadata
Metadata
Labels
agentsUsage: [Issues, PRs], Target: Single agentUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflowUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
Type
Projects
- StatusShow more project fieldsDone
Description
When a model streams two or more tool calls in the same turn and their argument
chunks interleave (call A's next fragment arrives before call B's previous
fragment has finished),
ChatResponse.from_updates/AgentResponse.from_updatesmerge the fragments into the wrong call. Depending on how the chunks interleave,
one call ends up empty and the other ends up with the two calls' arguments mashed
together into invalid JSON, or both calls get split across several duplicate
function_callcontent items that never reassemble into one complete call.Root cause is in
_process_updateinagent_framework/_types.py: it only everchecks whether
message.contents[-1](the last content item appended so far)is a
function_call, and merges the incoming chunk into that one. It neverlooks for the actual call the chunk belongs to. As long as tool calls stream one
fully to completion before the next one starts, "the last item" happens to be
the right target. As soon as two calls are in flight at the same time — which is
exactly what parallel tool calling is for — that assumption breaks.
This isn't just theoretical:
OpenAIResponsesClientalready trackscall_id/nameperoutput_indexprecisely so everyresponse.function_call_arguments.deltaevent carries the correct, realcall_id(seefunction_call_idsin_chat_client.py). Even with every chunkcorrectly tagged, the aggregation still corrupts the result, because it never
searches past the trailing item for a matching
call_id.Expected behavior
Streaming two (or more) tool calls whose argument deltas interleave should
produce one
FunctionCallContentper call in the final response, each with itsown arguments fully and correctly assembled — the same result as if the two
calls had streamed back-to-back instead of interleaved.
Actual behavior
The final assistant message contains corrupted and/or duplicated
function_callcontent: one call's arguments end up empty while the other'scontain a garbled concatenation of both calls' fragments (invalid JSON), or
both calls are split across multiple partial, non-mergeable content items.
Function invocation downstream then either fails to parse the arguments or
invokes the wrong function with the wrong (or partial) arguments.
Code Sample
Output today:
Six fragments instead of two complete calls —
get_weatherandget_timenever end up with usable arguments.
Error Messages / Stack Traces
No exception is raised; the corruption is silent. Depending on the exact
interleaving, the downstream symptom is either a
json.JSONDecodeErrorwhenparse_arguments()is called on the corrupted content, or a tool invoked withwrong/partial arguments.
Package Versions
agent-framework-core: 1.18.0
Python Version
Python 3.12
Additional Context
A second, narrower variant of the same defect class exists in
OpenAIChatCompletionClient._parse_tool_calls_from_openai: continuation deltasfor the legacy Chat Completions streaming API only carry a
call_idon thefirst chunk for each tool call, so once two calls are in flight their untagged
continuation fragments can't be told apart even if the aggregation above is
fixed. That's tracked separately since it needs index-tracking in that specific
client rather than a change to the shared aggregation logic.