-
Notifications
You must be signed in to change notification settings - Fork 116
Add Google GenAI plugin samples #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1676724
a7794d0
f02fca3
61ae58e
8fef9db
60939a2
49b6af1
771edf2
d703df4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # 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 | ||
| ``` | ||
|
|
||
| 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/<sample>/run_worker.py | ||
|
|
||
| # Terminal 2: start the Workflow | ||
| uv run google_genai/<sample>/run_workflow.py | ||
| ``` | ||
|
|
||
| For example, to run the tools sample: | ||
|
|
||
| ```bash | ||
| # Terminal 1 | ||
| uv run google_genai/tools/run_worker.py | ||
|
|
||
| # Terminal 2 | ||
| uv run google_genai/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/) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Temporal Google GenAI plugin samples.""" |
| 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=...)` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| - `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/agents/run_worker.py | ||
|
|
||
| # Terminal 2 | ||
| uv run google_genai/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 | | ||
| 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.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 |
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The snip start and end markers wrap entire files right now. Our snipsync convention typically prefers the smallest meaningful unit for workflow/activity snippets so docs readers don't wade through imports. Full-file regions are defensible for the worker/starter scripts (they're meant to be runnable), but consider tightening the workflow-class markers? Just a nit. |
||
| import asyncio | ||
| import os | ||
|
|
||
| from temporalio.client import Client | ||
|
|
||
| from google_genai.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 | ||
| 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 |
| 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/chat/run_worker.py | ||
|
|
||
| # Terminal 2 | ||
| uv run google_genai/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 | |
| 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.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 |
| 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.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 |
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trim this down. They can click to see the genai samples readme to see the full list of 10 things enumerated.