Fehler bei leisen RAG-Fehlern beheben: Retrieval, Chunking, Filter und Evaluierungen
RAG-Antworten können richtig klingen und dennoch falsch sein. Ein für Python-Entwickler gedachtes Handbuch zur Erfassung von Retrieval, Chunking, Metadatenfiltern, Evaluierungen, hybriden Suchverfahren und Grundlagen.
Quellen: RAG Is Failing Quietly: A Debugging Playbook for Python Teams
Die Fehler, die niemand sieht
Die lästigsten RAG-Fehler sind nicht die lautstarken. Lautstarke Fehler sind einfach zu erkennen: Der Bot gibt zu, nichts gefunden zu haben, die Vektordatenbank ist down, eine Embedding-Aufgabe ist abgestürzt oder die Antwort kommt ohne Quellenangaben.
Leise Fehler sind teurer. Dokumente werden abgerufen, die Antwort liest sich flüssig, die Quellenangabe scheint glaubwürdig und das Dashboard meldet eine erfolgreiche Abfrage. Dann liest ein Fachexperte die Antwort und sagt, sie sei falsch. In der Regel ist das der Moment, in dem ein Team erkennt, dass es die falsche Ebene beim Debuggen bearbeitet hat.
RAG ist keine einzelne Funktion. Es handelt sich um eine Abfolge von Entscheidungen, die sich überschneiden. Jemand entscheidet darüber, wie eine Frage gelesen und umformuliert wird, wo Dokumente geteilt werden, welcher Suchmechanismus verwendet wird, welche Metadaten die Kandidaten eingrenzen, wie die Shortlist neu rangiert wird, was letztendlich in die Anfrage aufgenommen wird, wie sich das Modell verhält, wenn die Beweise unzureichend sind, und ob die schriftliche Antwort tatsächlich aus dem Gelieferten abgeleitet werden kann. Wenn man nur auf die endgültige Anfrage schaut, kommt man erst, nachdem der Schaden bereits entstanden ist.
Der zu debuggende Prozess
Ein realistisches System sieht eher so aus wie das Folgende als wie das ordentliche Diagramm auf einer Präsentationsdiashow:
flowchart LR
A[User question] --> B[Query rewrite]
B --> C[Retriever]
C --> D[Reranker]
D --> E[Evidence pack]
E --> F[Answer generator]
F --> G[Verifier]
G --> H[Final answer]
C --> I[Trace log]
D --> I
E --> I
F --> I
G --> I
Jede Komponente kann versagen. Der Generator wird dafür verantwortlich gemacht, weil er die am meisten sichtbare Stufe ist, doch viele Fehler entstehen bereits früher in der Kette. Der Abrufmechanismus könnte den falschen Zeitraum ausgewählt haben. Der Neurankierer könnte einen unbedeutenden, ähnlichen Textabschnitt priorisiert haben. Das Beweismittelpaket könnte den einzigen nützlichen Absatz weggelassen haben, weil das Tokenbudget zu knapp war. Gutes Debuggen bedeutet, jede Stufe sichtbar zu machen.
Fehler 1: Ähnlicher Text ist kein nützliches Beweismittel
Die Vektorsuche erkennt semantische Ähnlichkeiten, die jedoch nicht immer mit der Aufgabenstellung zusammenhängen. Nehmen wir einen Nutzer, der fragt, ob Unternehmenskunden nach der Policy-Änderung im zweiten Quartal einen Rückzahlung erhalten können. Der Abrufmechanismus könnte einfach einen Abschnitt zur Rückzahlungspolitik zurückgeben, weil darin Begriffe wie „Unternehmen“, „Rückzahlung“ und „Policy“ vorkommen. Doch dieser Abschnitt könnte veraltet, unvollständig oder nicht mit dem zweiten Quartal verbunden sein.
Ein nützlicher Ausdruck zeigt die abgerufene Dokument-ID, ihre Version, das Inkrafttrittsdatum, die Ähnlichkeitsbewertung, die Neubewertungsscore, die angewendeten Metadatenfilter sowie an, ob die endgültige Antwort darauf verweist. Wenn die Protokolle nur eine Zahl wie 0,83 angeben, lässt sich der Fehler nicht diagnostizieren.
Fehler 2: Das Chunking hat die Bedeutung zerstört
Das Chunking ist kein Speicherdetail – es bestimmt vielmehr die Qualität der Antwort. Eine Rückerstattungspolitik könnte in Kategorien wie Berechtigung, Ausnahmen, ein Unternehmensanhang, Inkrafttrittsdaten und regionale Unterschiede unterteilt sein. Wenn das Chunking-Tool das Inkrafttrittsdatum von den Ausnahmen trennt, kann die Antwort zwar den Abschnitt zur Berechtigung zitieren, dabei aber die Bedingung übersehen, die das Ergebnis verändert.
Bessere Blöcke enthalten eine stabile Dokument-ID, einen Abschnittsüberschrift, eine Version, ein Datum, ausreichend umliegenden Text, um die Bedeutung zu bewahren, sowie einen Verweis auf das Original. All das spricht jedoch nicht für riesige Blöcke, die nur Unordnung verursachen. Ziel sind Blöcke, die den Punkt bewahren, an dem eine Entscheidung getroffen wird.
Fehler 3: Fehlende Metadatenfilter
Viele RAG-Systeme beginnen als reine semantische Suchsysteme. Das funktioniert, solange das gleiche Thema in verschiedenen Versionen, Produkten, Nutzern, Regionen oder Berechtigungsgruppen vorkommt. Eine Frage zur aktuellen Rückerstattungspolitik des Unternehmens sollte nicht dazu führen, dass das Suchsystem jedes Rückerstattungsdokument durchsucht, das das Unternehmen je erstellt hat. Zunächst sollten Filter angewandt werden.
from dataclasses import dataclass
from datetime import date
@dataclass(frozen=True)
class SearchFilters:
product: str | None
customer_tier: str | None
region: str | None
as_of: date
permission_group: str
def build_filters(user_context: dict) -> SearchFilters:
return SearchFilters(
product=user_context.get("product"),
customer_tier=user_context.get("tier"),
region=user_context.get("region"),
as_of=date.today(),
permission_group=user_context["permission_group"],
)
Die Reihenfolge ist wichtig. Wenden Sie Berechtigungs- und Frischheitsfilter an, bevor Sie das Modell bitten, zu reasoningen. Es ist keine gute Strategie, privaten oder veralteten Kontext abzurufen und zu hoffen, dass der Prompt das Problem löst.
Fehler 4: ein Evaluationsset mit problematischen Szenarien
Der schnellste Weg, ein RAG-System zu verbessern, besteht darin, ein kleines, gezielt ausgewähltes Evaluationsset zu verwenden. Vermeiden Sie tausende Beispiele – beginnen Sie mit etwa dreißig Beispielen, die tatsächliche Fehler des Systems widerspiegeln:
- Fragen, deren Antwort nicht im Korpus enthalten ist
- Fragen, die zwei Quellen erfordern
- Fragen, bei denen eine alte und eine neue Richtlinie miteinander kollidieren
- Fragen mit ähnlicher Formulierung zu unterschiedlichen Produkten
- Fragen, die von einer Tabelle abhängen
- Fragen, bei denen die richtige Antwort darin besteht, an einen Menschen weiterzuleiten
Ein einfaches Retrieval-Tool könnte so aussehen:
from dataclasses import dataclass
@dataclass(frozen=True)
class RagCase:
question: str
required_doc_ids: set[str]
forbidden_doc_ids: set[str]
def evaluate_retrieval(cases: list[RagCase], retrieve) -> dict:
total = len(cases)
hit = 0
leaked_forbidden = 0
for case in cases:
results = retrieve(case.question)
retrieved_ids = {item["doc_id"] for item in results}
if case.required_doc_ids & retrieved_ids:
hit += 1
if case.forbidden_doc_ids & retrieved_ids:
leaked_forbidden += 1
return {
"cases": total,
"required_hit_rate": hit / total,
"forbidden_leak_rate": leaked_forbidden / total,
}
Es ist absichtlich einfach gehalten. Es garantiert keine hohe Qualität der Antworten, erkennt aber viele Abruffehler, bevor das Modell überhaupt Kontext erhält.
Fehler 5: Beurteilung von Antworten ohne Beweise
Eine RAG-Antwort sollte anhand der tatsächlich bereitgestellten Beweise bewertet werden, nicht am gesamten Korpus. Der Unterschied ist aus drei Gründen wichtig. Wenn die richtige Antwort in der Wissensdatenbank vorhanden ist, aber nicht abgerufen wurde, kann man nicht erwarten, dass das Generierungsmodell sie kennt – das ist ein Abruffehler. Wenn die richtigen Beweise abgerufen wurden, die Antwort sie jedoch ignoriert, handelt es sich um einen Generierungsfehler. Wenn die Antwort auf eine Quelle verweist, die die Behauptung nicht stützt, ist es ein Verankerungsfehler. Für alle drei Fälle sind Kennzeichnungen erforderlich.
@dataclass(frozen=True)
class AnswerEval:
question: str
answer: str
evidence_doc_ids: set[str]
expected_claims: set[str]
def simple_claim_check(eval_case: AnswerEval) -> dict:
answer_lower = eval_case.answer.lower()
missing = [
claim
for claim in eval_case.expected_claims
if claim.lower() not in answer_lower
]
return {
"passed": len(missing) == 0,
"missing_claims": missing,
"evidence_count": len(eval_case.evidence_doc_ids),
}
Produktionsumgebungen können bei subtileren Behauptungen eine menschliche Überprüfung oder einen LLM-Prüfer hinzufügen, doch bereits eine grobe Überprüfung der Behauptungen ist besser als ein vager Zustimmungsklick.
Bessere Nachverfolgbarkeit
Jede Antwort sollte eine strukturierte Nachverfolgbarkeit hinterlassen:
import time
import uuid
from dataclasses import dataclass, field
@dataclass
class RagTrace:
run_id: str = field(default_factory=lambda: str(uuid.uuid4()))
started_at: float = field(default_factory=time.time)
query: str = ""
rewritten_query: str | None = None
filters: dict = field(default_factory=dict)
retrieved: list[dict] = field(default_factory=list)
evidence_doc_ids: list[str] = field(default_factory=list)
prompt_tokens: int = 0
completion_tokens: int = 0
verifier_result: str | None = None
latency_ms: int | None = None
def finish_trace(trace: RagTrace) -> RagTrace:
trace.latency_ms = int((time.time() - trace.started_at) * 1000)
return trace
Dadurch erhalten praktische Fragen Antworten. Wurde die richtige Sammlung durchsucht? Haben die Filter die richtigen Dokumente entfernt? Hat der Suchmechanismus die erwartete Quelle zutage gefördert? Enthielt das Beweismittelmaterial zu viel Kontext? Ist dem Überprüfer fehlende Zitate aufgefallen? Entstand die Verzögerung durch die Suche, die Erstellung oder die Überprüfung? Ohne Antworten auf diese Fragen gibt es noch kein RAG-System – nur einen Prompt mit einem angehängten Suchfeld.
Hybride Suche ist oft die langweilige Lösung
Teams wechseln oft viel zu schnell von einer einfachen Vektorsuche zu einer agierenden RAG-Lösung. Eine hybride Suchmethode sollte zunächst ausprobiert werden. Die semantische Suche kommt dann zum Tragen, wenn Benutzer und Dokument unterschiedliche Wörter verwenden. Die Schlüsselwortsuche ist dann vorteilhaft, wenn Genauigkeit bei wörtlichen Zeichenfolgen entscheidend ist – beispielsweise bei Identifikatoren, Fehlerrcodes, Feldnamen oder Formulierungen in rechtlichen Texten. Reale Systeme benötigen häufig beides.
def hybrid_rank(vector_results: list[dict], keyword_results: list[dict]) -> list[dict]:
scores: dict[str, float] = {}
items: dict[str, dict] = {}
for rank, item in enumerate(vector_results, start=1):
doc_id = item["doc_id"]
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (rank + 10)
items[doc_id] = item
for rank, item in enumerate(keyword_results, start=1):
doc_id = item["doc_id"]
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (rank + 10)
items[doc_id] = item
return sorted(
items.values(),
key=lambda item: scores[item["doc_id"]],
reverse=True,
)
Diese Art der Kombination mittels gegenseitiger Rangbewertung ist zwar nicht ausgefeilt, doch nützlich. Sie belohnt Dokumente, die in beiden Ergebnislisten auftauchen, gibt den Suchalgorithmen gleichzeitig aber auch Raum, etwas Wichtiges hervorzuheben.
Wann sich agenteles Suchen lohnt
Agenteles RAG eignet sich für Probleme, die eine Untersuchung erfordern. Geeignete Anwendungsfälle sind unter anderem Situationen, in denen die erste Durchsuchung oft nicht ausreichend Beweise liefert, Fragen, die mehrere Dokumente umfassen, Workflows, die zwischen SQL, Vektorabfragen und API-Aufrufen wählen müssen, Modelle, die prüfen müssen, ob sie ausreichend Beweise haben, sowie Abläufe, die von erneuten Versuchen mit einer anderen Abfrage profitieren.
Ungeeignete Anwendungsfälle sind einfache FAQ-Antworten, Eingabevervollständigung mit geringer Latenz, stabile Dokumentensuche sowie alle Fälle, in denen jeder zusätzliche Modellaufruf zu teuer ist. Das Problem muss eine Verwendung agenter Suchmethoden rechtfertigen. Wenn ein Metadatenfilter es lösen kann, sollte dieser verwendet werden.
Checkliste für die Produktion
Bevor Sie das Modell beschuldigen, prüfen Sie zunächst Folgendes:
- Sind die Dokumente versioniert?
- Werden Berechtigungen vor dem Abrufen überprüft?
- Werden veraltete Dokumente standardmäßig ausgeschlossen?
- Basiert die Aufteilung auf Inhalt statt nur auf Zeichenzahlen?
- Werden exakte Begriffe durch die Schlüsselwortsuche erfasst?
- Werden Tabellen und Bilder separat behandelt?
- Beinhalten die Bewertungen auch Fälle ohne Antwort?
- Werden die IDs der abgerufenen Dokumente protokolliert?
- Werden die zitierten Quellen überprüft?
- Wird die Qualität der Antwort getrennt von der Qualität des Abrufs gemessen?
Die meisten RAG-Fehler sind nicht rätselhaft – sie werden einfach nicht beobachtet.
Wo die Beweiskette reißt
RAG sorgt dafür, dass LLM-Systeme ein solides Fundament haben, doch diese Grundlage entsteht nicht automatisch. Das Modell kann nur die Beweismittel verwenden, die es erhält. Der Retriever kann nur das finden, was korrekt indiziert wurde. Der Index kann nur die Metadaten bereitstellen, die erhalten geblieben sind. Ein Evaluator kann nur die Fehler erkennen, die jemand extra benannt hat. Diese Arbeit ist weniger spektakulär als eine Demo, doch genau hier entsteht das Vertrauen.
Wenn also ein RAG-System leise versagt, liegt das Problem nicht im Prompt. Die bessere Frage lautet: An welcher Stelle ist die Beweiskette gerissen?