Skip to content

Add First-Class Gemini SDK Integration to Contrib#1378

Draft
JasonSteving99 wants to merge 1 commit intomainfrom
jason-experiment-gemini-sdk-integration
Draft

Add First-Class Gemini SDK Integration to Contrib#1378
JasonSteving99 wants to merge 1 commit intomainfrom
jason-experiment-gemini-sdk-integration

Conversation

@JasonSteving99
Copy link
Copy Markdown
Contributor

@JasonSteving99 JasonSteving99 commented Mar 18, 2026

Temporal Integration for the Google Gemini SDK

This adds a first-class integration that lets users call the Gemini SDK's AsyncClient directly from within Temporal workflows. Every API call and tool invocation becomes a durable Temporal activity — giving full crash recovery, visibility in workflow event history, and replay safety — while keeping credentials entirely on the worker side.

How it works

The integration shims three layers of the Gemini SDK so that workflows can use client.models, client.files, client.file_search_stores, client.chats, and all other SDK modules naturally:

TemporalApiClient (_temporal_api_client.py)

A BaseApiClient subclass that replaces the SDK's HTTP layer. Instead of making network calls, async_request and async_request_streamed serialize the request and dispatch it through workflow.execute_activity. The real HTTP call happens inside the activity on the worker, where the actual genai.Client with real credentials lives. Sync methods raise immediately. Per-request http_options are validated (non-serializable fields like httpx_client are rejected), and timeout is mapped to Temporal's start_to_close_timeout.

TemporalAsyncFiles / TemporalAsyncFileSearchStores (_temporal_files.py, _temporal_file_search_stores.py)

Subclasses of AsyncFiles and AsyncFileSearchStores that override upload, download, register_files, and upload_to_file_search_store to dispatch the entire operation as a Temporal activity. This avoids filesystem access (os module) and credential token refresh in the workflow sandbox. Methods like get, delete, list are inherited and work through the TemporalApiClient's async_request activity. File uploads accept str paths (resolved on the worker), os.PathLike, or io.IOBase (bytes serialized across the activity boundary).

TemporalAsyncClient (_temporal_async_client.py)

An AsyncClient subclass that wires in TemporalAsyncFiles and TemporalAsyncFileSearchStores. All other SDK modules (models, tunings, caches, batches, live, tokens, operations) are inherited unchanged since they only use async_request under the hood.

GeminiPlugin (_gemini_plugin.py)

A SimplePlugin that registers all activities, configures the Pydantic data converter, and passes google.genai through the workflow sandbox. Users pass a fully configured genai.Client — the plugin never constructs one itself. An optional extra_credentials parameter supports operations like register_files that need separate GCS credentials.

activity_as_tool (workflow.py)

Wraps any @activity.defn function so it looks like a regular async callable to Gemini's automatic function calling (AFC). When the model decides to call the tool, the SDK invokes the wrapper, which dispatches through workflow.execute_activity. Users can also pass plain workflow methods directly as tools — these run in-workflow without an activity.

Batched streaming

generate_content_stream is supported via a batched approach: the async_request_streamed activity collects all chunks from the real streaming response and returns them as a list. The workflow-side TemporalApiClient yields them back as an async generator so the SDK sees the expected interface.

Usage

# Worker side
client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
plugin = GeminiPlugin(client)

# Workflow side
@workflow.defn
class MyWorkflow:
    @workflow.run
    async def run(self, query: str) -> str:
        client = gemini_client()
        response = await client.models.generate_content(
            model="gemini-2.5-flash",
            contents=query,
            config=types.GenerateContentConfig(
                tools=[activity_as_tool(my_tool)],
            ),
        )
        return response.text

Testing

31 integration tests covering:

  • Basic generate_content and multi-chunk streaming
  • AFC tool calling (single-arg, multi-arg, workflow methods, sequential multi-tool, failure propagation)
  • Per-request http_options propagation (headers, api_version, base_url)
  • File upload via str path and io.BytesIO, file download
  • File search store upload
  • Multi-turn chat via client.chats
  • TemporalAsyncClient wiring verification
  • TemporalApiClient error paths (sync raises, low-level upload/download raises)
  • activity_as_tool validation and signature preservation
  • A full end-to-end integration test that exercises all real activity implementations (generate, stream, file upload, download, store upload, RAG query, store delete) with a mocked genai.Client — ensuring the actual activity code in _gemini_activity.py is covered, not just the workflow-side shims.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 18, 2026

CLA assistant check
All committers have signed the CLA.

Comment thread temporalio/contrib/google_gemini_sdk/_client_store.py Outdated
Comment thread temporalio/contrib/google_gemini_sdk/__init__.py Outdated
Comment thread temporalio/contrib/google_gemini_sdk/_sensitive_fields_codec.py Outdated
Comment thread temporalio/contrib/google_gemini_sdk/justfile
Comment thread temporalio/contrib/google_gemini_sdk/first_class_example/start_workflow.py Outdated
Comment thread temporalio/contrib/google_gemini_sdk/_gemini_plugin.py Outdated
Comment thread temporalio/contrib/google_gemini_sdk/_gemini_plugin.py Outdated
Comment thread temporalio/contrib/google_gemini_sdk/_gemini_plugin.py Outdated
@JasonSteving99 JasonSteving99 force-pushed the jason-experiment-gemini-sdk-integration branch from 500ab1e to b770922 Compare April 28, 2026 22:06
@semgrep-managed-scans
Copy link
Copy Markdown

Semgrep found 1 ssc-80bc2470-4c90-4cf6-811e-5a265b8f01d5 finding:

Risk: Affected versions of litellm are vulnerable to Exposure of Sensitive Information to an Unauthorized Actor / Use of Password Hash With Insufficient Computational Effort / Use of a Broken or Risky Cryptographic Algorithm. LiteLLM exposes password hashes via authenticated API endpoints and its login flow accepts a stored SHA-256 hash as a valid password, allowing an authenticated user to steal another user's hash and log in as that user, resulting in full authentication bypass and privilege escalation.

Fix: Upgrade this library to at least version 1.83.0 at sdk-python/uv.lock:2102.

Reference(s): GHSA-69x8-hrgq-fjj8

Semgrep found 1 ssc-9ebb5c4b-d829-4496-b42b-76424d99a7b1 finding:

Risk: Affected versions of litellm are vulnerable to Incorrect Authorization. This vulnerability stems from inadequate enforcement of access control policies, allowing authenticated users to perform actions beyond their intended privilege level and potentially alter sensitive system configurations or access restricted resources.

Fix: Upgrade this library to at least version 1.83.0 at sdk-python/uv.lock:2102.

Reference(s): GHSA-53mr-6c8q-9789, CVE-2026-35029

Semgrep found 1 ssc-4464d88f-a369-4669-9a83-d26a12248ef5 finding:

Risk: Affected versions of google-adk are vulnerable to Missing Authentication for Critical Function. Google Agent Development Kit (ADK) contains a missing authentication flaw that can let an unauthenticated remote attacker reach code-injection paths and execute arbitrary code on the server running the ADK instance, including ADK Web deployments on Python, Cloud Run, and GKE.

Fix: Upgrade this library to at least version 1.28.1 at sdk-python/uv.lock:982.

Reference(s): GHSA-rg7c-g689-fr3x, CVE-2026-4810

@JasonSteving99 JasonSteving99 force-pushed the jason-experiment-gemini-sdk-integration branch from 19b495c to 6763054 Compare April 29, 2026 00:34
# Temporal Integration for the Google Gemini SDK

This adds a first-class integration that lets users call the Gemini SDK's `AsyncClient` directly from within Temporal workflows. Every API call and tool invocation becomes a durable Temporal activity — giving full crash recovery, visibility in workflow event history, and replay safety — while keeping credentials entirely on the worker side.

## How it works

The integration shims three layers of the Gemini SDK so that workflows can use `client.models`, `client.files`, `client.file_search_stores`, `client.chats`, and all other SDK modules naturally:

### `TemporalApiClient` (`_temporal_api_client.py`)

A `BaseApiClient` subclass that replaces the SDK's HTTP layer. Instead of making network calls, `async_request` and `async_request_streamed` serialize the request and dispatch it through `workflow.execute_activity`. The real HTTP call happens inside the activity on the worker, where the actual `genai.Client` with real credentials lives. Sync methods raise immediately. Per-request `http_options` are validated (non-serializable fields like `httpx_client` are rejected), and `timeout` is mapped to Temporal's `start_to_close_timeout`.

### `TemporalAsyncFiles` / `TemporalAsyncFileSearchStores` (`_temporal_files.py`, `_temporal_file_search_stores.py`)

Subclasses of `AsyncFiles` and `AsyncFileSearchStores` that override `upload`, `download`, `register_files`, and `upload_to_file_search_store` to dispatch the entire operation as a Temporal activity. This avoids filesystem access (`os` module) and credential token refresh in the workflow sandbox. Methods like `get`, `delete`, `list` are inherited and work through the `TemporalApiClient`'s `async_request` activity. File uploads accept `str` paths (resolved on the worker), `os.PathLike`, or `io.IOBase` (bytes serialized across the activity boundary).

### `TemporalAsyncClient` (`_temporal_async_client.py`)

An `AsyncClient` subclass that wires in `TemporalAsyncFiles` and `TemporalAsyncFileSearchStores`. All other SDK modules (`models`, `tunings`, `caches`, `batches`, `live`, `tokens`, `operations`) are inherited unchanged since they only use `async_request` under the hood.

### `GeminiPlugin` (`_gemini_plugin.py`)

A `SimplePlugin` that registers all activities, configures the Pydantic data converter, and passes `google.genai` through the workflow sandbox. Users pass a fully configured `genai.Client` — the plugin never constructs one itself. An optional `extra_credentials` parameter supports operations like `register_files` that need separate GCS credentials.

### `activity_as_tool` (`workflow.py`)

Wraps any `@activity.defn` function so it looks like a regular async callable to Gemini's automatic function calling (AFC). When the model decides to call the tool, the SDK invokes the wrapper, which dispatches through `workflow.execute_activity`. Users can also pass plain workflow methods directly as tools — these run in-workflow without an activity.

### Batched streaming

`generate_content_stream` is supported via a batched approach: the `async_request_streamed` activity collects all chunks from the real streaming response and returns them as a list. The workflow-side `TemporalApiClient` yields them back as an async generator so the SDK sees the expected interface.

## Usage

```python
# Worker side
client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
plugin = GeminiPlugin(client)

# Workflow side
@workflow.defn
class MyWorkflow:
    @workflow.run
    async def run(self, query: str) -> str:
        client = gemini_client()
        response = await client.models.generate_content(
            model="gemini-2.5-flash",
            contents=query,
            config=types.GenerateContentConfig(
                tools=[activity_as_tool(my_tool)],
            ),
        )
        return response.text
```

## Testing

31 integration tests covering:
- Basic `generate_content` and multi-chunk streaming
- AFC tool calling (single-arg, multi-arg, workflow methods, sequential multi-tool, failure propagation)
- Per-request `http_options` propagation (headers, api_version, base_url)
- File upload via str path and `io.BytesIO`, file download
- File search store upload
- Multi-turn chat via `client.chats`
- `TemporalAsyncClient` wiring verification
- `TemporalApiClient` error paths (sync raises, low-level upload/download raises)
- `activity_as_tool` validation and signature preservation
- A full end-to-end integration test that exercises all real activity implementations (generate, stream, file upload, download, store upload, RAG query, store delete) with a mocked `genai.Client` — ensuring the actual activity code in `_gemini_activity.py` is covered, not just the workflow-side shims.
@JasonSteving99 JasonSteving99 force-pushed the jason-experiment-gemini-sdk-integration branch from 6763054 to bf58318 Compare April 29, 2026 00:42
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.

4 participants