From a64af0ec6db1bb3d26e394ea8fdf87656cc609f3 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 16 Nov 2025 01:54:16 +0000 Subject: [PATCH] feat: add multi-tool calls experiment - Add multiToolCalls to ExperimentId type - Update experiments schema and configuration - Add MULTI_TOOL_CALLS constant to shared experiments - Update tool-use prompt to conditionally support multi-tool calls - Add i18n translations for EN and zh-CN - Update test fixtures to include multiToolCalls property Implements #9288 --- packages/types/src/experiment.ts | 2 ++ src/core/prompts/sections/tool-use.ts | 13 +++++++++++-- src/core/prompts/system.ts | 2 +- src/shared/__tests__/experiments.spec.ts | 3 +++ src/shared/experiments.ts | 2 ++ .../__tests__/ExtensionStateContext.spec.tsx | 2 ++ webview-ui/src/i18n/locales/en/settings.json | 4 ++++ webview-ui/src/i18n/locales/zh-CN/settings.json | 4 ++++ 8 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts index 37c6eecee7..e75ead6cb0 100644 --- a/packages/types/src/experiment.ts +++ b/packages/types/src/experiment.ts @@ -12,6 +12,7 @@ export const experimentIds = [ "preventFocusDisruption", "imageGeneration", "runSlashCommand", + "multiToolCalls", ] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -28,6 +29,7 @@ export const experimentsSchema = z.object({ preventFocusDisruption: z.boolean().optional(), imageGeneration: z.boolean().optional(), runSlashCommand: z.boolean().optional(), + multiToolCalls: z.boolean().optional(), }) export type Experiments = z.infer diff --git a/src/core/prompts/sections/tool-use.ts b/src/core/prompts/sections/tool-use.ts index 9ece848fb4..ec24819079 100644 --- a/src/core/prompts/sections/tool-use.ts +++ b/src/core/prompts/sections/tool-use.ts @@ -1,6 +1,9 @@ import { ToolProtocol, TOOL_PROTOCOL, isNativeProtocol } from "@roo-code/types" -export function getSharedToolUseSection(protocol: ToolProtocol = TOOL_PROTOCOL.XML): string { +export function getSharedToolUseSection( + protocol: ToolProtocol = TOOL_PROTOCOL.XML, + experiments?: Record, +): string { if (isNativeProtocol(protocol)) { return `==== @@ -9,11 +12,17 @@ TOOL USE You have access to a set of tools that are executed upon the user's approval. Use the provider-native tool-calling mechanism. Do not include XML markup or examples.` } + const multiToolCallsEnabled = experiments?.multiToolCalls === true + + const toolUsageGuidance = multiToolCallsEnabled + ? `You have access to a set of tools that are executed upon the user's approval. You can use multiple tools per message when appropriate, especially for independent, low-risk operations like reading files or searching code. Every assistant message must include at least one tool call. When using multiple tools, batch independent operations (like multiple file reads or searches) to reduce round trips and improve efficiency.` + : `You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.` + return `==== TOOL USE -You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +${toolUsageGuidance} # Tool Use Formatting diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 9230619ebd..2493533f72 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -120,7 +120,7 @@ async function generatePrompt( ${markdownFormattingSection()} -${getSharedToolUseSection(effectiveProtocol)}${toolsCatalog} +${getSharedToolUseSection(effectiveProtocol, experiments)}${toolsCatalog} ${getToolUseGuidelinesSection(codeIndexManager, effectiveProtocol)} diff --git a/src/shared/__tests__/experiments.spec.ts b/src/shared/__tests__/experiments.spec.ts index 8a3c300441..26aad132f0 100644 --- a/src/shared/__tests__/experiments.spec.ts +++ b/src/shared/__tests__/experiments.spec.ts @@ -31,6 +31,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + multiToolCalls: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) @@ -42,6 +43,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + multiToolCalls: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true) }) @@ -53,6 +55,7 @@ describe("experiments", () => { preventFocusDisruption: false, imageGeneration: false, runSlashCommand: false, + multiToolCalls: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index 90495c56b7..39a0805f47 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -6,6 +6,7 @@ export const EXPERIMENT_IDS = { PREVENT_FOCUS_DISRUPTION: "preventFocusDisruption", IMAGE_GENERATION: "imageGeneration", RUN_SLASH_COMMAND: "runSlashCommand", + MULTI_TOOL_CALLS: "multiToolCalls", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -22,6 +23,7 @@ export const experimentConfigsMap: Record = { PREVENT_FOCUS_DISRUPTION: { enabled: false }, IMAGE_GENERATION: { enabled: false }, RUN_SLASH_COMMAND: { enabled: false }, + MULTI_TOOL_CALLS: { enabled: false }, } export const experimentDefault = Object.fromEntries( diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 92652733dd..2bafa3c58f 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -237,6 +237,7 @@ describe("mergeExtensionState", () => { newTaskRequireTodos: false, imageGeneration: false, runSlashCommand: false, + multiToolCalls: false, } as Record, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS + 5, } @@ -258,6 +259,7 @@ describe("mergeExtensionState", () => { newTaskRequireTodos: false, imageGeneration: false, runSlashCommand: false, + multiToolCalls: false, }) }) }) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index efcd4ffa33..25ed637e78 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -776,6 +776,10 @@ "RUN_SLASH_COMMAND": { "name": "Enable model-initiated slash commands", "description": "When enabled, Roo can run your slash commands to execute workflows." + }, + "MULTI_TOOL_CALLS": { + "name": "Enable multi-tool calls", + "description": "When enabled, Roo can issue multiple tool calls in a single message, especially for independent operations like file reads and searches. This reduces round trips and improves efficiency while maintaining safety for stateful operations." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 0e35914c76..c2c7a01dbe 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -772,6 +772,10 @@ "RUN_SLASH_COMMAND": { "name": "启用模型发起的斜杠命令", "description": "启用后 Roo 可运行斜杠命令执行工作流程。" + }, + "MULTI_TOOL_CALLS": { + "name": "启用多工具调用", + "description": "启用后,Roo 可以在单个消息中发出多个工具调用,特别是对于文件读取和搜索等独立操作。这减少了往返次数并提高了效率,同时保持了状态操作的安全性。" } }, "promptCaching": {