Skip to content

Add Google GenAI plugin samples - #319

Open
brianstrauch wants to merge 9 commits into
mainfrom
google-genai-plugin-samples
Open

Add Google GenAI plugin samples#319
brianstrauch wants to merge 9 commits into
mainfrom
google-genai-plugin-samples

Conversation

@brianstrauch

Copy link
Copy Markdown
Member

Adds 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

Sample Feature Automated test
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 runnable-only (live API)
interactions client.interactions stateful API runnable-only (live API)
agents client.agents CRUD runnable-only (live API)
vertex_ai vertexai=True configuration runnable-only (GCP creds)

Tests

tests/google_genai_plugin/ uses the plugin's GeminiTestServer to run the model-layer samples offline (no API key); the mcp test additionally registers a real echo MCP server. The files/interactions/agents/vertex_ai samples talk to backends GeminiTestServer does not mock, so they are runnable-only and documented as requiring live credentials.

Registration

  • pyproject.toml: google-genai dependency group (temporalio[google-genai,pydantic] + mcp) and wheel package
  • root README.md sample listing
  • .github/CODEOWNERS

Verification

  • uv run pytest tests/google_genai_plugin/ → 6 passed
  • mypy --check-untyped-defs --namespace-packages → no issues
  • ruff format --check + ruff check --select I → clean
  • all 10 sample modules import cleanly

Note: the google-genai extra is not yet in a released temporalio; until then install the SDK from source: uv pip install -e "../sdk-python[google-genai,pydantic]".

🤖 Generated with Claude Code

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>
@brianstrauch
brianstrauch requested review from a team as code owners June 18, 2026 18:45
brianstrauch and others added 8 commits June 18, 2026 12:07
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>
@DABH

DABH commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

PR description is stale now -

  • It says the google-genai extra "is not yet in a released temporalio; until then install the SDK from source." That's no longer true: the PR itself pins temporalio>=1.31.0 (released 2026-07-29), the lock resolves temporalio[google-genai,pydantic] from PyPI, and CI passes without any source install. Delete the note.
  • It says the suite is google_genai_plugin/, but the directory is google_genai/. The name itself is fine (matches the contrib module name, and google_adk_agents already breaks the *_plugin pattern), but the body should match reality.

),
)
recipe = response.parsed
assert isinstance(recipe, Recipe)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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=...)`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Comment thread README.md
* [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).

Copy link
Copy Markdown
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants