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
85 changes: 85 additions & 0 deletions contrib/samples/a2a_basic/A2AAgent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.example.a2a_basic;

import com.google.adk.a2a.A2AClient;
import com.google.adk.a2a.RemoteA2AAgent;
import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.a2a.client.http.JdkA2AHttpClient;
import io.a2a.spec.AgentCapabilities;
import io.a2a.spec.AgentCard;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/** Provides local roll logic plus a remote A2A agent for the demo. */
public final class A2AAgent {

private static final Random RANDOM = new Random();

@SuppressWarnings("unchecked")
public static ImmutableMap<String, Object> rollDie(int sides, ToolContext toolContext) {
ArrayList<Integer> rolls =
(ArrayList<Integer>) toolContext.state().computeIfAbsent("rolls", k -> new ArrayList<>());
int result = RANDOM.nextInt(Math.max(sides, 1)) + 1;
rolls.add(result);
return ImmutableMap.of("result", result);
}

public static final LlmAgent ROLL_AGENT =
LlmAgent.builder()
.name("roll_agent")
.model("gemini-2.0-flash")
.description("Handles rolling dice of different sizes.")
.instruction(
"""
When asked to roll a die, always call the roll_die tool with the requested number of
sides (default to 6 if unspecified). Do not fabricate results.
""")
.tools(ImmutableList.of(FunctionTool.create(A2AAgent.class, "rollDie")))
.build();

public static LlmAgent createRootAgent(String primeAgentBaseUrl) {
BaseAgent primeAgent = createRemoteAgent(primeAgentBaseUrl);
return LlmAgent.builder()
.name("root_agent")
.model("gemini-2.0-flash")
.instruction(
"""
You can roll dice locally and delegate prime-checking to the remote prime_agent.
1. When the user asks to roll a die, route the request to roll_agent.
2. When the user asks to check primes, delegate to prime_agent.
3. If the user asks to roll and then check, roll_agent first, then prime_agent with the result.
Always recap the die result before discussing primality.
""")
.subAgents(ImmutableList.of(ROLL_AGENT, primeAgent))
.build();
}

private static BaseAgent createRemoteAgent(String primeAgentBaseUrl) {
AgentCapabilities capabilities = new AgentCapabilities.Builder().build();
AgentCard agentCard =
new AgentCard.Builder()
.name("prime_agent")
.description("Stub agent metadata used for third-party A2A demo")
.url(primeAgentBaseUrl)
.version("1.0.0")
.capabilities(capabilities)
.defaultInputModes(List.of("text"))
.defaultOutputModes(List.of("text"))
.skills(List.of())
.security(List.of())
.build();
A2AClient client = new A2AClient(agentCard, new JdkA2AHttpClient(), /* defaultHeaders= */ null);
return RemoteA2AAgent.builder()
.name(agentCard.name())
.agentCardOrSource(agentCard)
.a2aClient(client)
.build();
}

private A2AAgent() {}
}
93 changes: 93 additions & 0 deletions contrib/samples/a2a_basic/A2AAgentRun.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.example.a2a_basic;

import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.LlmAgent;
import com.google.adk.agents.RunConfig;
import com.google.adk.artifacts.InMemoryArtifactService;
import com.google.adk.events.Event;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/** Main class to demonstrate running the A2A agent with sequential inputs. */
public final class A2AAgentRun {
private final String userId;
private final String sessionId;
private final Runner runner;

public A2AAgentRun(BaseAgent agent) {
this.userId = "test_user";
String appName = "A2AAgentApp";
this.sessionId = UUID.randomUUID().toString();

InMemoryArtifactService artifactService = new InMemoryArtifactService();
InMemorySessionService sessionService = new InMemorySessionService();
this.runner =
new Runner(agent, appName, artifactService, sessionService, /* memoryService= */ null);

ConcurrentMap<String, Object> initialState = new ConcurrentHashMap<>();
var unused =
sessionService.createSession(appName, userId, initialState, sessionId).blockingGet();
}

private List<Event> run(String prompt) {
System.out.println("\n--------------------------------------------------");
System.out.println("You> " + prompt);
Content userMessage =
Content.builder()
.role("user")
.parts(ImmutableList.of(Part.builder().text(prompt).build()))
.build();
return processRunRequest(userMessage);
}

private List<Event> processRunRequest(Content inputContent) {
RunConfig runConfig = RunConfig.builder().build();
Flowable<Event> eventStream =
this.runner.runAsync(this.userId, this.sessionId, inputContent, runConfig);
List<Event> agentEvents = Lists.newArrayList(eventStream.blockingIterable());
System.out.println("Agent>");
for (Event event : agentEvents) {
if (event.content().isPresent() && event.content().get().parts().isPresent()) {
event
.content()
.get()
.parts()
.get()
.forEach(
part -> {
if (part.text().isPresent()) {
System.out.println(" Text: " + part.text().get().stripTrailing());
}
});
}
if (event.actions() != null && event.actions().transferToAgent().isPresent()) {
System.out.println(" Actions: transferTo=" + event.actions().transferToAgent().get());
}
System.out.println(" Raw Event: " + event);
}
return agentEvents;
}

public static void main(String[] args) {
String primeAgentUrl = args.length > 0 ? args[0] : "http://localhost:9876/a2a/prime_agent";
LlmAgent agent = A2AAgent.createRootAgent(primeAgentUrl);
A2AAgentRun a2aRun = new A2AAgentRun(agent);

// First user input
System.out.println("Running turn 1");
a2aRun.run("Roll a dice of 6 sides.");

// Follow-up input triggers the remote prime agent so the A2A request is logged.
System.out.println("Running turn 2");
a2aRun.run("Is this number a prime number?");
}
}
49 changes: 49 additions & 0 deletions contrib/samples/a2a_basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# A2A Basic Sample

This sample shows how to invoke an A2A-compliant HTTP endpoint from the Google
ADK runtime using the reusable `google-adk-a2a` module. It wires a
`RemoteA2AAgent` to the production `JdkA2AHttpClient`, so you can exercise a
running service (for example the Spring Boot webservice in
`a2a/webservice`).

## Prerequisites

1. Start the Spring service (or point to any other A2A-compliant endpoint):

```bash
cd /google_adk
./mvnw -f a2a/webservice/pom.xml spring-boot:run \
-Dspring-boot.run.arguments=--server.port=8081
```

## Build and run

```bash
cd google_adk
./mvnw -f contrib/samples/a2a_basic/pom.xml exec:java \
-Dexec.args="http://localhost:8081/a2a/remote"
```

You should see the client log each turn, including the remote agent response
(e.g. `4 is not a prime number.`).

To run the client in the background and capture logs:

```bash
nohup env GOOGLE_GENAI_USE_VERTEXAI=FALSE \
GOOGLE_API_KEY=your_api_key \
./mvnw -f contrib/samples/a2a_basic/pom.xml exec:java \
-Dexec.args="http://localhost:8081/a2a/remote" \
> /tmp/a2a_basic.log 2>&1 & echo $!
```

Tail `/tmp/a2a_basic.log` to inspect the conversation.

## Key files

- `A2AAgent.java` – builds a root agent with a local dice-rolling tool and a
remote prime-checking sub-agent.
- `A2AAgentRun.java` – minimal driver that executes a single
`SendMessage` turn to demonstrate the remote call.
- `pom.xml` – standalone Maven configuration for building and running the
sample.
83 changes: 83 additions & 0 deletions contrib/samples/a2a_basic/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.google.adk</groupId>
<artifactId>google-adk-sample-a2a-basic</artifactId>
<version>0.3.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Google ADK - Sample - A2A Basic Client</name>
<description>Demonstrates sending A2A REST requests using the google-adk-a2a module.</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
<google-adk.version>0.3.1-SNAPSHOT</google-adk.version>
<google-adk-a2a.version>0.3.1-SNAPSHOT</google-adk-a2a.version>
<slf4j.version>2.0.16</slf4j.version>
</properties>

<dependencies>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>${google-adk.version}</version>
</dependency>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-a2a</artifactId>
<version>${google-adk-a2a.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${java.version}</release>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>.</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<mainClass>com.example.a2a_basic.A2AAgentRun</mainClass>
<classpathScope>runtime</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</project>
70 changes: 70 additions & 0 deletions contrib/samples/a2a_remote/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# A2A Remote Prime Service Sample

This sample starts a standalone Spring Boot service that exposes the
`remote_prime_agent` via the shared A2A webservice module
(`google-adk-a2a-webservice`). It behaves like a third‑party service that
implements the A2A JSON‑RPC contract and can be used by the ADK client (for
example, the `a2a_basic` demo) as its remote endpoint.

## Running the service

```bash
cd google_adk
mvn -f contrib/samples/a2a_remote/pom.xml package

GOOGLE_GENAI_USE_VERTEXAI=FALSE \
GOOGLE_API_KEY=<your_gemini_api_key> \
mvn -f contrib/samples/a2a_remote/pom.xml exec:java
```

`RemoteA2AApplication` imports the reusable controller/service from
`google-adk-a2a-webservice`, so the server listens on
`http://localhost:8080/a2a/remote/v1/message:send` by default. Override the
port with `-Dspring-boot.run.arguments=--server.port=<port>` when running via
`spring-boot:run` if you need to avoid collisions.

```
POST /a2a/remote/v1/message:send
Content-Type: application/json
```

and accepts standard A2A JSON‑RPC payloads (`SendMessageRequest`). The
response is a `SendMessageResponse` that contains either a `Message` or a
`Task` in the `result` field. Spring Boot logs the request/response lifecycle
to the console; add your preferred logging configuration if you need
persistent logs.

## Agent implementation

- `remote_prime_agent/Agent.java` hosts the LLM agent that checks whether
numbers are prime (lifted from the Stubby demo). The model name defaults
to `gemini-2.5-pro`; set `GOOGLE_API_KEY` before running.
- `RemoteA2AApplication` bootstraps the service by importing
`A2ARemoteConfiguration` and publishing the prime `BaseAgent` bean. The shared
configuration consumes that bean to create the `A2ASendMessageExecutor`.

## Sample request

```bash
curl -X POST http://localhost:8080/a2a/remote/v1/message:send \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": "demo-123",
"method": "message/send",
"params": {
"message": {
"role": "user",
"messageId": "msg-1",
"contextId": "ctx-1",
"parts": [
{"kind": "text", "text": "Check if 17 is prime"}
]
},
"metadata": {}
}
}'
```

The response contains the prime check result, and the interaction is logged in
the application console.
Loading