|
1 | 1 | # Google ADK on Temporal |
2 | 2 |
|
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. |
7 | 6 |
|
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. |
11 | 10 |
|
12 | 11 | ## Requirements |
13 | 12 |
|
14 | 13 | - 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. |
20 | 16 |
|
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. |
23 | 19 |
|
24 | | -## Run a sample |
| 20 | +## Run offline or with Gemini |
25 | 21 |
|
26 | | -Start a local Temporal Service: |
| 22 | +Run every command from the repository root. Start the Service in one terminal: |
27 | 23 |
|
28 | 24 | ```bash |
29 | 25 | temporal server start-dev |
30 | 26 | ``` |
31 | 27 |
|
32 | | -In another terminal, start the shared Worker from the repository root: |
| 28 | +Start the Worker in a second terminal, choosing one model mode: |
33 | 29 |
|
34 | 30 | ```bash |
35 | | -export GOOGLE_API_KEY=your_key |
36 | | -./gradlew :googleadk:worker |
| 31 | +ADK_MODEL_MODE=offline ./gradlew :googleadk:worker |
37 | 32 | ``` |
38 | 33 |
|
39 | | -Then run one of these commands in a third terminal: |
40 | | - |
41 | 34 | ```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 |
46 | 36 | ``` |
47 | 37 |
|
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 |
50 | 52 |
|
51 | | -| Scenario | Source | What it shows | |
| 53 | +| Category | Scenario | Durable behavior | |
52 | 54 | | --- | --- | --- | |
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. |
65 | 77 |
|
66 | 78 | ## Approve or reject a tool call |
67 | 79 |
|
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`. |
69 | 84 |
|
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. |
73 | 91 |
|
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 |
76 | 93 |
|
77 | 94 | ```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 |
83 | 96 | ``` |
84 | 97 |
|
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. |
91 | 101 |
|
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. |
102 | 107 |
|
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. |
108 | 112 |
|
109 | | -## Test without provider credentials |
| 113 | +## Boundaries |
110 | 114 |
|
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. |
114 | 122 |
|
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) |
134 | 124 | for the full API and limitations. |
0 commit comments