RAG (Retrieval-Augmented Generation)
Geben Sie Ihren Agents eine Wissensbasis, damit sie Fragen mithilfe Ihrer eigenen Daten beantworten können.
Was ist RAG?
RAG ermöglicht es Ihrem Agent, relevante Informationen aus Ihren Dokumenten nachzuschlagen, bevor er eine Frage beantwortet. Anstatt sich nur auf das zu verlassen, worauf das LLM trainiert wurde, durchsucht der Agent Ihre Daten und verwendet die Ergebnisse als Kontext.
Der Ablauf ist:
- Ingest — Dokumente werden in chunks aufgeteilt, in vector embeddings umgewandelt und gespeichert
- Query — Eine Frage wird eingebettet und anhand von Ähnlichkeit mit gespeicherten chunks abgeglichen
- Generate — Die relevantesten chunks werden dem LLM als Kontext für seine Antwort übergeben
Das bedeutet, Ihr Agent kann Fragen zu Produktdokumenten, Unternehmensrichtlinien, Codebasen oder jedem Text beantworten, den Sie ihm zuführen.
Wichtige Highlights:
- 📄 Beliebigen Text aufnehmen — Produktdokumente, Markdown, Code, Richtlinien
- 🔍 Semantische Suche — relevante Inhalte nach Bedeutung finden, nicht nur nach Schlüsselwörtern
- 🤖 Agentengesteuerte Abfrage — der Agent entscheidet, wann gesucht werden soll über
RagTool - 🔌 Steckbare Backends — Embedding-Anbieter und Vektorspeicher austauschen, ohne den Code zu ändern
Installation
[dependencies]
# Core only (in-memory store, all chunkers, no external deps)
adk-rag = "2.0.0"
# With Gemini embeddings (recommended for getting started)
adk-rag = { version = "2.0.0", features = ["gemini"] }
Schritt 1: Eine Pipeline erstellen
Ein RagPipeline verbindet vier Komponenten: einen Chunker, einen Embedding-Anbieter, einen Vektorspeicher und einen optionalen Reranker.
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(())
}
So funktioniert es:
FixedSizeChunkerteilt das Dokument in 256-Zeichen-Blöcke mit 50-Zeichen-ÜberlappungMockEmbedderwandelt jeden Block in einen 64-dimensionalen Vektor umInMemoryVectorStorespeichert die Vektoren und sucht nach Kosinus-Ähnlichkeitquery()bettet die Frage ein, findet die nächstgelegenen Blöcke und gibt sie nach Rangfolge zurück
Schritt 2: RAG zu einem Agent hinzufügen
Die wahre Stärke von RAG zeigt sich, wenn ein Agent es als Tool verwendet. RagTool verpackt die Pipeline als adk_core::Tool – der Agent ruft rag_search auf, wann immer er Informationen benötigt.
Wenn Sie RagTool mit Gemini-gestützten Agents verwenden, normalisiert ADK das Tool-Ergebnis automatisch in eine Gemini-kompatible Funktionsantwort. Dies ist wichtig, da rag_search natürlich eine Liste von Blöcken zurückgibt, während Gemini erwartet, dass functionResponse.response ein JSON-Objekt über die Leitung ist.
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-2.5-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(())
}
Wenn ein Benutzer fragt "Wie lautet Ihre Rückgaberichtlinie?", der Agent:
- Entscheidet, dass er die Wissensdatenbank durchsuchen muss
- Ruft
rag_searchmit{"query": "return policy"}auf - Erhält die relevanten Chunks mit Bewertungen zurück
- Verwendet die Chunks als Kontext, um eine natürliche Antwort zu generieren
Schritt 3: Eine Chunking-Strategie wählen
Wie Sie Dokumente aufteilen, beeinflusst die Abrufqualität. adk-rag bietet drei Chunker:
| Chunker | Am besten geeignet für | Wie es aufteilt |
|---|---|---|
FixedSizeChunker | Allgemeiner Text, Protokolle | Alle N Zeichen mit Überlappung |
RecursiveChunker | Artikel, Dokumente, Code-Kommentare | Absätze → Sätze → Wörter |
MarkdownChunker | Markdown-Dateien, READMEs | Nach Überschriften, unter Beibehaltung der Abschnittshierarchie |
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 ist die beste Standardwahl — es versucht zuerst Absatzumbrüche, dann Satzgrenzen, dann Wortgrenzen, wodurch natürlichere Blöcke entstehen als bei einer Aufteilung mit fester Größe.
MarkdownChunker fügt ein header_path Metadatenfeld zu jedem Chunk hinzu (z.B. "Getting Started > Installation"), was dem Agenten hilft, bestimmte Abschnitte zu zitieren.
Konfiguration
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()?;
| Parameter | Was es steuert | Anleitung |
|---|---|---|
chunk_size | Max characters per chunk | 200–500 für die meisten Anwendungsfälle. Kleiner = präziser, größer = mehr Kontext |
chunk_overlap | Gemeinsame Zeichen zwischen benachbarten Chunks | 10–20% der chunk_size verhindert Informationsverlust an den Grenzen |
top_k | Anzahl der zurückgegebenen Ergebnisse | Mehr Ergebnisse = mehr Kontext für das LLM, aber höherer Token-Verbrauch |
similarity_threshold | Mindestpunktzahl zur Aufnahme | 0.0 gibt alles zurück; 0.3–0.7 filtert schwache Übereinstimmungen |
Embedding-Anbieter
| Anbieter | Feature-Flag | Modell | Erfordert |
|---|---|---|---|
GeminiEmbeddingProvider | gemini | gemini-embedding-001 | 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");
Sie können auch EmbeddingProvider für jeden benutzerdefinierten Embedding-Dienst implementieren.
Vektorspeicher-Backends
| Backend | Feature-Flag | Am besten geeignet für |
|---|---|---|
InMemoryVectorStore | (default) | Entwicklung, Tests, kleine Datensätze |
QdrantVectorStore | qdrant | Produktion mit dedizierter Vektordatenbank |
LanceDBVectorStore | lancedb | Eingebettete Vektordatenbank (kein Server erforderlich) |
PgVectorStore | pgvector | Wenn Sie bereits PostgreSQL verwenden |
// 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?;
Benutzerdefinierter Reranker
Der Standard-NoOpReranker gibt Ergebnisse unverändert weiter. Schreiben Sie einen benutzerdefinierten Reranker, um die Präzision zu verbessern:
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()?;
Mehrere Sammlungen
Verwenden Sie separate Sammlungen für verschiedene Wissensdomänen. Der Agent kann bestimmte Sammlungen durchsuchen oder Sie können mehrere RagTool Instanzen erstellen:
// 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()?;
Der Agent kann die Sammlung auch zur Abfragezeit überschreiben, indem er "collection": "faq" im Tool-Aufruf übergibt.
Feature-Flags
Ziehen Sie nur die Abhängigkeiten, die Sie benötigen:
| Merkmal | Ermöglicht | Zusätzliche Abhängigkeit |
|---|---|---|
| (Standard) | Core traits, InMemoryVectorStore, alle chunkers | none |
gemini | GeminiEmbeddingProvider | adk-gemini |
openai | OpenAIEmbeddingProvider | reqwest |
qdrant | QdrantVectorStore | qdrant-client |
lancedb | LanceDBVectorStore | lancedb, arrow |
pgvector | PgVectorStore | sqlx |
full | Alles oben Genannte | alle |
# Just core
adk-rag = "2.0.0"
# With Gemini embeddings
adk-rag = { version = "2.0.0", features = ["gemini"] }
# Everything
adk-rag = { version = "2.0.0", features = ["full"] }
Hinweis: Die
lancedb-Funktion erfordert die Installation vonprotoc. Installieren Sie mitbrew install protobuf(macOS) oderapt install protobuf-compiler(Ubuntu).
Architektur
Ingestion
Documents ──→ [Chunker] ──→ [EmbeddingProvider] ──→ [VectorStore]
Query
Question ──→ [EmbeddingProvider] ──→ [VectorStore search] ──→ [Reranker] ──→ Results
│
▼
Agent uses as context
Der RagPipeline orchestriert beide Abläufe. Der RagTool kapselt die Pipeline als adk_core::Tool, sodass Agents sie bei Bedarf aufrufen können.
Beispiele ausführen
cargo adk new rag_agent --template rag
cd rag_agent
cargo run
Bewährte Verfahren
| Praxis | Warum |
|---|---|
Verwenden Sie RecursiveChunker als Standard | Erzeugt natürliche Segmentgrenzen |
| Abschnitte von 200–500 Zeichen beibehalten | Gleicht Präzision und Kontext aus |
| Verwenden Sie echte Einbettungen in der Produktion | Mock-Einbetter sind nur zum Testen |
Wenn similarity_threshold > 0 | Filtert irrelevantes Rauschen heraus |
| Sammlungen nach Domäne trennen | Verbessert die Präzision und ermöglicht Agenten, Suchen gezielter durchzuführen |
InMemoryVectorStore nur für die Entwicklung verwenden | Für die Produktion auf Qdrant/pgvector wechseln |
Verwandte Themen
- Function Tools - Benutzerdefinierte Tools erstellen
- LlmAgent - Tools zu Agents hinzufügen
- Memory - Langzeitgedächtnis (unterschiedlich zu RAG)
Zurück: ← UI Tools | Weiter: Sessions →