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, vor der Beantwortung einer Frage relevante Informationen aus Ihren Dokumenten abzurufen. Statt sich ausschließlich auf das zu verlassen, worauf der LLM trainiert wurde, durchsucht der Agent Ihre Daten und verwendet die Ergebnisse als Kontext.
Der Ablauf:
- Aufnahme — Dokumente werden in Abschnitte aufgeteilt, in Vektoreinbettungen umgewandelt und gespeichert
- Abfrage — Eine Frage wird eingebettet und anhand der Ähnlichkeit mit gespeicherten Abschnitten abgeglichen
- Generierung — Die relevantesten Abschnitte werden als Kontext für die Antwort an den LLM übergeben
Dadurch kann Ihr Agent Fragen zu Produktdokumentationen, Unternehmensrichtlinien, Codebasen oder beliebigen Texten beantworten, die Sie ihm zuführen.
Wichtige Merkmale:
- 📄 Beliebigen Text aufnehmen — Produktdokumentationen, Markdown, Code, Richtlinien
- 🔍 Semantische Suche — Relevante Inhalte anhand ihrer Bedeutung statt nur anhand von Schlüsselwörtern finden
- 🤖 Agentengesteuerter Abruf — Der Agent entscheidet über
RagTool, wann gesucht werden soll- 🔌 Austauschbare Backends — Einbettungsanbieter und Vektorspeicher austauschen, ohne den Code zu ändern
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"] }
Schritt 1: Eine Pipeline erstellen
Eine RagPipeline verbindet vier Komponenten: einen Chunker, einen Einbettungsanbieter, einen Vektorspeicher und optional einen 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(())
}
Funktionsweise:
FixedSizeChunkerteilt das Dokument in Abschnitte mit 256 Zeichen und einer Überlappung von 50 Zeichen aufMockEmbedderwandelt jeden Abschnitt in einen 64-dimensionalen Vektor umInMemoryVectorStorespeichert die Vektoren und führt die Suche anhand der Kosinusähnlichkeit durchquery()bettet die Frage ein, findet die ähnlichsten Abschnitte und gibt sie nach Punktzahl sortiert 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 kapselt die Pipeline als adk_core::Tool – der Agent ruft rag_search auf, wann immer er Informationen benötigt.
Wenn Sie RagTool mit Gemini-basierten Agenten verwenden, normalisiert ADK das Tool-Ergebnis automatisch in eine mit Gemini kompatible Funktionsantwort. Das ist wichtig, weil rag_search von Natur aus eine Liste von Chunks zurückgibt, während Gemini erwartet, dass functionResponse.response über die Leitung ein JSON-Objekt 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-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(())
}
Wenn ein Benutzer fragt: „Wie lautet Ihre Rückgaberegelung?“, geht der Agent folgendermaßen vor:
- Er entscheidet, dass er die Wissensdatenbank durchsuchen muss
- Er ruft
rag_searchmit{"query": "return policy"}auf - Er erhält die relevanten Chunks mit Bewertungen zurück
- Er verwendet die Chunks als Kontext, um eine natürlich formulierte Antwort zu generieren
Schritt 3: Eine Chunking-Strategie auswählen
Wie Sie Dokumente aufteilen, beeinflusst die Qualität des Abrufs. adk-rag stellt drei Chunker bereit:
| Chunker | Am besten geeignet für | Aufteilung |
|---|---|---|
FixedSizeChunker | Allgemeine Texte, Protokolle | Alle N Zeichen mit Überlappung |
RecursiveChunker | Artikel, Dokumentationen, Codekommentare | Absätze → Sätze → Wörter |
MarkdownChunker | Markdown-Dateien, READMEs | Nach Überschriften, wobei die Abschnittshierarchie erhalten bleibt |
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 – zunächst werden Absatzumbrüche berücksichtigt, dann Satzgrenzen und anschließend Wortgrenzen. Dadurch entstehen natürlichere Abschnitte als bei einer Aufteilung in Blöcke fester Größe.
MarkdownChunker fügt jedem Abschnitt ein Metadatenfeld header_path hinzu (z. B. "Getting Started > Installation"), wodurch der Agent bestimmte Abschnitte zitieren kann.
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 | Hinweise |
|---|---|---|
chunk_size | Maximale Zeichenanzahl pro Abschnitt | 200–500 für die meisten Anwendungsfälle. Kleiner = präziser, größer = mehr Kontext |
chunk_overlap | Gemeinsame Zeichen zwischen benachbarten Abschnitten | 10–20 % von chunk_size verhindert den Verlust von Informationen an den Abschnittsgrenzen |
top_k | Anzahl der zurückgegebenen Ergebnisse | Mehr Ergebnisse = mehr Kontext für LLM, aber höherer Token-Verbrauch |
similarity_threshold | Mindestpunktzahl für die Aufnahme | 0,0 gibt alles zurück; 0,3–0,7 filtert schwache Übereinstimmungen heraus |
Embedding-Anbieter
| Anbieter | Funktionsflag | Modell | Erfordert |
|---|---|---|---|
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");
Sie können EmbeddingProvider auch für jeden benutzerdefinierten Embedding-Dienst implementieren.
Vektorstore-Backends
| Backend | Feature-Flag | Am besten geeignet für |
|---|---|---|
InMemoryVectorStore | (Standard) | Entwicklung, Tests, kleine Datensätze |
QdrantVectorStore | qdrant | Produktion mit dedizierter Vektordatenbank |
LanceDBVectorStore | lancedb | Eingebettete Vektordatenbank (kein Server erforderlich) |
PgVectorStore | pgvector | Wenn Sie PostgreSQL bereits 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 standardmäßige NoOpReranker leitet 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 Instanzen von RagTool 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 zur Abfragezeit auch überschreiben, indem "collection": "faq" im Tool-Aufruf übergeben wird.
Feature-Flags
Binden Sie nur die Abhängigkeiten ein, die Sie benötigen:
| Funktion | Aktiviert | Zusätzliche Abhängigkeit |
|---|---|---|
| (Standard) | Kernmerkmale, InMemoryVectorStore, alle Chunker | keine |
gemini | GeminiEmbeddingProvider | adk-gemini |
openai | OpenAIEmbeddingProvider | reqwest |
qdrant | QdrantVectorStore | qdrant-client |
lancedb | LanceDBVectorStore | lancedb, arrow |
pgvector | PgVectorStore | sqlx |
full | Alle oben genannten | alle |
# 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"] }
Hinweis: Die
lancedb-Funktion erfordert, dassprotocinstalliert ist. Installation 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
RagPipeline koordiniert beide Abläufe. RagTool kapselt die Pipeline als adk_core::Tool, sodass Agenten sie bei Bedarf aufrufen können.
Beispiele ausführen
cargo adk new rag_agent --template rag
cd rag_agent
cargo run
Bewährte Vorgehensweisen
| Methode | Warum |
|---|---|
RecursiveChunker als Standard verwenden | Erzeugt natürliche Chunk-Grenzen |
| Chunks mit 200–500 Zeichen beibehalten | Gleicht Präzision und Kontext aus |
| Verwende echte Embeddings in der Produktion | Mock-Embeddings sind nur zum Testen vorgesehen |
Setze similarity_threshold > 0 | Filtert irrelevantes Rauschen heraus |
| Trenne Sammlungen nach Domäne | Verbessert die Präzision und ermöglicht es Agenten, Suchvorgänge gezielt auszurichten |
Verwende InMemoryVectorStore nur für die Entwicklung | Wechsle in der Produktion zu Qdrant/pgvector |
Verwandte Themen
- Funktionstools – Benutzerdefinierte Tools erstellen
- LlmAgent – Tools zu Agenten hinzufügen
- Speicher – Langzeitspeicher (unterscheidet sich von RAG)
Zurück: ← UI-Tools | Weiter: Sitzungen →