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 →

RAG (Génération augmentée par récupération) - Documentation ADK-Rust | ADK-Rust