Skip to content

Commit 8d6c96d

Browse files
committed
Expand Google ADK Java sample coverage and offline execution
1 parent 8ac6983 commit 8d6c96d

30 files changed

Lines changed: 1236 additions & 180 deletions

‎README.md‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ It contains the following modules:
88
* [SpringBoot](/springboot): showcases SpringBoot autoconfig integration.
99
* [SpringBoot Basic](/springboot-basic): Minimal sample showing SpringBoot autoconfig integration without any extra external dependencies.
1010
* [Spring AI](/springai): demonstrates the Temporal Spring AI integration — durable AI agents with chat models, tools, MCP servers, vector stores, and embeddings.
11+
* [Google ADK](/googleadk): native ADK agents with durable conversations, tools, MCP, human approval, structured output, and asynchronous tool completion; runnable with a local fake or Gemini.
1112
* [Lambda Worker](/lambda-worker): demonstrates running a Temporal Java Worker inside AWS Lambda.
1213

1314
## Learn more about Temporal and Java SDK
@@ -232,15 +233,15 @@ To run any of the SpringBoot samples in your Temporal Cloud namespace:
232233

233234
The [Google ADK samples](googleadk) use the Public Preview
234235
[`io.temporal:google-adk`](https://github.com/temporalio/ai-integrations/tree/main/java/google-adk)
235-
integration. They cover a basic agent, Activity tools, parallel agents, MCP, human approval, and a
236-
conversation that continues as new. The module requires Temporal Java SDK 1.40.0 or later.
236+
integration. They cover durable conversations, Workflow and Activity tools, parallel agents, MCP,
237+
human approval, structured output, and asynchronous tool completion. The module requires Temporal
238+
Java SDK 1.40.0 or later.
237239

238240
Run the Worker and a sample in separate terminals after starting `temporal server start-dev`:
239241

240242
```bash
241-
export GOOGLE_API_KEY=your_key
242-
./gradlew :googleadk:worker
243-
./gradlew :googleadk:starter --args='basic'
243+
ADK_MODEL_MODE=offline ./gradlew :googleadk:worker
244+
temporal workflow execute --type ToolsWorkflow --task-queue google-adk-samples --input '"What is the weather in Chicago?"'
244245
```
245246

246247
See the [sample README](googleadk/README.md) for the other scenarios and credential-free tests.

‎googleadk/README.md‎

Lines changed: 85 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,124 @@
11
# Google ADK on Temporal
22

3-
Run native [Google ADK Java](https://github.com/google/adk-java) agents in Temporal Workflows using
4-
the standalone [`io.temporal:google-adk:0.1.0`](https://central.sonatype.com/artifact/io.temporal/google-adk)
5-
integration. This integration is **Public Preview**, not Generally Available. APIs and configuration
6-
may change before General Availability.
3+
Run native [Google ADK Java](https://github.com/google/adk-java) agents durably with
4+
[`io.temporal:google-adk:0.1.0`](https://central.sonatype.com/artifact/io.temporal/google-adk).
5+
The integration is **Public Preview**; APIs and configuration may change before General Availability.
76

8-
Model calls and MCP operations run as Temporal Activities. Agent orchestration, ordinary function
9-
tools, and session state stay in the Workflow. Use `TemporalAdk.runnerBuilder()` to keep ADK's time,
10-
identifiers, and parallel execution deterministic, and `TemporalAdk.run()` to await an agent turn.
7+
Model calls and MCP operations run as Temporal Activities. Agent orchestration, deterministic function
8+
tools, and session state stay in the Workflow. Build Runners with `TemporalAdk.runnerBuilder()` and
9+
await agent turns with `TemporalAdk.run()` so ADK uses Workflow time, identifiers, and threads.
1110

1211
## Requirements
1312

1413
- Java 17 or later.
15-
- `io.temporal:google-adk:0.1.0`, which brings Google ADK Java 1.12.0.
16-
- Temporal Java SDK and testing library 1.40.0 or later. The module declares a strict `[1.40.0,2.0)`
17-
version range, preferring 1.40.0, independently of the other samples' SDK version.
18-
- For a live run, a local Temporal Service (`temporal server start-dev`) and `GOOGLE_API_KEY` in the
19-
Worker's environment. The Google GenAI client also supports Vertex AI's environment configuration.
14+
- A local Temporal Service: `temporal server start-dev`.
15+
- For live Gemini runs, `GOOGLE_API_KEY` in the Worker's environment, or Vertex AI configuration.
2016

21-
The plugin's version is independent of the SDK version. The plugin does not supply `temporal-sdk`;
22-
the application adds it explicitly. Dependencies come from Maven Central.
17+
Dependencies come from Maven Central. This module supplies Temporal SDK/testing in `[1.40.0,2.0)`,
18+
preferring 1.40.0, independently of other samples. The integration brings ADK Java 1.12.0.
2319

24-
## Run a sample
20+
## Run offline or with Gemini
2521

26-
Start a local Temporal Service:
22+
Run every command from the repository root. Start the Service in one terminal:
2723

2824
```bash
2925
temporal server start-dev
3026
```
3127

32-
In another terminal, start the shared Worker from the repository root:
28+
Start the Worker in a second terminal, choosing one model mode:
3329

3430
```bash
35-
export GOOGLE_API_KEY=your_key
36-
./gradlew :googleadk:worker
31+
ADK_MODEL_MODE=offline ./gradlew :googleadk:worker
3732
```
3833

39-
Then run one of these commands in a third terminal:
40-
4134
```bash
42-
./gradlew :googleadk:starter --args='basic'
43-
./gradlew :googleadk:starter --args='tools'
44-
./gradlew :googleadk:starter --args='multiagent'
45-
./gradlew :googleadk:starter --args='mcp'
35+
GOOGLE_API_KEY=your_key ADK_MODEL_MODE=live ./gradlew :googleadk:worker
4636
```
4737

48-
The starter prints the agent's answer. All samples share the `google-adk-samples` Task Queue and
49-
start a new Workflow ID for each invocation. Inspect their Activities at `http://localhost:8233`.
38+
`live` is the default. Offline mode uses a deliberately small, stateless scripted `BaseLlm` for these
39+
scenarios, including fixed Chicago weather and travel examples. It makes no provider requests and
40+
needs no key. Live mode constructs Gemini only on the Worker and disables GenAI's HTTP retries so
41+
Temporal Activities own retries. Stop the Worker before switching modes.
42+
43+
Both modes run the same Worker registration, plugin, Workflows, Activities, messages, and result
44+
path. The MCP scenario launches the real Java stdio server in both modes. Offline mode still needs
45+
Temporal running, and a first Gradle build may download dependencies.
46+
47+
Use a third terminal for the scenario commands below. Every Worker polls `google-adk-samples`.
48+
The Temporal UI at <http://localhost:8233> shows model and tool Activities. Use a new Workflow ID
49+
when repeating a command that names an ID.
50+
51+
## Coverage
5052

51-
| Scenario | Source | What it shows |
53+
| Category | Scenario | Durable behavior |
5254
| --- | --- | --- |
53-
| Basic | [`basic/`](src/main/java/io/temporal/samples/googleadk/basic) | A native `LlmAgent` with `TemporalModel`; one model call becomes `googleadk.InvokeModel`. |
54-
| Activity tools | [`tools/`](src/main/java/io/temporal/samples/googleadk/tools) | `ActivityTools.create` exposes a weather Activity to the agent. The Activity returns sample weather data; replace its body with provider I/O. |
55-
| Multiple agents | [`multiagent/`](src/main/java/io/temporal/samples/googleadk/multiagent) | A `ParallelAgent` schedules two model Activities concurrently on Workflow threads. |
56-
| MCP | [`mcp/`](src/main/java/io/temporal/samples/googleadk/mcp) | A self-contained Java stdio echo server, started on first use by the Worker. Listing and calling tools become Activities. No Node or separately running MCP service is required. |
57-
| Human approval | [`humanintheloop/`](src/main/java/io/temporal/samples/googleadk/humanintheloop) | An Activity tool waits for a human decision delivered by Signal. The shipping Activity returns sample data and makes no fulfillment request. |
58-
| Conversation | [`chat/`](src/main/java/io/temporal/samples/googleadk/chat) | Signals queue messages, a Query returns the latest reply, and a session snapshot preserves the conversation through Continue-As-New. |
59-
60-
[`SampleWorker`](src/main/java/io/temporal/samples/googleadk/SampleWorker.java) registers the
61-
`GoogleAdkPlugin` on the Temporal Client, so its Workers inherit the model/MCP Activities and payload
62-
converter. Credentials stay on the Worker. Native GenAI retries are disabled with one HTTP attempt;
63-
Temporal Activity retries handle model failures. Register the plugin on external clients too, as
64-
[`SampleStarter`](src/main/java/io/temporal/samples/googleadk/SampleStarter.java) does.
55+
| Agent Chat | [chat](src/main/java/io/temporal/samples/googleadk/chat) | Signals queue turns; Queries expose replies; a session snapshot and pending messages survive Continue-As-New. |
56+
| Tools | [tools](src/main/java/io/temporal/samples/googleadk/tools) | A nondeterministic weather observation runs in an Activity; a pure temperature conversion runs in the Workflow. |
57+
| MCP | [mcp](src/main/java/io/temporal/samples/googleadk/mcp) | List and call a bundled stateless Java stdio server through Activities. |
58+
| Streaming | Unsupported | Version 0.1.0 buffers ADK chunks until its model Activity completes; it has no Workflow Streams publisher or live bidirectional connection. |
59+
| Human in the Loop | [humanintheloop](src/main/java/io/temporal/samples/googleadk/humanintheloop) | A Signal approves or rejects ADK's pending tool confirmation; cancellation ends the durable wait. |
60+
| Structured Output | [structuredoutput](src/main/java/io/temporal/samples/googleadk/structuredoutput) | ADK supplies the model's output schema; the Workflow validates JSON and returns a typed `TravelPlan` or a non-retryable failure. |
61+
| Multi-Agent | [multiagent](src/main/java/io/temporal/samples/googleadk/multiagent) | ADK's `ParallelAgent` starts planner and reviewer model Activities concurrently on Workflow threads. |
62+
| Sandboxes | Unsupported | The plugin does not durably manage an agent-facing code execution environment. ADK code executors that perform host I/O cannot run on the Workflow thread. |
63+
| Observability | Replay-safe ADK tracing unsupported | ADK uses global OpenTelemetry providers; this plugin has no replay suppression, so installing a tracing exporter duplicates spans during replay. Temporal history and UI inspection remain available. |
64+
65+
The unsupported entries describe the published integration's
66+
[documented limits](https://github.com/temporalio/ai-integrations/blob/java/google-adk/v0.1.0/java/google-adk/README.md#limitations),
67+
not an absence of the corresponding feature in ADK itself.
68+
69+
Additional examples:
70+
71+
- [longrunning](src/main/java/io/temporal/samples/googleadk/longrunning) uses the plugin's
72+
`ActivityTools.createLongRunning`, `Hitl.longRunningCalls`, and `Hitl.functionResponse` APIs.
73+
A submitted report remains pending until a correlated callback Signal delivers its result.
74+
This differs from approving an operation before it executes: it resumes an operation already started.
75+
- [basic](src/main/java/io/temporal/samples/googleadk/basic) preserves the original single-turn example
76+
and its documentation snippets. Start with Agent Chat for the conversation-oriented entry point.
6577

6678
## Approve or reject a tool call
6779

68-
Start a fulfillment Workflow:
80+
See [humanintheloop](src/main/java/io/temporal/samples/googleadk/humanintheloop) for CLI commands to
81+
start, query, decide, and cancel. Query until `pendingConfirmations` contains a request before
82+
sending `decide`. A decision applies to the currently pending batch; premature Signals are ignored, and the first
83+
decision for a pending batch wins. Rejection never schedules `ShipOrder`.
6984

70-
```bash
71-
./gradlew :googleadk:starter --args='approval'
72-
```
85+
## Continue a conversation as new
86+
87+
See [chat](src/main/java/io/temporal/samples/googleadk/chat) for CLI commands. After three turns the
88+
Workflow exports its ADK session and carries queued messages and the latest reply into a new Run.
89+
`finish` drains queued messages and returns the last reply. Continue-As-New bounds each Run's history;
90+
the conversation snapshot itself still grows and must fit Temporal's payload limits.
7391

74-
Use the printed Workflow ID in place of `WORKFLOW_ID`. Query the pending tool name and arguments,
75-
then approve or reject the calls from that turn:
92+
## Test without provider credentials
7693

7794
```bash
78-
./gradlew :googleadk:starter --args='pending WORKFLOW_ID'
79-
./gradlew :googleadk:starter --args='decide WORKFLOW_ID true'
80-
# Or reject instead:
81-
./gradlew :googleadk:starter --args='decide WORKFLOW_ID false'
82-
./gradlew :googleadk:starter --args='result WORKFLOW_ID'
95+
./gradlew :googleadk:test :googleadk:spotlessCheck :googleadk:build
8396
```
8497

85-
`pending` can initially be empty while the model runs; query again once the agent asks for approval.
86-
The decision command sends a Signal; `result` waits for the final answer. The Workflow uses `Workflow.await` while paused,
87-
then supplies `Hitl.confirmationResponse` to the next ADK turn. A rejected call never schedules the
88-
shipping Activity. If the model asks for another confirmation, query and decide again.
89-
90-
## Continue a conversation as new
98+
Tests use the published `ScriptedModel` or the same `OfflineModel` as the runnable Worker, and
99+
`TestWorkflowEnvironment`. They need no provider credentials, external Temporal Service, or telemetry
100+
backend. The MCP tests launch the bundled local subprocess.
91101

92-
```bash
93-
./gradlew :googleadk:starter --args='chat'
94-
./gradlew :googleadk:starter --args='message WORKFLOW_ID My name is Ada.'
95-
./gradlew :googleadk:starter --args='reply WORKFLOW_ID'
96-
./gradlew :googleadk:starter --args='message WORKFLOW_ID Suggest a trip to Chicago.'
97-
./gradlew :googleadk:starter --args='message WORKFLOW_ID What should I pack?'
98-
./gradlew :googleadk:starter --args='message WORKFLOW_ID What is my name?'
99-
./gradlew :googleadk:starter --args='reply WORKFLOW_ID'
100-
./gradlew :googleadk:starter --args='finish WORKFLOW_ID'
101-
```
102+
Each supported scenario has a targeted test command in its README. Tests assert Activity scheduling,
103+
retry behavior, function results, confirmation decisions and cancellation, schema rejection,
104+
correlated callback completion, and session/queue recovery across Continue-As-New. Recorded histories
105+
replay on a fresh Worker without repeating model or tool calls. `OfflineSamplesTest` runs every
106+
scenario with `SampleWorker.register` and the actual offline provider.
102107

103-
`message` queues a turn without waiting for the model. Query again after the model Activity completes
104-
to see its new reply. After three turns, the Workflow exports its ADK session, continues as new, and
105-
imports the session in the next Run. Queued messages and the latest reply travel with the snapshot.
106-
`finish` drains pending messages and returns the final answer. Continue-As-New limits each Run's
107-
history; the snapshot still contains the conversation and must fit within Temporal's payload limits.
108+
The original `:googleadk:starter` task remains available for `basic`, `tools`, `multiagent`, `mcp`,
109+
`approval`, and `chat`, and their message commands. The scenario READMEs use Temporal CLI directly.
110+
External Java clients that exchange ADK `Session` or `Content` payloads must also register
111+
`GoogleAdkPlugin` to use its payload converter; ordinary CLI JSON inputs need no Java client.
108112

109-
## Test without provider credentials
113+
## Boundaries
110114

111-
```bash
112-
./gradlew :googleadk:test :googleadk:spotlessCheck
113-
```
115+
- Put host clocks, files, network services, and nondeterministic tools in Activities or MCP. The
116+
weather Activity deliberately reads `Instant.now()` alongside illustrative, fixed conditions;
117+
it does not call a real weather provider. Shipping and report submission also return sample data.
118+
- Do not override agent-level schedulers/executors or use RxJava blocking APIs inside Workflows.
119+
- Session snapshots preserve session-scoped state and events, not app-, user-, or temporary state.
120+
- A cancellation stops the Workflow's wait. It does not undo a shipment or cancel an external report
121+
already submitted; a real backend needs an explicit cancellation Activity.
114122

115-
The tests use `ScriptedModel` from the published integration and `TestWorkflowEnvironment`; they
116-
need no API keys or external Temporal Service. The MCP test starts the real local Java echo server.
117-
Tests verify Activity retries and results, concurrent model scheduling, approval and rejection,
118-
session snapshots and queued messages across Continue-As-New, and replay on a fresh Worker without
119-
repeating model or tool calls.
120-
121-
## Limitations
122-
123-
- Plain ADK function tools run in the Workflow and must be deterministic. Put network, file, and
124-
other I/O in Activities or MCP tools.
125-
- Build Runners with `TemporalAdk.runnerBuilder()`. Do not override agent-level schedulers or
126-
executors, or use RxJava blocking APIs inside a Workflow.
127-
- Streaming chunks arrive after the model Activity completes. Token streaming out of a Workflow
128-
and live bidirectional mode are unsupported.
129-
- Session snapshots preserve session-scoped state and events. They do not carry app-, user-, or
130-
temporary-scoped state.
131-
- ADK OpenTelemetry spans are emitted again during replay when a tracing SDK is installed.
132-
133-
See the [integration README](https://github.com/temporalio/ai-integrations/tree/main/java/google-adk)
123+
See the [integration reference](https://github.com/temporalio/ai-integrations/tree/main/java/google-adk)
134124
for the full API and limitations.

‎googleadk/build.gradle‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// ADK samples consume the independently versioned integration from Maven Central.
22
dependencies {
33
implementation 'io.temporal:google-adk:0.1.0'
4+
implementation 'com.fasterxml.jackson.core:jackson-databind'
45
implementation('io.temporal:temporal-sdk') {
56
version {
67
strictly '[1.40.0,2.0['

0 commit comments

Comments
 (0)