Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Künstliche Intelligenz 04.06.2026 · 10 min Lesezeit

RAG produktiv betreiben: Chunking, Hybrid-Search, Reranking und Antwortqualität messen

Warum RAG-Prototypen scheitern und wie du die Produktiv-Pipeline richtig aufbaust: hierarchisches Chunking, BM25+Vektor Hybrid-Search mit RRF, Cross-Encoder-Reranking und Evaluation mit RAGAS und DeepEval.

Futuristische 3D-Darstellung einer RAG-Datenpipeline mit leuchtenden Knoten und Ranking-Metriken

Retrieval-Augmented Generation klingt in der Theorie einfach: Dokumente einlesen, Embeddings erzeugen, bei einer Nutzeranfrage die passenden Chunks finden und dem LLM mitgeben. In der Praxis liefern mehr als die Hälfte aller RAG-Systeme beim produktiven Einsatz enttäuschende Ergebnisse – nicht weil das Embedding-Modell falsch gewählt wurde, sondern weil Chunking-Strategie, Search-Ansatz und Qualitätsmessung fehlen oder falsch konfiguriert sind. Diese Anleitung zeigt dir, wie du eine belastbare Produktiv-Pipeline aufbaust: vom richtigen Chunking über Hybrid-Search mit Reciprocal Rank Fusion bis zu Cross-Encoder-Reranking und messbarer Eval mit RAGAS und DeepEval.

Voraussetzungen

  • Python 3.10+ Umgebung (venv oder Conda)
  • Qdrant-Instanz lokal via Docker (docker run -p 6333:6333 qdrant/qdrant) oder Qdrant Cloud – mindestens Version 1.10 für native RRF-Unterstützung
  • API-Key für ein Embedding-Modell (OpenAI text-embedding-3-small/-large, Cohere embed-v4 oder self-hosted sentence-transformers/all-MiniLM-L6-v2)
  • API-Key für Cross-Encoder-Reranking: Cohere Rerank 3.5 oder Voyage rerank-2.5 (alternativ lokales ms-marco-MiniLM-L-6-v2 via sentence-transformers)
  • API-Key für Judge-LLM (GPT-4o empfohlen für RAGAS/DeepEval)
  • Dokumentkorpus bereinigt: Duplikate entfernt, Boilerplate gefiltert, Tabellen strukturell extrahiert
  • Mindestens 50 handgelabelte Query-Answer-Paare für die Eval-Baseline (oder RAGAS-Testset-Generator nutzen)
  • Python-Pakete: pip install qdrant-client ragas deepeval llama-index cohere voyageai sentence-transformers pdfplumber camelot-py

Schritt 1: Chunking-Strategie richtig wählen

Das größte Missverständnis im RAG-Aufbau: semantisches Chunking klingt fortschrittlich, erzeugt aber im Schnitt nur 43 Tokens pro Chunk – zu wenig Kontext, damit das LLM korrekte Antworten formulieren kann. Eine NAACL-2025-Auswertung zeigte, dass einfache Fixed-200-Wort-Chunks gleichwertige oder bessere Retrieval-Ergebnisse liefern als semantisches Chunking, bei deutlich geringeren Kosten.

Der Produktivansatz 2025/2026 ist hierarchisches Chunking (auch „Small-chunk-retrieve, large-chunk-generate" genannt): Kleine Leaf-Chunks (128–256 Tokens) werden für das Retrieval verwendet, weil sie präzise Treffer liefern. Die zugehörigen Parent-Chunks (512–2048 Tokens) werden dem LLM übergeben, weil es dort den nötigen Kontext für eine korrekte Antwort findet. Das reduziert Retrieval-Aufrufe nachweislich um 30–40 % gegenüber naivem Flat-Chunking.

AnsatzChunk-Größe (Tokens)StärkeSchwäche
Fixed-Size512–1024 + 10 % OverlapEinfach, reproduzierbarKein semantischer Zusammenhang
Semantisch (Sentence-Transformer)Ø 43 (zu klein)Natürliche GrenzenZu wenig LLM-Kontext
Hierarchisch (empfohlen)128 Leaf / 512–2048 ParentPräzises Retrieval + voller KontextKomplexeres Setup, mehr Speicher
Satzweise (strukturierte Docs)1–3 SätzeGut für API-Docs, GesetzeZu wenig Kontext bei langen Sätzen

Hierarchisches Chunking mit LlamaIndex lässt sich schnell einrichten – der HierarchicalNodeParser übernimmt die gesamte Logik:

from llama_index.core.node_parser import HierarchicalNodeParser
from llama_index.core import VectorStoreIndex

parser = HierarchicalNodeParser.from_defaults(
    chunk_sizes=[2048, 512, 128]  # Parent -> Middle -> Leaf
)
nodes = parser.get_nodes_from_documents(documents)
# Leaf-Nodes (128 Token) fuer Retrieval,
# Parent-Nodes (512/2048 Token) fuer LLM-Antwort

Schritt 2: PDF-Tabellen korrekt extrahieren

Tabellen sind der häufigste Grund für fehlerhafte RAG-Antworten. Ein einfacher Roh-Text-Parser linearisiert Tabellenzeilen zu unstrukturiertem Text – das LLM liest dann Zahlen aus dem falschen Kontext. Die Lösung ist eine zweistufige Extraktions-Pipeline:

import camelot
import pdfplumber

def extract_tables(pdf_path: str) -> list[str]:
    tables_md = []
    # Stufe 1: Camelot lattice mode fuer bordered Tabellen
    try:
        tables = camelot.read_pdf(pdf_path, flavor="lattice")
        for t in tables:
            tables_md.append(t.df.to_markdown(index=False))
    except Exception:
        pass
    # Stufe 2: pdfplumber als robuster Fallback
    if not tables_md:
        with pdfplumber.open(pdf_path) as pdf:
            for page in pdf.pages:
                for table in page.extract_tables():
                    tables_md.append(
                        "\n".join(" | ".join(str(c) for c in row)
                                  for row in table if row)
                    )
    return tables_md
# Markdown-Tabellen vor dem Chunking im Dokument einfuegen

Schritt 3: Qdrant-Collection für Hybrid-Search einrichten

Für eine vollständige Hybrid-Produktiv-Pipeline brauchst du in Qdrant drei Vektor-Typen: Dense-Vektoren (Cosine-Similarity für semantische Suche), Sparse-Vektoren (BM25 für Exact-Match-Retrieval) und optional Late-Interaction-Vektoren (ColBERT für Reranking). Ab Qdrant v1.10 ist RRF-Fusion server-seitig ohne Kostenbeschränkung verfügbar:

from qdrant_client import QdrantClient
from qdrant_client.models import (
    VectorParams, Distance, SparseVectorParams, SparseIndexParams,
    MultiVectorConfig, MultiVectorComparator
)

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="hybrid_rag",
    vectors_config={
        "dense": VectorParams(size=384, distance=Distance.COSINE),
        "colbert": VectorParams(size=96, distance=Distance.COSINE,
                               multivector_config=MultiVectorConfig(
                                   comparator=MultiVectorComparator.MAX_SIM))
    },
    sparse_vectors_config={
        "sparse": SparseVectorParams(
            index=SparseIndexParams(on_disk=False)
        )
    }
)
# dense  = sentence-transformers/all-MiniLM-L6-v2 (384d)
# sparse = qdrant/bm25
# colbert = answerdotai/answerai-colbert-small-v1 (96d)

Schritt 4: Hybrid-Search mit RRF-Fusion abfragen

Der entscheidende Fehler bei naivem Hybrid-Search: BM25 gibt unbegrenzt positive ganze Zahlen zurück, Cosine-Similarity liegt in [−1, 1]. Addierst du beide Scores direkt, dominiert BM25 immer. Die Lösung ist Reciprocal Rank Fusion (RRF) – ein rankbasiertes Verfahren, das ausschließlich auf Positionen, nicht auf Rohwerten arbeitet.

Die RRF-Formel lautet: score(d) = Σ 1 / (k + rank(d)) mit Standard-k=60 (Elasticsearch-Default). Ein Dokument auf Rang 1 trägt 0,0164 bei, auf Rang 100 nur 0,00625 – Faktor 2,6x. In Qdrant nutzt du die Query-API:

from qdrant_client.models import Prefetch, FusionQuery, Fusion

results = client.query_points(
    collection_name="hybrid_rag",
    prefetch=[
        Prefetch(query=dense_vector, using="dense", limit=50),
        Prefetch(query=sparse_vector, using="sparse", limit=50),
    ],
    query=FusionQuery(fusion=Fusion.RRF),  # k=60 Standard
    limit=10,
    with_payload=True
)
# Hybrid liefert NDCG=0.7068 vs. BM25=0.6983 / KNN=0.6953 (WANDS-Benchmark)

Wenn du RRF manuell debuggen möchtest, sind es drei Zeilen Python:

def rrf_score(rank: int, k: int = 60) -> float:
    return 1.0 / (k + rank)

def rrf_fusion(result_lists: list[list[str]]) -> dict[str, float]:
    scores: dict[str, float] = {}
    for result_list in result_lists:
        for rank, doc_id in enumerate(result_list, start=1):
            scores[doc_id] = scores.get(doc_id, 0) + rrf_score(rank)
    return dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))

Wichtig für Weaviate-Nutzer: Ab v1.24 ist der Fusion-Default von RRF auf „Relative Score Fusion" gewechselt. Bestehender Code ohne explizites fusionType-Feld ändert das Verhalten beim Upgrade still – ohne Fehlermeldung. Immer explizit setzen.

Schritt 5: Cross-Encoder-Reranking hinzufügen

Hybrid-Search mit RRF ist gut, aber ein Cross-Encoder macht den entscheidenden Qualitätssprung: Laut Benchmark erreicht die Zwei-Stufen-Pipeline (Hybrid Retrieval + Cross-Encoder Reranking) einen Recall@5 von 0,816 und MRR@3 von 0,605. Das Reranking allein bringt +17,2 Prozentpunkte MRR@3 und +12,1 Prozentpunkte Recall@5 gegenüber unreranked.

ModellSprachenKontext-FensterBesonderheiten
Cohere Rerank 3.5100+StandardTabellen, JSON, Code
Voyage rerank-2.5 (Aug. 2025)Mehrsprachig32.000 Tokens+7,94 % vs. Cohere (93 Datasets), bis 1.000 Docs/Aufruf
ms-marco-MiniLM-L-6-v2Englisch512 TokensSelf-hosted, kostenlos
import cohere
co = cohere.Client("YOUR_API_KEY")

results = co.rerank(
    model="rerank-v3.5",
    query=user_query,
    documents=[chunk.text for chunk in hybrid_candidates],  # top-50 bis 200
    top_n=5,
    return_documents=True
)

# Voyage rerank-2.5 (32K Token Kontextfenster):
# import voyageai
# vo = voyageai.Client()
# vo.rerank(query, docs, model="rerank-2.5", top_k=5)

Die Empfehlung für Top-K: Hybrid-Retrieval mit K=50, dann Cross-Encoder auf Top-5 reduzieren. Zu niedriges K (3) verfehlt die richtige Antwort; zu hohes K (50+) ohne Reranking füllt das Context-Window mit irrelevantem Text und erhöht Halluzinationen. Einen soliden Einstieg in die zugrunde liegende Vektordatenbank liefert die Anleitung Qdrant lokal mit Embeddings einrichten.

Schritt 6: Evaluation mit RAGAS und DeepEval einrichten

Ohne Messung weißt du nicht, ob deine Optimierungen wirklich helfen. BLEU und ROUGE taugen nicht für RAG: Sie erkennen weder faktische Fehler noch Retrieval-Schwächen. Der empfohlene Workflow: RAGAS für die Generierung eines synthetischen Testsets (Golden Dataset), DeepEval für laufende CI/CD-Integration und Quality-Gates.

RAGAS – Synthetischen Testset generieren:

from ragas.testset.generator import TestsetGenerator
from ragas.testset.evolutions import simple, reasoning, multi_context
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

generator = TestsetGenerator.from_langchain(
    generator_llm=ChatOpenAI(model="gpt-4o"),
    critic_llm=ChatOpenAI(model="gpt-4o"),
    embeddings=OpenAIEmbeddings()
)
testset = generator.generate_with_langchain_docs(
    documents,
    test_size=50,
    distributions={simple: 0.5, reasoning: 0.25, multi_context: 0.25}
)

DeepEval – 5 RAG-Metriken in einem Evaluate-Call:

from deepeval import evaluate
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
    FaithfulnessMetric, AnswerRelevancyMetric,
    ContextualRelevancyMetric, ContextualRecallMetric,
    ContextualPrecisionMetric
)

metrics = [
    FaithfulnessMetric(threshold=0.7, model="gpt-4", include_reason=True),
    AnswerRelevancyMetric(threshold=0.7, model="gpt-4"),
    ContextualRelevancyMetric(threshold=0.7, model="gpt-4"),
    ContextualRecallMetric(threshold=0.7, model="gpt-4"),
    ContextualPrecisionMetric(threshold=0.7, model="gpt-4"),
]

test_cases = []
for user_input in test_inputs:
    actual_output, retrieval_context = your_rag_pipeline(user_input)
    test_cases.append(LLMTestCase(
        input=user_input,
        actual_output=actual_output,
        expected_output=expected_outputs[user_input],
        retrieval_context=retrieval_context
    ))

evaluate(test_cases=test_cases, metrics=metrics)

Für CI/CD-Integration bietet DeepEval ein pytest-Plugin – Quality-Gates blockieren automatisch Deployments unter dem Schwellwert:

# test_rag.py
import pytest
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import FaithfulnessMetric

@pytest.mark.parametrize("test_case", test_cases)
def test_faithfulness(test_case):
    assert_test(test_case, [FaithfulnessMetric(threshold=0.7)])

# Ausfuehren:
# deepeval test run test_rag.py

Die konkreten Produktionsziele für deine Metriken im Überblick:

MetrikProduktionszielNicht ship-fähig unterKritische Anwendungen
Faithfulness≥ 0,750,70≥ 0,90 (DeepEval)
Answer Relevancy≥ 0,80
Context Precision≥ 0,70
Context Recall≥ 0,80

Das Monitoring der laufenden Produktionsdaten lässt sich gut in ein bestehendes Observability-Stack integrieren – eine bewährte Lösung zeigt die Anleitung Grafana-Dashboards bauen und Datenquellen einbinden.

Troubleshooting / Typische Fehler

  • Retrieval-Qualität bricht nach Embedding-Modell-Update ein: Alte und neue Embeddings liegen in verschiedenen Räumen – Einbruch bis −20 %. Lösung: Embeddings versionieren, bei Modellwechsel kompletten Re-Index anstoßen, vorher A/B-Test durchführen.
  • BM25 dominiert beim Score-Mischen: BM25 gibt positive ganze Zahlen zurück, Cosine-Similarity liegt in [−1, 1]. Nie Rohscores addieren – ausschließlich RRF verwenden.
  • Context-Window-Overflow durch naive Chunk-Konkatenation: Kritische Informationen am Ende werden abgeschnitten. Lösung: Relevanz-basiertes Pre-Filtering, Chunk-Kompression, Budget-Reservierung für System-Prompt und Query.
  • Kein Permission-Filtering: Mitarbeiter erhalten Kontext aus vertraulichen Dokumenten, weil der Vektorspeicher Zugriffsrechte ignoriert. Metadata-Filter mit User-Rolle/Tenant-ID bei jeder Query zwingend anwenden.
  • Duplicate Content und Boilerplate im Index: Seitenkopf, -fuß oder wiederholte Disclaimer verdrängen relevante Chunks. Deduplizierung und Boilerplate-Filter vor dem Indexing einrichten, regelmäßigen Corpus-Audit durchführen.
  • Weaviate v1.24+ Fusion-Default-Änderung: Ab v1.24 ist Relative Score Fusion der Default – nicht RRF. Immer fusionType explizit setzen, sonst ändert sich das Verhalten beim Upgrade ohne Fehlermeldung.
  • Evaluation nur mit BLEU/ROUGE: Diese Metriken erkennen keine faktischen Fehler. Faithfulness + Contextual Recall via RAGAS/DeepEval sind Pflicht.
  • Falsches Top-K: K=3 verfehlt die richtige Antwort; K=50 ohne Reranking überschwemmt das Context-Window. Empfehlung: Hybrid-Retrieval K=50, Cross-Encoder reduziert auf Top-5.

Häufige Fragen

Welche Chunk-Größe ist ein guter Startpunkt?

Für die meisten Text-Korpora: 512 Tokens für Retrieval-Chunks mit 10 % Overlap (ca. 50 Tokens), eingebettet in Parent-Chunks von 1024–2048 Tokens für die LLM-Antwort. Eine Januar-2026-Analyse zeigte, dass Overlap in manchen Setups keinen messbaren Vorteil bringt – hier lohnt sich ein eigener A/B-Test. Bei stark strukturierten Dokumenten wie API-Docs oder Gesetzestexten satzweise Segmentierung testen.

Muss ich einen Cross-Encoder-Reranker selbst hosten?

Nein. Cohere Rerank 3.5 und Voyage rerank-2.5 sind als API verfügbar, unterstützen Tabellen, JSON und Code, und verursachen typischerweise nur 50–150 ms Zusatzlatenz für 50 Kandidaten. Selbst-Hosting lohnt sich erst bei sehr hohem Abfragevolumen oder strengen Datenschutzanforderungen – dann bietet sich ms-marco-MiniLM-L-6-v2 via sentence-transformers an.

Was ist der Unterschied zwischen RAGAS und DeepEval?

RAGAS (Open-Source, Python) bietet das konzeptionelle Metriken-Framework und einen synthetischen Testset-Generator, der aus deinen eigenen Dokumenten Query-Answer-Paare erzeugt. DeepEval (Open-Source-Core + Cloud-Option) bietet tiefere CI/CD-Integration via pytest, Quality-Gates und Produktions-Monitoring. Empfohlener Workflow: RAGAS für die Golden-Dataset-Generierung, DeepEval für laufende Pipeline-Tests.

Wie oft sollte der Vektorindex aktualisiert werden?

Das hängt vom Dokument-Turnover ab: Bei täglich ändernden Quellen Inkremental-Indexing mit Timestamp-Metadaten einrichten; bei stabilen Korpora reicht wöchentliches Re-Indexing. Bei einem Embedding-Modell-Wechsel ist ein kompletter Re-Index zwingend. Speichere immer ein Versionsfeld im Payload, um Embedding-Drift frühzeitig erkennen zu können.

Ab wann ist Hybrid-Search gegenüber reinem Vektor-Search klar besser?

Sobald Nutzer nach Eigennamen, Produktnummern, Fehlercodes oder Akronymen suchen – BM25 findet Exact-Matches, die Vektor-Search regelmäßig verfehlt. Hybrid-Search ist fast immer gleich gut oder besser; der Mehraufwand ist gering (RRF sind drei Zeilen Code). Einzige Ausnahme: sehr homogene Korpora mit hochwertigen Domain-Embeddings.

Wie viele Beispiel-Queries brauche ich für ein aussagekräftiges Eval?

Mindestens 50 handgelabelte Query-Answer-Paare für eine sinnvolle Baseline. Unter 20 Paaren sind Schwankungen zu groß. Mit dem RAGAS-Testset-Generator lassen sich synthetische Paare (simple/reasoning/multi-context) aus deinen eigenen Dokumenten erzeugen und so der manuelle Aufwand erheblich reduzieren.

Fazit

Eine produktionsreife RAG-Pipeline ist mehr als ein Vektorspeicher mit Embedding-Suche. Die drei kritischen Bausteine sind: hierarchisches Chunking (kleine Chunks für Retrieval, große Parent-Chunks für das LLM), Hybrid-Search mit RRF statt naivem Score-Mixing, und Cross-Encoder-Reranking für die finalen Top-5. Der messbare Uplift rechtfertigt den Aufwand: +17,2 Prozentpunkte MRR@3 allein durch das Reranking. Ohne Evaluation mit Faithfulness und Contextual Recall weißt du nicht, ob dein System wirklich funktioniert – RAGAS und DeepEval liefern dir die Zahlen für den Produktions-Go/No-Go.

Weiterführende Anleitungen und Quellen

Quellen: Qdrant – Hybrid Search with Reranking (offizielle Dokumentation), qdrant.tech; Confident AI / DeepEval – RAG Evaluation Metrics, confident-ai.com; Digital Applied – Hybrid Search BM25, Vector & Reranking Reference 2026, digitalapplied.com; nb-data.com – 23 RAG Pitfalls and How to Fix Them; Firecrawl Blog – Best Chunking Strategies for RAG in 2026, firecrawl.dev.