From c1ccb2e9d375fedcd7dbb594300e66a1a0488a91 Mon Sep 17 00:00:00 2001 From: Mateusz Krawiec Date: Wed, 4 Mar 2026 01:59:51 -0800 Subject: [PATCH] refactor!: remove support for legacy `transferToAgent`, superseded by `transfer_to_agent` PiperOrigin-RevId: 878368864 --- .../adk/flows/llmflows/AgentTransfer.java | 25 +--- .../adk/flows/llmflows/BaseLlmFlow.java | 8 +- .../adk/flows/llmflows/AgentTransferTest.java | 127 +++++++----------- 3 files changed, 51 insertions(+), 109 deletions(-) diff --git a/core/src/main/java/com/google/adk/flows/llmflows/AgentTransfer.java b/core/src/main/java/com/google/adk/flows/llmflows/AgentTransfer.java index 402f71c9d..0a0da8761 100644 --- a/core/src/main/java/com/google/adk/flows/llmflows/AgentTransfer.java +++ b/core/src/main/java/com/google/adk/flows/llmflows/AgentTransfer.java @@ -55,22 +55,17 @@ public Single processRequest( .appendInstructions( ImmutableList.of(buildTargetAgentsInstructions(agent, transferTargets))); - // Note: this tool is not exposed to the LLM in GenerateContent request. It is there only to - // serve as a backwards-compatible instance for users who depend on the exact name of - // "transferToAgent". - builder.appendTools(ImmutableList.of(createTransferToAgentTool("legacyTransferToAgent"))); - - FunctionTool agentTransferTool = createTransferToAgentTool("transferToAgent"); + FunctionTool agentTransferTool = createTransferToAgentTool(); agentTransferTool.processLlmRequest(builder, ToolContext.builder(context).build()); return Single.just( RequestProcessor.RequestProcessingResult.create(builder.build(), ImmutableList.of())); } - private FunctionTool createTransferToAgentTool(String methodName) { + private FunctionTool createTransferToAgentTool() { Method transferToAgentMethod; try { transferToAgentMethod = - AgentTransfer.class.getMethod(methodName, String.class, ToolContext.class); + AgentTransfer.class.getMethod("transferToAgent", String.class, ToolContext.class); } catch (NoSuchMethodException e) { throw new IllegalStateException(e); } @@ -169,18 +164,4 @@ public static void transferToAgent( EventActions eventActions = toolContext.eventActions(); toolContext.setActions(eventActions.toBuilder().transferToAgent(agentName).build()); } - - /** - * Backwards compatible transferToAgent that uses camel-case naming instead of the ADK's - * snake_case convention. - * - *

It exists only to support users who already use literal "transferToAgent" function call to - * instruct ADK to transfer the question to another agent. - */ - @Schema(name = "transferToAgent") - public static void legacyTransferToAgent( - @Schema(name = "agentName") String agentName, - @Schema(optional = true) ToolContext toolContext) { - transferToAgent(agentName, toolContext); - } } diff --git a/core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java b/core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java index 549652e86..1249728d8 100644 --- a/core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java +++ b/core/src/main/java/com/google/adk/flows/llmflows/BaseLlmFlow.java @@ -577,13 +577,7 @@ public void onError(Throwable e) { .get() .content(event.content().get()); } - if (functionResponses.stream() - .anyMatch( - functionResponse -> - functionResponse - .name() - .orElse("") - .equals("transferToAgent")) + if (event.actions().transferToAgent().isPresent() || event.actions().endInvocation().orElse(false)) { sendTask.dispose(); connection.close(); diff --git a/core/src/test/java/com/google/adk/flows/llmflows/AgentTransferTest.java b/core/src/test/java/com/google/adk/flows/llmflows/AgentTransferTest.java index 6e6e99640..79552520b 100644 --- a/core/src/test/java/com/google/adk/flows/llmflows/AgentTransferTest.java +++ b/core/src/test/java/com/google/adk/flows/llmflows/AgentTransferTest.java @@ -24,12 +24,13 @@ import static com.google.common.truth.Truth.assertThat; import com.google.adk.agents.InvocationContext; +import com.google.adk.agents.LiveRequest; +import com.google.adk.agents.LiveRequestQueue; import com.google.adk.agents.LlmAgent; import com.google.adk.agents.LoopAgent; import com.google.adk.agents.RunConfig; import com.google.adk.agents.SequentialAgent; import com.google.adk.events.Event; -import com.google.adk.models.LlmRequest; import com.google.adk.runner.InMemoryRunner; import com.google.adk.runner.Runner; import com.google.adk.sessions.Session; @@ -44,6 +45,7 @@ import com.google.genai.types.Schema; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.subscribers.TestSubscriber; import java.util.List; import java.util.Map; import java.util.Optional; @@ -97,6 +99,50 @@ public void exitLoopTool_exitsLoop() { // TODO: b/413488103 - complete when LoopAgent is implemented. } + @Test + public void runLive_transferToAgent_closesConnection() throws Exception { + // Arrange + Content transferCallContent = Content.fromParts(createTransferCallPart("sub_agent_1")); + Content response1 = Content.fromParts(Part.fromText("response1")); + + TestLlm testLlm = + createTestLlm( + Flowable.just(createLlmResponse(transferCallContent)), + Flowable.just(createLlmResponse(response1))); + + LlmAgent subAgent1 = createTestAgentBuilder(testLlm).name("sub_agent_1").build(); + LlmAgent rootAgent = + createTestAgentBuilder(testLlm) + .name("root_agent") + .subAgents(ImmutableList.of(subAgent1)) + .build(); + InvocationContext invocationContext = createInvocationContext(rootAgent); + + Runner runner = getRunnerAndCreateSession(rootAgent, invocationContext.session()); + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + + // Act + TestSubscriber testSubscriber = + runner + .runLive(invocationContext.session(), liveRequestQueue, RunConfig.builder().build()) + .test(); + liveRequestQueue.content(Content.fromParts(Part.fromText("hi"))); + testSubscriber.await(); + + // Assert + testSubscriber.assertComplete(); + assertThat(simplifyEvents(testSubscriber.values())) + .containsExactly( + "root_agent: FunctionCall(name=transfer_to_agent, args={agent_name=sub_agent_1})", + "root_agent: FunctionResponse(name=transfer_to_agent, response={})", + "sub_agent_1: response1") + .inOrder(); + + long closedConnectionsCount = + testLlm.getLiveRequestHistory().stream().filter(LiveRequest::shouldClose).count(); + assertThat(closedConnectionsCount).isEqualTo(1); + } + @Test public void testAutoToAuto() { Content transferCallContent = Content.fromParts(createTransferCallPart("sub_agent_1")); @@ -412,85 +458,6 @@ public void testAutoToLoop() { assertThat(simplifyEvents(actualEvents)).containsExactly("root_agent: response5"); } - @Test - public void testLegacyTransferToAgent() { - Content transferCallContent = - Content.fromParts( - Part.fromFunctionCall("transferToAgent", ImmutableMap.of("agentName", "sub_agent_1"))); - Content response1 = Content.fromParts(Part.fromText("response1")); - Content response2 = Content.fromParts(Part.fromText("response2")); - - TestLlm testLlm = - createTestLlm( - Flowable.just(createLlmResponse(transferCallContent)), - Flowable.just(createLlmResponse(response1)), - Flowable.just(createLlmResponse(response2))); - - LlmAgent subAgent1 = createTestAgentBuilder(testLlm).name("sub_agent_1").build(); - LlmAgent rootAgent = - createTestAgentBuilder(testLlm) - .name("root_agent") - .subAgents(ImmutableList.of(subAgent1)) - .build(); - InvocationContext invocationContext = createInvocationContext(rootAgent); - - Runner runner = getRunnerAndCreateSession(rootAgent, invocationContext.session()); - List actualEvents = runRunner(runner, invocationContext); - - assertThat(simplifyEvents(actualEvents)) - .containsExactly( - "root_agent: FunctionCall(name=transferToAgent, args={agentName=sub_agent_1})", - "root_agent: FunctionResponse(name=transferToAgent, response={})", - "sub_agent_1: response1") - .inOrder(); - - actualEvents = runRunner(runner, invocationContext); - - assertThat(simplifyEvents(actualEvents)).containsExactly("sub_agent_1: response2"); - } - - @Test - public void testAgentTransferDoesNotExposeLegacyTransferToAgent() { - Content transferCallContent = - Content.fromParts( - Part.fromFunctionCall("transferToAgent", ImmutableMap.of("agentName", "sub_agent_1"))); - Content response1 = Content.fromParts(Part.fromText("response1")); - Content response2 = Content.fromParts(Part.fromText("response2")); - TestLlm testLlm = - createTestLlm( - Flowable.just(createLlmResponse(transferCallContent)), - Flowable.just(createLlmResponse(response1)), - Flowable.just(createLlmResponse(response2))); - LlmAgent subAgent1 = createTestAgentBuilder(testLlm).name("sub_agent_1").build(); - LlmAgent rootAgent = - createTestAgentBuilder(testLlm) - .name("root_agent") - .subAgents(ImmutableList.of(subAgent1)) - .build(); - InvocationContext invocationContext = createInvocationContext(rootAgent); - AgentTransfer processor = new AgentTransfer(); - LlmRequest request = LlmRequest.builder().build(); - - var processed = processor.processRequest(invocationContext, request); - - assertThat(processed.blockingGet().updatedRequest().config().get().tools()).isPresent(); - assertThat(processed.blockingGet().updatedRequest().config().get().tools().get()).hasSize(1); - assertThat( - processed - .blockingGet() - .updatedRequest() - .config() - .get() - .tools() - .get() - .get(0) - .functionDeclarations() - .get() - .get(0) - .name()) - .hasValue("transfer_to_agent"); - } - private Runner getRunnerAndCreateSession(LlmAgent agent, Session session) { Runner runner = new InMemoryRunner(agent, session.appName()); // Ensure the session exists before running the agent.