Skip to content

Commit d605c39

Browse files
authored
Merge branch 'main' into langgraph-hitl-interactive-feedback
2 parents db61eef + eb54b5e commit d605c39

117 files changed

Lines changed: 5149 additions & 124 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CODEOWNERS

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,25 @@
11
* @temporalio/sdk
2+
3+
# SDK & Nexus own the README, pyproject.toml, and uv.lock
4+
/README.md @temporalio/sdk @temporalio/nexus
5+
/pyproject.toml @temporalio/sdk @temporalio/nexus
6+
/uv.lock @temporalio/sdk @temporalio/nexus
7+
8+
# The Nexus team owns any folder whose name starts or ends with "nexus",
9+
# both at the repo root and under tests/
10+
/nexus*/ @temporalio/nexus
11+
/*nexus/ @temporalio/nexus
12+
/tests/nexus*/ @temporalio/nexus
13+
/tests/*nexus/ @temporalio/nexus
14+
15+
# The AI SDK team owns the AI integration samples and their tests. We add
16+
# @temporalio/sdk too, so the SDK team can continue to manage repo-wide concerns.
17+
/google_adk_agents/ @temporalio/sdk @temporalio/ai-sdk
18+
/langgraph_plugin/ @temporalio/sdk @temporalio/ai-sdk
19+
/langsmith_tracing/ @temporalio/sdk @temporalio/ai-sdk
20+
/openai_agents/ @temporalio/sdk @temporalio/ai-sdk
21+
/strands_plugin/ @temporalio/sdk @temporalio/ai-sdk
22+
/tests/google_adk_agents/ @temporalio/sdk @temporalio/ai-sdk
23+
/tests/langgraph_plugin/ @temporalio/sdk @temporalio/ai-sdk
24+
/tests/langsmith_tracing/ @temporalio/sdk @temporalio/ai-sdk
25+
/tests/strands_plugin/ @temporalio/sdk @temporalio/ai-sdk

README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,28 +72,41 @@ Some examples require extra dependencies. See each sample's directory for specif
7272
* [external_storage](external_storage) - Offload large payloads to S3-compatible object storage, plus a codec server for the Web UI and CLI.
7373
* [external_storage_redis](external_storage_redis) - Redis driver for external storage
7474
* [gevent_async](gevent_async) - Combine gevent and Temporal.
75+
* [google_adk_agents](google_adk_agents) - Run Google ADK agents as durable Temporal workflows (model calls, tools, multi-agent, MCP, streaming).
76+
* [hello_nexus](hello_nexus) - Define a Nexus service, implement operation handlers, and call them from a workflow.
7577
* [hello_standalone_activity](hello_standalone_activity) - Use activities without using a workflow.
76-
* [langchain](langchain) - Orchestrate workflows for LangChain.
78+
* [lambda_worker](lambda_worker) - Run a Temporal Worker inside an AWS Lambda function.
7779
* [langgraph_plugin](langgraph_plugin) - Run LangGraph workflows as durable Temporal workflows (Graph API and Functional API).
80+
* [langsmith_tracing](langsmith_tracing) - Trace Temporal workflows with LangSmith via the LangSmith plugin.
7881
* [message_passing/introduction](message_passing/introduction/) - Introduction to queries, signals, and updates.
7982
* [message_passing/safe_message_handlers](message_passing/safe_message_handlers/) - Safely handling updates and signals.
8083
* [message_passing/update_with_start/lazy_initialization](message_passing/update_with_start/lazy_initialization/) - Use update-with-start to update a Shopping Cart, starting it if it does not exist.
84+
* [nexus_cancel](nexus_cancel) - Fan out concurrent Nexus operations, take the first result, and cancel the rest.
8185
* [Nexus Messaging](nexus_messaging): Demonstrates how send signal, update and query messages through Nexus.
8286
This contains two samples, one sending messages to an existing workflow and a second that creates a workflow through Nexus
8387
and sends messages to it.
88+
* [nexus_multiple_args](nexus_multiple_args) - Map a Nexus operation to a handler workflow that takes multiple arguments.
89+
* [nexus_standalone_operations](nexus_standalone_operations) - Execute Nexus operations directly from client code,
90+
without wrapping them in a workflow.
8491
* [open_telemetry](open_telemetry) - Trace workflows with OpenTelemetry.
92+
* [openai_agents](openai_agents) - Run OpenAI Agents SDK agents as durable Temporal workflows.
8593
* [patching](patching) - Alter workflows safely with `patch` and `deprecate_patch`.
8694
* [polling](polling) - Recommended implementation of an activity that needs to periodically poll an external resource waiting its successful completion.
8795
* [prometheus](prometheus) - Configure Prometheus metrics on clients/workers.
88-
* [workflow_streams](workflow_streams) - Workflow-hosted durable event stream via `temporalio.contrib.workflow_streams`. **Experimental**
8996
* [pydantic_converter](pydantic_converter) - Data converter for using Pydantic models.
97+
* [pydantic_converter_v1](pydantic_converter_v1) - Data converter for Pydantic v1 models (prefer pydantic_converter for v2).
98+
* [replay](replay) - Verify that workflow code changes are compatible with existing histories.
99+
* [resource_pool](resource_pool) - Allocate a pool of shared resources across workflows.
90100
* [schedules](schedules) - Demonstrates a Workflow Execution that occurs according to a schedule.
91101
* [sentry](sentry) - Report errors to Sentry.
102+
* [sleep_for_days](sleep_for_days) - A workflow that runs forever, sending an email every 30 days.
103+
* [strands_plugin](strands_plugin) - Run Strands Agents as durable Temporal workflows (model calls, tools, MCP, HITL).
92104
* [trio_async](trio_async) - Use asyncio Temporal in Trio-based environments.
93105
* [updatable_timer](updatable_timer) - A timer that can be updated while sleeping.
106+
* [worker_multiprocessing](worker_multiprocessing) - Leverage Python multiprocessing to parallelize workflow tasks and other CPU bound operations by running multiple workers.
94107
* [worker_specific_task_queues](worker_specific_task_queues) - Use unique task queues to ensure activities run on specific workers.
95108
* [worker_versioning](worker_versioning) - Use the Worker Versioning feature to more easily version your workflows & other code.
96-
* [worker_multiprocessing](worker_multiprocessing) - Leverage Python multiprocessing to parallelize workflow tasks and other CPU bound operations by running multiple workers.
109+
* [workflow_streams](workflow_streams) - Workflow-hosted durable event stream via `temporalio.contrib.workflow_streams`. **Experimental**
97110

98111
## Test
99112

google_adk_agents/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Temporal Google ADK Integration
2+
3+
⚠️ **Experimental** — This integration is experimental and its interfaces may
4+
change prior to General Availability.
5+
6+
This directory contains samples demonstrating how to run
7+
[Google ADK](https://google.github.io/adk-docs/) agents durably inside Temporal
8+
workflows using `temporalio.contrib.google_adk_agents`. Each scenario is a
9+
self-contained subdirectory with its own worker, workflow starter, workflow and
10+
activity packages, and README.
11+
12+
## Overview
13+
14+
The integration combines:
15+
16+
- **Temporal workflows** for durable orchestration of agent control flow
17+
- **Google ADK** for agent creation, model calls, tools, and MCP integration
18+
19+
`GoogleAdkPlugin` configures a Pydantic payload converter, sandbox passthrough
20+
for `google.adk` / `google.genai` / `mcp`, a deterministic ADK runtime, and the
21+
model activities. `TemporalModel` runs each LLM call as an activity, so every
22+
model turn is durable and observable.
23+
24+
## Prerequisites
25+
26+
- Temporal server [running locally](https://docs.temporal.io/cli/server#start-dev)
27+
- Dependencies installed via `uv sync --group google-adk`
28+
- Google API key set as an environment variable:
29+
`export GOOGLE_API_KEY=your_key_here`
30+
31+
All scenarios default to the `gemini-2.5-flash` model. ADK also supports other
32+
providers (for example, non-Gemini models via LiteLLM); swap the model name on
33+
`TemporalModel` to use one.
34+
35+
## Scenarios
36+
37+
Each directory contains a complete example with its own README:
38+
39+
| Scenario | What it shows |
40+
| --- | --- |
41+
| [basic](./basic/README.md) | A single ADK agent with `TemporalModel` and one model call — no tools. The minimal end-to-end example. |
42+
| [tools](./tools/README.md) | A Temporal activity wrapped as an ADK tool with `activity_tool`, so tool calls run as their own activities. |
43+
| [agent_patterns](./agent_patterns/README.md) | A coordinator `LlmAgent` with `sub_agents`, each a `TemporalModel` with a per-agent activity summary. |
44+
| [mcp](./mcp/README.md) | A local echo MCP toolset via `TemporalMcpToolSet` / `TemporalMcpToolSetProvider`, running MCP tools as activities. Self-contained, no Node required. |
45+
| [streaming](./streaming/README.md) | Token streaming via `TemporalModel(streaming_topic=...)` + `WorkflowStream`, consumed by a starter with `WorkflowStreamClient`. |
46+
47+
To run any scenario, start its worker in one terminal and its workflow starter
48+
in another:
49+
50+
```bash
51+
uv run python -m google_adk_agents.<scenario>.run_worker
52+
uv run python -m google_adk_agents.<scenario>.run_<name>_workflow
53+
```

google_adk_agents/__init__.py

Whitespace-only changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Agent Patterns — Multi-Agent Coordinator
2+
3+
A coordinator `LlmAgent` with `sub_agents=[researcher, writer]`. Each agent uses
4+
its own `TemporalModel` with an `ActivityConfig(summary=...)`, so the agents
5+
show up as named activities in workflow history. This demonstrates ADK's
6+
built-in `transfer_to_agent` handoff running durably, with per-agent activity
7+
summaries.
8+
9+
Before running, review the [prerequisites in the suite README](../README.md)
10+
(Temporal dev server, `uv sync --group google-adk`, and
11+
`export GOOGLE_API_KEY=...`).
12+
13+
## Running
14+
15+
Start the worker in one terminal:
16+
17+
```bash
18+
uv run python -m google_adk_agents.agent_patterns.run_worker
19+
```
20+
21+
Then start the workflow in another terminal:
22+
23+
```bash
24+
uv run python -m google_adk_agents.agent_patterns.run_multi_agent_workflow
25+
```
26+
27+
## What to expect
28+
29+
The starter asks for a haiku about the ocean. The coordinator delegates to the
30+
researcher and then the writer; the starter prints the final haiku.
31+
32+
## In the Temporal UI
33+
34+
Open the workflow `google-adk-agents-agent-patterns-workflow-id`. The
35+
`invoke_model` activities are labeled with their agent summaries —
36+
"Coordinator Agent", "Researcher Agent", "Writer Agent" — so you can follow the
37+
handoffs between agents directly in the history.

google_adk_agents/agent_patterns/__init__.py

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import asyncio
2+
3+
from temporalio.client import Client
4+
from temporalio.contrib.google_adk_agents import GoogleAdkPlugin
5+
6+
from google_adk_agents.agent_patterns.workflows.multi_agent_workflow import (
7+
MultiAgentWorkflow,
8+
)
9+
10+
11+
async def main():
12+
client = await Client.connect("localhost:7233", plugins=[GoogleAdkPlugin()])
13+
14+
result = await client.execute_workflow(
15+
MultiAgentWorkflow.run,
16+
"the ocean",
17+
id="google-adk-agents-agent-patterns-workflow-id",
18+
task_queue="google-adk-agents-agent-patterns",
19+
)
20+
print(f"Result: {result}")
21+
22+
23+
if __name__ == "__main__":
24+
asyncio.run(main())
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
5+
from temporalio.client import Client
6+
from temporalio.contrib.google_adk_agents import GoogleAdkPlugin
7+
from temporalio.worker import Worker
8+
9+
from google_adk_agents.agent_patterns.workflows.multi_agent_workflow import (
10+
MultiAgentWorkflow,
11+
)
12+
13+
14+
async def main():
15+
plugin = GoogleAdkPlugin()
16+
17+
client = await Client.connect("localhost:7233", plugins=[plugin])
18+
19+
worker = Worker(
20+
client,
21+
task_queue="google-adk-agents-agent-patterns",
22+
workflows=[MultiAgentWorkflow],
23+
plugins=[plugin],
24+
)
25+
await worker.run()
26+
27+
28+
if __name__ == "__main__":
29+
asyncio.run(main())

google_adk_agents/agent_patterns/workflows/__init__.py

Whitespace-only changes.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from datetime import timedelta
2+
3+
from google.adk.agents import LlmAgent
4+
from google.adk.runners import Runner
5+
from google.adk.sessions import InMemorySessionService
6+
from google.genai import types
7+
from temporalio import workflow
8+
from temporalio.contrib.google_adk_agents import TemporalModel
9+
from temporalio.workflow import ActivityConfig
10+
11+
12+
# @@@SNIPSTART google-adk-agents-agent-patterns-multi-agent-workflow
13+
@workflow.defn
14+
class MultiAgentWorkflow:
15+
@workflow.run
16+
async def run(self, topic: str) -> str:
17+
session_service = InMemorySessionService()
18+
session = await session_service.create_session(
19+
app_name="multi_agent_app", user_id="user"
20+
)
21+
22+
# The ActivityConfig summary makes each model turn a named activity in
23+
# history.
24+
researcher = LlmAgent(
25+
name="researcher",
26+
model=TemporalModel(
27+
"gemini-2.5-flash",
28+
activity_config=ActivityConfig(summary="Researcher Agent"),
29+
),
30+
instruction="You are a researcher. Find information about the topic.",
31+
)
32+
33+
writer = LlmAgent(
34+
name="writer",
35+
model=TemporalModel(
36+
"gemini-2.5-flash",
37+
activity_config=ActivityConfig(summary="Writer Agent"),
38+
),
39+
instruction="You are a poet. Write a haiku based on the research.",
40+
)
41+
42+
# ADK's transfer_to_agent handoff runs durably here.
43+
coordinator = LlmAgent(
44+
name="coordinator",
45+
model=TemporalModel(
46+
"gemini-2.5-flash",
47+
activity_config=ActivityConfig(
48+
start_to_close_timeout=timedelta(seconds=30),
49+
summary="Coordinator Agent",
50+
),
51+
),
52+
instruction="You are a coordinator. Delegate to researcher then writer.",
53+
sub_agents=[researcher, writer],
54+
)
55+
56+
runner = Runner(
57+
agent=coordinator,
58+
app_name="multi_agent_app",
59+
session_service=session_service,
60+
)
61+
62+
final_text = ""
63+
user_msg = types.Content(
64+
role="user",
65+
parts=[
66+
types.Part(
67+
text=f"Write a haiku about {topic}. First research it, then write it."
68+
)
69+
],
70+
)
71+
async for event in runner.run_async(
72+
user_id="user", session_id=session.id, new_message=user_msg
73+
):
74+
if event.content and event.content.parts and event.content.parts[0].text:
75+
final_text = event.content.parts[0].text
76+
77+
return final_text
78+
79+
80+
# @@@SNIPEND

0 commit comments

Comments
 (0)