Add Google GenAI plugin samples - #319
Conversation
Add a google_genai_plugin/ sample suite for temporalio.contrib.google_genai, mirroring the strands_plugin/ layout (one feature per sub-directory, each with workflow.py / run_worker.py / run_workflow.py / README.md). Samples cover every major plugin feature: - hello_world: generate_content - tools: automatic function calling (activity_as_tool + plain workflow-method tool) - streaming: generate_content_stream + streaming_topic/WorkflowStream - chat: multi-turn client.chats - structured_output: response_schema + Pydantic - mcp: TemporalMcpClientSession with a local echo MCP server - files: client.files.upload (live API) - interactions: client.interactions stateful API (live API) - agents: client.agents CRUD (live API) - vertex_ai: vertexai=True configuration (GCP credentials) Tests under tests/google_genai_plugin/ use the plugin's GeminiTestServer to run the model-layer samples offline; the mcp test additionally registers a real echo MCP server. files/interactions/agents/vertex_ai are runnable-only (require live credentials) and documented as such. Register the suite in pyproject.toml (google-genai dependency group + wheel package), the root README, and CODEOWNERS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap the workflow.py, run_worker.py, and run_workflow.py bodies of each sample in @@@SNIPSTART/@@@SNIPEND markers (python-google-genai-<sample>-<part>) so the code can be embedded in docs, matching the strands_plugin convention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve conflicts in .github/CODEOWNERS, README.md, and pyproject.toml by keeping both the google-genai-plugin and incoming AI SDK sample entries in alphabetical order, and regenerate uv.lock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The stream publishes Pydantic GenerateContentResponse chunks, so the consumer needs the Pydantic data converter to decode them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump every temporalio requirement to >=1.31.0. The 1.31 google-adk extra requires google-adk 2.x, so widen that pin too, and relax the interactions sample's typing since create/get now return a union with the streaming response type. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…o google-genai-plugin-samples # Conflicts: # .github/CODEOWNERS # README.md # pyproject.toml # uv.lock
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The google-genai and strands-agents extras of temporalio shipped in 1.31, which pyproject.toml already requires, so `uv sync --group ...` is enough. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
PR description is stale now -
|
| ), | ||
| ) | ||
| recipe = response.parsed | ||
| assert isinstance(recipe, Recipe) |
There was a problem hiding this comment.
If Gemini returns something response.parsed can't produce (None on malformed JSON), the AssertionError is a workflow task failure, so the workflow retries the task indefinitely instead of failing visibly. Raise temporalio.exceptions.ApplicationError (arguably non-retryable) with a clear message instead
| ) | ||
| return response.text or "" | ||
|
|
||
| async def recommend_activity(self, weather: str) -> str: |
There was a problem hiding this comment.
Activity is overloaded term :) Say "pastime" or something instead of "activity" for function name? :)
|
|
||
| # Subscribe to the "gemini" topic and print chunks as the model produces them. | ||
| stream = WorkflowStreamClient.create(client, workflow_id) | ||
| async for item in stream.subscribe( |
There was a problem hiding this comment.
We only exit the subscribe loop when a chunk carries finish_reason - if generation errors mid-stream the consumer hangs; and the workflow itself waits forever on the finish signal if the subscriber dies before signaling. For a sample this is tolerable, but a workflow.wait_condition(..., timeout=...) (or at least a README sentence or comment about it) would model the production-grade pattern
|
|
||
| ## What This Sample Demonstrates | ||
|
|
||
| - `client.agents.create(id=..., system_instruction=...)` |
There was a problem hiding this comment.
client.agents.create(id=fixed_id) is non-idempotent: if the create activity succeeds but the completion is lost, the retry will hit "already exists"; and if get/list raises, the agent leaks because delete never runs. A try/finally around the delete - or a short README note on idempotent creates under retries - would teach the right habit in a suite whose whole pitch is durable execution.
| index["i"] += 1 | ||
| return SdkHttpResponse(headers={"content-type": "application/json"}, body=body) | ||
|
|
||
| genai_client._api_client.async_request = fake_async_request # type: ignore[assignment] |
There was a problem hiding this comment.
Worth filing an issue asking for GeminiTestServer.plugin(mcp_servers=...) so this helper using a private internal API can be deleted
| @@ -0,0 +1,27 @@ | |||
| """Start the agents workflow.""" | |||
|
|
|||
| # @@@SNIPSTART python-google-genai-agents-run-workflow | |||
There was a problem hiding this comment.
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.
| * [external_storage_redis](external_storage_redis) - Redis driver for external storage | ||
| * [gevent_async](gevent_async) - Combine gevent and Temporal. | ||
| * [google_adk_agents](google_adk_agents) - Run Google ADK agents as durable Temporal workflows (model calls, tools, multi-agent, MCP, streaming). | ||
| * [google_genai](google_genai) - Run the Google Gemini SDK inside durable Temporal workflows (generate_content, tools/AFC, streaming, chat, structured output, MCP, files, interactions, agents, Vertex AI). |
There was a problem hiding this comment.
Trim this down. They can click to see the genai samples readme to see the full list of 10 things enumerated.
Adds a
google_genai_plugin/sample suite fortemporalio.contrib.google_genai, mirroring thestrands_plugin/layout (one feature per sub-directory, each withworkflow.py/run_worker.py/run_workflow.py/README.md).Samples
hello_worldgenerate_contenttoolsactivity_as_tool+ plain workflow-method tool)streaminggenerate_content_stream+streaming_topic/WorkflowStreamchatclient.chatsstructured_outputresponse_schema+ PydanticmcpTemporalMcpClientSessionwith a local echo MCP serverfilesclient.files.uploadinteractionsclient.interactionsstateful APIagentsclient.agentsCRUDvertex_aivertexai=TrueconfigurationTests
tests/google_genai_plugin/uses the plugin'sGeminiTestServerto run the model-layer samples offline (no API key); themcptest additionally registers a real echo MCP server. Thefiles/interactions/agents/vertex_aisamples talk to backendsGeminiTestServerdoes not mock, so they are runnable-only and documented as requiring live credentials.Registration
pyproject.toml:google-genaidependency group (temporalio[google-genai,pydantic]+mcp) and wheel packageREADME.mdsample listing.github/CODEOWNERSVerification
uv run pytest tests/google_genai_plugin/→ 6 passedmypy --check-untyped-defs --namespace-packages→ no issuesruff format --check+ruff check --select I→ clean🤖 Generated with Claude Code