Skip to content

Commit 05a60b5

Browse files
damianmomotgooglecopybara-github
authored andcommitted
fix: adjust default ToolExecutionMode to SEQUENTIAL as it was actual and widely used behavior for all ADK Java users before parallel tool execution fix
PiperOrigin-RevId: 921491583
1 parent 020499b commit 05a60b5

3 files changed

Lines changed: 45 additions & 12 deletions

File tree

core/src/main/java/com/google/adk/agents/RunConfig.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ public enum StreamingMode {
3939
}
4040

4141
/**
42-
* Tool execution mode for the runner, when they are multiple tools requested (by the models or
43-
* callbacks).
42+
* Execution mode when the model requests multiple tools.
4443
*
45-
* <p>NONE: default to PARALLEL.
44+
* <p>NONE: defaults to SEQUENTIAL.
4645
*
47-
* <p>SEQUENTIAL: Multiple tools are executed in the order they are requested.
46+
* <p>SEQUENTIAL: tools execute in request order on the caller thread.
4847
*
49-
* <p>PARALLEL: Multiple tools are executed in parallel.
48+
* <p>PARALLEL: tools execute concurrently on worker threads. Tool implementations must be
49+
* thread-safe.
5050
*/
5151
public enum ToolExecutionMode {
5252
NONE,

core/src/main/java/com/google/adk/flows/llmflows/Functions.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -236,19 +236,15 @@ public static Maybe<Event> handleFunctionCallsLive(
236236
}
237237

238238
/**
239-
* Builds the tool-execution {@link Observable}.
240-
*
241-
* <p>SEQUENTIAL (or a single call, where parallelism is moot) runs on the caller thread via
242-
* {@code concatMapMaybe} to keep synchronous semantics. PARALLEL with multiple calls dispatches
243-
* each tool on a worker so blocking calls run concurrently; {@code concatMapEager} preserves
244-
* input order required by {@link #mergeParallelFunctionResponseEvents}.
239+
* Sequential by default; only {@link ToolExecutionMode#PARALLEL} with multiple calls dispatches
240+
* tools on workers (using {@code concatMapEager} to preserve input order).
245241
*/
246242
private static Observable<Event> buildToolExecutionObservable(
247243
InvocationContext invocationContext,
248244
List<FunctionCall> validFunctionCalls,
249245
Function<FunctionCall, Maybe<Event>> functionCallMapper) {
250246
boolean parallel =
251-
invocationContext.runConfig().toolExecutionMode() != ToolExecutionMode.SEQUENTIAL
247+
invocationContext.runConfig().toolExecutionMode() == ToolExecutionMode.PARALLEL
252248
&& validFunctionCalls.size() > 1;
253249
if (!parallel) {
254250
return Observable.fromIterable(validFunctionCalls).concatMapMaybe(functionCallMapper);

core/src/test/java/com/google/adk/flows/llmflows/FunctionsTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,43 @@ public void getAskUserConfirmationFunctionCalls_eventWithConfirmationFunctionCal
398398
assertThat(result).containsExactly(confirmationCall1, confirmationCall2);
399399
}
400400

401+
// Default ToolExecutionMode.NONE must execute tools sequentially.
402+
@Test
403+
public void handleFunctionCalls_defaultMode_blockingTools_runSequentially() {
404+
long sleepMillis = 300L;
405+
int toolCount = 2;
406+
InvocationContext invocationContext =
407+
createInvocationContext(createRootAgent(), RunConfig.builder().build());
408+
409+
Map<String, BaseTool> tools = new LinkedHashMap<>();
410+
List<Part> callParts = new ArrayList<>();
411+
for (int i = 1; i <= toolCount; i++) {
412+
String toolName = "slow_tool_" + i;
413+
tools.put(toolName, new SleepingTool(toolName, sleepMillis));
414+
callParts.add(
415+
Part.builder()
416+
.functionCall(
417+
FunctionCall.builder()
418+
.id("call_" + i)
419+
.name(toolName)
420+
.args(ImmutableMap.of())
421+
.build())
422+
.build());
423+
}
424+
Event event =
425+
createEvent("event").toBuilder()
426+
.content(Content.fromParts(callParts.toArray(new Part[0])))
427+
.build();
428+
429+
long start = System.currentTimeMillis();
430+
Event functionResponseEvent =
431+
Functions.handleFunctionCalls(invocationContext, event, tools).blockingGet();
432+
long durationMillis = System.currentTimeMillis() - start;
433+
434+
assertThat(functionResponseEvent).isNotNull();
435+
assertThat(durationMillis).isAtLeast((long) toolCount * sleepMillis);
436+
}
437+
401438
@Test
402439
public void handleFunctionCalls_parallel_blockingTools_runConcurrently_twoTools() {
403440
runParallelBlockingToolsTest(/* toolCount= */ 2);

0 commit comments

Comments
 (0)