Technischer Leitfaden

RAG reicht nicht: Was zuverlässige KI-Systeme zusätzlich brauchen

Wo RAG in der Produktion versagt und was du darauf aufbauen musst. Chunk-Qualität, Orchestrierung, Hybrid Search, Halluzinationsgrenzen, Kostenmanagement und wann du RAG komplett weglassen solltest.

18. Februar 202618 Min. LesezeitOronts Engineering Team

Die RAG-Demo-Falle

Jede RAG-Demo funktioniert. PDFs hochladen, chunken, embedden, abfragen, Antwort bekommen. Die Demo ist immer beeindruckend. Die Stakeholder sind begeistert. Das Team schätzt zwei Wochen bis zum Produktionsstart.

Sechs Monate später ist das System immer noch unzuverlässig. Nutzer bekommen falsche Antworten aus veralteten Chunks. Das Modell zitiert selbstbewusst Dokumente, die das Gegenteil von dem sagen, was die Antwort behauptet. Die Kosten sind 10x höher als ursprünglich geschätzt. Und niemand kann erklären, warum dieselbe Frage je nach Tageszeit unterschiedliche Antworten liefert.

RAG ist keine Lösung. RAG ist ein Retrieval-Pattern. Ein zuverlässiges KI-System braucht eine Orchestrierungsschicht, Qualitätskontrollen, Hybrid Search, Halluzinationsgrenzen, Kostenmanagement und Monitoring auf RAG drauf. Dieser Artikel behandelt, was wir beim Bau von Produktions-RAG-Systemen gelernt haben.

Für breiteren Kontext zu Enterprise-RAG-Architektur und Vector Search decken diese Guides die grundlegenden Patterns ab. Dieser Artikel fokussiert sich darauf, wo diese Patterns brechen und was du darüber hinaus brauchst.

Wo RAG versagt

FehlermodusWas passiertWie häufig
Chunk-QualitätFalsche Chunk-Grenzen zerreißen Kontext, Antwort basiert auf unvollständiger InformationSehr häufig
Veraltete DatenIndex nicht aktualisiert, Antwort basiert auf veraltetem DokumentHäufig
Retrieval-FehlerRelevantes Dokument existiert, aber Embedding-Ähnlichkeit findet es nichtHäufig
Halluzination trotz RetrievalModell ignoriert abgerufenen Kontext und generiert aus TrainingsdatenHäufig
Context-Window-OverflowZu viele Chunks abgerufen, Modell verliert den FokusMittel
Cross-Document-VerwirrungChunks aus verschiedenen Dokumenten gemischt, Modell vermischt widersprüchliche FaktenMittel
KostenexplosionEmbedding- + Retrieval- + Generierungskosten skalieren mit AbfragevolumenSchleichend
Latenz-SpikesVector Search + Reranking + Generierung dauert zu lang für interaktive NutzungMittel

Chunk-Qualität ist alles

Das am meisten unterschätzte Problem. Wenn deine Chunks einen Absatz mitten im Gedanken zerteilen, ist der abgerufene Kontext unvollständig. Wenn deine Chunks zu groß sind, verwässert irrelevanter Inhalt die nützliche Information. Wenn deine Chunks die Dokumentstruktur nicht bewahren (Überschriften, Tabellen, Listen), verliert das Modell den organisatorischen Kontext.

// Schlecht: Chunks fester Größe zerreißen Kontext
function naiveChunk(text: string, size: number): string[] {
    const chunks = [];
    for (let i = 0; i < text.length; i += size) {
        chunks.push(text.slice(i, i + size));
    }
    return chunks;
    // Problem: zerschneidet Sätze, Absätze, Tabellen mitten im Inhalt
}

// Besser: Semantisches Chunking mit Overlap
function semanticChunk(text: string, options: ChunkOptions): Chunk[] {
    const sections = splitByHeadings(text);      // Dokumentstruktur respektieren
    const paragraphs = sections.flatMap(s =>
        splitByParagraphs(s, { maxSize: options.maxChunkSize })
    );

    return paragraphs.map((p, i) => ({
        content: p.text,
        metadata: {
            section: p.sectionTitle,
            pageNumber: p.pageNumber,
            documentId: p.documentId,
            position: i,
        },
        // Overlap: letzte 2 Sätze des vorherigen Chunks einbeziehen
        prefix: i > 0 ? getLastSentences(paragraphs[i - 1].text, 2) : '',
    }));
}

Das Overlap ist entscheidend. Ohne Overlap bekommt eine Frage, die zwei Chunks überspannt, partiellen Kontext von jedem und eine vollständige Antwort von keinem. Mit 2-3 Sätzen Overlap hat das Modell genug Kontext, um Chunk-Grenzen zu überbrücken.

Chunk-Metadaten sind genauso kritisch. Jeder Chunk muss seine Quelldokument-ID, Abschnittstitel, Seitennummer und Position mitbringen. Ohne Metadaten kannst du dem Nutzer nicht sagen, woher die Antwort stammt. Ohne Quellenangabe ist die Antwort nicht verifizierbar.

Retrieval-Qualität vs. Retrieval-Quantität

Mehr Chunks abzurufen bedeutet nicht bessere Antworten. In der Praxis haben wir festgestellt, dass 3-5 hochwertige Chunks konstant besser abschneiden als 10-15 mittelmäßige.

Abgerufene ChunksAntwortqualitätLatenzKosten
1-2Risiko von fehlendem KontextSchnellNiedrig
3-5Beste Balance (empfohlen)ModeratModerat
5-10Abnehmender Ertrag, etwas RauschenLangsamerHöher
10+Kontext-Verwässerung, Modell verwirrtLangsamHoch

Die Lösung: Breit abrufen, dann aggressiv reranken.

async function retrieveAndRerank(query: string, options: RetrievalOptions) {
    // Schritt 1: Breites Retrieval (20 Kandidaten holen)
    const candidates = await vectorStore.search(query, { limit: 20 });

    // Schritt 2: Reranking mit Cross-Encoder (jeden Kandidaten gegen die Query scoren)
    const reranked = await reranker.rank(query, candidates, {
        model: 'cross-encoder/ms-marco-MiniLM-L-12-v2',
    });

    // Schritt 3: Top 5 nach Reranking nehmen
    const topChunks = reranked.slice(0, 5);

    // Schritt 4: Nach Mindest-Relevanz-Score filtern
    return topChunks.filter(c => c.score > options.minRelevanceScore);
}

Der Reranker ist ein Cross-Encoder-Modell, das jeden Kandidaten gegen die Query mit deutlich höherer Genauigkeit als Cosine Similarity auf Embeddings bewertet. Er ist langsamer (läuft Inferenz pro Kandidat), aber die Qualitätsverbesserung ist erheblich. Ihn auf 20 Kandidaten laufen zu lassen, um 5 auszuwählen, fügt 100-200ms Latenz hinzu, was für die meisten Anwendungsfälle akzeptabel ist.

Die Orchestrierungsschicht, die RAG braucht

Rohes RAG ist: Query embedden, Vektoren durchsuchen, Kontext in den Prompt stopfen, generieren. Ein Produktionssystem braucht eine Orchestrierungsschicht zwischen Retrieval und Generierung.

User Query
    │
    ▼
┌──────────────────┐
│  Query-Analyse    │  Intent klassifizieren, Entitäten extrahieren, Sprache erkennen
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Routing          │  Welcher Index? Welche Retrieval-Strategie? Cache-Treffer?
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Retrieval        │  Vector Search + Keyword Search (hybrid)
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Reranking        │  Cross-Encoder-Scoring, niedrig-relevante Chunks filtern
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Context Assembly │  Chunks ordnen, Metadaten hinzufügen, Token-Budget einhalten
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Generierung      │  LLM-Aufruf mit zusammengebautem Kontext + System Prompt
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Output-Validierung│  Halluzination prüfen, Zitate verifizieren, PII-Scan
└────────┬─────────┘
         │
         ▼
    Antwort

Query-Analyse

Nicht jede Query braucht RAG. Manche Queries sind konversationell ("hallo", "danke"). Manche beziehen sich auf das System selbst ("wie benutze ich dieses Tool?"). Manche sind mehrdeutig und brauchen Klärung. Der Query-Analyzer klassifiziert den Intent, bevor Retrieval ausgelöst wird.

async function analyzeQuery(query: string): Promise<QueryAnalysis> {
    // Schnelle Klassifikation (kann ein kleines Modell oder regelbasiert sein)
    const intent = await classifyIntent(query);

    if (intent === 'greeting' || intent === 'meta') {
        return { needsRetrieval: false, intent, response: getStaticResponse(intent) };
    }

    if (intent === 'ambiguous') {
        return { needsRetrieval: false, intent, clarificationNeeded: true };
    }

    return {
        needsRetrieval: true,
        intent,
        extractedEntities: await extractEntities(query),
        detectedLanguage: await detectLanguage(query),
    };
}

Routing

Verschiedene Queries können verschiedene Indizes, verschiedene Retrieval-Strategien oder verschiedene Modelle erfordern.

Query-TypIndexStrategieModell
ProduktfrageProducts-IndexHybrid (Text + Vektor)Schnelles Modell (GPT-4o-mini)
Rechts-/Compliance-FragePolicies-IndexNur Vektor (präzise)Genaues Modell (GPT-4o)
Technischer SupportKnowledge-Base-IndexHybrid + RerankSchnelles Modell
Mehrsprachige QueryMultilingual-IndexVektor mit SprachfilterMultilinguales Modell

Context Assembly

Nach Retrieval und Reranking müssen Chunks zu einem Prompt zusammengebaut werden, der das Token-Budget des Modells respektiert.

function assembleContext(chunks: RankedChunk[], tokenBudget: number): string {
    let context = '';
    let tokensUsed = 0;

    for (const chunk of chunks) {
        const chunkTokens = estimateTokens(chunk.content);
        if (tokensUsed + chunkTokens > tokenBudget) break;

        context += `\n\n---\nSource: ${chunk.metadata.documentTitle} (${chunk.metadata.section})\n`;
        context += chunk.content;
        tokensUsed += chunkTokens;
    }

    return context;
}

Das Token-Budget muss den System Prompt, die User Query, den zusammengebauten Kontext UND die erwartete Antwortlänge berücksichtigen. Ein häufiger Fehler ist, das gesamte Context Window mit abgerufenen Chunks zu füllen und keinen Platz für eine qualitativ hochwertige Antwort zu lassen.

Hybrid Search: Text + Vektor

Reine Vector Search versagt bei keyword-spezifischen Queries. Ein Nutzer, der nach "Fehlercode E-4021" sucht, bekommt schlechte Ergebnisse von Embedding-Ähnlichkeit, weil Fehlercodes semantisch nicht aussagekräftig sind. Reine Textsuche versagt bei semantischen Queries. Ein Nutzer, der nach "wie löse ich Login-Probleme" sucht, findet kein Dokument mit dem Titel "Authentifizierungs-Fehlerbehebungsleitfaden."

Hybrid Search kombiniert beides:

async function hybridSearch(query: string, options: SearchOptions) {
    // Parallele Ausführung
    const [vectorResults, textResults] = await Promise.all([
        vectorStore.search(query, { limit: options.vectorLimit }),
        textIndex.search(query, { limit: options.textLimit }),
    ]);

    // Reciprocal Rank Fusion (RRF) zum Zusammenführen der Ergebnisse
    const merged = reciprocalRankFusion(vectorResults, textResults, {
        vectorWeight: 0.6,
        textWeight: 0.4,
    });

    return merged.slice(0, options.totalLimit);
}

function reciprocalRankFusion(
    vectorResults: SearchResult[],
    textResults: SearchResult[],
    weights: { vectorWeight: number; textWeight: number },
): SearchResult[] {
    const scores = new Map<string, number>();
    const k = 60; // RRF-Konstante

    vectorResults.forEach((result, rank) => {
        const score = (scores.get(result.id) || 0) + weights.vectorWeight / (k + rank + 1);
        scores.set(result.id, score);
    });

    textResults.forEach((result, rank) => {
        const score = (scores.get(result.id) || 0) + weights.textWeight / (k + rank + 1);
        scores.set(result.id, score);
    });

    return Array.from(scores.entries())
        .sort(([, a], [, b]) => b - a)
        .map(([id, score]) => ({ id, score }));
}

Das Gewichtsverhältnis (Vektor 0.6, Text 0.4) ist ein Ausgangspunkt. Passe es an deine Query-Verteilung an. Wenn die meisten Queries keyword-lastig sind (Produkt-SKUs, Fehlercodes), erhöhe das Textgewicht. Wenn die meisten Queries natürliche Sprache sind, erhöhe das Vektorgewicht.

Für mehr zu Such-Architektur im Commerce-Kontext, schau dir unseren E-Commerce-Plattformen-Guide an.

Halluzinationsgrenzen

RAG reduziert Halluzinationen im Vergleich zu reiner LLM-Generierung. Es eliminiert sie nicht. Das Modell kann immer noch:

  • Abgerufenen Kontext ignorieren und aus Trainingsdaten generieren
  • Informationen aus mehreren Chunks falsch vermischen
  • Zitate erfinden, die im abgerufenen Kontext nicht existieren
  • Über das hinaus extrapolieren, was der Kontext belegt

Gegenmaßnahmen

1. Eingeschränkte System Prompts:

You are a support assistant. Answer ONLY based on the provided context.
If the context does not contain enough information to answer, say
"I don't have enough information to answer that question."
Do NOT use information from your training data.
Every claim must reference a specific source from the context.

2. Zitatverifizierung:

async function verifyCitations(response: string, chunks: RankedChunk[]): VerificationResult {
    const citations = extractCitations(response);
    const verified = [];
    const unverified = [];

    for (const citation of citations) {
        const found = chunks.some(chunk =>
            chunk.content.includes(citation.claimedText) ||
            fuzzyMatch(chunk.content, citation.claimedText, 0.85)
        );
        (found ? verified : unverified).push(citation);
    }

    return {
        allVerified: unverified.length === 0,
        verified,
        unverified,
        confidenceScore: verified.length / (verified.length + unverified.length),
    };
}

3. Confidence Scoring:

Wenn die Antwort des Modells nicht gut mit dem abgerufenen Kontext übereinstimmt (geringer Overlap, keine direkten Zitate), markiere sie als niedriges Vertrauen. Zeige dem Nutzer eine Warnung oder eskaliere an einen Menschen.

Für mehr zu KI-Fehlermodi und wie du damit umgehst, schau dir unseren KI-Fehlermodi-Guide an.

Kosten und Latenz

RAG-Kosten skalieren mit dem Abfragevolumen über drei Dimensionen:

KomponenteKostentreiberTypischer Bereich
Embedding der QueryPro Query (Modell-Inferenz)$0,0001 pro Query
Vector SearchPro Query (Compute + I/O)$0,0005 pro Query
RerankingPro Query * Kandidaten (Modell-Inferenz)$0,001 pro Query
LLM-GenerierungInput-Tokens (Kontext) + Output-Tokens$0,01-0,10 pro Query
Embedding von DokumentenEinmalig pro Dokument (beim Ingest)$0,0001 pro Seite

LLM-Generierung dominiert die Kosten. Die Kontextgröße reduzieren (weniger Chunks, kürzere Chunks) senkt direkt die teuerste Komponente.

Caching-Strategien

// Semantischer Cache: Antworten für ähnliche Queries cachen
async function cachedQuery(query: string): Promise<string | null> {
    // Query embedden
    const queryEmbedding = await embedder.embed(query);

    // Cache-Index nach ähnlichen Queries durchsuchen
    const cached = await cacheIndex.search(queryEmbedding, {
        minSimilarity: 0.95,  // Hoher Schwellenwert für Cache-Treffer
        limit: 1,
    });

    if (cached.length > 0) {
        return cached[0].response;  // Cache-Treffer
    }

    return null;  // Cache-Fehler, volle RAG-Pipeline ausführen
}

Semantisches Caching funktioniert, weil viele Nutzer ähnliche Fragen auf leicht unterschiedliche Weise stellen. "Wie setze ich mein Passwort zurück?" und "Anleitung zum Passwort-Reset" sind verschiedene Strings, aber semantisch identisch. Ein Ähnlichkeitsschwellenwert von 0.95 stellt sicher, dass nur nahezu identische Queries gecachte Antworten bekommen.

Latenz-Budget

Für interaktive Nutzung (Chatbot, Support-Assistent) muss die gesamte Pipeline in unter 3 Sekunden abgeschlossen sein:

PhaseBudgetOptimierung
Query-Analyse50msRegelbasiert oder kleines Modell
Cache-Prüfung30msIn-Memory-Vektor-Index
Vector Search100msDedizierter Such-Cluster
Textsuche100msParallel zur Vector Search
Reranking200msKleiner Cross-Encoder, Kandidaten limitieren
Context Assembly10msIn-Memory
LLM-Generierung1.500msStreaming, schnelles Modell
Output-Validierung100msRegelbasiert + kleines Modell
Gesamt~2.100ms

Die LLM-Antwort an den Nutzer zu streamen, während die Generierung läuft, macht die wahrgenommene Latenz deutlich geringer. Der Nutzer sieht die ersten Tokens in 300-500ms, obwohl die vollständige Antwort 1.500ms dauert.

Wann du RAG komplett weglassen solltest

RAG ist nicht immer das richtige Pattern. Manchmal funktionieren einfachere Ansätze besser:

SzenarioBesserer AnsatzWarum
Statische FAQ (< 50 Fragen)Keyword-Match + Template-AntwortSchneller, günstiger, deterministisch
Strukturierte DatenabfragenSQL-/API-Abfrage + TemplateLLM fügt Latenz und Halluzinationsrisiko hinzu
Echtzeitdaten (Aktienkurse, Bestand)Direkter API-AufrufEmbeddings sind per Definition veraltet
Einfache KlassifikationFeinabgestimmter ClassifierGünstiger, schneller, zuverlässiger
DokumentenzusammenfassungDirekter LLM-Aufruf (kein Retrieval)Das vollständige Dokument IST der Kontext

RAG macht Sinn, wenn du eine große Wissensbasis hast (Hunderte bis Tausende von Dokumenten), natürlichsprachliche Queries, die nicht per Keyword gematchet werden können, und den Bedarf nach synthetisierten Antworten aus mehreren Quellen. Wenn dein Anwendungsfall nicht zu diesem Profil passt, wird ein einfacherer Ansatz zuverlässiger und günstiger sein.

Wie wir diese Architekturentscheidungen in unserer KI-Services-Praxis angehen und welche breiteren Patterns es im KI-Workflow-Design gibt, erfährst du auf diesen Seiten.

Häufige Fallstricke

  1. Chunks fester Größe. Chunks, die Absätze, Tabellen oder Codeblöcke mitten im Inhalt zerschneiden, produzieren unbrauchbares Retrieval. Nutze semantisches Chunking, das die Dokumentstruktur respektiert.

  2. Kein Chunk-Overlap. Ohne Overlap bekommen Queries, die Chunk-Grenzen überspannen, partiellen Kontext von jedem Chunk und eine vollständige Antwort von keinem.

  3. Kein Reranking. Embedding-Ähnlichkeit ist ein grober Filter. Ein Cross-Encoder-Reranker verbessert die Qualität der Top-5-Ergebnisse dramatisch.

  4. Gesamtes Context Window füllen. Lass Platz für den System Prompt, die User Query UND die erwartete Antwort. Ein Prompt, der mit 15 Chunks vollgestopft ist, lässt keinen Raum für eine qualitativ hochwertige Antwort.

  5. Kein Hybrid Search. Reine Vector Search versagt bei Keyword-Queries (Fehlercodes, Produkt-SKUs). Reine Textsuche versagt bei semantischen Queries. Nutze beides.

  6. Kein semantisches Caching. Ähnliche Fragen von verschiedenen Nutzern lösen jedes Mal die volle RAG-Pipeline aus. Ein semantischer Cache mit 0.95 Ähnlichkeitsschwellenwert reduziert die Kosten erheblich.

  7. RAG-Output ohne Verifizierung vertrauen. RAG reduziert Halluzinationen. Es eliminiert sie nicht. Verifiziere Zitate gegen den abgerufenen Kontext. Markiere unverifizierte Behauptungen.

  8. Kein Monitoring. Du musst Retrieval-Qualität tracken (wurden die richtigen Chunks abgerufen?), Antwortqualität (fand der Nutzer die Antwort hilfreich?), Latenz, Kosten pro Query und Cache-Trefferquote.

Zentrale Erkenntnisse

  • RAG ist ein Retrieval-Pattern, keine Lösung. Ein zuverlässiges System braucht Query-Analyse, Routing, Hybrid Search, Reranking, Context Assembly, Generierung und Output-Validierung auf dem Retrieval drauf.

  • Chunk-Qualität bestimmt Antwortqualität. Semantisches Chunking mit Overlap, Metadaten und Bewahrung der Dokumentstruktur ist das Fundament. Alles andere baut auf guten Chunks auf.

  • Breit abrufen, aggressiv reranken. 20 Kandidaten per Embedding-Ähnlichkeit holen. Mit einem Cross-Encoder scoren. Die Top 5 nehmen. Nach Mindest-Relevanz filtern.

  • Hybrid Search fängt auf, was Vektoren verpassen. Keywords, Fehlercodes, Produkt-IDs und exakte Treffer brauchen Textsuche. Semantische Queries brauchen Vector Search. Nutze beides mit Reciprocal Rank Fusion.

  • LLM-Generierung dominiert die Kosten. Die Kontextgröße reduzieren (weniger, bessere Chunks) ist die effektivste Kostenoptimierung.

  • Manchmal ist RAG das falsche Pattern. Statische FAQs, strukturierte Datenabfragen, Echtzeitdaten und einfache Klassifikation haben alle einfachere, zuverlässigere Lösungen.

Wir bauen Produktions-RAG-Systeme als Teil unserer KI-Services und Data-Engineering-Praxis. Wenn du ein RAG-System baust oder eines debuggst, das unzuverlässig ist, sprich mit unserem Team oder fordere ein Angebot an. Du kannst auch unsere Methodologie-Seite besuchen, um zu erfahren, wie wir KI-Projekte angehen.

Behandelte Themen

RAG ProduktionRAG LimitierungenKI-ZuverlässigkeitRetrieval Augmented Generation ProblemeRAG-ArchitekturHybrid SearchRAG-OrchestrierungRAG Halluzination

Bereit, produktionsreife KI-Systeme zu bauen?

Unser Team ist spezialisiert auf produktionsreife KI-Systeme. Lass uns besprechen, wie wir deinem Unternehmen helfen können.

Gespräch starten