A Rust client library for Google's Generative AI (Gemini) API, built on the Interactions API (wire revision 2026-05-20) — plus an optional native client for Google's Antigravity local agent runtime.
use genai_rs::Client;
#[tokio::main]
async fn main() -> Result<(), genai_rs::GenaiError> {
let client = Client::new(std::env::var("GEMINI_API_KEY").unwrap());
let response = client
.interaction()
.with_model("gemini-3-flash-preview")
.with_text("Explain Rust's ownership model in one sentence.")
.create()
.await?;
println!("{}", response.as_text().unwrap_or_default());
Ok(())
}| Feature | Description |
|---|---|
| Steps Model | Interactions API revision 2026-05-20: responses and history as typed Steps, per-step usage, typed citation annotations |
| Streaming | Real-time step deltas with resume capability; function-call arguments stream incrementally |
| Stateful Conversations | Multi-turn context via previous_interaction_id, or stateless step replay with with_history() |
| Function Calling | Auto-discovery with #[tool] macro or manual control |
| Typed Response Formats | JSON schema, audio, image, and video output via with_response_format() |
| Thinking Mode | Model reasoning with configurable depth, thought summaries and signatures |
| Background + Webhooks | Background execution, per-request webhook routing, full /webhooks resource (CRUD, ping, secret rotation) |
| Environments & Agents | environment request field and the /agents resource client |
| Multi-Speaker TTS | speech_config list form for multi-voice dialogue |
| Wire Inspection | Structured WireEvent stream via the WireInspector trait; LOUD_WIRE=1 and tracing built-ins |
| Local Agents | antigravity feature: native client for the Antigravity harness (workspaces, policies, subagents) |
| Tool | Method | Use Case |
|---|---|---|
| Google Search | with_google_search() |
Real-time web grounding |
| Code Execution | with_code_execution() |
Python sandbox |
| URL Context | with_url_context() |
Web page analysis |
| Google Maps | with_google_maps() |
Places and geographic grounding |
| File Search | add_tool(FileSearchConfig::new(stores)) |
Semantic retrieval from vector stores |
| Computer Use | add_tool(ComputerUseConfig::new()) |
Browser/desktop automation (allowlisted keys) |
| MCP Servers | add_tool(McpServerConfig::new(name, url)) |
Model Context Protocol tools |
| Retrieval | add_tool(RetrievalConfig::new()...) |
Vertex AI Search / RAG stores (Vertex-only, see below) |
| Input | Output |
|---|---|
| Images, Audio, Video, PDFs | Text, Images, Audio (TTS, incl. multi-speaker), Video config |
The crate models the full 2026-05-20 Interactions API surface, with wire
shapes verified live against the Gemini API. A few knobs are modeled but
gated to Vertex AI and rejected by the Gemini API today: the Retrieval
tool, DeepResearchConfig::with_bigquery_tool(), and video gcs_uri
delivery. Details and per-feature live-verification notes are in
docs/INTERACTIONS_API_GAP.md.
[dependencies]
genai-rs = "0.8"
tokio = { version = "1.0", features = ["full"] }
# Optional
genai-rs-macros = "0.8" # For #[tool] macro
futures-util = "0.3" # For streamingRequirements: Rust 1.88+ (edition 2024), Gemini API key
TLS note: the crate uses rustls and (as of reqwest 0.13) verifies certificates against the OS trust store. Minimal containers (scratch/distroless) need a CA bundle installed.
Runnable examples covering all features:
export GEMINI_API_KEY=your-key
cargo run --example simple_interactionQuick Reference:
| I want to... | Example |
|---|---|
| Make my first API call | simple_interaction |
| Stream responses | streaming |
| Use function calling | auto_function_calling |
| Multi-turn conversations | stateful_interaction |
| Generate images | image_generation |
| Text to speech | text_to_speech |
| Get structured JSON | structured_output |
| Route results to webhooks | webhooks_and_background |
| Ground answers in my documents | retrieval_grounding |
| Run a local agent on my repo | repo_auditor (requires --features antigravity) |
| Implement retry logic | retry_with_backoff |
See Examples Index for the complete categorized list.
use futures_util::StreamExt;
let mut stream = client.interaction()
.with_text("Write a haiku about Rust.")
.create_stream();
while let Some(Ok(event)) = stream.next().await {
// delta_text() extracts text from StreamChunk::StepDelta events
if let Some(text) = event.chunk.delta_text() {
print!("{}", text);
}
}use genai_rs_macros::tool;
#[tool(location(description = "City name, e.g. Tokyo"))]
fn get_weather(location: String) -> String {
format!(r#"{{"temp": 72, "conditions": "sunny"}}"#)
}
let result = client.interaction()
.with_text("What's the weather in Tokyo?")
.add_function(GetWeatherCallable.declaration())
.create_with_auto_functions()
.await?;// First turn (enable storage for multi-turn)
let r1 = client.interaction()
.with_system_instruction("You are a helpful assistant.")
.with_text("My name is Alice.")
.with_store_enabled()
.create().await?;
// Continue conversation (r1.id is Option<String>)
let r2 = client.interaction()
.with_previous_interaction(r1.id.as_ref().expect("stored interactions have IDs"))
.with_text("What's my name?") // Remembers: Alice
.create().await?;For stateless deployments, replay history as steps — output_steps()
preserves the signature fields the API requires on replay:
use genai_rs::Step;
let mut history = vec![Step::user_text("My name is Alice.")];
history.extend(r1.output_steps()); // replay model output verbatim
let r2 = client.interaction()
.with_history(history)
.with_text("What's my name?")
.create().await?;use genai_rs::ThinkingLevel;
let response = client.interaction()
.with_thinking_level(ThinkingLevel::High)
.with_text("What's 15% of 847?")
.create().await?;
// Check if model used reasoning (thoughts contain cryptographic signatures)
if response.has_thoughts() {
println!("Model used {} thought blocks", response.thought_signatures().count());
}use genai_rs::{Webhook, WebhookConfig, WebhookEvent};
// Register a managed webhook once...
let webhook = client.create_webhook(&Webhook::new(
"https://example.com/hooks/genai",
vec![WebhookEvent::InteractionCompleted, WebhookEvent::InteractionFailed],
)).await?;
// ...then route long-running interactions to it (background required)
let response = client.interaction()
.with_agent("deep-research-preview-04-2026")
.with_text("Research the history of the Rust programming language.")
.with_background(true)
.with_webhook_config(WebhookConfig::new().with_uris(vec![webhook.uri.clone()]))
.create().await?;use genai_rs::InteractionRequest;
// Build request without executing (Clone + Serialize)
let request: InteractionRequest = client.interaction()
.with_model("gemini-3-flash-preview")
.with_text("Hello!")
.build()?;
// Execute separately - enables retry loops
let response = client.execute(request.clone()).await?;
// On error, check if retryable: error.is_retryable()See retry_with_backoff for a complete retry example using the backon crate.
The antigravity feature (off by default) adds a native Rust client for
Google's Antigravity localharness agent runtime — the same harness behind
the hosted antigravity-preview-05-2026 agent, running locally with your
workspaces, your tools, and Rust-side policy enforcement. The harness binary
executes the agent loop (shell, file edits, search, MCP, subagents); this
crate speaks its protocol directly, with no Python in the loop:
use genai_rs::antigravity::{AntigravityAgent, policy};
let mut agent = AntigravityAgent::builder()
.with_api_key(std::env::var("GEMINI_API_KEY")?)
.with_model("gemini-3-flash-preview")
.add_workspace("/path/to/repo")
.add_policy(policy::deny_all()) // policies evaluated in Rust,
.add_policy(policy::allow("view_file")) // before every tool dispatch
.spawn()
.await?;
let response = agent.chat("Summarize the layout of this repo.").await?;
println!("{}", response.text());
agent.shutdown().await?;The same #[tool] functions work in both modes, and LOUD_WIRE=1 covers
harness sessions too. Setup (pip install google-antigravity==0.1.5),
capabilities, policies/hooks, MCP servers, subagents, triggers, and session
resume are covered in docs/ANTIGRAVITY.md; see
repo_auditor for a complete
agentic code-review application.
| Guide | Description |
|---|---|
| Examples Index | All examples, categorized |
| Function Calling | #[tool] macro, ToolService, manual execution |
| Multi-Turn Patterns | Stateful/stateless, signature replay, inheritance rules |
| Streaming API | Stream types, resume, auto-functions |
| Multimodal | Images, audio, video, PDFs |
| Output Modalities | Image generation, text-to-speech |
| Thinking Mode | Reasoning depth, thought signatures |
| Built-in Tools | Google Search, code execution, URL context, Maps |
| Configuration | Client options, generation config |
| Conversation Patterns | Multi-turn, context management |
| Antigravity | Local agent harness: setup, policies, subagents |
| Agents & Background | Hosted agents, long-running tasks, polling |
| Document | Description |
|---|---|
| Builder API | Method naming conventions, validation |
| Error Handling | Error types, recovery patterns |
| Reliability Patterns | Retries, timeouts, resilience |
| Logging Strategy | Log levels, LOUD_WIRE debugging |
| Enum Wire Formats | Verified wire formats, Unknown variants |
| API Gap Analysis | Coverage tracker, Vertex-only findings |
| Testing Guide | Test strategies, assertions |
| API Reference | Generated API documentation |
| Resource | Description |
|---|---|
| Interactions API Reference | Official API specification |
| Interactions API Guide | Usage patterns |
| Function Calling Guide | Google's function calling docs |
# Wire-level request/response logging (colored, secrets redacted)
LOUD_WIRE=1 cargo run --example simple_interaction
# Wire events via tracing
RUST_LOG=genai_rs::wire=debug cargo run --example simple_interaction
# Library debug logs
RUST_LOG=genai_rs=debug cargo run --example simple_interactionFor programmatic capture (snapshot tests, bug reports), implement the
WireInspector trait and register it with
ClientBuilder::add_wire_inspector() — inspectors receive structured
WireEvents (requests, response bodies, SSE frames, harness WebSocket
traffic) with per-client correlation ids and secret redaction applied.
See Logging Strategy for details.
This library follows the Evergreen philosophy: unknown API types deserialize into Unknown variants instead of failing. Always include wildcard arms:
match step {
Step::ModelOutput { content, .. } => { /* text, images, ... */ }
Step::FunctionCall { name, .. } => println!("call: {name}"),
_ => {} // Handles future variants gracefully
}make test # Unit tests (uses cargo-nextest)
make test-all # Full integration suite (requires GEMINI_API_KEY)genai-rs/ # Main crate: Client, InteractionBuilder, types
genai-rs-macros/ # Procedural macro for #[tool]
docs/ # Comprehensive guides
examples/ # Runnable examples
Contributions welcome! Please read:
- CLAUDE.md - Development guidelines and architecture
- CHANGELOG.md - Version history and migration guides
- SECURITY.md - Security policy and reporting
Common issues and solutions are documented in TROUBLESHOOTING.md.
Quick fixes:
- "API key not valid" - Check
GEMINI_API_KEYis set - "Model not found" - Use
gemini-3-flash-preview - Functions not executing - Use
create_with_auto_functions() - TLS errors in minimal containers - Install a CA bundle (OS trust store is used since reqwest 0.13)