This tutorial walks you through building a durable AI agent that uses the Gemini API for reasoning and Temporal for durability. It uses Temporal's built-in Gemini SDK integration.
The agent can call tools, like looking up weather alerts or geolocating an IP address, and will loop until it has enough information to respond.
What makes this different from a typical agent demo is durability. Every LLM call and every tool invocation is persisted by Temporal. If the process crashes, the network drops, or an API times out, Temporal automatically retries and resumes from the last completed step. No conversation history is lost, and no tool calls are incorrectly repeated.
Architecture
The architecture consists of three parts:
- Workflow: A single
generate_contentcall. The Gemini SDK's automatic function calling (AFC) loop runs inside the Workflow, and Temporal makes every step of it durable. - Activities: Individual units of work that Temporal makes durable. The Gemini API calls become Activities automatically.
- Worker: The process that executes the Workflows and Activities, and the only place your API key lives.
In this example, you will place all three of these pieces in a single file
(durable_agent_worker.py). In a real-world implementation, you would separate
them to allow for various deployment and scalability advantages. You will supply
prompts to the agent with the Temporal CLI, so there is no client code to write.
Prerequisites
To complete this guide, you'll need:
- A Gemini API key. You can create one for free in Google AI Studio.
- Python version 3.10 or later.
- uv for dependency management.
- The Temporal CLI for running a local development server and starting Workflows.
Setup
Before you begin, ensure you have a Temporal development server running locally:
temporal server start-devNext, create a project and install the required dependencies:
uv init durable-gemini-agentcd durable-gemini-agentuv add "temporalio[google-genai]" httpx python-dotenv
uv creates and manages the virtual environment for you, so every Python command
later in this tutorial runs through uv run.
Create a .env file in your project directory with your Gemini API key. You
can get an API key from
Google AI Studio.
echo "GOOGLE_API_KEY=your-api-key-here" > .envImplementation
The rest of this tutorial walks through durable_agent_worker.py from top to
bottom, building up the agent piece by piece. Create the file and follow along.
Imports and sandbox setup
Start with the imports that must be defined up-front. The
workflow.unsafe.imports_passed_through() block tells Temporal's Workflow
sandbox to let httpx pass through without restriction. Importing httpx
executes class _CookieCompatRequest(urllib.request.Request), and the sandbox
blocks subclassing that stdlib class.
Your tools use httpx, and activity_as_tool() needs the Workflow to import
those tool functions so Gemini can derive their schemas from the signatures. So
httpx reaches the sandbox no matter how you split the files—moving the tools
into their own module doesn't avoid it.
from temporalio import workflow
with workflow.unsafe.imports_passed_through():
import httpx
You don't need to list google.genai here. The Temporal plugin you configure
later adds it—along with pydantic_core and annotated_types—to the sandbox
passthrough set for you.
System instructions
Next, define the agent's personality. The system instructions tell the model how to behave. This agent is instructed to respond in haikus when no tools are needed.
SYSTEM_INSTRUCTIONS = """
You are a helpful agent that can use tools to help the user.
You will be given an input from the user and a list of tools to use.
You may or may not need to use the tools to satisfy the user ask.
If no tools are needed, respond in haikus.
"""
Tool definitions
Now define the tools the agent can use. Each tool is an ordinary Temporal
Activity: an async function decorated with @activity.defn, with type-annotated
parameters and a descriptive docstring. Gemini builds the function declaration
from that signature and docstring, so document each parameter in the Args
section.
import json
from temporalio import activity
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
@activity.defn
async def get_weather_alerts(state: str) -> str:
"""Get weather alerts for a US state.
Args:
state: Two-letter US state code (e.g. CA, NY)
"""
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, timeout=5.0)
response.raise_for_status()
return json.dumps(response.json())
Next, define tools for IP address geolocation:
@activity.defn
async def get_ip_address() -> str:
"""Get the public IP address of the current machine."""
async with httpx.AsyncClient() as client:
response = await client.get("https://icanhazip.com")
response.raise_for_status()
return response.text.strip()
@activity.defn
async def get_location_info(ipaddress: str) -> str:
"""Get the location information for an IP address including city, state, and country.
Args:
ipaddress: An IP address to look up
"""
async with httpx.AsyncClient() as client:
response = await client.get(f"http://ip-api.com/json/{ipaddress}"