RU | EN | DE

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 Dimensionen

Modelle:

  • 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 LLM

Reranking (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 Reranking

RAG-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_docs

4. 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_chunks

Evaluation-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 Anfrage

Caching

# 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 result

Monitoring

# 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 filtered

Häufige Probleme und Lösungen

Problem 1: Irrelevante Ergebnisse

# Lösung: Verbesserung der Chunking-Strategie
# - Kleinere Chunks
# - Metadaten hinzufügen
# - Bessere Embeddings verwenden

Problem 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 hochrelevante

Best Practices

  1. Gute Chunk-Größe finden

    • Zu klein = Kontext verlieren
    • Zu groß = irrelevante Info
  2. Metadaten nutzen

    metadata = {
        "source": "documentation.pdf",
        "date": "2024-01-15",
        "author": "John Doe",
        "section": "API Reference"
    }
  3. Hybrid Search verwenden

    • Vektorsuche für semantische Ähnlichkeit
    • Schlüsselwortsuche für exakte Übereinstimmungen
  4. Bewertung automatisieren

    from ragas import evaluate
     
    scores = evaluate(
        dataset=test_questions,
        metrics=[faithfulness, answer_relevance]
    )
  5. 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.