Zum Hauptinhalt springen
Retrieval-Augmented Generation, kurz RAG, ist eines der nützlichsten Muster für KI-Anwendungen, die Antworten aus eigenen Dokumenten geben sollen. Statt das Modell allein aus dem Gedächtnis antworten zu lassen, holst du zuerst relevantes Quellmaterial, schickst diesen Kontext an das Modell und lässt es mit Quellenangaben antworten. In diesem Tutorial bauen wir einen privaten RAG-Bot mit Python, Venice für Embeddings und Chat-Completions, Qdrant für die Vektorsuche und FastEmbed für lokales Re-Ranking. Am Ende hast du die Kernbausteine für einen lokalen Dokumentenassistenten, der deine Dateien einlesen, relevante Chunks abrufen, neu ranken und mit Quellenangaben antworten kann. Der RAG-Bot in Aktion Bevor wir loslegen: Wenn du den Code aus diesem Artikel laufen lassen willst, brauchst du einen Venice-API-Schlüssel. Exportiere ihn als Umgebungsvariable:
Interesse an der vollständigen Implementierung? Schau in das GitHub-Repo.

Wie ein moderner RAG-Bot funktioniert

Eine gute RAG-Pipeline ist mehr als „Dokumente in eine Vektordatenbank stecken”. Der grundlegende Flow sieht so aus: Der Re-Ranking-Schritt ist das Upgrade, das das Ganze deutlich nützlicher macht als eine einfache RAG-Demo. Die Vektorsuche ist schnell und findet semantisch ähnliche Chunks gut, kann aber Passagen liefern, die nur thema-nah, aber nicht direkt nützlich sind. Ein Cross-Encoder liest die Frage und jeden Kandidaten-Chunk zusammen und bewertet, wie gut der Chunk die Frage tatsächlich beantwortet.

Abhängigkeiten installieren

Wir verwenden das OpenAI-Python-SDK, weil Venice eine OpenAI-kompatible API bereitstellt. Dazu nehmen wir Qdrants Python-Client mit FastEmbed-Support:
Wenn du Abhängigkeiten lieber in einer Datei pflegst, lege requirements.txt mit denselben Paketen an:

Modelle auswählen

Erstelle eine Datei rag_bot.py und beginne mit Imports, Datenstrukturen, API-URL und Modellnamen:
Der Name des Embedding-Modells ist bewusst OpenAI-kompatibel. Venice mappt kompatible Embedding-Modellnamen auf Venice-gehostete Embedding-Modelle, sodass bestehender OpenAI-SDK-Code in der Regel durch Änderung von base_url und API-Schlüssel umziehen kann. Verfügbare Venice-Modelle kannst du so auflisten:
Für Chat-Modelle:

Venice- und Qdrant-Clients erzeugen

Erstelle einen OpenAI-kompatiblen Venice-Client für Embeddings und Chat-Completions:
Für Qdrant hast du drei sinnvolle Modi: Für einen privaten lokalen Bot empfiehlt sich ein persistierter lokaler Pfad:
Für Produktion gibt es verschiedene Deployment-Optionen. Wenn du jedoch ein remotes Qdrant-Deployment nutzt, denke daran, dass dort deine Dokument-Chunks und Metadaten gespeichert werden. Venice kann die Inferenzschicht privat halten, aber du solltest dennoch ein passendes Qdrant-Deployment für deine Daten wählen.

Dokumente laden und chunken

Für dieses Tutorial soll der Bot lokale Dateien oder Ordner einlesen. Starten wir mit .md-, .rst- und .txt-Dateien:
Sobald die Dateien geladen sind, müssen wir den Text per „Chunking” splitten – also in Datenstücke aufteilen. Eine naive Strategie würde gleichmäßig splitten. Das verliert allerdings oft Information an semantischen Grenzen und kann die Effektivität deines RAG-Systems senken. Unsere Chunking-Strategie bevorzugt Absatz- oder Satzgrenzen, damit das Modell kohärenten Kontext bekommt:
Eine Chunk-Größe von 1000 Zeichen mit 150 Zeichen Überlappung ist ein guter Default für gemischte Markdown- und Textdokumente. Kleinere Chunks können die Präzision erhöhen. Größere Chunks bewahren mehr Kontext. Die richtige Einstellung hängt oft von der Art deiner Dokumente ab.

Dokumente mit Venice embedden

Sobald wir Chunks haben, embedden wir sie in Batches:
Batching ist wichtig. Einzeln pro Chunk zu embedden ist einfach, aber bringt vermeidbare Latenz. Halte die Batch-Größe konfigurierbar, um den Durchsatz an dein Workload anzupassen.

Vektoren in Qdrant speichern

Bevor du Punkte einfügst, erstelle eine Qdrant-Collection mit der richtigen Vektorgröße. Die Vektorgröße erfährst du am einfachsten, indem du den ersten Batch embeddest und len(embeddings[0]) nutzt.
Jeder Point speichert den Vektor plus Payload-Metadaten. Das Payload enthält den Originaltext und einen Quellpfad, damit die Antwort zitieren kann, woher der Kontext kommt:
Verwende deterministische UUIDs, abgeleitet aus source, chunk_index und Inhalt. So wird wiederholtes Ingesting für unveränderte Chunks idempotent.

Kandidaten-Chunks abrufen

Zur Fragezeit embeddet der Bot die Nutzerfrage und fragt Qdrant nach den besten Vektortreffern:
limit ist hier die Kandidatenzahl. Sie sollte normalerweise höher sein als die Anzahl der Chunks, die du ans Modell schickst, weil der nächste Schritt sie neu rankt. Ein guter Default ist, 8 Kandidaten abzurufen und die besten 4 ans Chat-Modell zu senden.

Re-Ranking mit FastEmbed

Jetzt fügen wir den Teil hinzu, der das Retrieval deutlich schlauer wirken lässt.
Der wichtige Unterschied zwischen Embedding-Suche und Cross-Encoder-Re-Ranking liegt in der Bewertung. Die Embedding-Suche vergleicht einen Vektor für die Frage mit je einem Vektor pro Chunk. Sie ist schnell und skalierbar. Ein Cross-Encoder bewertet Frage und Chunk zusammen. Er ist langsamer, kann Relevanz aber direkter beurteilen. Deshalb ist das übliche Muster:
  1. Mit Vektorsuche eine größere Kandidatenmenge abrufen.
  2. Nur diese Kandidaten lokal neu ranken.
  3. Die obersten Chunks an das Sprachmodell senden.
Ein guter Startpunkt ist candidate_k=8 und top_k=4. Erhöhe candidate_k, wenn die richtige Quelle oft in der Nähe ist, aber nicht in den finalen Kontext kommt.

Antworten mit Venice-Chat-Completions

Sobald der Kontext gewählt ist, formatiere ihn mit Quellnummern:
Dann schick den Kontext an ein Venice-Chat-Modell:
Beachte den System-Prompt: Der Bot wird angewiesen, nur aus dem mitgelieferten Kontext zu antworten. Das ist eine einfache, aber wichtige Leitplanke. Ein RAG-Assistent sollte nicht selbstbewusst aus allgemeinem Modellwissen antworten, wenn die abgerufenen Dokumente das nicht stützen.

Den Bot ausführen

Wenn du die Teile zu einem Skript zusammenbaust, speichere es als rag_bot.py. Ein einfacher erster Lauf kann ein paar eingebaute Beispieldokumente verwenden, damit du die Pipeline prüfen kannst, bevor du eigene Dateien einliest:
Eigene Dokumente einlesen:
Lokale Qdrant-Collection auf Disk halten und einen interaktiven Chat starten:
Das Skript gibt die Antwort aus und danach die Quellen mit Vektor- und Re-Ranking-Scores:
Wenn du den tatsächlich an das Modell übergebenen Text inspizieren willst, ergänze:

Nützliche CLI-Optionen

Mach die wichtigsten Retrieval-Stellschrauben als CLI-Optionen verfügbar, damit du den Bot ohne Code-Änderungen tunen kannst: Für wiederholte lokale Entwicklung ist ein üblicher Flow:
Anschließend Folgefragen stellen, ohne erneut zu ingesten:

Privacy-Hinweise

Für ein privates RAG-Setup betrachte jede Schicht einzeln: Der privacy-freundlichste Default für dieses Tutorial ist Venice für Inferenz, lokales Qdrant auf Disk und lokales FastEmbed-Re-Ranking. Das ergibt einen praxistauglichen RAG-Bot, ohne deine Vector-DB-Payloads an einen Drittanbieter zu senden.

Häufige Fehler, auf die du vorbereitet sein solltest

Wenn du Embedding-Modelle wechselst, erstelle die Qdrant-Collection neu. Verschiedene Embedding-Modelle können Vektoren unterschiedlicher Dimension liefern, und Qdrant-Collections erwarten eine feste Vektorgröße.

Wie es weitergeht

Sobald die Basis läuft, sind die wirkungsvollsten Verbesserungen meist:
  • Dokumentspezifische Loader für PDFs, HTML, Tickets oder interne Wiki-Seiten ergänzen.
  • Reichere Metadaten speichern: Titel, Überschriften, Daten, Owner, URLs.
  • candidate_k, top_k, Chunk-Größe und Überlappung an echten Fragen tunen.
  • Bewertungsfragen anlegen, um Retrieval-Qualität vor und nach Änderungen zu messen.
  • Die finale Venice-Chat-Completion streamen für ein besseres interaktives Chat-Erlebnis.
RAG-Systeme lassen sich leicht demoen und – überraschend leicht – mittelmäßig bauen. Das Muster „Vektorsuche plus Re-Ranking” ist eine solide Grundlage, weil es das Retrieval schnell hält und dem Bot zugleich bessere Chancen gibt, dem Sprachmodell den richtigen Kontext zu schicken.