Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@
/*nexus/ @temporalio/nexus
/tests/nexus*/ @temporalio/nexus
/tests/*nexus/ @temporalio/nexus

/google_genai_plugin/ @temporalio/sdk @temporalio/ai-sdk
/tests/google_genai_plugin/ @temporalio/sdk @temporalio/ai-sdk
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Some examples require extra dependencies. See each sample's directory for specif
* [external_storage](external_storage) - Offload large payloads to S3-compatible object storage, plus a codec server for the Web UI and CLI.
* [external_storage_redis](external_storage_redis) - Redis driver for external storage
* [gevent_async](gevent_async) - Combine gevent and Temporal.
* [google_genai_plugin](google_genai_plugin) - Run the Google Gemini SDK inside durable Temporal workflows (generate_content, tools/AFC, streaming, chat, structured output, MCP, files, interactions, agents, Vertex AI).
* [hello_standalone_activity](hello_standalone_activity) - Use activities without using a workflow.
* [langchain](langchain) - Orchestrate workflows for LangChain.
* [langgraph_plugin](langgraph_plugin) - Run LangGraph workflows as durable Temporal workflows (Graph API and Functional API).
Expand Down
82 changes: 82 additions & 0 deletions google_genai_plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Google GenAI Samples

These samples demonstrate the [Temporal Google GenAI plugin](https://github.com/temporalio/sdk-python/tree/main/temporalio/contrib/google_genai), which runs the [Google Gemini SDK](https://googleapis.github.io/python-genai/) inside Temporal Workflows. Workflows construct a `TemporalAsyncClient`, and every Gemini API call — `generate_content`, tool calls, streaming, files, interactions, agents — runs as a Temporal Activity. You get durable execution, Temporal-managed retries and timeouts, and your credentials never enter the workflow or its event history.

## Samples

| Sample | Description |
|--------|-------------|
| [hello_world](hello_world) | Minimal `generate_content` call. Start here. |
| [tools](tools) | Automatic function calling: an `activity_as_tool`-wrapped activity and a plain workflow-method tool on one call. |
| [streaming](streaming) | Forward `generate_content_stream` chunks to an external subscriber via `streaming_topic` + `WorkflowStream`. |
| [chat](chat) | Multi-turn conversation with `client.chats`. |
| [structured_output](structured_output) | Typed JSON output via `response_schema` and a Pydantic model. |
| [mcp](mcp) | Give Gemini an MCP server's tools via `TemporalMcpClientSession`. |
| [files](files) | Upload a file with `client.files` and reference it in a call. *(needs a live API key)* |
| [interactions](interactions) | Stateful server-side conversations via `client.interactions`. *(needs a live API key)* |
| [agents](agents) | Managed-agent CRUD via `client.agents`. *(needs a live API key)* |
| [vertex_ai](vertex_ai) | The hello-world flow against Vertex AI (`vertexai=True`). *(needs GCP credentials)* |

## Prerequisites

1. Install dependencies:

```bash
uv sync --group google-genai
```

> The `google-genai` extra of `temporalio` is shipping in an upcoming release. Until then, install the SDK from the source checkout:
>
> ```bash
> uv pip install -e ../sdk-python --extra google-genai --extra pydantic
> ```

2. Configure credentials. Most samples use the Gemini Developer API and read an API key from the environment:

```bash
export GOOGLE_API_KEY=...
```

The [vertex_ai](vertex_ai) sample instead uses Vertex AI with Google Cloud Application Default Credentials — see its README. You can authenticate with `gcloud auth application-default login` and set `GOOGLE_CLOUD_PROJECT` (and optionally `GOOGLE_CLOUD_LOCATION`).

3. Start a [Temporal dev server](https://docs.temporal.io/cli#start-dev-server):

```bash
temporal server start-dev
```

## Running a Sample

Each sample has two scripts. Start the Worker first, then the Workflow starter in a separate terminal:

```bash
# Terminal 1: start the Worker
uv run google_genai_plugin/<sample>/run_worker.py

# Terminal 2: start the Workflow
uv run google_genai_plugin/<sample>/run_workflow.py
```

For example, to run the tools sample:

```bash
# Terminal 1
uv run google_genai_plugin/tools/run_worker.py

# Terminal 2
uv run google_genai_plugin/tools/run_workflow.py
```

## Key Features Demonstrated

- **Durable API calls** — every Gemini call runs as an activity with configurable timeouts and retries; no credentials enter workflow history.
- **Automatic function calling** — the SDK's AFC loop runs in-workflow; tools can be durable activities (`activity_as_tool`) or plain workflow methods.
- **Streaming** — forward model chunks live to external subscribers via `WorkflowStream`.
- **Structured output** — Pydantic-typed results through the plugin's Pydantic data converter.
- **MCP integration** — register MCP servers on the worker; tool calls dispatched through per-server activities.
- **Full API surface** — chat, the Files API, the Interactions API, managed agents, and Vertex AI.

## Related

- [Temporal Google GenAI plugin docs](https://github.com/temporalio/sdk-python/tree/main/temporalio/contrib/google_genai)
- [Google Gemini SDK (`google-genai`)](https://googleapis.github.io/python-genai/)
1 change: 1 addition & 0 deletions google_genai_plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Temporal Google GenAI plugin samples."""
35 changes: 35 additions & 0 deletions google_genai_plugin/agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Managed Agents

Managed agents (`client.agents`) are server-side resources you can create, fetch,
list, and delete. This sample runs the full CRUD cycle, each operation as a
Temporal activity.

> **Requires a live Gemini API key.** The Agents API talks to a real backend that
> the plugin's test server does not mock, so this sample has no automated test —
> run it against a real `GOOGLE_API_KEY`.

## What This Sample Demonstrates

- `client.agents.create(id=..., system_instruction=...)`
- `client.agents.get(id)`, `client.agents.list(page_size=...)`, `client.agents.delete(id)`

## Running the Sample

Prerequisites: install dependencies, set `GOOGLE_API_KEY`, and start a Temporal
dev server. See the [suite README](../README.md).

```bash
# Terminal 1
uv run google_genai_plugin/agents/run_worker.py

# Terminal 2
uv run google_genai_plugin/agents/run_workflow.py
```

## Files

| File | Description |
|------|-------------|
| `workflow.py` | `AgentsWorkflow` — create, get, list, delete a managed agent |
| `run_worker.py` | Registers `GoogleGenAIPlugin`, starts the worker |
| `run_workflow.py` | Executes the workflow and prints the result |
Empty file.
35 changes: 35 additions & 0 deletions google_genai_plugin/agents/run_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Worker for the agents sample."""

# @@@SNIPSTART python-google-genai-agents-worker
import asyncio
import os

from google import genai
from temporalio.client import Client
from temporalio.contrib.google_genai import GoogleGenAIPlugin
from temporalio.worker import Worker

from google_genai_plugin.agents.workflow import AgentsWorkflow


async def main() -> None:
genai_client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
plugin = GoogleGenAIPlugin(genai_client)

client = await Client.connect(
os.environ.get("TEMPORAL_ADDRESS", "localhost:7233"),
plugins=[plugin],
)

worker = Worker(
client,
task_queue="google-genai-agents",
workflows=[AgentsWorkflow],
)
print("Worker started. Ctrl+C to exit.")
await worker.run()


if __name__ == "__main__":
asyncio.run(main())
# @@@SNIPEND
27 changes: 27 additions & 0 deletions google_genai_plugin/agents/run_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Start the agents workflow."""

# @@@SNIPSTART python-google-genai-agents-run-workflow
import asyncio
import os

from temporalio.client import Client

from google_genai_plugin.agents.workflow import AgentsWorkflow


async def main() -> None:
client = await Client.connect(os.environ.get("TEMPORAL_ADDRESS", "localhost:7233"))

result = await client.execute_workflow(
AgentsWorkflow.run,
"samples-demo-agent",
id="google-genai-agents",
task_queue="google-genai-agents",
)

print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())
# @@@SNIPEND
35 changes: 35 additions & 0 deletions google_genai_plugin/agents/workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Managed agents CRUD via client.agents.

Managed agents are server-side resources you create, fetch, list, and delete.
Each operation runs as a Temporal activity.
"""

# @@@SNIPSTART python-google-genai-agents-workflow
from typing import Any

from temporalio import workflow
from temporalio.contrib.google_genai import TemporalAsyncClient


@workflow.defn
class AgentsWorkflow:
@workflow.run
async def run(self, agent_id: str) -> dict[str, Any]:
client = TemporalAsyncClient()

created = await client.agents.create(
id=agent_id,
system_instruction="You are a helpful assistant.",
)
fetched = await client.agents.get(agent_id)
listing = await client.agents.list(page_size=10)
await client.agents.delete(agent_id)

return {
"created_id": created.id,
"fetched_id": fetched.id,
"listed_ids": [a.id for a in (listing.agents or [])],
}


# @@@SNIPEND
31 changes: 31 additions & 0 deletions google_genai_plugin/chat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Chat

A multi-turn conversation using `client.chats`. The chat session carries history
across turns, and each `send_message` call runs as a durable Temporal activity.

## What This Sample Demonstrates

- Creating a chat session with `client.chats.create(...)`
- Sending multiple turns with `await chat.send_message(...)`
- Conversation state persisting across durable activity calls

## Running the Sample

Prerequisites: install dependencies, set `GOOGLE_API_KEY`, and start a Temporal
dev server. See the [suite README](../README.md).

```bash
# Terminal 1
uv run google_genai_plugin/chat/run_worker.py

# Terminal 2
uv run google_genai_plugin/chat/run_workflow.py
```

## Files

| File | Description |
|------|-------------|
| `workflow.py` | `ChatWorkflow` — sends a list of prompts over one chat session |
| `run_worker.py` | Registers `GoogleGenAIPlugin`, starts the worker |
| `run_workflow.py` | Executes the workflow and prints each turn's reply |
Empty file.
35 changes: 35 additions & 0 deletions google_genai_plugin/chat/run_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Worker for the chat sample."""

# @@@SNIPSTART python-google-genai-chat-worker
import asyncio
import os

from google import genai
from temporalio.client import Client
from temporalio.contrib.google_genai import GoogleGenAIPlugin
from temporalio.worker import Worker

from google_genai_plugin.chat.workflow import ChatWorkflow


async def main() -> None:
genai_client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
plugin = GoogleGenAIPlugin(genai_client)

client = await Client.connect(
os.environ.get("TEMPORAL_ADDRESS", "localhost:7233"),
plugins=[plugin],
)

worker = Worker(
client,
task_queue="google-genai-chat",
workflows=[ChatWorkflow],
)
print("Worker started. Ctrl+C to exit.")
await worker.run()


if __name__ == "__main__":
asyncio.run(main())
# @@@SNIPEND
31 changes: 31 additions & 0 deletions google_genai_plugin/chat/run_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Start the chat workflow with a multi-turn conversation."""

# @@@SNIPSTART python-google-genai-chat-run-workflow
import asyncio
import os

from temporalio.client import Client

from google_genai_plugin.chat.workflow import ChatWorkflow


async def main() -> None:
client = await Client.connect(os.environ.get("TEMPORAL_ADDRESS", "localhost:7233"))

result = await client.execute_workflow(
ChatWorkflow.run,
[
"My favorite color is teal. Remember that.",
"What is my favorite color?",
],
id="google-genai-chat",
task_queue="google-genai-chat",
)

for turn, reply in enumerate(result, start=1):
print(f"Turn {turn}: {reply}")


if __name__ == "__main__":
asyncio.run(main())
# @@@SNIPEND
26 changes: 26 additions & 0 deletions google_genai_plugin/chat/workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Multi-turn chat using client.chats.

A chat session keeps conversation history across turns. Each ``send_message``
call runs as a durable Temporal activity, and the SDK threads prior turns into
each request automatically.
"""

# @@@SNIPSTART python-google-genai-chat-workflow
from temporalio import workflow
from temporalio.contrib.google_genai import TemporalAsyncClient


@workflow.defn
class ChatWorkflow:
@workflow.run
async def run(self, prompts: list[str]) -> list[str]:
client = TemporalAsyncClient()
chat = client.chats.create(model="gemini-2.5-flash")
replies: list[str] = []
for prompt in prompts:
response = await chat.send_message(prompt)
replies.append(response.text or "")
return replies


# @@@SNIPEND
Loading