Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,17 @@ public Single<RequestProcessor.RequestProcessingResult> 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);
}
Expand Down Expand Up @@ -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.
*
* <p>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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Event> 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"));
Expand Down Expand Up @@ -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<Event> 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.
Expand Down