# adk-gemini

Rust client library for Google's Gemini API — content generation, streaming, function calling, embeddings, image/speech generation, batch processing, caching, and Vertex AI.

[![Crates.io](https://img.shields.io/crates/v/adk-gemini.svg)](https://crates.io/crates/adk-gemini)
[![Documentation](https://docs.rs/adk-gemini/badge.svg)](https://docs.rs/adk-gemini)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Overview

`adk-gemini` is a comprehensive Rust client for the Google Gemini API, maintained as part of the [ADK-Rust](https://github.com/zavora-ai/adk-rust) project. It provides full coverage of the Gemini API surface:

- Content generation (text, images, audio)
- Real-time streaming responses
- Function calling and tool integration (including Google Search and URL Context)
- Thinking mode (Gemini 2.5 / Gemini 3)
- Thought signatures for multi-turn thinking context
- Text embeddings
- Image generation and editing
- Text-to-speech (single and multi-speaker)
- Batch processing
- Content caching
- File upload and management
- Structured JSON output
- Grounding with Google Search
- Vertex AI (Google Cloud) support with ADC, service accounts, and WIF
- Multimodal input (images, video, PDF, audio)

## Installation

```toml
[dependencies]
adk-gemini = "0.6.0"
```

Or through `adk-model`:

```toml
[dependencies]
adk-model = { version = "0.6.0", features = ["gemini"] }
```

## Quick Start

```rust
use adk_gemini::Gemini;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Gemini::new(std::env::var("GEMINI_API_KEY")?)?;

    let response = client
        .generate_content()
        .with_user_message("Hello, Gemini!")
        .execute()
        .await?;

    println!("{}", response.text());
    Ok(())
}
```

## Client Constructors

| Constructor | Description |
|-------------|-------------|
| `Gemini::new(api_key)` | Default model (gemini-2.5-flash) via v1beta |
| `Gemini::pro(api_key)` | Gemini 2.5 Pro | Gemini 3 Pro
| `Gemini::with_model(api_key, model)` | Specific model |
| `Gemini::with_v1(api_key)` | Stable v1 API |
| `Gemini::with_model_v1(api_key, model)` | Specific model on v1 |
| `Gemini::with_base_url(api_key, url)` | Custom endpoint |
| `Gemini::with_google_cloud(api_key, project, location)` | Vertex AI |
| `Gemini::with_google_cloud_adc(project, location)` | Vertex AI with ADC |
| `Gemini::with_service_account_json(json)` | Service account (auto-detects project) |
| `Gemini::with_google_cloud_wif_json(json, project, location, model)` | Workload Identity Federation |

## Examples

### Streaming

```rust
use adk_gemini::Gemini;
use futures_util::TryStreamExt;

let client = Gemini::new(api_key)?;

let mut stream = client
    .generate_content()
    .with_system_prompt("You are a helpful assistant.")
    .with_user_message("Write a short story about a robot.")
    .execute_stream()
    .await?;

while let Some(chunk) = stream.try_next().await? {
    print!("{}", chunk.text());
}
```

### Function Calling

```rust
use adk_gemini::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct WeatherRequest {
    location: String,
    unit: Option<String>,
}

let get_weather = FunctionDeclaration::new(
    "get_weather",
    "Get the current weather for a location",
    None,
).with_parameters::<WeatherRequest>();

let response = client
    .generate_content()
    .with_user_message("What's the weather in Tokyo?")
    .with_function(get_weather)
    .with_function_calling_mode(FunctionCallingMode::Any)
    .execute()
    .await?;

if let Some(call) = response.function_calls().first() {
    println!("Call: {} with args: {}", call.name, call.args);
}
```

### Google Search Grounding

```rust
use adk_gemini::{Gemini, Tool};

let response = client
    .generate_content()
    .with_user_message("What is the current Google stock price?")
    .with_tool(Tool::google_search())
    .execute()
    .await?;

println!("{}", response.text());

// Access grounding metadata
if let Some(grounding) = response.candidates.first()
    .and_then(|c| c.grounding_metadata.as_ref())
{
    if let Some(chunks) = &grounding.grounding_chunks {
        for chunk in chunks {
            if let Some(web) = &chunk.web {
                println!("Source: {} - {}", web.title, web.uri);
            }
        }
    }
}
```

### URL Context

```rust
use adk_gemini::{Gemini, Tool};

let response = client
    .generate_content()
    .with_user_message("Summarize this page: https://docs.rs/tokio/latest/tokio/")
    .with_tool(Tool::url_context())
    .execute()
    .await?;
```

### Thinking Mode (Gemini 2.5 / Gemini 3)

#### Gemini 3: Level-Based Thinking

```rust
use adk_gemini::{ThinkingLevel, ThinkingConfig};

let response = client
    .generate_content()
    .with_user_message("Solve: what is the integral of x^2 * e^x dx?")
    .with_thinking_level(ThinkingLevel::High)
    .with_thoughts_included(true)
    .execute()
    .await?;
```

Available levels: `Minimal`, `Low`, `Medium`, `High`.

#### Gemini 2.5: Budget-Based Thinking

```rust
let response = client
    .generate_content()
    .with_user_message("Solve: what is the integral of x^2 * e^x dx?")
    .with_thinking_budget(2048)
    .with_thoughts_included(true)
    .execute()
    .await?;

// Access the model's reasoning
for thought in response.thoughts() {
    println!("Thought: {}", thought);
}
println!("Answer: {}", response.text());
```

### Thought Signatures (Multi-Turn Thinking Context)

When using thinking mode with function calling, Gemini 2.5+ returns `thoughtSignature` values that preserve the model's reasoning context across conversation turns. Include these signatures in subsequent requests to maintain coherent multi-turn thinking.

```rust
let response = client
    .generate_content()
    .with_user_message("What's the weather in Tokyo?")
    .with_tool(Tool::new(weather_function))
    .with_thinking_config(
        ThinkingConfig::new().with_dynamic_thinking().with_thoughts_included(true),
    )
    .execute()
    .await?;

// Extract function calls with their thought signatures
for (call, signature) in response.function_calls_with_thoughts() {
    println!("Function: {} (signature: {:?})", call.name, signature.is_some());

    // When building the next turn, include the signature at the Part level
    let model_content = Content {
        parts: Some(vec![Part::FunctionCall {
            function_call: call.clone(),
            thought_signature: signature.cloned(),
        }]),
        role: Some(Role::Model),
    };
    // Add model_content to the next request's conversation history
}
```

> **Note**: `thoughtSignature` is serialized at both the Part level and inside the `FunctionCall` object when present. Gemini 3.x models require the signature in both locations for multi-turn function calling. The field uses `skip_serializing_if = "Option::is_none"`, so it is omitted entirely when `None` — backward compatible with older models that don't return thought signatures.

### Structured JSON Output

```rust
use serde_json::json;

let schema = json!({
    "type": "object",
    "properties": {
        "name": { "type": "string" },
        "year_created": { "type": "integer" },
        "key_features": { "type": "array", "items": { "type": "string" } }
    },
    "required": ["name", "year_created", "key_features"]
});

let response = client
    .generate_content()
    .with_user_message("Tell me about the Rust programming language.")
    .with_response_mime_type("application/json")
    .with_response_schema(schema)
    .execute()
    .await?;

let parsed: serde_json::Value = serde_json::from_str(&response.text())?;
```

### Text Embeddings

```rust
use adk_gemini::{Gemini, Model, TaskType};

let client = Gemini::with_model(api_key, Model::GeminiEmbedding001)?;

let response = client
    .embed_content()
    .with_text("Hello, world!")
    .with_task_type(TaskType::RetrievalDocument)
    .execute()
    .await?;

println!("Embedding dimensions: {}", response.embedding.values.len());
```

### Image Generation

```rust
let client = Gemini::with_model(
    api_key,
    "models/gemini-2.5-flash-image-preview".to_string(),
)?;

let response = client
    .generate_content()
    .with_user_message("A photorealistic image of a mountain lake at sunset")
    .execute()
    .await?;

// Response contains inline image data (base64-encoded)
for candidate in &response.candidates {
    if let Some(parts) = &candidate.content.parts {
        for part in parts {
            if let adk_gemini::Part::InlineData { inline_data } = part {
                // inline_data.data contains base64-encoded image bytes
                // inline_data.mime_type contains the MIME type
            }
        }
    }
}
```

### Text-to-Speech

```rust
use adk_gemini::*;

let client = Gemini::with_model(
    api_key,
    "models/gemini-2.5-flash-preview-tts".to_string(),
)?;

let response = client
    .generate_content()
    .with_user_message("Hello! This is AI-generated speech.")
    .with_generation_config(GenerationConfig {
        response_modalities: Some(vec!["AUDIO".to_string()]),
        speech_config: Some(SpeechConfig {
            voice_config: Some(VoiceConfig {
                prebuilt_voice_config: Some(PrebuiltVoiceConfig {
                    voice_name: "Puck".to_string(),
                }),
            }),
            multi_speaker_voice_config: None,
        }),
        ..Default::default()
    })
    .execute()
    .await?;
```

### Content Caching

```rust
use std::time::Duration;

let cache = client
    .create_cache()
    .with_display_name("My Analysis Cache")?
    .with_system_instruction("You are a literary analyst.")
    .with_user_message(long_document_text)
    .with_ttl(Duration::from_secs(3600))
    .execute()
    .await?;

// Reuse the cache across multiple queries
let response = client
    .generate_content()
    .with_cached_content(&cache)
    .with_user_message("What is the central theme?")
    .execute()
    .await?;
```

### Batch Processing

```rust
let request1 = client
    .generate_content()
    .with_user_message("What is the meaning of life?")
    .build();

let request2 = client
    .generate_content()
    .with_user_message("What is the best programming language?")
    .build();

let batch = client
    .batch_generate_content()
    .with_request(request1)
    .with_request(request2)
    .execute()
    .await?;
```

### Multi-Turn Conversation

```rust
let response1 = client
    .generate_content()
    .with_system_prompt("You are a travel assistant.")
    .with_user_message("I'm planning a trip to Japan.")
    .execute()
    .await?;

let response2 = client
    .generate_content()
    .with_system_prompt("You are a travel assistant.")
    .with_user_message("I'm planning a trip to Japan.")
    .with_model_message(response1.text())
    .with_user_message("What about cherry blossom season?")
    .execute()
    .await?;
```

### Vertex AI (Google Cloud)

```rust
// API key auth (regional endpoint)
let client = Gemini::with_google_cloud(api_key, "my-project", "us-central1")?;

// Application Default Credentials (regional endpoint)
let client = Gemini::with_google_cloud_adc("my-project", "us-central1")?;

// Global endpoint — uses https://aiplatform.googleapis.com
// No custom base URL workaround needed for Gemini 3 models.
let client = Gemini::with_google_cloud_adc("my-project", "global")?;

// Service account
let sa_json = std::fs::read_to_string("service-account.json")?;
let client = Gemini::with_google_cloud_service_account_json(
    &sa_json, "my-project", "us-central1", "gemini-2.5-flash",
)?;

// Workload Identity Federation
let wif_json = std::fs::read_to_string("wif-credentials.json")?;
let client = Gemini::with_google_cloud_wif_json(
    &wif_json, "my-project", "us-central1", "gemini-2.5-flash",
)?;
```

When `location` is `"global"`, the endpoint resolves to `https://aiplatform.googleapis.com`. For any other location (e.g., `"us-central1"`, `"europe-west4"`), the regional format `https://{location}-aiplatform.googleapis.com` is used.

### Generation Config

```rust
use adk_gemini::GenerationConfig;

let response = client
    .generate_content()
    .with_user_message("Tell me a joke.")
    .with_generation_config(GenerationConfig {
        temperature: Some(0.9),
        top_p: Some(0.95),
        top_k: Some(40),
        max_output_tokens: Some(1024),
        ..Default::default()
    })
    .execute()
    .await?;
```

## Backend Architecture

`adk-gemini` uses a pluggable backend trait to support both AI Studio and Vertex AI:

| Backend | Transport | Auth | Use Case |
|---------|-----------|------|----------|
| `StudioBackend` | REST | API key | Default — Google AI Studio |
| `VertexBackend` | REST SSE + gRPC fallback | ADC / Service Account / WIF | Google Cloud Vertex AI |

The `GeminiBackend` trait defines `send_request()` and `send_streaming_request()` methods. All `Gemini::new()` / `Gemini::with_model()` constructors use `StudioBackend` by default. Vertex AI constructors (`with_google_cloud`, `with_google_cloud_adc`, etc.) use `VertexBackend`.

```rust
use adk_gemini::{Gemini, GeminiBuilder};

// Studio backend (default)
let client = Gemini::new(api_key)?;

// Vertex AI backend
let client = Gemini::with_google_cloud_adc("my-project", "us-central1")?;

// Explicit backend via builder
let client = GeminiBuilder::new()
    .with_api_key(api_key)
    .with_model("gemini-2.5-flash")
    .build()?;
```

## API Modules

| Module | Description |
|--------|-------------|
| `generation` | Content generation (text, images, audio) |
| `embedding` | Text embedding generation |
| `batch` | Batch processing for multiple requests |
| `files` | File upload and management |
| `cache` | Content caching for reusable contexts |
| `safety` | Content moderation and safety settings |
| `tools` | Function calling and tool integration |
| `models` | Core primitive types (Content, Part, Role, Blob) |
| `backend` | Pluggable backend trait (StudioBackend, VertexBackend) |
| `prelude` | Convenient re-exports of commonly used types |

## Environment Variables

```bash
# Gemini API
GEMINI_API_KEY=your-api-key
# or
GOOGLE_API_KEY=your-api-key

# Vertex AI (Google Cloud)
GOOGLE_CLOUD_PROJECT=my-project
GOOGLE_CLOUD_LOCATION=us-central1
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
```

## Running Examples

```bash
export GEMINI_API_KEY=your-api-key

cargo run -p adk-gemini --example simple
cargo run -p adk-gemini --example streaming
cargo run -p adk-gemini --example tools
cargo run -p adk-gemini --example google_search
cargo run -p adk-gemini --example thinking_basic
cargo run -p adk-gemini --example simple_thought_signature
cargo run -p adk-gemini --example thought_signature_example
cargo run -p adk-gemini --example embedding
cargo run -p adk-gemini --example image_generation
cargo run -p adk-gemini --example structured_response
cargo run -p adk-gemini --example cache_basic
cargo run -p adk-gemini --example batch_generate
cargo run -p adk-gemini --example url_context
cargo run -p adk-gemini --example simple_speech_generation
```

## Related Crates

- [adk-rust](https://crates.io/crates/adk-rust) - Meta-crate with all components
- [adk-model](https://crates.io/crates/adk-model) - Multi-provider LLM integrations (uses adk-gemini internally)
- [adk-core](https://crates.io/crates/adk-core) - Core `Llm` trait
- [adk-agent](https://crates.io/crates/adk-agent) - Agent implementations

## License

MIT

Original work Copyright (c) 2024 [@flachesis](https://github.com/flachesis)
Modifications Copyright (c) 2024 Zavora AI

## Part of ADK-Rust

This crate is part of the [ADK-Rust](https://adk-rust.com) framework for building AI agents in Rust.

---

## Attribution

This crate is a fork of the excellent [gemini-rust](https://github.com/flachesis/gemini-rust) library by [@flachesis](https://github.com/flachesis). We are deeply grateful for their work in creating and maintaining this high-quality Gemini API client.

**Upstream Project**
- Repository: [github.com/flachesis/gemini-rust](https://github.com/flachesis/gemini-rust)
- Crates.io: [crates.io/crates/gemini-rust](https://crates.io/crates/gemini-rust)
- Original Author: [@flachesis](https://github.com/flachesis)

**Why a Fork?**

The ADK-Rust project requires certain extensions for deep integration with the Agent Development Kit — exporting additional types (e.g., `GroundingMetadata`, `GroundingChunk`) for grounding support, future ADK-specific extensions for agent workflows, and workspace-level version management. We regularly sync with upstream to incorporate improvements and fixes.

**Our Commitment**
1. Staying aligned with the upstream gemini-rust project as much as possible
2. Contributing back any general improvements that benefit the broader community
3. Maintaining attribution and respecting the original MIT license
4. Minimizing divergence — only adding ADK-specific extensions when necessary

**Acknowledgments**
- [@flachesis](https://github.com/flachesis) — Creator and maintainer of the original gemini-rust library
- [@npatsakula](https://github.com/npatsakula) — Major contributions to the upstream project
