Startseite / Artikel

Prüfung von RAG-Aufrufen hinsichtlich veralteter Datenblöcke, Abweichungen und störenden Kontexten.

Eine Python-Auditorie für RAG-Auswertung: Goldene Abfragen, Kosinus-Relevanzprüfungen, Kolmogorov-Smirnov-Drift-Prüfungen sowie Kontexteffizienz – dazu erklärt, wie man den Bericht liest.

Verlag: REACTAPP.TOP12 Min. Lesezeit2728 Wörter

Quellen: I Built a RAG System That Audits Itself. Here’s How (With Code).

Die stillen Fehlfälle bei RAG

Stellen Sie sich einen Support-Bot vor, der einem Kunden mitteilt, dass die Rückgabefrist 30 Tage beträgt, obwohl sie tatsächlich 14 Tage sind. Das Richtliniedokument wurde im Januar geändert. Der Embedding-Index wurde zuletzt im Oktober angepasst. Niemand wird geschädigt und es folgt kein Rechtsstreit, doch beunruhigend ist, dass niemand sagen kann, wie lange die falsche Antwort bereits verbreitet wird. Eine Woche? Drei Monate? Ohne Möglichkeit zur Überprüfung lässt sich das nicht feststellen.

Das ist der hier bedeutsame Fehlschluss – und es handelt sich nicht um den dramatischen Fall, in dem ein Modell Halluzinationen hat oder unzusammenhängend spricht. Es ist der stillen Fehlschluss. Die Dashboards zeigen grüne Werte, und ein kleiner Anteil der Nutzer wird monatelang in die Irre geführt, ohne dass ein einziger Alarm ausgelöst wird.

Die meisten Teams, die RAG-Systeme entwickeln, messen das Falsche. Sie prüfen, ob die endgültige Antwort gut klingt, und übergehen dabei die Frage, ob das Material, aus dem diese Antwort stammt, überhaupt von guter Qualität war. Meist liegt das daran, dass niemand ihnen ein einfaches Werkzeug dafür zur Verfügung gestellt hat. Folgendes ist ein solches Werkzeug: ein Python-Skript, das die Abrufschicht automatisch überprüft, aus Open-Source-Bibliotheken sowie einer JSON-Datei mit Testabfragen erstellt wird, bei dessen Einrichtung etwa 30 Minuten benötigt werden und für dessen Nutzung weder eine Observability-Plattform noch Kenntnisse in Statistik erforderlich sind.

Achten Sie auf den Abrufprozess, nicht auf das Modell

Wenn ein RAG-System nicht richtig funktioniert, ist die erste Reaktion, das Modell dafür verantwortlich zu machen: falsche Anfrage, falsche Temperatur, falsche Version. Man passt die Anfrage an oder wechselt zu einem neueren Modell – dadurch verbessern sich die Ergebnisse für einige Wochen, bis das Problem wieder auftritt, weil der eigentliche Fehler woanders liegt. In Produktionsumgebungen verläuft die Kette in der Regel wie folgt:

Ein Dokument ändert sich, doch der Einbettungsindex wird nicht aktualisiert. Es wird ein veralteter Datenblock abgerufen, das LLM antwortet selbstsicher auf der Grundlage dieses veralteten Kontexts – die Antwort ist falsch, und niemand kann erklären, warum.

Das Modell lügt nicht. Es tut genau das, was ihm aufgetragen wurde, nämlich eine Antwort aus den bereitgestellten Datenblöcken zu erzeugen. Liefern Sie ihm Datenblöcke, die sechs Monate alt sind, dann wird auch die Antwort sechs Monate alt sein – und dennoch fließend sowie mit fehlerfreier Grammatik dargeboten werden.

Das Problem liegt in der üblichen Bewertungsmethode, die von Anfang bis Ende erfolgt: Eine Anfrage wird eingegeben und die herauskommende Antwort wird bewertet. RAGAS, eines der am weitesten verbreiteten Bewertungsframeworks, basiert auf dem gesamten Prozessfluss und bewertet die Präzision des Kontexts, die Treue sowie die Relevanz der Antwort (Es et al., 2023, EACL 2024). Es handelt sich um ein solides Framework. Dennoch bewertet es das System insgesamt und nicht allein die Suchschicht, wodurch eine gute Gesamtbewertung das langsame Nachlassen der Suchleistung verschleiern kann, wenn das Modell gelegentlich Glück hat und dies ausgleichen kann.

Ein weiteres Problem liegt darunter. Liu et al. berichteten 2024, dass Sprachmodelle deutlich schlechter abschneiden, wenn die relevanten Informationen mitten in einem langen Kontext versteckt sind – selbst dann, wenn es sich um Modelle handelt, die speziell für lange Eingaben trainiert wurden. Somit schadet das Abrufen von fünf Abschnitten, von denen drei nur mäßig relevant sind, dem System aktiv, und die darauf verwendeten Token beeinträchtigen die Qualität der Antwort. Keines dieser Probleme tritt auf, es sei denn, die Abrufschicht wird direkt überprüft, was auch der Plan ist.

Drei Versagensmuster, die Standardmetriken übersehen

Chunk-Vergiftung

Der Begriff „Chunk Poisoning“ stammt nicht aus einer wissenschaftlichen Veröffentlichung. Er beschreibt Fragmente, die aufgrund übereinstimmender Formulierungen mit der Abfrage als relevant erscheinen und daher in der Vektorsuche hoch rangieren, obwohl sie keine Antwort enthalten. Eine häufige Ursache ist eine Aufteilung, bei der die Schlüsselwörter in einem Fragment und der eigentliche Inhalt im nächsten liegen. Das schlüsselwortreiche Fragment erzielt gute Werte bei der Kosinusähnlichkeit, das nützliche Fragment gerät nicht in die oberen Ergebnisse, und das Modell erstellt eine plausibel klingende, aber falsche Antwort.

Veralteter Inhalt

Nehmen wir an, eine FAQ hat ihre Rückerstattungspolitik im Januar aktualisiert und der Index wurde zuletzt im Oktober aktualisiert. Das alte Polittextfragment rangiert weiterhin für Fragen zur Rückerstattung und wird vom Modell weiterhin als „relevant“ eingestuft. Die Nutzer erhalten widersprüchliche Antworten vom Support-Team, obwohl die Metriken nichts anzeigen.

Embedding-Drift

Langsam und schrittweise – das ist die gefährlichste der drei Probleme. Ein Embedding-Modell speichert das Korpus in dem Zustand, in dem es zum Indexieren vorlag. Jede Änderung an einem Dokument, jedes neue Wort, das in den Bereich aufgenommen wird, sowie jede Veränderung darin, wie Nutzer Abfragen formulieren, verringert allmählich die Genauigkeit dieses „Schnappschusses“. Die mittlere Kosinusähnlichkeit kann innerhalb von sechs Monaten von 0,82 auf 0,74 sinken. Nichts bricht, es wird keine Warnung ausgelöst – der Rückgang wird erst sichtbar, wenn die Antworten deutlich schlechter werden, zu diesem Zeitpunkt ist das System bereits monatelang hinterher.

Auch Störungen im Kontext haben Folgen. Wenn im Fenster fünf Abschnitte vorhanden sind und drei davon irrelevant sind, werden Token verschwendet und die Qualität leidet, da laut Liu et al. der Inhalt in der Mitte eines langen Kontexts weniger Aufmerksamkeit erhält als der Inhalt an den Rändern.

Was die Prüfung leistet

Drei Überprüfungen beziehen sich auf diese drei Fehlermuster.

  1. Relevanz und Trefferzahlen. Für jede Abfrage im Testset holt das Skript die Top-K-Blöcke ab und berechnet die Kosinusähnlichkeit zwischen der Embedding-Struktur der Abfrage und der Embedding-Struktur jedes Blocks. Zudem wird überprüft, ob die Blöcke, von denen bekannt ist, dass sie die Antwort enthalten, zurückgegeben wurden. Falls nicht, wird die Abfrage als Fehlschlag markiert.
  2. Abweichung gegenüber einer Baseline. Jeder Ausführungsvorgang speichert seine Verteilung der Ähnlichkeitswerte, und spätere Ausführungen vergleichen diese neue Verteilung mit der gespeicherten Baseline mithilfe des Kolmogorov-Smirnov-Tests. Wenn sich die beiden Verteilungen signifikant unterscheiden, wird ein Warnsignal ausgelöst. Es ist nicht notwendig, die Mathematik zu verstehen: Ein p-Wert unter 0,05 deutet lediglich darauf hin, dass sich etwas geändert hat und genauer untersucht werden sollte.
  • Kontexteffizienz. Die Skript misst, welcher Anteil der abgerufenen Token im Antworttext des Modells vorkommt. Ein hoher Anteil deutet auf eine präzise, relevante Abfrage hin, während ein niedriger Anteil darauf hindeutet, dass Störungen den Kontext verfälschen. Ein Wert unter 40 % ist ein Grund zur Untersuchung.
  • Die Überprüfung erzeugt einen strukturierten JSON-Bericht. Ein wöchentlicher Cron-Job reicht aus, wobei besonderes Augenmerk auf der Fehlerquote und der Drift-Analyse liegt. Die meisten RAG-Systeme verfügen bisher nicht über eine solche Überwachungsebene.

    Die goldene Abfragesammlung

    Vor dem Einsatz jeglichen Codes ist eine goldene Abfragesammlung erforderlich: eine JSON-Datei mit repräsentativen Abfragen, wobei jede Abfrage mit den Chunk-IDs verknüpft ist, die in ihren Ergebnissen erscheinen sollten.

    [
      {
        "query": "What is the refund policy for digital products?",
        "expected_chunk_ids": ["faq_doc_chunk_12", "faq_doc_chunk_13"],
        "notes": "Customer FAQ, policy updated 2024-Q1"
      },
      {
        "query": "How do I reset my API key?",
        "expected_chunk_ids": ["api_docs_chunk_07"],
        "notes": "API documentation, stable"
      }
    ]
    

    Die Anzahl ist wichtig. Anfangs schien zwanzig Abfragen ausreichend zu sein, was jedoch nicht der Fall war, denn der Drift-Test benötigt mehr Datensätze als das – 20 ergeben eine zu spärliche Verteilung, der man nicht trauen kann. Betrachten Sie 50 als Mindestwert und 100 oder mehr als den Punkt, ab dem Drift-Signale aussagekräftig werden.

    Beginnen Sie damit, die häufigsten Benutzerabfragen aus den Anwendungsprotokollen zu nehmen. Führen Sie für jede Abfrage manuell eine Suche durch, prüfen Sie die Ergebnisse und notieren Sie, welche Chunk-IDs in den oberen Positionen erscheinen. Die Arbeit ist monoton und dauert einige Stunden, doch sie ist der einzige Teil, der nicht automatisiert werden kann – alles Weitere hängt davon ab. Einige Gewohnheiten helfen dabei:

    • Verteilen Sie die Abfragen auf verschiedene Themenkategorien, anstatt sie um ein einziges Dokument zu gruppieren.
    • Fügen Sie einige Abfragen hinzu, die überhaupt kein Ergebnis liefern sollten, da negative Beispiele falsch-positive Suchergebnisse aufdecken.
    • Erneuern Sie die goldene Sammlung, wenn sich das Korpus erheblich ändert – nicht kontinuierlich. Es handelt sich um eine stabile Referenz und nicht um ein aktuelles Datensatz-Set.

    Die Projektstruktur und der Hauptskript

    Das Repository ist wie folgt organisiert:

    rag-retrieval-audit/
    ├── audit/
    │   ├── __init__.py           # Package exports
    │   ├── rag_audit.py          # Main audit logic — all three checks
    │   └── config.py             # All thresholds and model settings
    ├── examples/
    │   ├── golden_queries.json   # Sample golden set (10 queries)
    │   └── run_audit.py          # End-to-end demo — runs without an existing collection
    ├── tests/
    │   └── test_audit.py         # Unit tests for all scoring functions
    ├── requirements.txt          # sentence-transformers, chromadb, scipy, numpy
    └── README.md                 # Setup, usage, how to read the report
    

    Und hier ist die Struktur von rag_audit.py, der Datei, die alle drei Überprüfungen enthält. Es handelt sich um einen Überblick zur Orientierung und nicht um die vollständige Implementierung.

    # rag_audit.py — structure overview
    # Full implementation: https://github.com/satyam671/rag-retrieval-audit
    
    # ── CONFIGURATION (tune to your pipeline)
    EMBEDDING_MODEL      = "all-MiniLM-L6-v2"  # must match your index
    TOP_K                = 5
    RELEVANCE_THRESHOLD  = 0.70
    DRIFT_P_THRESHOLD    = 0.05
    EFFICIENCY_THRESHOLD = 0.40
    
    # ── CHECK 1: Are the right chunks coming back?
    def score_relevance(query_embedding, chunk_embeddings, expected_ids, retrieved_ids):
        """Cosine similarity per chunk + expected chunk hit/miss against golden set."""
        ...
    # ── CHECK 2: Has retrieval quality shifted over time?
    def detect_drift(current_sims, baseline_path=None):
        """Two-sample KS test comparing current similarity distribution to baseline."""
        ...
    # ── CHECK 3: How much of the context window is signal?
    def score_efficiency(retrieved_docs, answer):
        """Token overlap between retrieved chunks and the LLM answer."""
        ...
    # ── RUNNER
    def run_audit(golden_set_path, collection_name, chroma_persist_dir,
                  baseline_path=None, answers_path=None, output_path="rag_audit_report.json"):
        """Runs all three checks, writes a structured JSON report, saves the baseline."""
        ...
    

    Die README-Datei des Skripts erklärt, wie man es kloniert, darauf eine ChromaDB-Kollektion verweist und es ausführt – einschließlich der Anleitung, wie man einen anderen Vektor-Speicher einsetzt.

    Durchgehen jeder Überprüfung

    Bewertung der Relevanz der Blöcke

    Die Relevanzprüfung erledigt zwei Dinge, die Menschen beim Selbstaufbau oft übersehen.

    Zunächst berechnet sie die Kosinusähnlichkeit direkt aus den Embeddings und verwendet nicht die Entfernungswerte, die ChromaDB zurückgibt. Die Standardmetrik von ChromaDB ist L2, nicht die Kosinusähnlichkeit, und die beiden sind nicht austauschbar. Ihr Mischen ergibt Werte, die sinnvoll erscheinen, aber etwas anderes messen.

    Zweitens bestätigt dies, dass die erwarteten Chunk-IDs des goldenen Sets in den Top-K-Werten auftauchten, und genau das macht das Problem des Chunk-Poisonings sichtbar. Eine durchschnittliche Ähnlichkeit von 0,75 erscheint in Ordnung – bis man erfährt, dass der Chunk, der die Anfrage beantworten sollte, nur 0,62 erreichte und somit auf dem sechsten Platz landete, einen Platz über der Schwellenwertgrenze. Das Modell erhielt nie die richtige Antwort, obwohl die Ähnlichkeitswerte weiterhin akzeptabel aussahen.

    Der praktische Unterschied zwischen Gateing und Nicht-Gateing ist im Code klar erkennbar.

    # WITHOUT the relevance gate — standard approach most teams use
    def build_context_naive(query, collection, top_k=5):
        results = collection.query(
            query_embeddings=[embed(query)],
            n_results=top_k,
            include=["documents"]
        )
        # Pass everything back, no quality check
        return "\n\n".join(results["documents"][0])
    
    # WITH the relevance gate - what the audit tells you to build
    def build_context_gated(query, collection, model, top_k=5, threshold=0.70):
        q_emb   = model.encode([query], normalize_embeddings=True)[0]
        results = collection.query(
            query_embeddings=[q_emb.tolist()],
            n_results=top_k,
            include=["documents", "embeddings", "ids"]
        )
        passed_chunks = []
        for i, chunk_emb in enumerate(results["embeddings"][0]):
            sim = cosine_sim(q_emb, np.array(chunk_emb))
            if sim >= threshold:
                passed_chunks.append(results["documents"][0][i])
        # Empty context is better than wrong context
        return "\n\n".join(passed_chunks)
    

    In der naiven Version werden die am höchsten bewerteten Abschnitte unabhängig von ihrer Qualität berücksichtigt. In der kontrollierten Version wird jeder Abschnitt, der sich seine Position noch nicht verdient hat, zurückgehalten. Eine Antwort wie „Ich habe nicht genügend Informationen, um darauf zu antworten“, ausgehend von einem leeren Kontext, ist besser als eine selbstsichere, falsche Antwort, die auf unzuverlässigen Daten beruht.

    Erkennung von Embedding-Drift

    Die Prüfung auf Drift basiert auf dem Kolmogorov-Smirnov-Test. Er vergleicht die Form der aktuellen Ähnlichkeitsverteilung mit einer gespeicherten Referenz und weist signifikante Abweichungen aus. Warum Verteilungen und nicht Durchschnittswerte? Weil eine Zahl zu viel verbergen kann. Der Durchschnitt könnte bei 0,78 konstant bleiben, während die darunterliegende Verteilung flacher wird und sich ausdehnt – das bedeutet, dass einige Abfragen deutlich schlechter werden, während andere dies ausgleichen. Der KS-Test erkennt Veränderungen in den Verteilungen, die durchschnittliche Werte übersehen.

    Der Beispieldiagramm zeigt die Verschiebung bei der Einbettung in einem produktiven RAG-System sechs Monate nach Korpusaktualisierungen ohne Neindekxisierung: eine KS-Statistik von 0,21 und ein p-Wert von 0,003.

    Eine Einschränkung: Der Test benötigt ausreichend Daten, um zuverlässig zu sein. Bei 15 Abfragen schwanken die p-Werte stark und führen zu falschem Vertrauen, während 50 oder mehr Abfragen ein stabiles Signal liefern. Das ist ein weiterer Grund, warum die Größe des „goldenen Sets“ wichtiger ist, als es scheint.

    Effizienz des Kontextfensters

    Das ist die schwierigste der drei Überprüfungen, da es echte Grenzen gibt. Die Überschneidung auf Wortebene zwischen den abgerufenen Teilen und der Antwort des Modells ist nur ein Indikator. Wenn ein Modell stark umformuliert, wie es Modelle der GPT-4-Klasse tun, unterschätzt die Überschneidung, wie viel vom Kontext tatsächlich verwendet wurde.

    Trotzdem erkennt es systematische Probleme. Wenn die Effizienzwerte konstant unter 40 % liegen, stimmt etwas weiter oben im Prozess nicht: Die Datenblöcke sind zu groß, der Wert von top-K ist zu hoch, oder die Suche zieht thematisch verwandten Text heran, der die Anfrage nicht beantwortet. Die Überprüfung ist nicht präzise, aber sie weist in die richtige Richtung, und das reicht aus, um eine Untersuchung einzuleiten.

    Lesen des Berichts

    Die Ausgabe ist eine JSON-Datei, und folgende Felder verdienen Aufmerksamkeit:

    • flag_rate gibt den Anteil der Anfragen an, bei denen eine Fehlerbedingung auftrat. Ein Wert über 20 % bedeutet, dass bei der Suche nachbessert werden muss, und ein Anstieg von 8 % in einer Woche auf 22 % in der nächsten deutet darauf hin, dass sich etwas geändert hat.
    • drift_analysis.drift_detected ist das zu beobachtende Boolesche Feld. Wenn es true ist, sollte man mean_delta prüfen, um die Richtung festzustellen. Ein negativer Delta-Wert bedeutet, dass die Ähnlichkeit abnimmt, was ein Signal zum Neuindexieren ist.
    • drift_analysis.ks_statistic steigt mit der Größe des Verlagerungsmusters der Verteilung. Werte unter 0,10 deuten wahrscheinlich auf Rauschen hin. Werte über 0,20 stellen ein echtes Problem dar, unabhängig vom p-Wert.
    • query_results[i].relevance.expected_chunk_hit gibt die tatsächliche Referenzwerte pro Abfrage an. Ein false bedeutet, dass der Chunk, der die Abfrage beantworten sollte, nicht zu den Top-K gehörte. Wenn dies bei mehr als 15 % der Referenzdaten der Fall ist, muss der Index neu erstellt werden.

    Das Beispiel wird mit einem Befehl gestartet, wie hier gezeigt.

    python -m examples.run_audit
    

    Ein Cheat Sheet fasst die Prüfung in einem Überblick zusammen.

    Führen Sie sie wöchentlich durch und speichern Sie jede Ausgangswertbasis. Der Trend zwischen den Durchführungen sagt mehr aus als jede einzelne Durchführung.

    Eine Lektion über die Stichprobengröße

    Die Durchführung der Drift-Erkennung an einem 15-Anfragen umfassenden „Golden Set“ ergab sinnlose p-Werte. Der Test lief zwar technisch, war aber unzuverlässig, und es war einfach, zwei Tage lang an einer fehlerhaften statistischen Implementierung zu zweifeln, bevor man erkannte, dass die Daten einfach zu gering waren.

    Bei weniger als 50 Anfragen sind die Drift-Werte nicht vertrauenswürdig, daher sollte zunächst das Datenset erstellt und erst danach die Drift-Prüfung aktiviert werden. Ein Korpus, das mehrere verschiedene Themenbereiche umfasst, erfordert ein „Golden Set“ pro Bereich, der separat geprüft wird, denn eine Verteilung über ein gemischtes Korpus kann einen Rückgang in einem einzelnen Bereich verbergen, der bei einer Einzelprüfung auffallen würde.

    Beschränkungen dieses Ansatzes

    Die Skript prüft, ob die richtigen Abschnitte abgerufen werden. Es beurteilt nicht, ob die Grenzen der Abschnitte gut platziert sind, ob die Größe der Abschnitte zu den Abfragen passt oder ob semantisches Aufteilen anstelle eines fester Größen festgelegten Aufteilens verwendet werden sollte.

    Diese Fragen sind wichtig. NVIDIA’s Benchmarks an fünf Datensätzen, wie in einer FloTorch-Analyse zusammengefasst, zeigten, dass sich die beste Abschnittsgröße je nach Abfragetyp unterscheidet: Faktabfragen erreichen ihren Höhepunkt bei 256 bis 512 Token, während analytische Abfragen 1024 oder mehr Token benötigen. Jede einzelne Abschnittsgröße stellt einen Kompromiss dar, und ein „ideales“ Set, das beide Abfragetypen kombiniert, führt letztendlich zu einem durchschnittlichen Mismatch. Die direkte Überprüfung der Aufteilungsstrategie sowie der Vergleich von semantischem Aufteilen mit fester Größenfestlegung anhand konkreter Zahlen ist eine separate Aufgabe.

    Referenzen

    • Der RAGAS-Artikel zur automatisierten Bewertung von retrieval-augmented Generation (arXiv 2309.15217, 2023; EACL 2024).
    • Die Studie „Verloren im Mittel“ zum Verhalten von Sprachmodellen bei langen Kontexten, veröffentlicht im TACL Band 12 im Jahr 2024.
    • NVIDIAs angewandte Forschung zu Chunking-Strategien für verschiedene Dokumenttypen aus dem Jahr 2024.