gemini_crate 0.1.0

A robust Rust client library for Google's Gemini AI API with built-in error handling, retry logic, and comprehensive model support
Documentation
//! Batch processing example using the Gemini crate
//!
//! This example demonstrates how to process multiple prompts concurrently
//! using the gemini_crate library with proper error handling and rate limiting.
//!
//! Usage:
//! 1. Set your GEMINI_API_KEY in a .env file
//! 2. Run: cargo run --example batch_processing
//! 3. Watch as multiple prompts are processed concurrently

use futures::future::try_join_all;
use gemini_crate::{client::GeminiClient, errors::Error};
use std::time::{Duration, Instant};
use tokio::time::sleep;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load environment variables from .env file
    dotenvy::dotenv().ok();

    // Create the Gemini client
    let client = match GeminiClient::new() {
        Ok(c) => c,
        Err(Error::Config(msg)) => {
            eprintln!("❌ Configuration error: {}", msg);
            eprintln!("💡 Make sure to set GEMINI_API_KEY in your .env file");
            eprintln!("   Example: GEMINI_API_KEY=your_api_key_here");
            return Ok(());
        }
        Err(e) => {
            eprintln!("❌ Failed to create Gemini client: {}", e);
            return Ok(());
        }
    };

    println!("🚀 Gemini Batch Processing Example");
    println!("===================================\n");

    // Define a batch of prompts to process
    let prompts = vec![
        "What is the capital of France?",
        "Explain photosynthesis in one sentence.",
        "What's the largest planet in our solar system?",
        "Who invented the telephone?",
        "What is the speed of light?",
        "Name three programming languages.",
        "What year did World War II end?",
        "What is the chemical symbol for gold?",
        "How many continents are there?",
        "What is the square root of 64?",
    ];

    println!("📋 Processing {} prompts...\n", prompts.len());

    // Method 1: Simple concurrent processing
    println!("🔄 Method 1: Simple Concurrent Processing");
    println!("-----------------------------------------");
    let start_time = Instant::now();

    match process_batch_simple(&client, &prompts).await {
        Ok(responses) => {
            let duration = start_time.elapsed();
            println!(
                "✅ Completed {} requests in {:.2?}\n",
                responses.len(),
                duration
            );

            for (i, response) in responses.iter().enumerate() {
                println!("Q{}: {}", i + 1, prompts[i]);
                println!("A{}: {}\n", i + 1, response);
            }
        }
        Err(e) => {
            eprintln!("❌ Batch processing failed: {}", e);
        }
    }

    println!("\n{}\n", "=".repeat(60));

    // Method 2: Rate-limited processing
    println!("⏱️  Method 2: Rate-Limited Processing");
    println!("------------------------------------");
    let start_time = Instant::now();

    match process_batch_rate_limited(&client, &prompts, 3, Duration::from_millis(500)).await {
        Ok(responses) => {
            let duration = start_time.elapsed();
            println!(
                "✅ Completed {} requests in {:.2?} (with rate limiting)\n",
                responses.len(),
                duration
            );

            for (i, response) in responses.iter().enumerate() {
                println!("Q{}: {}", i + 1, prompts[i]);
                println!("A{}: {}\n", i + 1, response);
            }
        }
        Err(e) => {
            eprintln!("❌ Rate-limited batch processing failed: {}", e);
        }
    }

    println!("\n{}\n", "=".repeat(60));

    // Method 3: Resilient processing with retry
    println!("🛡️  Method 3: Resilient Processing");
    println!("----------------------------------");
    let start_time = Instant::now();

    match process_batch_resilient(&client, &prompts).await {
        Ok(results) => {
            let duration = start_time.elapsed();
            let successful = results.iter().filter(|r| r.is_ok()).count();
            let failed = results.len() - successful;

            println!(
                "✅ Completed {} requests in {:.2?}",
                results.len(),
                duration
            );
            println!("   Successful: {}, Failed: {}\n", successful, failed);

            for (i, result) in results.iter().enumerate() {
                println!("Q{}: {}", i + 1, prompts[i]);
                match result {
                    Ok(response) => println!("A{}: {}\n", i + 1, response),
                    Err(e) => println!("A{}: ❌ Error: {}\n", i + 1, e),
                }
            }
        }
        Err(e) => {
            eprintln!("❌ Resilient batch processing failed: {}", e);
        }
    }

    Ok(())
}

/// Simple concurrent processing - fastest but no rate limiting
async fn process_batch_simple(
    client: &GeminiClient,
    prompts: &[&str],
) -> Result<Vec<String>, Error> {
    let tasks = prompts
        .iter()
        .map(|&prompt| async move { client.generate_text("gemini-2.5-flash", prompt).await });

    try_join_all(tasks).await
}

/// Rate-limited processing - processes in chunks with delays
async fn process_batch_rate_limited(
    client: &GeminiClient,
    prompts: &[&str],
    chunk_size: usize,
    delay_between_chunks: Duration,
) -> Result<Vec<String>, Error> {
    let mut all_responses = Vec::new();

    for chunk in prompts.chunks(chunk_size) {
        println!("  📦 Processing chunk of {} items...", chunk.len());

        let tasks = chunk
            .iter()
            .map(|&prompt| async move { client.generate_text("gemini-2.5-flash", prompt).await });

        let chunk_responses = try_join_all(tasks).await?;
        all_responses.extend(chunk_responses);

        // Add delay between chunks (except for the last one)
        if all_responses.len() < prompts.len() {
            sleep(delay_between_chunks).await;
        }
    }

    Ok(all_responses)
}

/// Resilient processing - handles individual failures gracefully
async fn process_batch_resilient(
    client: &GeminiClient,
    prompts: &[&str],
) -> Result<Vec<Result<String, String>>, Box<dyn std::error::Error>> {
    let tasks = prompts.iter().enumerate().map(|(i, &prompt)| async move {
        println!("  🔄 Processing request {} of {}...", i + 1, prompts.len());

        match resilient_generate(client, "gemini-2.5-flash", prompt, 3).await {
            Ok(response) => {
                println!("  ✅ Request {} completed", i + 1);
                Ok(response)
            }
            Err(e) => {
                println!("  ❌ Request {} failed: {}", i + 1, e);
                Err(format!("{}", e))
            }
        }
    });

    let results = futures::future::join_all(tasks).await;
    Ok(results)
}

/// Generate text with retry logic for better reliability
async fn resilient_generate(
    client: &GeminiClient,
    model: &str,
    prompt: &str,
    max_retries: u32,
) -> Result<String, Error> {
    let mut retries = 0;

    loop {
        match client.generate_text(model, prompt).await {
            Ok(response) => return Ok(response),
            Err(Error::Network(_)) if retries < max_retries => {
                retries += 1;
                let delay = Duration::from_secs(2_u64.pow(retries));
                println!(
                    "    🔄 Network error, retrying in {}s... (attempt {}/{})",
                    delay.as_secs(),
                    retries + 1,
                    max_retries + 1
                );
                sleep(delay).await;
            }
            Err(Error::Api(ref msg)) if msg.contains("429") && retries < max_retries => {
                // Rate limit error
                retries += 1;
                let delay = Duration::from_secs(5 * 2_u64.pow(retries));
                println!(
                    "    ⏰ Rate limit hit, waiting {}s... (attempt {}/{})",
                    delay.as_secs(),
                    retries + 1,
                    max_retries + 1
                );
                sleep(delay).await;
            }
            Err(e) => return Err(e),
        }
    }
}