RAG (Génération augmentée par récupération)

Donnez à vos agents une base de connaissances afin qu’ils puissent répondre aux questions en utilisant vos propres données.


Qu’est-ce que RAG ?

RAG permet à votre agent de rechercher des informations pertinentes dans vos documents avant de répondre à une question. Au lieu de s’appuyer uniquement sur les données avec lesquelles le LLM a été entraîné, l’agent recherche dans vos données et utilise les résultats comme contexte.

Le processus est le suivant :

  1. Ingestion — Les documents sont divisés en segments, convertis en représentations vectorielles, puis stockés
  2. Requête — Une question est convertie en représentation vectorielle et comparée aux segments stockés par similarité
  3. Génération — Les segments les plus pertinents sont transmis au LLM comme contexte pour sa réponse

Ainsi, votre agent peut répondre à des questions portant sur la documentation produit, les politiques de l’entreprise, les bases de code ou tout autre texte que vous lui fournissez.

Points forts :

  • 📄 Ingérer n’importe quel texte — documentation produit, markdown, code, politiques
  • 🔍 Recherche sémantique — trouver du contenu pertinent selon son sens, et pas uniquement selon les mots-clés
  • 🤖 Récupération agentique — l’agent décide quand effectuer une recherche via RagTool
  • 🔌 Backends enfichables — remplacer les fournisseurs de représentations vectorielles et les magasins de vecteurs sans modifier le code

Installation

[dependencies]
# Core only (in-memory store, all chunkers, no external deps)
adk-rag = "2.1.0"

# With Gemini embeddings (recommended for getting started)
adk-rag = { version = "2.1.0", features = ["gemini"] }

Étape 1 : Créer un pipeline

Un RagPipeline relie quatre composants : un segmenteur, un fournisseur de représentations vectorielles, un magasin de vecteurs et un reranker facultatif.

use std::collections::HashMap;
use std::sync::Arc;
use adk_rag::{
    Document, FixedSizeChunker, InMemoryVectorStore,
    RagConfig, RagPipeline, EmbeddingProvider,
};

// Mock embedder for demos — no API key needed.
// In production, use GeminiEmbeddingProvider or OpenAIEmbeddingProvider.
struct MockEmbedder;

#[async_trait::async_trait]
impl EmbeddingProvider for MockEmbedder {
    async fn embed(&self, text: &str) -> adk_rag::Result<Vec<f32>> {
        let hash = text.bytes().fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64));
        let mut v = vec![0.0f32; 64];
        for (i, x) in v.iter_mut().enumerate() {
            *x = ((hash.wrapping_add(i as u64)) as f32).sin();
        }
        let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
        if norm > 0.0 { v.iter_mut().for_each(|x| *x /= norm); }
        Ok(v)
    }
    fn dimensions(&self) -> usize { 64 }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pipeline = RagPipeline::builder()
        .config(RagConfig::builder()
            .chunk_size(256)
            .chunk_overlap(50)
            .top_k(3)
            .build()?)
        .embedding_provider(Arc::new(MockEmbedder))
        .vector_store(Arc::new(InMemoryVectorStore::new()))
        .chunker(Arc::new(FixedSizeChunker::new(256, 50)))
        .build()?;

    // Create a collection and ingest a document
    pipeline.create_collection("docs").await?;
    pipeline.ingest("docs", &Document {
        id: "intro".into(),
        text: "Rust is a systems programming language focused on safety and speed.".into(),
        metadata: HashMap::from([("topic".into(), "rust".into())]),
        source_uri: None,
    }).await?;

    // Query
    let results = pipeline.query("docs", "safe programming").await?;
    for r in &results {
        println!("[{:.3}] {}", r.score, r.chunk.text);
    }
    Ok(())
}

Fonctionnement :

  1. FixedSizeChunker divise le document en segments de 256 caractères avec un chevauchement de 50 caractères
  2. MockEmbedder convertit chaque segment en un vecteur de 64 dimensions
  3. InMemoryVectorStore stocke les vecteurs et effectue les recherches par similarité cosinus
  4. query() convertit la question en représentation vectorielle, trouve les segments les plus proches et les renvoie classés par score

Étape 2 : Ajouter RAG à un agent

La véritable puissance de RAG se révèle lorsqu’un agent l’utilise comme outil. RagTool encapsule le pipeline sous forme de adk_core::Tool — l’agent appelle rag_search chaque fois qu’il a besoin d’informations.

Lorsque vous utilisez RagTool avec des agents reposant sur Gemini, ADK normalise automatiquement le résultat de l’outil en une réponse de fonction compatible avec Gemini. Cela est important, car rag_search renvoie naturellement une liste de fragments, tandis que Gemini attend que functionResponse.response soit un objet JSON sur le réseau.

use std::sync::Arc;
use adk_agent::LlmAgentBuilder;
use adk_model::gemini::GeminiModel;
use adk_rag::{
    Document, GeminiEmbeddingProvider, InMemoryVectorStore,
    RagConfig, RagPipeline, RagTool, RecursiveChunker,
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;

    // Build pipeline with real embeddings
    let pipeline = Arc::new(
        RagPipeline::builder()
            .config(RagConfig::builder().chunk_size(300).chunk_overlap(50).top_k(3).build()?)
            .embedding_provider(Arc::new(GeminiEmbeddingProvider::new(&api_key)?))
            .vector_store(Arc::new(InMemoryVectorStore::new()))
            .chunker(Arc::new(RecursiveChunker::new(300, 50)))
            .build()?,
    );

    // Ingest documents
    pipeline.create_collection("kb").await?;
    pipeline.ingest("kb", &Document {
        id: "returns".into(),
        text: "Our return policy allows returns within 30 days with a receipt.".into(),
        metadata: Default::default(),
        source_uri: None,
    }).await?;

    // Wrap pipeline as a tool and attach to an agent
    let agent = LlmAgentBuilder::new("support")
        .instruction("Answer questions using the rag_search tool. Cite your sources.")
        .model(Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?))
        .tool(Arc::new(RagTool::new(pipeline, "kb")))
        .build()?;

    // The agent now calls rag_search automatically when it needs knowledge base info
    adk_cli::console::run_console(Arc::new(agent), "app".into(), "user1".into()).await?;
    Ok(())
}

Lorsqu’un utilisateur demande « Quelle est votre politique de retour ? », l’agent :

  1. Détermine qu’il doit rechercher dans la base de connaissances
  2. Appelle rag_search avec {"query": "return policy"}
  3. Récupère les fragments pertinents avec leurs scores
  4. Utilise les fragments comme contexte pour générer une réponse naturelle

Étape 3 : Choisir une stratégie de segmentation

La manière dont vous divisez les documents influence la qualité de la récupération. adk-rag fournit trois segmenteurs :

SegmenteurIdéal pourComment il segmente
FixedSizeChunkerTexte général, journauxTous les N caractères avec chevauchement
RecursiveChunkerArticles, documentation, commentaires de codeParagraphes → phrases → mots
MarkdownChunkerFichiers Markdown, fichiers READMEPar en-têtes, en préservant la hiérarchie des sections
use adk_rag::{FixedSizeChunker, RecursiveChunker, MarkdownChunker};

// Fixed: simple, predictable chunks
let chunker = FixedSizeChunker::new(512, 100);

// Recursive: respects natural text boundaries
let chunker = RecursiveChunker::new(512, 100);

// Markdown: each section becomes a chunk with header_path metadata
let chunker = MarkdownChunker::new(512, 100);

RecursiveChunker est le meilleur choix par défaut — il essaie d’abord les sauts de paragraphe, puis les limites entre les phrases, et enfin les limites entre les mots, produisant des segments plus naturels qu’un découpage de taille fixe.

MarkdownChunker ajoute un champ de métadonnées header_path à chaque segment (par exemple "Getting Started > Installation"), ce qui aide l’agent à citer des sections spécifiques.


Configuration

use adk_rag::RagConfig;

let config = RagConfig::builder()
    .chunk_size(256)            // max characters per chunk (default: 512)
    .chunk_overlap(50)          // overlap between chunks (default: 100)
    .top_k(5)                   // results to return (default: 10)
    .similarity_threshold(0.5)  // minimum score to include (default: 0.0)
    .build()?;
ParamètreCe que cela contrôleRecommandations
chunk_sizeNombre maximal de caractères par segment200–500 pour la plupart des cas d’utilisation. Une valeur plus petite offre davantage de précision, une valeur plus grande fournit davantage de contexte
chunk_overlapNombre de caractères partagés entre les segments adjacents10–20 % de chunk_size évite de perdre des informations aux limites
top_kNombre de résultats renvoyésPlus de résultats = davantage de contexte pour le LLM, mais une utilisation plus importante de tokens
similarity_thresholdScore minimal à inclure0,0 renvoie tout ; 0,3–0,7 filtre les correspondances faibles

Fournisseurs d’Embedding

FournisseurDrapeau de fonctionnalitéModèleNécessite
GeminiEmbeddingProvidergeminigemini-embedding-2GOOGLE_API_KEY
OpenAIEmbeddingProvideropenaitext-embedding-3-smallOPENAI_API_KEY
// Gemini
use adk_rag::GeminiEmbeddingProvider;
let embedder = GeminiEmbeddingProvider::new(&api_key)?;

// OpenAI
use adk_rag::OpenAIEmbeddingProvider;
let embedder = OpenAIEmbeddingProvider::new(&api_key, "text-embedding-3-small");

Vous pouvez également implémenter EmbeddingProvider pour tout service d’embeddings personnalisé.


Backends de stockage vectoriel

BackendIndicateur de fonctionnalitéIdéal pour
InMemoryVectorStore(par défaut)Développement, tests, petits jeux de données
QdrantVectorStoreqdrantProduction avec une base de données vectorielle dédiée
LanceDBVectorStorelancedbBase de données vectorielle intégrée (aucun serveur requis)
PgVectorStorepgvectorLorsque vous utilisez déjà PostgreSQL
// In-memory (no setup needed)
use adk_rag::InMemoryVectorStore;
let store = InMemoryVectorStore::new();

// Qdrant (requires running Qdrant server)
use adk_rag::QdrantVectorStore;
let store = QdrantVectorStore::new("http://localhost:6334").await?;

// pgvector (requires PostgreSQL with pgvector extension)
use adk_rag::PgVectorStore;
let store = PgVectorStore::new("postgres://user:pass@localhost/db").await?;

Réordonnanceur personnalisé

Le NoOpReranker par défaut transmet les résultats sans modification. Écrivez un réordonnanceur personnalisé pour améliorer la précision :

use adk_rag::{Reranker, SearchResult};

struct KeywordBoostReranker { boost: f32 }

#[async_trait::async_trait]
impl Reranker for KeywordBoostReranker {
    async fn rerank(
        &self,
        query: &str,
        mut results: Vec<SearchResult>,
    ) -> adk_rag::Result<Vec<SearchResult>> {
        let keywords: Vec<String> = query.split_whitespace()
            .filter(|w| w.len() > 3)
            .map(|w| w.to_lowercase())
            .collect();

        for r in &mut results {
            let text = r.chunk.text.to_lowercase();
            let hits = keywords.iter().filter(|kw| text.contains(kw.as_str())).count();
            r.score += hits as f32 * self.boost;
        }
        results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
        Ok(results)
    }
}

// Add to pipeline
let pipeline = RagPipeline::builder()
    .config(config)
    .embedding_provider(embedder)
    .vector_store(store)
    .chunker(chunker)
    .reranker(Arc::new(KeywordBoostReranker { boost: 0.1 }))
    .build()?;

Collections multiples

Utilisez des collections distinctes pour différents domaines de connaissances. L’agent peut effectuer des recherches dans des collections spécifiques, ou vous pouvez créer plusieurs instances de RagTool :

// Create collections for different content types
pipeline.create_collection("docs").await?;
pipeline.create_collection("faq").await?;
pipeline.create_collection("changelog").await?;

// Ingest into each
pipeline.ingest("docs", &setup_doc).await?;
pipeline.ingest("faq", &faq_doc).await?;
pipeline.ingest("changelog", &release_doc).await?;

// One tool per collection — the agent picks which to search
let docs_tool = RagTool::new(pipeline.clone(), "docs");
let faq_tool = RagTool::new(pipeline.clone(), "faq");

let agent = LlmAgentBuilder::new("support")
    .instruction("Search 'docs' for how-to questions, 'faq' for common questions.")
    .model(Arc::new(model))
    .tool(Arc::new(docs_tool))
    .tool(Arc::new(faq_tool))
    .build()?;

L’agent peut également remplacer la collection au moment de la requête en transmettant "collection": "faq" dans l’appel de l’outil.


Indicateurs de fonctionnalités

N’incluez que les dépendances dont vous avez besoin :

FonctionnalitéActiveDépendance supplémentaire
(par défaut)Traits fondamentaux, InMemoryVectorStore, tous les segmenteursaucune
geminiGeminiEmbeddingProvideradk-gemini
openaiOpenAIEmbeddingProviderreqwest
qdrantQdrantVectorStoreqdrant-client
lancedbLanceDBVectorStorelancedb, arrow
pgvectorPgVectorStoresqlx
fullTout ce qui précèdetout
# Just core
adk-rag = "2.1.0"

# With Gemini embeddings
adk-rag = { version = "2.1.0", features = ["gemini"] }

# Everything
adk-rag = { version = "2.1.0", features = ["full"] }

Remarque : La fonctionnalité lancedb nécessite l’installation de protoc. Installez-la avec brew install protobuf (macOS) ou apt install protobuf-compiler (Ubuntu).


Architecture

                        Ingestion
Documents ──→ [Chunker] ──→ [EmbeddingProvider] ──→ [VectorStore]

                          Query
Question ──→ [EmbeddingProvider] ──→ [VectorStore search] ──→ [Reranker] ──→ Results
                                                                               │
                                                                               ▼
                                                                    Agent uses as context

Le RagPipeline orchestre les deux flux. Le RagTool encapsule le pipeline sous la forme d’un adk_core::Tool afin que les agents l’appellent à la demande.


Exécuter les exemples

cargo adk new rag_agent --template rag
cd rag_agent
cargo run

Bonnes pratiques

PratiquePourquoi
Utiliser RecursiveChunker par défautProduit des limites de segments naturelles
Conserver des segments de 200 à 500 caractèresÉquilibre la précision et le contexte
Utiliser de vrais embeddings en productionLes générateurs d’embeddings simulés sont réservés aux tests
Définir similarity_threshold > 0Filtre le bruit non pertinent
Séparer les collections par domaineAméliore la précision et permet aux agents de cibler leurs recherches
Utiliser InMemoryVectorStore uniquement pour le développementPasser à Qdrant/pgvector pour la production


Précédent : ← Outils d’interface utilisateur | Suivant : Sessions →