Gemini Provider

The Gemini provider enables integration with Google’s Gemini models through the Generative Language API. It supports both synchronous and streaming message completion with full tool calling capabilities. Gemini offers extended context windows up to 1 million tokens and unique features like grounding with web search and safety ratings.

The provider handles Gemini-specific API formats, including the content/parts structure, function declarations, and response metadata extraction. Safety filters and grounding metadata are automatically parsed and made available through the response.


GeminiProvider Struct

The provider is defined in src/client/providers/gemini/mod.rs:

pub struct GeminiProvider {
    api_key: String,
    model: String,
}

impl GeminiProvider {
    pub fn new(api_key: String, model: String) -> Self {
        Self { api_key, model }
    }

    pub fn model(&self) -> &str {
        &self.model
    }
}

API Configuration

The Gemini provider uses the following API settings:

SettingValue
Base URLhttps://generativelanguage.googleapis.com/v1beta/models
Generate/{model}:generateContent
Stream/{model}:streamGenerateContent?alt=sse
Content-Typeapplication/json

Request headers:

Content-Type: application/json
x-goog-api-key: <api_key>

LLMSessionConfig Builder

Create a Gemini session configuration using the google() builder method:

use agent_air::controller::LLMSessionConfig;

let config = LLMSessionConfig::google("AIza...", "gemini-2.5-pro");

The google() method sets these defaults:

OptionDefault Value
max_tokens4096
streamingtrue
context_limit1,000,000
compactionThreshold (default)

Builder Methods

Customize the configuration using builder methods:

let config = LLMSessionConfig::google("AIza...", "gemini-2.5-pro")
    .with_max_tokens(8192)
    .with_system_prompt("You are a helpful assistant.")
    .with_temperature(0.7)
    .with_streaming(true)
    .with_context_limit(1_000_000);

Available methods:

MethodDescription
with_max_tokens(u32)Set maximum response tokens
with_system_prompt(impl Into<String>)Set the system prompt
with_temperature(f32)Set sampling temperature
with_streaming(bool)Enable or disable streaming
with_context_limit(i32)Set context window size
with_threshold_compaction(config)Configure compaction
without_compaction()Disable compaction

Streaming Support

The Gemini provider fully supports streaming via Server-Sent Events (SSE). When streaming is enabled, responses arrive incrementally as StreamEvent values:

pub enum StreamEvent {
    MessageStart { message_id: String, model: String },
    TextDelta(String),
    ToolUse { id: String, name: String, input: Value },
    MessageStop,
    Error(String),
}

The streaming endpoint appends ?alt=sse to enable SSE format.


Request Format

Gemini uses a contents[] with parts[] structure. The provider automatically handles format conversion:

{
  "systemInstruction": {
    "parts": [{"text": "You are a helpful assistant."}]
  },
  "contents": [
    {
      "role": "user",
      "parts": [{"text": "Hello"}]
    }
  ],
  "generationConfig": {
    "maxOutputTokens": 4096,
    "temperature": 0.7
  }
}

Role Mapping

Generic RoleGemini Role
Useruser
Assistantmodel
SystemsystemInstruction (separate field)

Tool Use Format

Tools are sent as function declarations:

{
  "tools": [{
    "functionDeclarations": [{
      "name": "get_weather",
      "description": "Get weather for a location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {"type": "string"}
        },
        "required": ["location"]
      }
    }]
  }],
  "toolConfig": {
    "functionCallingConfig": {
      "mode": "AUTO"
    }
  }
}

Tool choice modes:

ModeDescription
AUTOModel decides whether to use tools
ANYModel must use at least one tool
NONEModel cannot use tools

Tool Results

Gemini matches tool results by function name, not by unique ID. The provider handles this automatically by using the function name as the tool use ID:

{
  "role": "user",
  "parts": [{
    "functionResponse": {
      "name": "get_weather",
      "response": {"result": "72F and sunny"}
    }
  }]
}

Response Metadata

Gemini responses include additional metadata that the provider extracts:

Safety Ratings

Every response includes safety ratings for content categories:

pub struct SafetyRating {
    pub category: String,      // e.g., "HARM_CATEGORY_HARASSMENT"
    pub probability: String,   // e.g., "NEGLIGIBLE", "LOW", "MEDIUM", "HIGH"
    pub blocked: bool,
}

Access safety ratings from the response:

if let Some(metadata) = message.response_metadata {
    if let Some(ratings) = metadata.safety_ratings {
        for rating in ratings {
            println!("{}: {} (blocked: {})",
                rating.category,
                rating.probability,
                rating.blocked
            );
        }
    }
}

Grounding Metadata

When grounding is enabled, responses include web search citations:

pub struct GroundingMetadata {
    pub web_search_queries: Vec<String>,
    pub grounding_chunks: Vec<GroundingChunk>,
    pub grounding_supports: Vec<GroundingSupport>,
}

pub struct GroundingChunk {
    pub source_type: String,  // "web"
    pub uri: Option<String>,
    pub title: Option<String>,
}

Environment Variables

Configure Gemini via environment variables:

VariableDescriptionDefault
GOOGLE_API_KEYAPI key (required)None
GOOGLE_MODELModel identifiergemini-2.5-pro

Available Models

Common Gemini model identifiers:

ModelContextDescription
gemini-2.5-pro1MLatest and most capable
gemini-2.5-flash1MFast, cost-effective
gemini-1.5-pro2MPrevious generation pro
gemini-1.5-flash1MPrevious generation flash

YAML Configuration

Configure in your agent’s config file:

providers:
  - provider: google
    api_key: AIza...
    model: gemini-2.5-pro
    system_prompt: "You are a helpful coding assistant."

default_provider: google

Complete Example

use agent_air::AgentAir;
use agent_air::controller::LLMSessionConfig;

struct MyConfig;

impl AgentConfig for MyConfig {
    fn config_path(&self) -> &str { ".myagent/config.yaml" }
    fn default_system_prompt(&self) -> &str { "You are helpful." }
    fn log_prefix(&self) -> &str { "myagent" }
    fn name(&self) -> &str { "MyAgent" }
}

fn main() -> std::io::Result<()> {
    let mut agent = AgentAir::new(&MyConfig)?;

    // Configuration is loaded automatically from:
    // 1. ~/.myagent/config.yaml (if exists)
    // 2. GOOGLE_API_KEY environment variable (fallback)

    agent.run()
}

Programmatic Configuration

For direct configuration without files:

use agent_air::controller::{LLMSessionConfig, LLMController};

let config = LLMSessionConfig::google(
    std::env::var("GOOGLE_API_KEY").expect("API key required"),
    "gemini-2.5-pro"
)
.with_system_prompt("You are a helpful assistant.")
.with_max_tokens(8192)
.with_streaming(true);

// Create session with this config
let controller = LLMController::new(None);
let session_id = controller.create_session(config).await?;

Error Handling

Gemini API errors are converted to LlmError:

pub struct LlmError {
    pub error_code: String,
    pub error_message: String,
}

Common error patterns:

Error CodeDescription
GEMINI_ERROR_400Invalid request (bad API key, malformed request)
GEMINI_ERROR_429Rate limit exceeded
PROMPT_BLOCKEDPrompt blocked by safety filters
CONTENT_BLOCKEDResponse blocked by safety filters

Safety Filter Errors

When content is blocked by safety filters, the error message includes details:

// Error message format:
// "Prompt blocked by Gemini safety filters. Reason: SAFETY.
//  Safety concerns: HARM_CATEGORY_SEXUALLY_EXPLICIT: HIGH"

Comparison with Other Providers

FeatureGeminiAnthropicOpenAI
Max context1-2M200K128K
StreamingSupportedSupportedSupported
System messagesystemInstructionsystem fieldIn messages array
Tool ID matchingBy nameBy unique IDBy unique ID
Safety ratingsYesNoNo
Grounding/CitationsYesNoNo