ADR-007: Cross-Encoder Re-Ranking für RAG-Pipeline¶
Status: Akzeptiert Datum: 2026-04-02 Entscheider: Kamerplanter Development Team
Kontext¶
Die RAG-Pipeline (ADR-006) verwendet Hybrid Search (Vektor + BM25 Full-Text mit Reciprocal Rank Fusion). Der RAG-Benchmark zeigt 29% Gesamtscore mit folgender Fehlerverteilung:
- 57% GENERATION_MISS — LLM erhält irrelevante Chunks und halluziniert
- 11% RETRIEVAL_MISS — Hybrid Search findet den richtigen Chunk nicht in Top-K
- 21% SYNONYM_GAP — Eval-Pattern erkennt korrekte Antworten nicht (unabhängig)
Problem¶
Bi-Encoder (E5-base) und BM25 ranken unabhängig voneinander. Ihre Fusion via RRF ist effektiv für Recall, aber nicht optimal für Precision: Irrelevante Chunks mit hohem BM25-Score (Keyword-Match ohne semantische Relevanz) verrauschen den LLM-Kontext. Das führt zur dominierenden Fehlerklasse GENERATION_MISS.
Entscheidung¶
Cross-Encoder Re-Ranking als optionale Pipeline-Stufe zwischen Retrieval und LLM-Generation.
Architektur¶
- Separater Microservice (
reranker-service), analog zum Embedding-Service (ONNX Runtime + FastAPI) - Modell:
BAAI/bge-reranker-v2-m3(multilingual, 568M Parameter, Apache-2.0) - Graceful Degradation: Ohne Reranker-URL arbeitet die Pipeline wie bisher (nur Hybrid Search)
Warum separater Service statt In-Process?¶
- Cross-Encoder-Inferenz ist CPU-intensiv (~500ms für 20 Paare) — eigenes Memory/CPU-Budget
- Bestehende Architektur (Embedding-Service) bewährt sich
- Optional deploybar — keine zusätzliche Python-Dependency im Knowledge-Service
Warum bge-reranker-v2-m3?¶
| Kriterium | bge-reranker-v2-m3 | ms-marco-MiniLM-L-12-v2 |
|---|---|---|
| Sprachen | Multilingual (DE/EN) | Englisch-fokussiert |
| Parameter | 568M | 33M |
| BEIR-Benchmark | State-of-the-art | Gut, aber EN-only |
| ONNX-Export | Via optimum (bis 2026-09-24; siehe Nachtrag unten) | Via optimum (bis 2026-09-24; siehe Nachtrag unten) |
| Lizenz | Apache-2.0 | Apache-2.0 |
| Kamerplanter-Usecase | DE-Wissensbasis | Ungeeignet |
Abgelehnte Alternativen¶
- Nur RRF-Weight-Tuning: Verbessert Precision minimal, löst das Grundproblem nicht (Keyword-Matches ohne semantische Relevanz)
- ColBERT/Late Interaction: Höherer Recall, aber Re-Ranking war nicht das Recall-Problem
- LLM-basiertes Re-Ranking: Zu langsam (>5s), zu teuer für lokale Inferenz
Konsequenzen¶
Positiv¶
- Präziserer LLM-Kontext → weniger GENERATION_MISS
- Erwartete Benchmark-Verbesserung: 10-20 Prozentpunkte
- Keine Breaking Changes — Graceful Degradation bei fehlendem Reranker
- Gleiche Deployment-Patterns wie Embedding-Service (bekannt, getestet)
Negativ¶
- Zusätzlicher Microservice (+1.5-4 GB RAM, +500ms Latenz pro Query)
- ONNX-Export via
optimumim Docker-Build (längere Build-Zeit) — bis 2026-09-24, siehe Nachtrag unten - Erste Docker-Build-Zeit ~10-15 Minuten (Modell-Download + Export) — bis 2026-09-24, siehe Nachtrag unten
Neutral¶
- Knowledge-Service-Config: 3 neue Umgebungsvariablen (
RERANKER_URL,RERANKER_INITIAL_K,RERANKER_TOP_K) - Helm/Skaffold: Neuer Controller, Service, NetworkPolicy (analog Embedding-Service)
Nachtrag (2026-09-24): Export-Mechanismus durch gepinnten Download ersetzt¶
Seit 2026-09-24 exportiert der reranker-service beim Docker-Build keine Modelle mehr selbst per optimum nach ONNX. Die Download-Stufen im Dockerfile holen stattdessen Modell-Dateien, die bereits als ONNX vorliegen, direkt von Hugging Face — jeweils an eine feste Commit-Revision gepinnt (onnx-community/bge-reranker-v2-m3-ONNX bzw. cross-encoder/ms-marco-MiniLM-L12-v2). Zur Laufzeit tokenisiert der Service mit der tokenizers-Bibliothek statt mit transformers. transformers, optimum und torch sind aus dem Image und aus dem Dependency-Lock entfernt (intern verfolgt als Issue #1480).
Grund: optimum-onnx, der ONNX-Exporter, begrenzt transformers in jeder bislang veröffentlichten Version auf eine Version kleiner als 4.58. Das hielt den Reranker-Service auf transformers 4.57.6 fest — einer Version mit drei HIGH- und einer MEDIUM-CVE.
Gemessen gegen den vorherigen optimum-Export: identische Tokenizer-Ausgabe, identische Graph-Ein- und -Ausgaben sowie eine maximale Score-Abweichung von 4,7 × 10⁻⁶ bei bge-reranker-v2-m3 (bei ms-marco-MiniLM-L12-v2 keine messbare Abweichung), jeweils mit identischem Ranking. Die Build-Zeit sinkt entsprechend: Der 10–15-minütige Export entfällt, es bleibt nur noch der Download.
Die API (/rerank, /health, /ready) ist unverändert — mit einer Ausnahme: Eine leere documents-Liste liefert seither ein leeres Ergebnis statt eines Fehlers.
Die eigentliche Entscheidung dieses ADRs — Cross-Encoder Re-Ranking mit bge-reranker-v2-m3 als Standardmodell — sowie ihre Begründung bleiben unverändert; geändert hat sich nur der Beschaffungsweg der ONNX-Datei.
Nachtrag (2026-09-25): RERANKER_INITIAL_K-Default gesenkt, neue Zeichenobergrenze pro Dokument¶
Der ursprünglich angenommene Aufwand von „~500ms für 20 Paare" (siehe „Warum separater Service statt In-Process?" oben) hat sich am 2-CPU-Limit des Helm-Charts als deutlich zu niedrig erwiesen: bge-reranker-v2-m3 kostet dort real ~3 Sekunden je (Query, Dokument)-Paar mit 512 Tokens. Ein realer HTTP-Test über alle 100 Benchmark-Fragen gegen den Reranker-Sidecar bestätigt das für den ursprünglichen Default RERANKER_INITIAL_K=20 mit vollständigen Dokumenten bis 512 Tokens: Jede Anfrage traf die serverseitige 25-Sekunden-Deadline ({"status": "timeout"} nach ~27 Sekunden; rechnerisch entspräche das ~54 Sekunden Gesamtaufwand). In der Praxis lief Re-Ranking dadurch nie durch; jede Suche wartete zusätzlich ~27 Sekunden auf einen Fallback, der ohnehin eintrat.
Seit diesem Datum gilt:
RERANKER_INITIAL_Khat den Default 15 (statt 20).- Neue Umgebungsvariable
RERANKER_MAX_DOCUMENT_CHARS(Default 500): Jeder Kandidaten-Chunk (titel\ninhalt) wird vor dem Versand an den Reranker-Sidecar auf diese Zeichenzahl gekürzt (Paar-Median 166 Tokens, maximal 222). Der an das LLM übergebene Chunk behält seinen vollständigen Text — nur die Reranker-Eingabe wird verkürzt. - Beide Werte erfüllen zusammen exakt das vom Reranker-Service erzwungene Zeichenbudget
RERANKER_INITIAL_K × RERANKER_MAX_DOCUMENT_CHARS ≤ 7.500(RERANK_SCORED_CHARS_BUDGETinconfig.py, 15 × 500 = 7.500). - Die Budget-Grenze bestimmt nur, wie viele Kandidaten neu bewertet werden — nie, wie viele Ergebnisse ein Aufrufer erhält:
KnowledgeService.search()ruftmax(RERANKER_INITIAL_K, top_k)Kandidaten ab, bewertet nur den Kopf der LängeRERANKER_INITIAL_Kneu und hängt den Rest unverändert in Hybrid-Reihenfolge an. Bei einem Fallback entspricht das Gesamtergebnis vollständig der ungerankten Hybrid-Reihenfolge.
Mit den neuen Standardwerten waren im selben Test alle 100 Anfragen erfolgreich (p50 12,9 Sekunden, p90 15,0 Sekunden, maximal 16,0 Sekunden — innerhalb des 25-Sekunden-Budgets). Die Kontextqualität (Anteil der erwarteten Themen im zurückgegebenen Kontext, 54 Fragen/9 Kategorien) hängt vom angeforderten top_k ab: Bei top_k=5 steigt sie von 0,927 ohne Re-Ranking auf 0,974 mit den neuen Standardwerten (ungebremstes Re-Ranking aller Kandidaten: 0,982, passt aber nicht in die Deadline); beim Chat-Standardwert top_k=10 ist der Gewinn klein (0,978 → 0,979, ungebremst 0,995) — der größere Nutzen liegt bei Aufrufern mit wenigen angeforderten Chunks wie dem Glossar (top_k=5) oder dem Diagnose-Assistenten (top_k=8). Details zu den Fallback-Gründen (reranker_fallback, u. a. reason=deadline) stehen in der KI-Architektur.
Die Entscheidung für Cross-Encoder Re-Ranking mit bge-reranker-v2-m3 bleibt unverändert; geändert haben sich nur die Standardwerte für Over-Retrieval, eine neue, zusätzliche Zeichenobergrenze pro Dokument und die explizite Bewertungs-Invariante.