Multiple tool_calls Aggregation Produces Corrupted Output
Author: wangengzhengCreated Jun 11, 2026Updated Jun 11, 2026
Description
When the model returns multiple tool_calls in a streaming response, the tool call data is incorrectly concatenated, causing malformed output and making it impossible to reliably parse individual function calls.
Expected Behavior
The response should contain 3 independent tool calls:
Tool 1
{
"function": "execute_shell_command",
"arguments": {
"shell_command": "dotnet --version 2>/dev/null || echo \"dotnet not found\""
}
}Tool 2
{
"function": "execute_shell_command",
"arguments": {
"shell_command": "sw_vers 2>/dev/null && uname -a"
}
}Tool 3
{
"function": "execute_shell_command",
"arguments": {
"shell_command": "ls -la *.csproj 2>/dev/null || find . -name \"*.csproj\" -maxdepth 3 2>/dev/null | head -5"
}
}Actual Behavior
Multiple tool calls are merged into a single corrupted string:
execute_shell_command
({"shell_command":"dotnet --version ..."})
{"shell_command":"sw_vers 2>/dev/null && uname -a"}
{"shell_command":"ls -la *.csproj ..."}Resulting output contains:
- Function names repeated unexpectedly
- Arguments from different tool calls concatenated together
- Invalid JSON fragments
- Corrupted characters (
^M, broken quotes, malformed command strings) - Loss of
tool_callboundaries
Example corrupted output:
execute_shell_command ({"shell_command":
"dotnet --version 2>/dev/null || echo \"dotnet not found\""})
{"shell_command":"sw_vers 2>/dev/null && uname -a"}
{"shell_command":"ls -la ^M*.csproj 2>/dev/null || find . ^M-name \"*.csproj\" -maxdepth 3 2>/dev/null | head -5"}Impact
- Cannot reliably reconstruct tool calls from stream chunks.
- JSON parsing fails.
- Multi-tool execution workflow breaks.
- Tool arguments may become mixed between different function calls.
- Tool invocation order cannot be guaranteed.
Root Cause Analysis
The streaming parser appears to aggregate delta.tool_calls incorrectly.
Each streamed chunk contains a tool_call.index identifying which tool call the delta belongs to.
Example:
Chunk 1
{
"tool_calls": [
{
"index": 0,
"function": {
"arguments": "dotnet"
}
}
]
}Chunk 2
{
"tool_calls": [
{
"index": 1,
"function": {
"arguments": "sw_vers"
}
}
]
}Chunk 3
{
"tool_calls": [
{
"index": 2,
"function": {
"arguments": "ls -la"
}
}
]
}Current Aggregation Result
dotnetsw_versls -laExpected Aggregation Result
{
"tool_calls": [
{
"index": 0,
"arguments": "dotnet ..."
},
{
"index": 1,
"arguments": "sw_vers ..."
},
{
"index": 2,
"arguments": "ls -la ..."
}
]
}Recommended Fix
Aggregate streamed tool call fragments by tool_call.index.
Example Implementation
tool_calls = {}
for tc in delta.tool_calls:
idx = tc.index
if idx not in tool_calls:
tool_calls[idx] = {
"id": "",
"function": {
"name": "",
"arguments": ""
}
}
if tc.id:
tool_calls[idx]["id"] += tc.id
if tc.function.name:
tool_calls[idx]["function"]["name"] += tc.function.name
if tc.function.arguments:
tool_calls[idx]["function"]["arguments"] += tc.function.argumentsFinal Processing
for idx in sorted(tool_calls.keys()):
process(tool_calls[idx])Acceptance Criteria
- Multiple tool calls are reconstructed independently.
- Tool call boundaries are preserved.
- Arguments are appended only to their corresponding
tool_call.index. - Generated JSON is valid.
- Tool execution order matches the original response.
- Streaming and non-streaming responses produce equivalent tool call structures.
Severity
High
Multi-tool-call responses cannot be parsed correctly, leading to:
- Invalid JSON payloads
- Tool execution failures
- Corrupted command arguments
- Unreliable agent behavior
- Broken streaming compatibility
Environment
| Item | Value |
|---|---|
| Feature | Streaming Chat Completion |
| Response Type | tool_calls |
| Scenario | Multiple tool calls in a single assistant response |
| Impact Scope | All function-calling agents using streaming mode |
| Severity | High |
Source: TheR1D/shell_gpt