Building a Robust Rust Expert AI with Rig and Gemini

Published 2026-05-03 04:12:54 · 224 views

Building a Robust Rust Expert AI with Rig and Gemini

In this guide, we will build a command-line tool that acts as a Rust Programming Language Expert. We will use the Rig framework to interface with Googleโ€™s Gemini 2.5 Flash Lite model.

Our goal is to move beyond a simple "hello world" script and build a modular, production-ready CLI with proper error handling and environment management.

๐Ÿ›  Prerequisites

Before we dive into the code, ensure you have:


๐Ÿ— Project Setup

First, create a new Rust project and navigate into the directory:

Bash
cargo new rust-expert-ai
cd rust-expert-ai

The .env File

Create a file named .env in your project root. This keeps your sensitive API keys out of your source code.

Code snippet
# Replace with your actual Gemini API Key
GEMINI_API_KEY=your_api_key_here

๐Ÿ“ฆ The Crates (Dependencies)

We are using four key crates to make this project work:

  1. rig-core: A high-level library for building LLM-powered applications. It provides the "Agent" abstraction.

  2. tokio: The industry-standard asynchronous runtime for Rust. Necessary because AI requests are network-bound.

  3. dotenvy: A modern fork of dotenv used to load variables from our .env file.

  4. anyhow: Makes error handling significantly easier by providing a flexible error type.

Add them to your Cargo.toml:

Bash
cargo add rig-core tokio --features full
cargo add dotenvy anyhow

๐Ÿ’ป The Implementation

Weโ€™ve structured the code into modular functions to ensure readability and maintainability.

1. The Entry Point and Initialization

Our main function acts as the orchestrator. It handles the environment setup before handing control over to the chat loop.

Rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Load .env and fetch API key
    dotenvy::dotenv().ok();
    let api_key = std::env::var("GEMINI_API_KEY")
        .expect("GEMINI_API_KEY must be set");

    // Initialize the Gemini Client
    let client = rig::providers::gemini::Client::new(&api_key);

    // Build the specialized Agent
    let agent = build_expert_agent(&client);

    println!("๐Ÿš€ Rust Expert AI is online!");
    run_chat_loop(agent).await?;

    Ok(())
}

2. Defining the Agent's "Personality"

By separating the agent configuration, we can easily tweak the model or the "Preamble" (the instructions given to the AI).

Rust
fn build_expert_agent(client: &rig::providers::gemini::Client) -> rig::agent::Agent<rig::providers::gemini::CompletionModel> {
    client
        .agent("gemini-2.5-flash-lite")
        .preamble("You are a Rust Programming Language Expert. Provide idiomatic, safe, and performant Rust advice.")
        .build()
}

3. The Interactive I/O Loop

This function handles the "Human-in-the-loop" logic, including flushing the terminal and handling exit commands.

Rust
async fn run_chat_loop(agent: rig::agent::Agent<rig::providers::gemini::CompletionModel>) -> anyhow::Result<()> {
    use std::io::{self, Write};
    use rig::completion::Prompt;

    loop {
        print!("\n> Ask Rust Expert: ");
        io::stdout().flush()?;

        let mut input = String::new();
        io::stdin().read_line(&mut input)?;
        let input = input.trim();

        if input.eq_ignore_ascii_case("exit") || input.eq_ignore_ascii_case("quit") {
            break;
        }

        if input.is_empty() { continue; }

        println!("Thinking...");
        match agent.prompt(input).await {
            Ok(res) => println!("\nResponse: {}", res),
            Err(e) => eprintln!("Error: {}", e),
        }
    }
    Ok(())
}

๐Ÿš€ Running the Project

To start your AI assistant, simply run:

Bash
cargo run

You can now ask questions like:

  • "What is the difference between String and &str?"

  • "Explain the Borrow Checker using a library analogy."

  • "How do I implement a thread-safe singleton?"


๐Ÿ’ก Summary of Best Practices Used

  • Asynchronous I/O: Leveraging tokio to keep the application responsive during API calls.

  • Environment Safety: Using dotenvy to prevent leaking API keys.

  • Conciseness: Using anyhow to reduce boilerplate match statements for errors.

  • Separation of Concerns: Modularizing the agent creation and the UI loop makes the code testable.

Happy coding, and may your borrow checker always be happy!