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 :
- Ingestion — Les documents sont divisés en segments, convertis en représentations vectorielles, puis stockés
- Requête — Une question est convertie en représentation vectorielle et comparée aux segments stockés par similarité
- 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 :
FixedSizeChunkerdivise le document en segments de 256 caractères avec un chevauchement de 50 caractèresMockEmbedderconvertit chaque segment en un vecteur de 64 dimensionsInMemoryVectorStorestocke les vecteurs et effectue les recherches par similarité cosinusquery()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 :
- Détermine qu’il doit rechercher dans la base de connaissances
- Appelle
rag_searchavec{"query": "return policy"} - Récupère les fragments pertinents avec leurs scores
- 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 :
| Segmenteur | Idéal pour | Comment il segmente |
|---|---|---|
FixedSizeChunker | Texte général, journaux | Tous les N caractères avec chevauchement |
RecursiveChunker | Articles, documentation, commentaires de code | Paragraphes → phrases → mots |
MarkdownChunker | Fichiers Markdown, fichiers README | Par 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ètre | Ce que cela contrôle | Recommandations |
|---|---|---|
chunk_size | Nombre maximal de caractères par segment | 200–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_overlap | Nombre de caractères partagés entre les segments adjacents | 10–20 % de chunk_size évite de perdre des informations aux limites |
top_k | Nombre de résultats renvoyés | Plus de résultats = davantage de contexte pour le LLM, mais une utilisation plus importante de tokens |
similarity_threshold | Score minimal à inclure | 0,0 renvoie tout ; 0,3–0,7 filtre les correspondances faibles |
Fournisseurs d’Embedding
| Fournisseur | Drapeau de fonctionnalité | Modèle | Nécessite |
|---|---|---|---|
GeminiEmbeddingProvider | gemini | gemini-embedding-2 | GOOGLE_API_KEY |
OpenAIEmbeddingProvider | openai | text-embedding-3-small | OPENAI_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
| Backend | Indicateur de fonctionnalité | Idéal pour |
|---|---|---|
InMemoryVectorStore | (par défaut) | Développement, tests, petits jeux de données |
QdrantVectorStore | qdrant | Production avec une base de données vectorielle dédiée |
LanceDBVectorStore | lancedb | Base de données vectorielle intégrée (aucun serveur requis) |
PgVectorStore | pgvector | Lorsque 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é | Active | Dépendance supplémentaire |
|---|---|---|
| (par défaut) | Traits fondamentaux, InMemoryVectorStore, tous les segmenteurs | aucune |
gemini | GeminiEmbeddingProvider | adk-gemini |
openai | OpenAIEmbeddingProvider | reqwest |
qdrant | QdrantVectorStore | qdrant-client |
lancedb | LanceDBVectorStore | lancedb, arrow |
pgvector | PgVectorStore | sqlx |
full | Tout ce qui précède | tout |
# 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é
lancedbnécessite l’installation deprotoc. Installez-la avecbrew install protobuf(macOS) ouapt 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
| Pratique | Pourquoi |
|---|---|
Utiliser RecursiveChunker par défaut | Produit 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 production | Les générateurs d’embeddings simulés sont réservés aux tests |
Définir similarity_threshold > 0 | Filtre le bruit non pertinent |
| Séparer les collections par domaine | Améliore la précision et permet aux agents de cibler leurs recherches |
Utiliser InMemoryVectorStore uniquement pour le développement | Passer à Qdrant/pgvector pour la production |
Articles associés
- Outils de fonctions - Créer des outils personnalisés
- LlmAgent - Ajouter des outils aux agents
- Mémoire - Mémoire à long terme (différente de RAG)
Précédent : ← Outils d’interface utilisateur | Suivant : Sessions →