Handle parallel tool calls in OpenAI Response API paths - #1420
Conversation
The Response API paths kept only the first function call the model asked for, so parallel tool calls were dropped and the model re-asked for the rest on the next turn. The streaming path was worse: each output-item-done update overwrote the previous call, keeping only the last. Collect every FunctionCallResponseItem and populate ToolCalls on the returned RoleDialogModel, matching the Chat API paths. ToolCallId, FunctionName and FunctionArgs still carry the first call, so existing single-call consumers are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR Summary by QodoHandle parallel tool calls in OpenAI Response API paths
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1. Parallel calls remain unhandled
|
| ToolCallId = functionCall.CallId, | ||
| FunctionName = functionCall.FunctionName.NormalizeFunctionName(), | ||
| FunctionArgs = functionCall.FunctionArguments?.ToString(), | ||
| ToolCalls = toolCalls, |
There was a problem hiding this comment.
1. Parallel calls remain unhandled 🐞 Bug ≡ Correctness
Although the provider populates ToolCalls, RoutingService.InvokeAgent copies only the legacy first-call fields and invokes one function, so every later requested call is discarded in the standard execution path. The next Responses API request consequently contains only that first call and output, allowing the model to request the omitted calls again.
Agent Prompt
## Issue description
The Responses API provider now returns every requested tool call in `RoleDialogModel.ToolCalls`, but the standard routing pipeline copies and executes only the legacy first-call fields. Update the routing and conversation-history flow to execute each call, preserve each call ID and arguments, and send every corresponding result back to the model while retaining single-call compatibility.
## Issue Context
The same correction must cover non-streaming and streaming Responses API results. Ensure each tool result remains associated with its provider call ID and that completion resumes only after all requested calls have produced results.
## Fix Focus Areas
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[28-49]
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[526-537]
- src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs[51-65]
- src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs[89-130]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
The Response API paths kept only the first function call the model asked for, so parallel tool calls were dropped and the model re-asked for the rest on the next turn. The streaming path was worse: each output-item-done update overwrote the previous call, keeping only the last.
Collect every FunctionCallResponseItem and populate ToolCalls on the returned RoleDialogModel, matching the Chat API paths. ToolCallId, FunctionName and FunctionArgs still carry the first call, so existing single-call consumers are unaffected.