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 â