RAG-Wissensdatenbank
pacod-kb
Dateien und Webseiten in versioniertes, durchsuchbares Wissen verwandeln — mit einem stabilen Vertrag für die Modelle, die es lesen.
pacod-kb nimmt Dateien und Webseiten auf, wandelt sie in kanonisches Markdown um, indexiert sie für hybride semantische und Schlüsselwortsuche, bewahrt jede Version auf und liefert nachgelagerten Modellen Chunks mit einem stabilen Metadatenvertrag. Es läuft allein auf der CPU.
- Apache-2.0
- Version 1.0.0
- Stabil
Funktionen
-
Alles aufnehmen
PDF, DOCX, HTML, Markdown, Text und JSON — hochgeladen, in einen Ordner gelegt, von einer URL abgerufen oder auf Änderungen überwacht.
-
Kanonisches Markdown
Strukturbewusste Überschriften für Rechtstexte in acht Sprachen, wissenschaftliche Arbeiten und Artikel; Boilerplate wird herausgefiltert.
-
Inhaltssicherheit
Ein mehrstufiger Scan auf Prompt Injection und Spam blockiert Dokumente oder stellt sie unter Quarantäne, bevor sie indexiert werden; Ergebnisse werden als nicht vertrauenswürdige Daten abgegrenzt.
-
Hybride Suche
Dichte BGE-M3-Vektoren in Qdrant plus BM25-Schlüsselwörter, zusammengeführt per Reciprocal Rank Fusion.
-
Jede Version bleibt erhalten
Abgelöste Versionen bleiben über Abfragen zu einem Stichtag durchsuchbar; ein Git-Archiv zeichnet jede Änderung auf.
-
Kalibrierte Relevanz
Ein optionaler Cross-Encoder-Reranker liefert einen kalibrierten Relevanzwert von 0–1.
-
Nur CPU
Embedding, OCR und Suche bleiben allein auf der CPU reaktionsschnell, auch während Dokumente aufgenommen werden.
-
UI und CLI
Eine Web-UI und ein CLI für Dokumente, Projekte, Jobs, KI-Nutzung und -Kosten sowie Mitglieder mit den Rollen Viewer, Editor und Admin.
Einordnung
Warum es das gibt
Der größte Teil der Arbeit in Retrieval-Augmented Generation passiert, bevor ein Modell ein Wort liest: Dokumente hineinbekommen, sie sauber halten, die richtige Passage finden und dem Modell sagen, woher sie stammt. pacod-kb ist genau dieser Teil, gebaut als eigener Dienst.
Drei Anforderungen haben es geprägt. Antworten müssen zitierbar sein, deshalb trägt jeder Chunk seinen Überschriftenpfad und Byte-Offsets in einen stabilen Markdown-Text. Quellen ändern sich, deshalb wird nichts überschrieben: Eine neue Version löst die alte ab, die nach Datum durchsuchbar bleibt. Und aufgenommener Text ist für ein Modell nicht vertrauenswürdige Eingabe, deshalb wird er vor dem Indexieren auf Prompt Injection gescannt und bei der Ausgabe als Daten eingezäunt.
Von Dokumenten zu Antworten
Ein Dokument nimmt denselben Weg, ob es hochgeladen, im Dropzone-Ordner abgelegt, von einer URL abgerufen oder von einer überwachten Webquelle erneut geholt wurde:
- Erfassen. Der SHA-256 der Quellbytes ist die Dokument-ID, dieselbe Datei wird also nie zweimal aufgenommen.
- Parsen. PDF (docling, mit optionaler OCR für gescannte Seiten), DOCX, HTML, Markdown, Text und JSON werden zu Markdown mit YAML-Front-Matter.
- Sprache prüfen. Englisch, Deutsch, Bulgarisch, Russisch, Serbisch, Mazedonisch, Kroatisch und Slowenisch werden angenommen; andere Sprachen werden abgelehnt.
- Scannen des deterministischen Parsing-Ergebnisses auf Prompt Injection und Spam, bevor irgendein LLM den Text sieht.
- Umwandeln (optional). Ein LLM-Durchlauf über pacod-gw räumt das Markdown auf. Eine abgeschnittene, verweigerte oder leere Antwort lässt das deterministische Parsing-Ergebnis stehen.
- Bereinigen und strukturieren. Ein Rauschfilter entfernt Seitenzahlen, laufende Kopfzeilen und Boilerplate; die Überschriften-Erkennung erfasst die Struktur von Rechtstexten, Papers und Artikeln.
- Speichern von
document.md, einem Manifest und einem Snapshot der Quelle. - Indexieren. Überschriftenbewusste Chunks von bis zu 500 Tokens werden mit BGE-M3 in Qdrant eingebettet und in einen Schlüsselwortindex geschrieben, in der eigenen Collection des Projekts.
Architektur
pacod-kb besteht aus zwei Diensten, kb-pipeline und kb-ui, dazu dem Modell und dem Speicher, die sie nutzen:
| Teil | Aufgabe |
|---|---|
Pipeline-API (kb-pipeline, :8000) |
Aufnahme, Indexierung, Suche, Jobs, Einstellungen; der einzige Teil, der das KI-Gateway aufruft |
Web-UI (kb-ui, :8080) |
Anmeldung über pacod-id, Sitzungen, Mitglieder, die Seiten; ruft die Pipeline mit einem API-Schlüssel auf |
| BGE-M3 | Embeddings, im eigenen Prozess der Pipeline oder über einen gemeinsamen Embeddings-Dienst |
| Qdrant | Dichte Vektoren, eingebettet in die Pipeline oder als Server, den auch andere Produkte nutzen können |
| SQLite FTS5 oder PostgreSQL | Der BM25-Schlüsselwortindex |
| Dateisystem oder PostgreSQL | Dokumente, Manifeste und das Ledger der KI-Nutzung und -Kosten |
Die Trennung ist gewollt. Die Pipeline parst nicht vertrauenswürdige Dokumente und ruft beliebige
URLs ab, deshalb erhält sie keines der Geheimnisse von pacod-id; die UI bekommt den
Gateway-Schlüssel nie zu sehen. Alle Container hängen an einem gemeinsamen Docker-Netzwerk,
pacod-net, die Trennung beruht also auf Geheimnissen, nicht auf Netzwerken: Jeder Container
erhält eine explizite Liste von Umgebungsvariablen, und ein Test prüft sie.
Jeder Aufruf aus der UI trägt X-KB-Actor, die ID der angemeldeten Person. Die Pipeline vertraut
ihm nur zusammen mit dem API-Schlüssel, vermerkt ihn an Jobs, Einstellungsänderungen und Nutzung
und reicht ihn an das Gateway weiter. Löscht pacod-id ein Konto, ersetzt kb diese ID überall durch
deleted-user, bevor es die Löschung bestätigt.
Sicherheitsmodell
Inhaltssicherheit. Der Scan normalisiert den Text, um Verschleierung auszuhebeln, gleicht ihn mit einem mehrsprachigen Regelwerk ab und liefert eines von drei Urteilen:
- block: ein einzelner schwerwiegender Injection- oder Verschleierungsbefund oder ein hoher Bedrohungswert. Die Aufnahme schlägt fehl, nichts wird gespeichert.
- quarantine: Das Dokument wird mit Status
reviewbehalten und nie indexiert. Spam allein führt nie zu einer Blockierung. - clean: Das Dokument wird indexiert.
Eine optionale zweite Meinung eines LLM über das Gateway wird nur eingeholt, wenn die Regeln nicht
blockiert haben; sie hat 15 Sekunden Zeit und lässt im Fehlerfall durch. Als sicher bekanntes
Material kann den Scan mit kb ingest --trusted überspringen. Bei der Ausgabe umschließt /query
jeden Chunk mit einer Markierung für nicht vertrauenswürdige Daten.
Abrufen. WEB_FETCH_ALLOW_PRIVATE=false, für die Produktion empfohlen, verweigert
URL-Aufnahmen, die zu privaten Adressen auflösen — ein Schutz gegen Server-Side Request Forgery.
Personen. Die UI meldet Personen über pacod-id mit OIDC und PKCE an und hält die Tokens in einer verschlüsselten serverseitigen Sitzung; das Cookie trägt nur ein zufälliges Geheimnis. Rollen kommen aus dem Autorisierungs-Feed von pacod-id. Ist die Kopie der UI älter als 60 Sekunden, antworten die Seiten mit 503, statt zu raten, wer was darf.
| Rolle | Darf |
|---|---|
| viewer | Lesen und suchen |
| editor | Dokumente aufnehmen, neu indexieren und löschen; Projekte verwalten |
| admin | AI Management, Prompts, Laufzeitschalter, Mitglieder |
Gemessene Suche
Eine Abfrage läuft gegen Qdrant und den Schlüsselwortindex, und die beiden Rankings werden per
Reciprocal Rank Fusion (k = 60) zusammengeführt. Datums- und Verlaufsfilter greifen innerhalb
beider Suchen. Ein optionaler Cross-Encoder, BAAI/bge-reranker-v2-m3, ordnet die besten
Kandidaten neu und liefert einen Relevanzwert von 0 bis 1, der über Abfragen hinweg vergleichbar
ist, sodass min_score Nicht-Antworten aussortieren kann.
Die Standardwerte stammen aus einem Evaluierungs-Harness: 14 Dokumente in sechs Sprachen (DSGVO, die Richtlinie zum Datenschutz bei Polizei und Justiz, vier Verfassungen, Wikipedia-Artikel) und 92 handgeschriebene Abfragen — Paraphrasen, exakte Begriffe, sprachübergreifende, muttersprachliche und unbeantwortbare. Gemessen auf der CPU eines Entwicklungsrechners — die Doku betont, dass das keine Produktionszahlen sind:
- Ohne Reranker liegt der richtige Abschnitt bei 98,7 % der beantwortbaren Abfragen unter den ersten fünf; R@1 beträgt 0,83, bei etwa 50 ms.
- Mit Reranker steigt R@1 auf 0,91, bei etwa 2 s pro Abfrage.
- Unter hoher Aufnahmelast wartete eine Suche früher etwa 24 s auf einen ganzen Embedding-Batch. Heute haben Abfragen Vorrang vor der Aufnahmearbeit und warten höchstens auf einen kleinen Mikro-Batch: etwa 0,4 s (p50), wenn Aufnahme-Jobs nacheinander laufen, wie die Pipeline sie ausführt.
- Nach Länge sortierte Batches machen das Einbetten bei der Aufnahme etwa 2,2× schneller, bei unveränderten Vektoren.
Der Reranker ist standardmäßig aus: Sein Gewinn blieb auf den zurückgehaltenen Abfragen knapp unter der Schwelle, und seine Latenz muss erst auf der Produktions-CPU bestätigt werden.
Was ein Modell bekommt
POST /query liefert Chunks unter einem stabilen, dokumentierten Metadatenvertrag:
{"query": "maximum fine for a data breach", "top_k": 5, "as_of": "2025-12-30"}
Die Antwort ist {"results": [...], "reranked": false, "score_kind": "rrf"}. Jedes Ergebnis
enthält:
| Feld | Wofür |
|---|---|
chunk_id |
Eine stabile ID, die die Überschriftenstruktur kodiert |
source_id |
Die Dokument-ID: SHA-256 der Quellbytes |
heading_path |
Ein Brotkrumenpfad wie ["Chapter 1", "§ 5"], für Zitate und Abschnittsfilter |
byte_start, byte_end |
UTF-8-Offsets in den gespeicherten Markdown-Text |
valid_until |
Wann diese Version abgelöst wurde, oder null, solange sie aktuell ist |
text |
Der eingebettete Text, als nicht vertrauenswürdige Daten eingezäunt |
score, rrf_score, rerank_score |
Der Sortierwert und seine Bestandteile |
Jedes Dokument hat außerdem ein Manifest: Titel, Quelle, Dokumenttyp, Status, Projekt, der Parser
oder das Modell, das den Text erzeugt hat, und ein Lebenszyklus-Protokoll (ingested,
superseded, security_flagged, llm_usage und weitere).
Versionen statt Überschreiben
Eine mit --supersedes aufgenommene neue Version oder eine geänderte überwachte Quelle markiert die
alte mit valid_until — im Front Matter, im Manifest, im Schlüsselwortindex und in den
Vektor-Payloads zugleich. Suchen liefern standardmäßig aktuelle Chunks; as_of liefert, was an
einem bestimmten Tag gültig war; include_historical liefert alles. Kehrt eine überwachte Seite
zu einem früheren Stand zurück, wird die frühere Version wieder in Kraft gesetzt. Ablösungen und
Neuaufbauten verwenden die Vektoren unveränderter Chunks wieder.
Im Betrieb
kb folgt der gemeinsamen Netzwerkkonvention von pacod: Jeder Container hängt an einem
Docker-Netzwerk, pacod-net. docker-compose.yml enthält die eigenen Dienste von kb, kb-pipeline
und kb-ui. docker-compose.standalone.yml enthält die Kopien der gemeinsamen Komponenten, die
Docker-Compose-Profile einschalten: Qdrant, PostgreSQL, pacod-id mit seiner Datenbank neben der
von kb, einen Mail-Catcher und ein Entwicklungs-Gateway, dessen Mock-Provider alles auf der
Maschine halten. make up lädt beide Dateien. Von jeder gemeinsamen Komponente kann auf einer
Maschine nur eine Kopie laufen. In Produktion verweist man kb auf ein echtes pacod-gw, stellt
pacod-id hinter TLS und lässt die Entwicklungsprofile weg.
pacod-demo, live unter demo.pacod.io, betreibt kb zusammen mit den anderen pacod-Projekten. Es lädt nur die App-Datei von kb: kb legt seine Datenbank im gemeinsamen PostgreSQL und seine Collections im gemeinsamen Qdrant ab, bettet über den gemeinsamen Embeddings-Dienst ein und stellt seine UI hinter dem gemeinsamen Caddy-Edge bereit.
- Speicher.
KB_STORAGE_BACKENDistfilesystemoderdatabase; ein Wechsel migriert keine Dokumente. - Daten. Die Datenverzeichnisse sind Variablen (
KB_ROOT,KB_MODEL_CACHE,KB_QDRANT_DATA,KB_POSTGRES_DATA). Ein Name ohne Schrägstrich ist ein Docker-Volume; unter macOS verwendetmake setupVolumes. - CPU.
TORCH_NUM_THREADS=0folgt der CPU-Quote des Containers.EMBED_BATCH_TOKENS(1024) begrenzt, wie lange eine Abfrage hinter einer Aufnahme warten kann. - Modelle offline. Einmal mit Netzwerkzugang laufen lassen, um den Modell-Cache zu füllen,
dann
HF_HUB_OFFLINE=1setzen. - Gemeinsame Modelle. Mit
EMBEDDINGS_URLbettet die Pipeline über den gemeinsamen Embeddings-Dienst ein, statt BGE-M3 selbst zu laden. Die Vektoren sind dieselben, es wird also nichts neu eingebettet, und die Pipeline braucht etwa 2 GB weniger Arbeitsspeicher.RERANKER_URLtut dasselbe für den Reranker. - Gemeinsames Qdrant. Auf einem Qdrant, das auch andere Produkte nutzen, zeigen und berühren
die Qdrant-Seiten der UI nur die Collections von kb (
KB_QDRANT_COLLECTION_PREFIX, Standardkb_). - Laufzeitschalter. Das Dashboard schaltet Inhaltsscan, KI-Umwandlung und Sprachprüfung ohne Neustart ein und aus; beim nächsten Start gelten wieder die Umgebungsvariablen.
- KI. Admins wählen Modelle und bearbeiten Prompts in AI Management. Jeder Aufruf trägt eine opake Zuordnung (Funktion, Job, Projekt, Person), und kb erfasst die Tokens mit den Kosten des Gateways. Ist kein Gateway konfiguriert, ist KI aus, und sonst geht nichts kaputt.
Backups umfassen KB_ROOT oder die PostgreSQL-Datenbank von kb, den Qdrant-Speicher (oder man
baut ihn mit kb rebuild --all-projects neu auf), die Datenbank von pacod-id im selben
PostgreSQL samt ihrem Schlüsselverschlüsselungsschlüssel und .env.
Grenzen und weiterführende Lektüre
- Pipeline und UI laufen jeweils mit einem einzigen Worker: Das eingebettete Qdrant hält eine Dateisperre, und die Sitzungsverwaltung der UI setzt einen Prozess voraus.
- Ein Reranker im eigenen Prozess der Pipeline verdoppelt ungefähr ihren Arbeitsspeicher; mit
RERANKER_URLlädt die Pipeline keinen. - Serbisch wird angenommen, aber die Spracherkennung hat kein serbisches Profil, sodass serbischer Text falsch erkannt oder abgelehnt werden kann.
- Für die nächste Neuindexierung dokumentiert, nicht ausgeliefert: BGE-M3-Sparse-Vektoren für die Schlüsselworthälfte, Aufräumarbeiten am Chunker und der Neuaufbau in einer Schatten-Collection vor dem Umschalten.
Mehr dazu:
- Architektur: Container, Anmeldung, das Gateway, wer gehandelt hat.
- Metadatenvertrag: jedes Chunk- und Manifest-Feld.
- Retrieval-Evaluierung: die vollständigen Zahlen und die Entscheidungen hinter den Standardwerten.
- Runbook: Entwicklung, Produktion, Betrieb und Fehlersuche.
- Migrationen: das Upgrade einer
bestehenden Installation, auch der Umzug auf
pacod-net.
Tech-Stack
- Geschrieben in
- Python
- Tech-Stack
- FastAPI
- Qdrant
- BGE-M3
- BM25
- SQLite
- PostgreSQL
- HTMX
- Docker
Schnellstart
make setupmake upopen http://localhost:8080/kbuv syncmake checkmake evalFunktioniert mit
-
pacod-id
Personen melden sich über pacod-id an; die Rollen Viewer, Editor und Admin werden dort vergeben.
-
pacod-gw
Der optionale LLM-Bereinigungsschritt läuft über das Gateway, das jeden Aufruf bepreist und zuordnet.
-
pacod-ap
Läuft kb neben pacod-ap, kann es über dessen gemeinsamen Embeddings-Dienst einbetten und reranken, mit denselben BGE-M3-Vektoren.
Lizenz
Apache-2.0