← KI-Engineering und Agentic AI
RAG in Produktion · im Betrieb

RAG-Chatboard für wissenschaftliche Fragen

Stellen Sie eine Frage zu einem Bestand an Forschungsarbeiten und erhalten Sie eine Antwort mit ihren Quellen. Ein Befehl genügt, auf einem gewöhnlichen Laptop.

0,8373
ALCE-Zitationsgenauigkeit
84 QASPER-Fragen
0,7151
ALCE-Zitations-Recall
nach der Korrektur an extract_final_answer
0,72
Faithfulness (RAGAS)
1 Zeitüberschreitung übersprungen
47 810
Indexierte Chunks
QASPER-Korpus aus Papern der Sprachverarbeitung

Die Aufgabenstellung

Die wissenschaftliche Literatur wächst schneller, als eine Forscherin sie lesen kann, und die Fragen, auf die es ankommt, reichen über einen ganzen Bestand statt über ein Paper. Die Stichwortsuche gibt eine Rangliste zurück und überlässt der lesenden Person das Öffnen, Überfliegen, Vergleichen und Zusammensetzen. Dieses System liefert die belegte Antwort direkt und als Stream, über einen Bestand an Papern der Sprachverarbeitung, aus einem einzigen Docker-Compose-Befehl, auf CPU oder GPU.

Die Aufgabe ist eine Frage-Antwort-Aufgabe in offener Domäne über einem vorab indexierten Bestand. Zu einer Frage in natürlicher Sprache holt das System die 7 besten Textstellen aus einem Index mit 47 810 Chunks auf FAISS und BM25 (aus dem QASPER-Benchmark, 5 049 Frage-Antwort-Paare zu Papern der Sprachverarbeitung) und schreibt dann mit einem lokalen Modell eine belegte Antwort mit Quellenangabe. Fällt der gefundene Kontext unter die Konfidenzschwelle, hält die Pipeline an, statt aus dünner Beleglage zu generieren.

Die Architektur der Anwendung

Der Stack besteht aus 2 Diensten unter Docker Compose: einem FastAPI-Backend auf Port 8080 und einer Chainlit-Chatoberfläche auf Port 8001. Beide Container laufen mit network_mode: host, was dem Backend direkten Zugriff auf Ollama auf dem Host gibt. Das Frontend wartet auf ein Backend, das seine Health-Prüfung bestanden hat (condition: service_healthy), fragt /health alle 30 Sekunden ab und räumt eine Startphase von 300 Sekunden ein, damit der Download der Artefakte beim ersten Start durchläuft.

Die FAISS-Indizes liegen bei etwa 230 MB (dense.index bei 147 MB, die Metadaten bei 31 MB, sparse.pkl bei 51 MB). gdown holt sie beim ersten Start von Google Drive und schreibt sie auf ein eingebundenes Volume, also ist jeder weitere Neustart sofort da. Das Backend erkennt CUDA und wählt HuggingFace Transformers auf der GPU oder Ollama auf der CPU, ohne dass jemand die Konfiguration ändert.

Die RAG-Pipeline in 4 Stufen

Stufe 1. Hybrides Retrieval. Die dichte FAISS-Suche (SPECTER2-Embeddings, 768 Dimensionen, IndexFlatIP, 47 810 Vektoren) wird mit der dünnbesetzten BM25-Suche über Reciprocal Rank Fusion (k=60) zu den 50 besten Kandidaten verschmolzen. Eine Anfrage unter 10 Wörtern löst HyDE aus: das Modell schreibt eine hypothetische Textstelle, die zur dichten Anfrage wird, was den Recall bei kurzen oder mehrdeutigen Eingaben hebt.

Stufe 2. ColBERT-v2-Reranking. Die Token-Embeddings der Dokumente entstehen offline, also kostet die Anfragezeit nur ein MaxSim über Token-Embeddings. Das erkauft die Genauigkeit eines Cross-Encoders im Maßstab des Retrievals und schneidet die besten 50 auf die besten 7 herunter.

Stufe 3. Das CRAG-Gate. Drei Ausgänge: Correct schickt die Textstellen weiter zur Generierung, Incorrect stoppt die Generierung und gibt eine Warnung wegen geringer Konfidenz zurück, und Ambiguous erzeugt eine vorsichtige Antwort. Das Skript calibrate_crag.py im Forschungs-Repository setzt die Schwelle.

Stufe 4. Generierung im Stream. Llama-3.1-8B-Instruct, über Ollama auf der CPU oder HuggingFace Transformers auf der GPU, schreibt eine Antwort mit Chain-of-Thought, Zitaten [Doc N] im Text und 6 Runden Gesprächsgedächtnis. Eine asynchrone Warteschlange schiebt die Tokens an einen NDJSON-Streaming-Endpunkt.

Die CI/CD-Pipeline

Jeder Push auf master startet einen GitHub-Actions-Workflow, der sich mit dem automatischen GITHUB_TOKEN bei GHCR anmeldet, also ohne zu verwaltende Secrets, und danach beide Docker-Images unter 2 Tags baut und pusht: :latest und :sha-<commit>. Der an den Commit gebundene Tag macht aus einem Rollback eine Änderung von einer Zeile.

Die Evaluation

Bewertet an 84 QASPER-Fragen. Die ALCE-Zitationsgenauigkeit stieg von 0,057 auf 0,8373, und zwar durch eine einzige Änderung: extract_final_answer(). Der Reasoning-Block trug keine Zitate und blähte die Zahl der falsch negativen Treffer beim Zitations-Recall auf. Nimmt man ihn heraus, werden die belegten Antworten als das bewertet, was sie sind. Das ist der Fehlertyp, den ein Evaluationsrahmen sichtbar macht und eine Demo verdeckt.

Die Wirkung

  • Betrieb ohne Bauschritt. Nutzende starten docker compose up und ziehen fertige GHCR-Images. Keine Python-Umgebung anzulegen, kein Skript zum Modell-Download und kein Index von Hand zu verwalten.
  • Läuft auf der CPU. Das Ollama-Backend arbeitet auf gewöhnlicher Hardware, mit etwa 2 bis 5 Tokens pro Sekunde.
  • Nachvollziehbares Reasoning. Jede Antwort zeigt ihren Reasoning-Block vor der endgültigen Antwort, damit die lesende Person die Logik dahinter prüfen kann.
  • Rückfragen funktionieren. 6 Runden Gedächtnis ersparen es, den Kontext erneut zu nennen.
  • FastAPI
  • Chainlit
  • SPECTER2
  • FAISS
  • BM25
  • RRF
  • ColBERT v2
  • CRAG
  • HyDE
  • Llama-3.1-8B
  • Ollama
  • Docker
  • GHCR
  • GitHub Actions
  • QASPER