DANIEL KIRCHNER
Alle Artikel
7. September 202612 min Lesezeit

RAG in Produktion

RAGProduktionLangChainEvaluation

Ein Prototyp mit RAG muss heute niemanden mehr beeindrucken. LangChain, ein Embedding-Modell, eine Vektordatenbank, drei Tage Arbeit, fertig ist die Demo. Der Unterschied zwischen dieser Demo und einem System, das in einem regulierten Haus produktiv läuft, ist groß. Die Demo beantwortet die Fragen aus dem Testset. Das Produktionssystem muss beantworten, warum es eine Aussage getroffen hat, wer sie prüfen kann und was passiert, wenn die Basis fehlt.

Dieser Text ist der Überblick über den Weg von der Demo zu diesem System. Die einzelnen Stationen habe ich an anderer Stelle im Detail beschrieben, mit Code und mit Befunden aus echten Projekten. Hier stehen sie im Zusammenhang, in der Reihenfolge, in der ich sie in einem Review auch prüfe.

Naives RAG ist keine Produktion

Naives RAG sieht in jedem Tutorial gleich aus: Dokumente laden, in Chunks schneiden, Vektoren schreiben, bei einer Frage die nächsten Chunks suchen und ins Prompt geben. Ich habe das selbst gebaut, in einem Gesundheitsprojekt, mit LangChain, Milvus und GPT-4o. 500+ Videos und PDFs, rund 50.000 Chunks, Antworten unter 100 ms. Schnell war die Pipeline. Richtig war sie nicht automatisch.

Naives Retrieval hat drei Eigenschaften, die in der Demo nicht auffallen und in Produktion jede Woche ein Ticket erzeugen:

  • Chunking nach fester Zeichenzahl, ohne Rücksicht auf Überschriften, Tabellen oder Absätze
  • nur Vektorsuche, kein Keyword-Abgleich für Aktenzeichen, Normnummern oder Produktcodes
  • Top-k Chunks gehen ungeprüft ins Prompt, „ähnlich“ wird wie „belegt“ behandelt
  • Der Unterschied zwischen Demo und Produktion ist nicht Skalierung. Ein System, das für zehn Nutzer falsch antwortet, antwortet für tausend Nutzer genauso falsch, nur öfter. Der Unterschied ist, ob jemand die Antwort später erklären kann. In der Demo fragt das niemand. In Produktion fragt es die Rechtsabteilung, der Kunde oder ein Auditor.

    Chunking: die erste Fehlerquelle

    chunk_size=1000, chunk_overlap=200: dieser Zweizeiler steht in jedem zweiten RAG-Tutorial und stand auch in mindestens einem eigenen Projekt von mir. Er ignoriert die Struktur des Dokuments komplett. Eine Tabelle reißt mitten in der Zeile. Eine Überschrift landet im letzten Drittel eines Chunks, der Rest der Sektion im nächsten. Bei der Suche gewinnt dann der Chunk, der zufällig mehr passende Wörter enthält, nicht der Chunk, der die Antwort trägt.

    Was stattdessen funktioniert:

  • Chunking entlang der Dokumentstruktur: Überschriften, Absätze, Tabellenzeilen bleiben zusammen
  • kleinere Chunks für dichte Fachtexte, größere für Fließtext
  • Metadaten pro Chunk: Dokument, Version, Abschnitt, Seite
  • Strukturbasiertes Chunking heißt konkret: der Splitter kennt die Gliederung, nicht nur die Zeichenzahl.

    from langchain_text_splitters import MarkdownHeaderTextSplitter
    
    headers = [("##", "section"), ("###", "subsection")]
    splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers)
    sections = splitter.split_text(document_markdown)
    

    Jeder Abschnitt trägt jetzt seine Überschrift als Metadatum mit sich, nicht nur den Fließtext. Bei PDFs ohne Markdown-Struktur braucht es vorher eine Layout-Erkennung, die Überschriften, Tabellen und Fußnoten unterscheidet, bevor überhaupt gesplittet wird. Diese Vorstufe kostet Zeit bei der Ingestion. Sie spart die Zeit später, wenn ein Reviewer nachvollziehen will, warum ein Chunk zitiert wurde.

    Der letzte Punkt oben wird oft übersehen. Ohne Metadaten kann niemand später sagen, aus welcher Fassung eines Dokuments eine Antwort stammt. Das ist Kontextmanagement, nicht nur Chunking. Wer eine neue Version eines Handbuchs hochlädt und die alte nicht entfernt, bekommt irgendwann eine Antwort aus der überholten Fassung. Chunk-Qualität schlägt Quantität, aber nur wenn die Herkunft am Chunk hängt.

    Hybrid Retrieval statt reiner Vektorsuche

    Vektorsuche findet Paraphrasen gut. Sie findet keine Normnummer, kein Aktenzeichen, keinen Produktcode zuverlässig, weil Embeddings auf Bedeutung trainiert sind, nicht auf exakte Zeichenketten. Eine Frage nach „§ 14 Abs. 2“ kann semantisch nah an einem völlig anderen Paragraphen liegen und trotzdem als bester Treffer zurückkommen.

    Hybrid-Suche kombiniert deshalb Keyword-Suche, etwa BM25 oder ein Volltextindex, mit Vektorsuche und führt beide Ergebnislisten zusammen:

    def hybrid_search(query: str, vectorstore, bm25_index, k: int = 20):
        vector_hits = vectorstore.similarity_search(query, k=k)
        keyword_hits = bm25_index.search(query, k=k)
    
        seen = {}
        for hit in vector_hits + keyword_hits:
            seen[hit.id] = hit
        return list(seen.values())
    

    Das ist kein fertiges Retrieval, nur der erste Schritt: beide Kanäle liefern, dann wird zusammengeführt und dedupliziert. Wer nur Vektoren nutzt, verliert exakte Treffer. Wer nur Keywords nutzt, verliert Paraphrasen und Synonyme.

    Ein einfaches Zusammenführen wie oben reicht für einen ersten Wurf. Besser ist Reciprocal Rank Fusion: statt Treffer nur zu deduplizieren, gewichtet RRF jeden Treffer nach seinem Rang in beiden Listen, nicht nach dem absoluten Score, weil BM25-Scores und Cosine-Similarity auf völlig unterschiedlichen Skalen liegen und sich nicht einfach addieren lassen.

    def rrf_fuse(vector_ranked: list[str], keyword_ranked: list[str], k: int = 60):
        scores: dict[str, float] = {}
        for rank, doc_id in enumerate(vector_ranked):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
        for rank, doc_id in enumerate(keyword_ranked):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
        return sorted(scores, key=scores.get, reverse=True)
    

    RRF ist kein Ersatz für Reranking danach. Es ist die bessere Vorstufe: eine Rangliste, die beide Suchpfade fair gewichtet, bevor der Reranker die endgültige Reihenfolge nach der Frage bestimmt. Beides zusammen, ohne diese zwei Sortierschritte, liefert trotzdem nur eine längere Liste mit ungefährer Reihenfolge. Das reicht nicht.

    Reranking: die Reihenfolge nach der Frage, nicht nach der Ähnlichkeit

    Ein Reranker nimmt die kombinierte Trefferliste und sortiert sie neu, nach der tatsächlichen Frage, nicht nach Cosine-Similarity oder BM25-Score. Modelle wie Cohere Rerank oder ein selbst-gehostetes Cross-Encoder-Modell bewerten jedes Paar aus Frage und Chunk einzeln:

    def rerank(query: str, candidates: list[Chunk], reranker, top_n: int = 5):
        scored = reranker.score(query, [c.text for c in candidates])
        ranked = sorted(zip(candidates, scored), key=lambda x: x[1], reverse=True)
        return [chunk for chunk, score in ranked[:top_n]]
    

    Ohne diesen Schritt gewinnt oft der lauteste Nachbar: ein Chunk, der viele passende Begriffe enthält, aber die Frage nicht beantwortet. Mit Reranking sinkt die Zahl der falschen Top-Treffer deutlich. Das ist der Grund, warum Hybrid-RAG und Reranking zusammen genannt werden und nicht als zwei getrennte Optionen behandelt werden sollten. Hybrid ohne Reranking ist eine bessere Trefferliste. Hybrid mit Reranking ist ein Retrieval, das eine Antwort tragen kann.

    Quellenbindung: keine Aussage ohne Beleg

    Reranking verbessert die Trefferliste. Es verhindert nicht, dass ein Modell trotzdem etwas erfindet, wenn die Trefferliste leer oder schwach ist. Genau da beginnt Quellenbindung: eine harte Regel im Ablauf, nicht ein höflicher Hinweis im Prompt.

    Drei Anforderungen, die ich in jedem Projekt durchsetze:

  • Jede fachliche Aussage hängt an einem Chunk, den ein Mensch öffnen kann.
  • Fehlt ein passender Chunk, sagt das System das. Es füllt die Lücke nicht mit einer plausiblen Vermutung.
  • Die Oberfläche zeigt den Beleg neben der Antwort, nicht in einem separaten Log, das niemand öffnet.
  • „Sei hilfreich, aber erfinde nichts“ im Prompt reicht nicht. Ein Modell hält sich daran, solange die Frage einfach ist, und weicht ab, sobald die Trefferliste dünn wird. Die Regel muss im Ablauf sitzen: retrieval, prüfen ob genug Belege da sind, erst dann generieren. Fehlt dieser Zwischenschritt, produziert das System irgendwann einen Absatz, der sich liest wie aus der Norm, es aber nicht ist. Details dazu stehen in LLM-Halluzinationen sind ein Compliance-Problem.

    Evaluation: messen statt hoffen

    Jeder Wechsel am System, neues Modell, neuer Prompt, neuer Index, kann die Qualität verändern, in beide Richtungen. Ohne Evaluation merkt das niemand, bis eine Nutzerin sich beschwert oder ein Auditor nachfragt.

    Was ein Evaluation-Set braucht:

  • ein festes Set von Fragen mit erwarteter Quelle oder erwarteter Entscheidung
  • jemand, der das Set pflegt, wenn sich Dokumente ändern
  • einen Lauf nach jedem Modell-, Prompt- oder Index-Wechsel, nicht nur vor dem Launch
  • Werkzeuge wie RAGAS berechnen Kennzahlen wie Kontext-Präzision oder Antworttreue automatisiert. Sie ersetzen nicht die Entscheidung, was gemessen wird. Ein typischer Fund: ein Team wechselt von einem kleineren auf ein größeres Modell, weil die Texte flüssiger klingen. Die Quellenquote fällt gleichzeitig, weil das größere Modell selbstbewusster formuliert, auch ohne Beleg. Ohne ein Set, das diese Quote misst, sieht das niemand vor dem nächsten Audit. Mehr dazu in LLM-Evaluation in Produktion.

    Observability: die Antwort muss sich rekonstruieren lassen

    Eine Beschwerde kommt rein: falsche Antwort, vor drei Tagen, Details vergessen. Ohne Observability ist dieser Fall nicht bearbeitbar. Was pro Anfrage geloggt werden muss, damit ein Lauf sich später nachbauen lässt:

  • welcher Prompt, in welcher Version
  • welche Chunks in welcher Reihenfolge, inklusive Reranking-Score
  • welches Modell, wie viele Token, welche Kosten
  • was die Nutzerin tatsächlich gesehen hat
  • Ein Dashboard mit Token-Summen ist FinOps, nützlich, aber eine andere Frage. Es beantwortet nicht, warum ein bestimmter Satz eine bestimmte Normstelle erfunden hat. Werkzeuge wie Langfuse bilden diesen Pfad ab, Frage zu Chunks zu Antwort, an einem Ort, den ein Reviewer ohne den Chatverlauf der Nutzerin öffnen kann. Der Name des Tools ist zweitrangig. Der Pfad ist die Anforderung. Ausführlicher in Eine schlechte Antwort muss sich rekonstruieren lassen.

    Mandantentrennung: der Pilot ist kein Produkt

    Ein Pilot läuft mit einem Kunden, einem Index, einem Prompt. Der zweite Kunde kommt, und plötzlich liegen zwei Akten im selben Vektorraum. Ohne Mandantentrennung bleibt das System ein Einzelkunden-Werkzeug, egal wie viele Verträge unterschrieben werden.

    Getrennt sein muss der komplette Retrieval-Pfad, nicht nur die Oberfläche:

  • eigener Filter oder eigener Index pro Mandant, im Code erzwungen, nicht in der Query-Absicht
  • keine Chunks über die Mandantengrenze, auch nicht als „ähnlicher“ Kontext
  • Secrets und Keys nicht im gemeinsamen Repo-Default
  • Agent-State, der beim Mandantenwechsel nicht mitwandert
  • Der häufigste Fund ist ein gemeinsamer Vektorraum mit einem Metadata-Filter, der einmal beim Reindexieren fehlt. Dann zitiert die Antwort den Nachbarn, und niemand merkt es, weil die Antwort trotzdem plausibel klingt. Ausführlich mit Codebeispiel in Mandantentrennung für RAG und Agenten.

    Agenten, LangGraph und MCP: dieselbe Regel gilt weiter

    Sobald aus RAG ein Agent wird, der Tools aufruft, ändert sich die Aufgabe, nicht die Regel. LangGraph orchestriert Schritte, Retrieval, Tool-Call, Prüfung, Antwort, als Graph statt als eine Kette. Das macht Zwischenschritte sichtbar und erzwingbar: ein Knoten kann prüfen, ob genug Belege vorliegen, bevor der nächste Knoten überhaupt startet.

    MCP-Server bringen externe Tools in diesen Ablauf, ein Ticketsystem, eine interne API, eine Suchmaschine für Produktdaten. Jeder Tool-Call ist ein zusätzlicher Punkt, an dem eine Aussage entstehen kann, die niemand belegen kann. Ein Agent, der ein MCP-Tool aufruft und das Ergebnis ungeprüft in die Antwort schreibt, hat dasselbe Problem wie ein RAG-System ohne Quellenbindung, nur mit einer zusätzlichen Fehlerquelle. Die Regel aus der Quellenbindung gilt für jeden Knoten im Graphen, nicht nur für den Retrieval-Schritt am Anfang. Wer einen Agenten baut, ohne diese Regel auf jeden Tool-Call anzuwenden, baut ein Retrieval-Problem mit mehr beweglichen Teilen.

    Betrieb: Latenz, Kosten, Skalierung

    In dem Gesundheitsprojekt mit 500+ Videos und PDFs lagen am Ende rund 50.000 Chunks in Milvus, Antworten kamen unter 100 ms zurück. Das war Betrieb, nicht nur Architektur. Betriebskonzept heißt, dass jemand vorher entschieden hat, wie viele Chunks pro Sekunde die Vektordatenbank verarbeiten muss, wie oft neu indexiert wird, wenn sich Dokumente ändern, und was ein Reranking-Schritt an zusätzlicher Latenz kostet.

    Reranking kostet Zeit. Ein Cross-Encoder über zwanzig Kandidaten ist langsamer als eine reine Vektorsuche über fünf. Das ist ein akzeptabler Kompromiss, wenn die zusätzliche Latenz im niedrigen zweistelligen Millisekundenbereich liegt und die Trefferqualität deutlich steigt. Es ist kein akzeptabler Kompromiss, wenn niemand das Budget vorher festgelegt hat und ein Nutzer nach drei Sekunden abbricht.

    Kosten laufen ähnlich unbemerkt aus dem Ruder. Jeder zusätzliche Chunk im Prompt kostet Token, bei jeder Anfrage, nicht nur einmal. Ein Reranking-Schritt, der die Kandidatenliste von zwanzig auf fünf reduziert, bevor der Kontext ins Prompt geht, senkt diese Kosten spürbar und verbessert gleichzeitig die Qualität. Wer das nicht misst, merkt den Anstieg erst auf der Rechnung am Monatsende. Praxis dazu, mit Zahlen aus einem laufenden System: der Gesundheits-Chatbot.

    Der Weg von der Demo zum Betrieb

    Keiner dieser Punkte ist für sich schwer. Hybrid-Suche ist ein zusätzlicher Suchpfad. Reranking ist ein zusätzlicher Aufruf. Quellenbindung ist eine Regel im Code. Zusammen sind es die Bausteine, die zwischen einer Demo und einem System liegen, das ein Auditor durchlässt. Die Reihenfolge, in der ich das in einem Review prüfe, richtige Chunks, Hybrid-Retrieval, Reranking, Quellenbindung, Evaluation, Observability, Mandanten, Betrieb, ist genau die Reihenfolge dieses Texts.

    Wer wissen will, wo ein bestehendes System auf dieser Liste steht, bekommt das im LLM-Readiness-Check: fünf Tage, schriftlicher Befund, ab 5.000 €, ohne Kaufzwang für ein Folgeprojekt. Die Umsetzung selbst läuft unter KI-Engineering.

    Verwandte Artikel

    LLM-Kosten: Warum die Rechnung erst nach dem Rollout kommt

    Kontextlänge, Reranking und Retries treiben die Kosten, nicht das Modell allein. Was ich vor einem LLM-Rollout an Kosten pro Anfrage messe.

    Artikel lesen

    Hybrid-RAG und Reranking statt naiver Vektorsuche

    Chunking nach Zeichenzahl und reine Vektorsuche erzeugen erwartbare Halluzinationen. Was Hybrid-Suche und Reranking ändern, und was der ältere RAG-Artikel falsch lehrt.

    Artikel lesen

    Termin

    30 Minuten. Wenn der Use-Case nicht in den Betrieb kann, sage ich das.