Skip to content

Commit ff8bc9e

Browse files
henrypark133claude
andauthored
fix: parallel tool calls by deferring citation instructions (#397)
* Fix parallel tool calls by deferring citation instructions For parallel tool calls (e.g., multiple web searches), all providers (OpenAI, Anthropic, Gemini) require tool results to be consecutive after the assistant message with tool_calls. Previously, citation instructions were added immediately after each tool result, which broke the consecutive sequence: - tool result 1 - system message (citation) <-- breaks sequence - tool result 2 Now, citation instructions are collected during tool execution and added AFTER all tool results: - tool result 1 - tool result 2 - system message (citation) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * combine all instructions into one system prompt message * add draining/system prompt updates for mulitple types of tool call with multi-turn --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent beed835 commit ff8bc9e

1 file changed

Lines changed: 31 additions & 9 deletions

File tree

crates/services/src/responses/service.rs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,26 +1210,47 @@ impl ResponseServiceImpl {
12101210

12111211
tracing::debug!("Executing {} tool calls", stream_result.tool_calls.len());
12121212

1213-
// Execute each tool call
1213+
// Execute each tool call, collecting any deferred instructions
1214+
let mut deferred_instructions: Vec<String> = Vec::new();
12141215
for tool_call in stream_result.tool_calls {
12151216
match Self::execute_and_emit_tool_call(
12161217
ctx,
12171218
emitter,
12181219
&tool_call,
12191220
messages,
12201221
process_context,
1222+
&mut deferred_instructions,
12211223
)
12221224
.await?
12231225
{
12241226
tools::ToolExecutionResult::Success => {
12251227
// Continue processing tool calls
12261228
}
12271229
tools::ToolExecutionResult::ApprovalRequired => {
1228-
// MCP tool requires approval - pause the agent loop
1230+
// MCP tool requires approval - flush any deferred instructions before pausing
1231+
if !deferred_instructions.is_empty() {
1232+
messages.push(CompletionMessage {
1233+
role: "system".to_string(),
1234+
content: std::mem::take(&mut deferred_instructions).join("\n\n"),
1235+
tool_call_id: None,
1236+
tool_calls: None,
1237+
});
1238+
}
12291239
return Ok(AgentLoopResult::ApprovalRequired);
12301240
}
12311241
}
12321242
}
1243+
1244+
// Add deferred instructions AFTER all tool results (combined into one system message)
1245+
// This ensures tool results are consecutive (required by OpenAI/Anthropic/Gemini)
1246+
if !deferred_instructions.is_empty() {
1247+
messages.push(CompletionMessage {
1248+
role: "system".to_string(),
1249+
content: deferred_instructions.join("\n\n"),
1250+
tool_call_id: None,
1251+
tool_calls: None,
1252+
});
1253+
}
12331254
}
12341255

12351256
Ok(AgentLoopResult::Completed)
@@ -1239,12 +1260,17 @@ impl ResponseServiceImpl {
12391260
///
12401261
/// Returns `Ok(ToolExecutionResult::Success)` if the tool executed normally,
12411262
/// or `Ok(ToolExecutionResult::ApprovalRequired)` if the tool requires user approval.
1263+
///
1264+
/// Any instructions (e.g., citation instructions from web search) are collected into
1265+
/// `deferred_instructions` to be added after all tool results. This ensures tool results
1266+
/// are consecutive (required by OpenAI/Anthropic/Gemini for parallel tool calls).
12421267
async fn execute_and_emit_tool_call(
12431268
ctx: &mut crate::responses::service_helpers::ResponseStreamContext,
12441269
emitter: &mut crate::responses::service_helpers::EventEmitter,
12451270
tool_call: &crate::responses::service_helpers::ToolCallInfo,
12461271
messages: &mut Vec<crate::completions::ports::CompletionMessage>,
12471272
process_context: &mut ProcessStreamContext,
1273+
deferred_instructions: &mut Vec<String>,
12481274
) -> Result<tools::ToolExecutionResult, errors::ResponseError> {
12491275
use crate::completions::ports::CompletionMessage;
12501276

@@ -1388,14 +1414,10 @@ impl ResponseServiceImpl {
13881414
tool_calls: None,
13891415
});
13901416

1391-
// Add citation instruction if provided (first web search)
1417+
// Defer citation instruction to be added after all tool results
1418+
// This ensures tool results are consecutive (required by OpenAI/Anthropic/Gemini)
13921419
if let Some(instruction) = instruction {
1393-
messages.push(CompletionMessage {
1394-
role: "system".to_string(),
1395-
content: instruction,
1396-
tool_call_id: None,
1397-
tool_calls: None,
1398-
});
1420+
deferred_instructions.push(instruction);
13991421
}
14001422

14011423
Ok(tools::ToolExecutionResult::Success)

0 commit comments

Comments
 (0)