Skip to content

Handle parallel tool calls in OpenAI Response API paths - #1420

Merged
iceljc merged 1 commit into
SciSharp:masterfrom
iceljc:feature/rule-engine-parallel-execution
Sep 3, 2026
Merged

Handle parallel tool calls in OpenAI Response API paths#1420
iceljc merged 1 commit into
SciSharp:masterfrom
iceljc:feature/rule-engine-parallel-execution

Conversation

@iceljc

@iceljc iceljc commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

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.

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>
@iceljc
iceljc merged commit d6a2399 into SciSharp:master Sep 3, 2026
3 of 4 checks passed
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Handle parallel tool calls in OpenAI Response API paths

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Preserve every tool call returned through synchronous, asynchronous, and streaming Response API
 paths.
• Keep legacy single-call fields mapped to the first call for backward compatibility.
• Log all requested function names to improve parallel-call observability.
Diagram

graph TD
  A["OpenAI Response"] --> B["Function Items"] --> C{"Response Path"} -->|Non-streaming| D["Collect All Calls"] --> F["Role Dialog"] --> G["Tool Execution"]
  C -->|Streaming| E["Accumulate Calls"] --> F
Loading
High-Level Assessment

The chosen approach is appropriate because it mirrors the established Chat API representation while preserving existing single-call consumers. Replacing the scalar fields would be breaking, and a broader cross-provider abstraction would add unnecessary scope for this focused Response API fix.

Files changed (1) +26 / -7

Bug fix (1) +26 / -7
ChatCompletionProvider.Response.csPreserve parallel tool calls across all Response API paths +26/-7

Preserve parallel tool calls across all Response API paths

• Collects every FunctionCallResponseItem for synchronous, callback-based asynchronous, and streaming responses, then maps them into RoleDialogModel.ToolCalls. The first call still populates ToolCallId, FunctionName, and FunctionArgs, while logging now reports all requested functions.

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Parallel calls remain unhandled 🐞 Bug ≡ Correctness
Description
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.
Code

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[49]

+                ToolCalls = toolCalls,
Evidence
The provider collects all output calls and assigns them to the returned model, but the standard
agent path reconstructs a function message using only ToolCallId, FunctionName, and
FunctionArgs, then calls its single-function executor once. Responses history reconstruction
likewise emits only one function-call item and one output from those legacy fields, proving that
later entries in ToolCalls do not reach execution or the subsequent model turn.

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[28-49]
src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs[51-65]
src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs[89-130]
src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[526-537]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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


Grey Divider

Context sources
Review mode: ⚖️ Balanced: This modifies runtime OpenAI response and streaming tool-call handling across multiple paths, with behavioral and compatibility implications, but is not dense enough to warrant redundant extended review.

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

ToolCallId = functionCall.CallId,
FunctionName = functionCall.FunctionName.NormalizeFunctionName(),
FunctionArgs = functionCall.FunctionArguments?.ToString(),
ToolCalls = toolCalls,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

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

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.

1 participant