RAG (Retrieval-Augmented Generation)
Was ist RAG?
RAG — ist ein Ansatz, bei dem ein LLM mit einer externen Wissensbasis kombiniert wird, um genauere und relevantere Antworten zu geben.
Problem: LLMs wissen nichts über Ihre internen Daten (Dokumente, Datenbanken, APIs).
Lösung: Relevante Informationen abrufen und dem LLM im Kontext übergeben.
Analogie: Prüfung mit Spickzetteln
Ohne RAG:
Student (LLM) antwortet aus dem Gedächtnis
→ kann halluzinieren oder veraltete Info geben
Mit RAG:
Student (LLM) + Spickzettel (externe DB)
→ findet relevante Info und antwortet basierend darauf
RAG-Algorithmus
1. Chunking (Aufteilung)
Text wird in kleinere Teile (Chunks) aufgeteilt.
Strategien:
# Fixed size
chunk_size = 500 # Tokens
overlap = 50 # Überlappung zwischen Chunks
# Semantic
# Teile nach Absätzen, Überschriften, logischen Blöcken
# By structure
# Teile nach Dokumentabschnitten (Kapitel, Unterkapitel)Best Practices:
- Chunk-Größe: 200-1000 Tokens
- Überlappung: 10-20% der Chunk-Größe
- Berücksichtige Dokumentstruktur
2. Embeddings (Einbettungen)
Jeder Chunk wird in einen Vektor (Zahlenarray) umgewandelt.
from openai import OpenAI
client = OpenAI()
text = "Python ist eine Programmiersprache"
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
embedding = response.data[0].embedding
# embedding = [0.023, -0.015, 0.048, ...] # 1536 DimensionenModelle:
- OpenAI:
text-embedding-3-small(1536d),text-embedding-3-large(3072d) - Sentence-BERT: verschiedene Modelle für verschiedene Sprachen
- Cohere:
embed-multilingual-v3.0
3. Vector Database (Vektor-Datenbank)
Chunks und ihre Embeddings werden gespeichert.
Beliebte Datenbanken:
- Pinecone — vollständig verwalteter Cloud-Service
- Weaviate — open-source mit GraphQL
- Chroma — leichtgewichtig, für lokale Entwicklung
- Qdrant — schnell, mit Filterung
- Milvus — skalierbar für Production
Speicherung:
import chromadb
client = chromadb.Client()
collection = client.create_collection("docs")
collection.add(
embeddings=[embedding],
documents=[text],
metadatas=[{"source": "doc1.pdf", "page": 1}],
ids=["chunk_1"]
)4. Retrieval (Abruf)
Finde ähnlichste Chunks zur Benutzeranfrage.
# Benutzeranfrage
query = "Wie erstelle ich eine Funktion in Python?"
# Query-Embedding erstellen
query_embedding = get_embedding(query)
# Ähnlichste Chunks suchen
results = collection.query(
query_embeddings=[query_embedding],
n_results=5
)
# results enthält 5 relevanteste ChunksÄhnlichkeitsmetrik:
- Cosine Similarity — am häufigsten verwendet
- Euclidean Distance — einfacher, aber weniger präzise
- Dot Product — schnell, aber erfordert normalisierte Vektoren
5. Generation (Generierung)
Übertrage Kontext + Anfrage an LLM.
context = "\n\n".join([doc for doc in results['documents'][0]])
prompt = f"""
Kontext: {context}
Frage: {query}
Antwort basierend NUR auf dem bereitgestellten Kontext.
Wenn die Antwort nicht im Kontext ist, sage "Ich weiß nicht".
"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)Fortgeschrittene Techniken
Hybrid Search (Hybride Suche)
Kombination aus Vektor- und Schlüsselwortsuche.
# Vektorsuche
vector_results = vector_db.search(query_embedding)
# Schlüsselwortsuche (BM25)
keyword_results = bm25_search(query)
# Kombiniere Ergebnisse
final_results = merge_and_rerank(vector_results, keyword_results)Parent Document Retrieval
Finde kleine Chunks, aber gib größere Kontexte zurück.
# Chunk in DB speichern: kleine Teile
small_chunk = "Python ist eine Programmiersprache"
# Aber speichere auch Verweis auf großes Dokument
parent_doc_id = "python_tutorial_chapter_1"
# Bei Abruf:
# 1. Finde relevanten small_chunk
# 2. Rufe gesamtes parent_doc ab
# 3. Übergib größeren Kontext an LLMReranking (Neuordnung)
Ordne abgerufene Dokumente nach Relevanz neu.
from cohere import Client
co = Client()
# Erste Abruf-Phase
docs = vector_db.search(query, top_k=100)
# Reranking
reranked = co.rerank(
query=query,
documents=[doc.text for doc in docs],
top_n=5
)
# Verwende nur top-5 nach RerankingRAG-Bewertung
Metriken
1. Faithfulness (Treue) Antwortet LLM basierend NUR auf dem Kontext?
# Überprüfe: Sind alle Fakten in der Antwort im Kontext?
faithfulness_score = check_all_facts_in_context(answer, context)2. Answer Relevance (Antwortrelevanz) Antwortet LLM auf die gestellte Frage?
# Überprüfe: Beantwortet die Antwort die Frage?
relevance_score = check_answer_relevance(question, answer)3. Context Recall (Kontext-Recall) Wurden alle relevanten Dokumente abgerufen?
# Überprüfe: Sind alle notwendigen Fakten im abgerufenen Kontext?
recall = relevant_docs_retrieved / total_relevant_docs4. Context Precision (Kontext-Präzision) Wie viele abgerufene Dokumente sind wirklich relevant?
# Überprüfe: Wie viele abgerufene Chunks sind relevant?
precision = relevant_chunks / total_retrieved_chunksEvaluation-Frameworks
- RAGAS — automatische RAG-Bewertung
- TruLens — Monitoring und Bewertung
- LangSmith — End-to-end Testing
Praktische Aspekte
Kosten
# Typische Kosten für eine Anfrage:
# 1. Embedding der Anfrage: ~$0.0001
# 2. Vektor-Suche: fast kostenlos (selbst gehostet) oder ~$0.001 (Cloud)
# 3. LLM-Generierung: $0.001 - $0.03 (abhängig vom Modell)
# Gesamt: ~$0.001 - $0.03 pro AnfrageCaching
# Cache häufige Anfragen
cache = {}
def rag_with_cache(query):
if query in cache:
return cache[query]
result = full_rag_pipeline(query)
cache[query] = result
return resultMonitoring
# Verfolge wichtige Metriken
metrics = {
"retrieval_latency": time_to_retrieve,
"llm_latency": time_to_generate,
"total_tokens": input_tokens + output_tokens,
"confidence_score": model_confidence,
"num_chunks_used": len(context_chunks)
}
log_metrics(metrics)Sicherheit
# Filtere vertrauliche Informationen
def sanitize_context(context, user_permissions):
# Entferne Chunks, auf die der Benutzer keinen Zugriff hat
filtered = [
chunk for chunk in context
if user_can_access(chunk, user_permissions)
]
return filteredHäufige Probleme und Lösungen
Problem 1: Irrelevante Ergebnisse
# Lösung: Verbesserung der Chunking-Strategie
# - Kleinere Chunks
# - Metadaten hinzufügen
# - Bessere Embeddings verwendenProblem 2: Veraltete Informationen
# Lösung: Regelmäßige Aktualisierungen
def update_vector_db():
new_docs = fetch_updated_docs()
for doc in new_docs:
chunks = chunk_document(doc)
embeddings = get_embeddings(chunks)
vector_db.upsert(chunks, embeddings)Problem 3: Zu viel Kontext
# Lösung: Intelligentes Filtern
# 1. Verwende kleinere top_k
# 2. Setze Ähnlichkeitsschwellenwert
# 3. Verwende Reranking
results = vector_db.search(query, top_k=10)
filtered = [r for r in results if r.score > 0.7] # Nur hochrelevanteBest Practices
-
Gute Chunk-Größe finden
- Zu klein = Kontext verlieren
- Zu groß = irrelevante Info
-
Metadaten nutzen
metadata = { "source": "documentation.pdf", "date": "2024-01-15", "author": "John Doe", "section": "API Reference" } -
Hybrid Search verwenden
- Vektorsuche für semantische Ähnlichkeit
- Schlüsselwortsuche für exakte Übereinstimmungen
-
Bewertung automatisieren
from ragas import evaluate scores = evaluate( dataset=test_questions, metrics=[faithfulness, answer_relevance] ) -
Production-Ready gestalten
- Caching implementieren
- Monitoring einrichten
- Fehlerbehandlung hinzufügen
- Rate Limiting verwenden
Fazit
RAG ist eine Brücke zwischen LLMs und realen Daten:
- Ermöglicht Arbeit mit privaten/aktuellen Daten
- Reduziert Halluzinationen
- Liefert Quellen für Fakten
- Einfacher als Fine-Tuning
Für die meisten Anwendungen ist RAG der beste Ansatz, LLMs mit unternehmensinternen Daten zu verbinden.