@@ -398,9 +398,10 @@ public void getAskUserConfirmationFunctionCalls_eventWithConfirmationFunctionCal
398398 assertThat (result ).containsExactly (confirmationCall1 , confirmationCall2 );
399399 }
400400
401- // Default ToolExecutionMode.NONE must execute tools sequentially.
401+ // Default ToolExecutionMode.NONE behaves like PARALLEL: blocking tools still execute serially
402+ // on the caller thread (no worker scheduler is used), preserving the historical default.
402403 @ Test
403- public void handleFunctionCalls_defaultMode_blockingTools_runSequentially () {
404+ public void handleFunctionCalls_defaultMode_blockingTools_runSerially () {
404405 long sleepMillis = 300L ;
405406 int toolCount = 2 ;
406407 InvocationContext invocationContext =
@@ -435,29 +436,69 @@ public void handleFunctionCalls_defaultMode_blockingTools_runSequentially() {
435436 assertThat (durationMillis ).isAtLeast ((long ) toolCount * sleepMillis );
436437 }
437438
439+ // PARALLEL mode does NOT introduce worker threads; blocking tools still run serially on the
440+ // caller thread. PARALLEL_SUBSCRIBE is the mode that runs blocking tools concurrently.
438441 @ Test
439- public void handleFunctionCalls_parallel_blockingTools_runConcurrently_twoTools () {
440- runParallelBlockingToolsTest (/* toolCount= */ 2 );
442+ public void handleFunctionCalls_parallel_blockingTools_runSerially () {
443+ long sleepMillis = 300L ;
444+ int toolCount = 2 ;
445+ InvocationContext invocationContext =
446+ createInvocationContext (
447+ createRootAgent (),
448+ RunConfig .builder ().setToolExecutionMode (ToolExecutionMode .PARALLEL ).build ());
449+
450+ Map <String , BaseTool > tools = new LinkedHashMap <>();
451+ List <Part > callParts = new ArrayList <>();
452+ for (int i = 1 ; i <= toolCount ; i ++) {
453+ String toolName = "slow_tool_" + i ;
454+ tools .put (toolName , new SleepingTool (toolName , sleepMillis ));
455+ callParts .add (
456+ Part .builder ()
457+ .functionCall (
458+ FunctionCall .builder ()
459+ .id ("call_" + i )
460+ .name (toolName )
461+ .args (ImmutableMap .of ())
462+ .build ())
463+ .build ());
464+ }
465+ Event event =
466+ createEvent ("event" ).toBuilder ()
467+ .content (Content .fromParts (callParts .toArray (new Part [0 ])))
468+ .build ();
469+
470+ long start = System .currentTimeMillis ();
471+ Event functionResponseEvent =
472+ Functions .handleFunctionCalls (invocationContext , event , tools ).blockingGet ();
473+ long durationMillis = System .currentTimeMillis () - start ;
474+
475+ assertThat (functionResponseEvent ).isNotNull ();
476+ assertThat (durationMillis ).isAtLeast ((long ) toolCount * sleepMillis );
441477 }
442478
443479 @ Test
444- public void handleFunctionCalls_parallel_blockingTools_runConcurrently_threeTools () {
445- runParallelBlockingToolsTest (/* toolCount= */ 3 );
480+ public void handleFunctionCalls_parallelSubscribe_blockingTools_runConcurrently_twoTools () {
481+ runParallelSubscribeBlockingToolsTest (/* toolCount= */ 2 );
446482 }
447483
448484 @ Test
449- public void handleFunctionCalls_parallel_blockingTools_runConcurrently_fiveTools () {
450- runParallelBlockingToolsTest (/* toolCount= */ 5 );
485+ public void handleFunctionCalls_parallelSubscribe_blockingTools_runConcurrently_threeTools () {
486+ runParallelSubscribeBlockingToolsTest (/* toolCount= */ 3 );
487+ }
488+
489+ @ Test
490+ public void handleFunctionCalls_parallelSubscribe_blockingTools_runConcurrently_fiveTools () {
491+ runParallelSubscribeBlockingToolsTest (/* toolCount= */ 5 );
451492 }
452493
453494 /** Single-tool case bypasses the parallel scheduler path; must still return the correct event. */
454495 @ Test
455- public void handleFunctionCalls_parallel_blockingTool_singleTool () {
496+ public void handleFunctionCalls_parallelSubscribe_blockingTool_singleTool () {
456497 long sleepMillis = 200L ;
457498 InvocationContext invocationContext =
458499 createInvocationContext (
459500 createRootAgent (),
460- RunConfig .builder ().setToolExecutionMode (ToolExecutionMode .PARALLEL ).build ());
501+ RunConfig .builder ().setToolExecutionMode (ToolExecutionMode .PARALLEL_SUBSCRIBE ).build ());
461502 SleepingTool tool = new SleepingTool ("slow_tool_1" , sleepMillis );
462503 Event event =
463504 createEvent ("event" ).toBuilder ()
@@ -491,13 +532,16 @@ public void handleFunctionCalls_parallel_blockingTool_singleTool() {
491532 .build ());
492533 }
493534
494- /** Asserts that {@code toolCount} blocking tools in PARALLEL mode run faster than sequential. */
495- private static void runParallelBlockingToolsTest (int toolCount ) {
535+ /**
536+ * Asserts that {@code toolCount} blocking tools in PARALLEL_SUBSCRIBE mode run faster than
537+ * sequential, since each tool is subscribed on a worker thread.
538+ */
539+ private static void runParallelSubscribeBlockingToolsTest (int toolCount ) {
496540 long sleepMillis = 500L ;
497541 InvocationContext invocationContext =
498542 createInvocationContext (
499543 createRootAgent (),
500- RunConfig .builder ().setToolExecutionMode (ToolExecutionMode .PARALLEL ).build ());
544+ RunConfig .builder ().setToolExecutionMode (ToolExecutionMode .PARALLEL_SUBSCRIBE ).build ());
501545
502546 Map <String , BaseTool > tools = new LinkedHashMap <>();
503547 List <Part > callParts = new ArrayList <>();
0 commit comments