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>> {
dotenvy::dotenv().ok();
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");
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());
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));
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));
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(())
}
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
}
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);
if all_responses.len() < prompts.len() {
sleep(delay_between_chunks).await;
}
}
Ok(all_responses)
}
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)
}
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 => {
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),
}
}
}