Zwei Fenster der Suchseite von pacod-kb, hell und dunkel, überlappend. Beide fragen „How does the gateway keep provider keys?“ mit Top K 5; das dunkle zeigt die erste Passage, aus dem Abschnitt „How it is put together“ von pacod-demo, mit RRF 0.0164, ihrer Dokument-ID und den Bytes 16916–17985, und die nächste, aus pacod-gw, mit RRF 0.0161.
Suche bei Tag und bei Nacht. Eine schlichte Frage an die sieben READMEs von PACOD liefert gereihte Passagen, jede mit dem Überschriftenpfad, der Dokument-ID und dem Bytebereich, die man zum Zitieren braucht; die Werte 0.0164 und 0.0161 (1/61 und 1/62) zeigen, dass die Reihenfolge allein aus der Vektorsuche stammt.

pacod-kb nimmt Dateien und Webseiten auf, wandelt sie in kanonisches Markdown um, indexiert sie für hybride semantische und Schlüsselwortsuche und bewahrt jede Version auf. Jedes Dokument wird auf Prompt Injection gescannt, bevor irgendetwas indexiert wird, und nachgelagerte Modelle erhalten zitierbare Passagen unter einem dokumentierten Metadatenvertrag. Es läuft auf der CPU, und Suchen bleiben reaktionsschnell, während Dokumente aufgenommen werden.

RAG-Wissensdatenbank

  • Lizenz: Apache-2.0
  • Version: 1.0.0 · Stabil
  • Geschrieben in: Python
  • Tech-Stack: FastAPI, HTMX, docling, BGE-M3, Qdrant, SQLite FTS5, PostgreSQL, Docker
  • Auf GitHub ansehen · Issue eröffnen

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

Überschriften werden in Rechtstexten in acht Sprachen, in wissenschaftlichen Arbeiten und in Artikeln erkannt; Seitenzahlen, laufende Kopfzeilen und Boilerplate werden herausgefiltert.

Inhaltssicherheit

Ein mehrstufiger Scan auf Prompt Injection und Spam blockiert ein Dokument oder stellt es unter Quarantäne, bevor es indexiert wird; Suchergebnisse werden als nicht vertrauenswürdige Daten eingezäunt.

Hybride Suche

Dichte BGE-M3-Vektoren in Qdrant plus ein Schlüsselwortindex, zusammengeführt per Reciprocal Rank Fusion, in Projekten, die die Dokumente der anderen nie zu sehen bekommen.

Jede Version bleibt erhalten

Eine neue Version löst die alte ab, die nach Datum durchsuchbar bleibt; kehrt eine überwachte Seite zu einem früheren Stand zurück, wird die frühere Version wieder in Kraft gesetzt.

Kalibrierte Relevanz

Ein optionaler Cross-Encoder-Reranker liefert einen Relevanzwert von 0–1, der über Abfragen hinweg Bestand hat, sodass min_score Nicht-Antworten aussortieren kann.

Ein Vertrag für Konsumenten

Jede Passage trägt eine stabile ID, ihren Überschriftenpfad und Byte-Offsets in das gespeicherte Markdown, Feld für Feld dokumentiert.

Für die CPU gemacht

Abfragen haben Vorrang vor der Aufnahmearbeit und warten höchstens auf einen kleinen Batch; Embeddings können auch von einem gemeinsamen Dienst kommen.

Web-UI und CLI

Eine Web-UI im gemeinsamen pacod-Look, hell oder dunkel, für Dokumente, Projekte, Jobs, KI-Nutzung und -Kosten sowie Mitglieder; das CLI kb für Aufnahme, Suche, Überwachung, Neuaufbau und Export.

Das Plattform-Diagramm mit hervorgehobenem pacod-kb und abgeblendeten übrigen Projekten: eine graue Linie von den Menschen zu pacod-kb, eine grün gepunktete Linie von pacod-kb zu pacod-gw, unter dem die Modellanbieter liegen, und eine gold gepunktete Linie zu pacod-id; ein gestrichelter Rahmen mit pacod-demo umfasst alle.
Wo pacod-kb sitzt. Menschen nutzen es direkt (grau), seine optionalen LLM-Aufrufe laufen über pacod-gw, das die Schlüssel der Anbieter hält (grün), und die Anmeldung läuft über pacod-id, wo die Rollen in kb vergeben werden (gold). pacod-demo betreibt es mit den übrigen.

Screenshots

Die helle Dokumentenseite von pacod-kb für das Projekt kb_chunks: ein Filterfeld mit All, Active, Superseded und Errors, Export-Schaltflächen und eine Tabelle mit sieben englischen Leitfäden, pacod-kb, pacod-id, pacod-gw, pacod-demo, pacod-cms, pacod-benchmark und pacod-ap, mit je 4 bis 28 Chunks, alle aktiv, alle am 2026-10-28 aufgenommen.
Die Dokumente eines Projekts: die sieben READMEs von PACOD, als Markdown hochgeladen, jedes aktiv und mit der Zahl seiner Chunks. Die ID eines Dokuments ist der SHA-256 seiner Bytes, und jedes lässt sich ansehen, als Quelle oder als gespeichertes Markdown herunterladen oder neu indexieren.
Das helle Dashboard von pacod-kb: Kacheln mit 7 Dokumenten insgesamt, 7 aktiven, 0 abgelösten und 0 Fehlern; ein Arbeitsbereich, angeführt von einer grünen Schaltfläche Ingest document; ein Wartungsbereich mit Scan dropzone, Run watch, Regenerate from DB, Convert no-index und einem roten Rebuild full index; und Konfigurationszeilen mit eingeschaltetem Inhaltsscan und deterministischer Umwandlung.
Das Dashboard: die Dokumente des Projekts nach Zustand, der tägliche Arbeitsablauf, die Wartung (der Dropzone-Ordner, beobachtete Webquellen, ein vollständiger Neuaufbau) und die Schalter, die ohne Neustart wirken. Der Inhaltsscan ist an; die Umwandlung ist deterministisch, kein Modell berührt diese Dokumente.
Die dunkle Seite der Qdrant-Collections von pacod-kb: ein Formular für eine neue Collection mit Name, 1024 Dimensionen und Kosinus-Abstand und eine Tabelle mit kb_chunks, 87 Punkten, einer Schaltfläche Export all und einem Link Delete.
Die Qdrant-Collections von kb, bei Nacht. kb_chunks hält 87 Punkte, einen je Chunk der sieben Dokumente, als 1024-dimensionale BGE-M3-Vektoren mit Kosinus-Abstand; auf einem Qdrant, den andere Produkte mitnutzen, zeigt die Seite nur die eigenen Collections von kb.

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 mit einer HTTP-API, einer Web-UI und einem CLI.

Drei Anforderungen haben es geprägt:

  • Antworten müssen zitierbar sein. Jede Passage trägt ihren Überschriftenpfad und Byte-Offsets in einen stabilen Markdown-Text, sodass ein Konsument sie zitieren, verlinken und ihren Kontext abrufen kann.
  • Quellen ändern sich. Nichts wird überschrieben: Eine neue Version löst die alte ab, die nach Datum durchsuchbar bleibt.
  • Aufgenommener Text ist für ein Modell nicht vertrauenswürdige Eingabe. Er wird vor dem Indexieren auf Prompt Injection gescannt und bei der Ausgabe als Daten eingezäunt.

Ein Dokument, Schritt für Schritt

Ein Dokument nimmt denselben Weg, ob es hochgeladen, im Dropzone-Ordner abgelegt, von einer URL abgerufen oder von einer überwachten Webquelle erneut geholt wurde:

  1. Erfassen. Der SHA-256 der Quellbytes ist die Dokument-ID; eine bekannte ID wird übersprungen, dieselbe Datei wird also nie zweimal aufgenommen.
  2. Parsen. PDF über docling, mit optionaler OCR für gescannte Seiten (ist tesseract installiert, werden zwei Engines Seite für Seite verglichen), DOCX, HTML, Markdown, Text und JSON, dessen Schlüssel zu Überschriften werden. Das Ergebnis ist Markdown mit YAML-Front-Matter.
  3. Sprache prüfen. Englisch, Deutsch, Bulgarisch, Russisch, Serbisch, Mazedonisch, Kroatisch und Slowenisch werden angenommen, andere Sprachen abgelehnt, bevor irgendetwas geschrieben wird. Text, dessen Sprache sich nicht erkennen lässt, wird als unknown angenommen, mit einer Warnung.
  4. Scannen des deterministischen Parsing-Ergebnisses auf Prompt Injection und Spam. Das geschieht, bevor irgendein Modell den Text sieht: Ein Konverter könnte eingeschleusten Anweisungen folgen, und die Umwandlung würde die Nullbreitenzeichen und das versteckte Markup entfernen, nach denen der Scan sucht.
  5. Umwandeln, optional. Ein LLM-Durchlauf über pacod-gw räumt das Markdown auf; ein PDF bis 20 MiB geht als Datei an das Modell, alles andere als extrahierter Text. Ein Dokument in Quarantäne wird nie gesendet. Eine abgeschnittene, verweigerte oder leere Antwort lässt das deterministische Parsing-Ergebnis stehen, und ein umgewandelter Text wird ein zweites Mal gescannt.
  6. Bereinigen und strukturieren. Ein Rauschfilter entfernt Seitenzahlen, laufende Kopfzeilen, Cookie-Hinweise und Blöcke aus Navigationslinks. In Gesetzbüchern, Verordnungen und Richtlinien (acht Sprachen) sowie in wissenschaftlichen Arbeiten werden Überschriften als solche ausgezeichnet; ein Artikel von einer URL erhält außerdem Autor, Datum und Publikation.
  7. Speichern von document.md, einem Manifest mit seinem Lebenszyklus-Protokoll und einem Snapshot der Quelle: in Ordnern unter KB_ROOT oder in PostgreSQL.
  8. Indexieren, nur wenn der Status des Dokuments ok ist. Überschriftenbewusste Chunks von bis zu 500 Tokens werden mit BGE-M3 eingebettet, jeder mit einem vorangestellten Brotkrumenpfad [Title > Heading > Subheading] (eine Tabelle bekommt zusätzlich den Absatz davor), und landen in der Qdrant-Collection und im Schlüsselwortindex des Projekts. Chunks, die seit einer früheren Version unverändert sind, behalten ihre Vektoren.

Aufnahmen aus der UI oder über die API laufen als Jobs, einer nach dem anderen auf einem eigenen Worker, nie auf den Threads, die Suchen beantworten. Im Datenbankmodus werden Jobs in PostgreSQL gehalten, und ein Neustart markiert die, die er unterbrochen hat, als fehlgeschlagen.

Architektur

pacod-kb besteht aus zwei Diensten, kb-pipeline und kb-ui, dazu den Modellen und Speichern, 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) FastAPI und HTMX; Anmeldung über pacod-id, Sitzungen, Mitglieder, die Seiten; ruft die Pipeline mit einem API-Schlüssel auf
BGE-M3 1024-dimensionale Embeddings, im eigenen Prozess der Pipeline oder vom Embeddings-Dienst von pacod-ap
Qdrant Die Vektoren: eingebettet in die Pipeline oder als Server, den auch andere Produkte nutzen können
Schlüsselwortindex SQLite FTS5 (BM25) bei Speicherung im Dateisystem, PostgreSQL-Volltextsuche bei Speicherung in der Datenbank
Dokumente und Ledger Ordner unter KB_ROOT oder PostgreSQL; das Ledger hält Einstellungen, Prompts, 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. Jeder Container erhält eine explizite Liste von Umgebungsvariablen, und ein Test prüft diese Liste.

Jeder Container tritt dem gemeinsamen Docker-Netzwerk pacod-net bei, in dem jeder die anderen erreichen kann. Der interne Listener von pacod-id (Tokens, Signaturschlüssel, der Autorisierungs-Feed, die Admin-API) ist die Ausnahme: Er antwortet nur in pacod-admin-net, einem internen Netzwerk ohne Route nach außen, als pacod-id-internal:8071. kb-ui tritt diesem Netzwerk bei; die Pipeline nicht, und sie kann ihn nicht erreichen.

Wer gehandelt hat. Jeder Aufruf aus der UI trägt X-KB-Actor, die ID der angemeldeten Person. Die Pipeline vertraut diesem Header nur zusammen mit dem API-Schlüssel, vermerkt die ID an Jobs, Einstellungsänderungen und Nutzung und reicht sie an das Gateway weiter. Löscht pacod-id ein Konto, beendet kb dessen Sitzungen, ersetzt diese ID überall durch deleted-user und bestätigt erst dann die Löschung.

Die Suche und was ein Modell bekommt

Eine Abfrage läuft gegen Qdrant und den Schlüsselwortindex, und die beiden Rankings werden per Reciprocal Rank Fusion zusammengeführt: Jede Liste addiert 1/(60 + Rang) für jede Passage, die sie gefunden hat. Datums- und Verlaufsfilter greifen innerhalb beider Suchen, sodass hundert abgelöste Passagen die eine, die gültig ist, nicht verdrängen können.

Ein RRF-Wert ordnet eine Ergebnisliste und bedeutet über Abfragen hinweg nichts. Ein Wert von 0,0164 ist 1/61: Platz eins in einer Liste und in der anderen gar nicht vorhanden. Genau das bekommen die meisten natürlichen Fragen standardmäßig, weil die Schlüsselworthälfte (KEYWORD_QUERY_MODE=legacy) verlangt, dass jedes Wort der Abfrage passt. Die analysierten Modi (or, and_or) lassen stattdessen Stoppwörter weg und führen Wörter auf ihren Stamm zurück; sie bleiben aus, bis eine Evaluierung zeigt, dass sie helfen.

Ein optionaler Cross-Encoder, BAAI/bge-reranker-v2-m3, ordnet die besten zehn Kandidaten neu und gibt jedem eine Relevanz zwischen 0 und 1, die über Abfragen hinweg vergleichbar ist, sodass min_score Nicht-Antworten aussortieren kann. Er lädt im Hintergrund, sobald er eingeschaltet wird, und eine Anfrage lädt ihn nie.

POST /query nimmt die Frage und ihre Filter entgegen:

{"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, keine laufende Nummer
source_id Die Dokument-ID: der 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

Die Offsets sind Bytes, keine Zeichen, was bei kyrillischem Text einen Unterschied macht:

body_bytes = body.encode("utf-8")
passage = body_bytes[result["byte_start"]:result["byte_end"]].decode("utf-8")

Jedes Dokument hat außerdem ein Manifest: Titel, Quelle, Dokumenttyp, Status, Projekt, der Parser oder das Gateway-Modell, das den Text erzeugt hat, und ein Lebenszyklus-Protokoll (ingested, superseded, reinstated, security_flagged, review_flagged, llm_usage).

Versionen und Projekte

Eine mit --supersedes aufgenommene neue Version oder eine überwachte Quelle, die sich geändert hat, markiert die alte mit valid_until, an vier Stellen zugleich: im Front Matter, im Manifest, im Schlüsselwortindex und in den Vektor-Payloads. Suchen liefern standardmäßig aktuelle Passagen; 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. Im Dateisystemmodus wird mit GIT_AUTOCOMMIT=true außerdem jede Aufnahme in ein Git-Repository in KB_ROOT committet.

Ein Projekt ist eine isolierte Sammlung: eine eigene Qdrant-Collection und eigene Zeilen im Schlüsselwortindex. Eine Suche sieht ein Projekt, ein Neuaufbau verwirft den Index eines Projekts und füllt ihn neu (--all-projects baut jedes in seine eigene Collection neu auf), und das Wechseln des aktiven Projekts wird verweigert, solange ein Job wartet oder läuft.

Gemessene Suche

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. Ein Ergebnis zählt, wenn es aus dem annotierten Abschnitt stammt, sodass die Annotationen Änderungen am Chunker überstehen. Gemessen wurde auf der CPU eines Entwicklungsrechners (Apple M5, vier Threads), und die Doku betont, dass das keine Produktionszahlen sind:

Messgröße Ergebnis
Richtiger Abschnitt unter den fusionierten Kandidaten bei jeder beantwortbaren Abfrage
Richtiger Abschnitt unter den ersten fünf, ohne Reranker 98,7 % (R@1 0,83, etwa 50 ms)
Mit Reranker, zehn Kandidaten R@1 0,91, etwa 2 s pro Abfrage
Eine Suche, während Dokumente aufgenommen werden (p50) vorher 24,3 s; jetzt 0,41 s, mit nacheinander laufenden Jobs
Embedding bei der Aufnahme, nach Länge sortierte Batches etwa 2,2× schneller, Vektoren unverändert

Der Wert des Rerankers trennt Antworten von Nicht-Antworten: min_score 0.1 sortiert 14 von 15 unbeantwortbaren Abfragen und 2 von 70 richtigen Top-Antworten aus, wo RRF-Werte sie überhaupt nicht trennen konnten. Standardmäßig ist er trotzdem aus: Sein Gewinn blieb auf der zurückgehaltenen Hälfte knapp unter der Schwelle, und seine Latenz muss erst auf der Produktions-CPU bestätigt werden.

Die analysierten Schlüsselwortmodi halfen bei muttersprachlichen Abfragen und exakten Begriffen, kosteten aber mehr bei sprachübergreifenden und paraphrasierten und verloren auf der zurückgehaltenen Hälfte: legacy bleibt der Standard.

Sicherheitsmodell

Inhaltssicherheit. Der Scan arbeitet auf einer normalisierten Kopie des Textes (Unicode-normalisiert, in Kleinbuchstaben, gesperrt geschriebene Wörter zusammengezogen) und wendet Regeltabellen an: Injection-Phrasen auf Englisch, Deutsch, Russisch und Bulgarisch, Tricks mit Bidi-Overrides und Nullbreitenzeichen, Base64- und Hex-Blobs (dekodiert und erneut gescannt), verstecktes Markup und Spam-Kennzahlen wie Linkdichte und wiederholte Zeilen. Jede Regel hat einen Schweregrad. Als hoch gelten nur eine eindeutige Übersteuerung der Anweisungen („ignore previous instructions“), ein Bidi-Override-Zeichen oder eine in Base64 versteckte Anweisung; Phrasen, die auch in technischen Texten vorkommen, etwa „enable developer mode“, gelten als niedrig, sodass ein einzelner Treffer kein Dokument blockieren kann. Tests prüfen, dass lange Gesetzestexte, darunter die DSGVO, als unbedenklich durchgehen. Das Urteil ist eines von dreien:

  • block: ein Befund mit hohem Schweregrad oder ein hoher Bedrohungswert. Die Aufnahme schlägt fehl, nichts wird gespeichert.
  • quarantine: Das Dokument wird mit Status review behalten, nie umgewandelt 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 (standardmäßig aus) 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 jede Passage mit einer Markierung für nicht vertrauenswürdige Daten.

Abrufen. URL-Aufnahmen akzeptieren nur http und https, folgen höchstens fünf Weiterleitungen und prüfen jede davon, und sie brechen bei 50 MB oder 180 Sekunden ab; eine URL-Aufnahme liest nie eine lokale Datei. Zwei Standardwerte passen eher zu einem Laptop als zu einem Server: Mit WEB_FETCH_ALLOW_PRIVATE=false lehnt kb Adressen ab, die zu privaten Netzwerken auflösen (ein Schutz gegen Server-Side Request Forgery), und mit ALLOW_INSECURE_TLS=false unterbleibt nach einem TLS-Fehler der erneute Versuch ohne Zertifikatsprüfung.

Personen. Die UI meldet Personen über pacod-id mit OIDC und PKCE an, auf den eigenen Seiten von pacod-id im Branding von kb. Sie prüft das Access-Token, hält die Tokens in einer verschlüsselten serverseitigen Sitzung und gibt dem Browser ein Cookie, das nur ein zufälliges Geheimnis trägt. Die Rollen von kb sind ein Manifest, das die UI bei jedem Start an pacod-id überträgt; die Rolle einer Person kommt dann aus dem Autorisierungs-Feed von pacod-id. Ohne kb-Rolle sieht eine Person eine Seite „kein Zugriff“; ist die Kopie des Feeds in der UI älter als 60 Sekunden, antworten die Seiten mit 503, statt zu raten. Die Abmeldung beendet die Sitzung bei pacod-id, und eine dort widerrufene Sitzung endet auch in kb.

Rolle Darf
viewer Lesen und suchen
editor Dokumente aufnehmen, neu indexieren und löschen; Jobs ausführen; Projekte verwalten
admin AI Management, Prompts, die Laufzeitschalter, Mitglieder

Admins laden auf der Mitgliederseite Personen ein und ändern Rollen, über die Admin-API von pacod-id: Aufgeführt werden nur die Rollen von kb, und der letzte Admin kann nicht entfernt werden.

Die Pipeline-API vergleicht ihren Schlüssel in konstanter Zeit und verweigert im Zweifel den Zugriff: Ohne PIPELINE_API_SECRET antwortet jede Route außer /health mit 503.

Deployment und Betrieb

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 kb mitbringt, eingeschaltet über Compose-Profile: Qdrant, PostgreSQL, pacod-id mit seiner Datenbank neben der von kb, einen Mail-Catcher und ein Entwicklungs-Gateway, pacod-gw-lite vor den Mock-Anbietern von pacod-gw, sodass nichts die Maschine verlässt. make up lädt beide Dateien; es braucht Checkouts von pacod-id und pacod-gw neben dem von kb, und von jeder gemeinsamen Komponente kann auf einer Maschine nur eine Kopie laufen. Der Mock gibt den Prompt als Echo zurück, deshalb lässt der Entwicklungs-Stack die Umwandlung aus; AI Management → Test model durchläuft trotzdem den ganzen Weg, Kosten eingeschlossen.

In Produktion verweist man kb auf ein echtes Gateway (pacod-gw mit einer Organisation, einem Workspace und einem Schlüssel für kb, oder pacod-gw-lite mit den Schlüsseln der Anbieter), stellt pacod-id hinter TLS oder nutzt ein bestehendes 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 kb_-Collections im gemeinsamen Qdrant ab, bettet über den gemeinsamen Embeddings-Dienst ein und stellt seine UI hinter dem gemeinsamen Caddy-Edge bereit. Auch die Demo lässt die Umwandlung aus.

  • Speicher. KB_STORAGE_BACKEND ist filesystem oder database; 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 verwendet make setup Volumes.
  • CPU. TORCH_NUM_THREADS=0 folgt 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=1 setzen.
  • Gemeinsame Modelle. Mit EMBEDDINGS_URL bettet 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_URL tut dasselbe für den Reranker. Wird das Modell dieses Dienstes je ersetzt, muss der Index von kb neu aufgebaut werden.
  • 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, Standard kb_).
  • Laufzeitschalter. Das Dashboard schaltet Inhaltsscan, KI-Umwandlung und Sprachprüfung ohne Neustart ein und aus; sie werden nicht gespeichert, und beim nächsten Start gilt wieder die Umgebung.
  • KI. Admins wählen Modelle und bearbeiten Prompts in AI Management, das auch die vom Gateway gemeldete Nutzung und die Kosten zeigt. Jeder Aufruf trägt eine opake Zuordnung (Funktion, Job, Projekt, Person); ein 429 wird einmal wiederholt, nach seinem Retry-After. Ist kein Gateway konfiguriert, ist KI aus, und sonst geht nichts kaputt.
  • Logs. Ein Ereignis pro Zeile im gemeinsamen pacod-Format, JSON oder Text (LOG_FORMAT), auf der Stufe LOG_LEVEL, ohne Query-Strings. Docker behält fünf Dateien zu 10 MB pro Container.

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 samt ihrem Schlüsselverschlüsselungsschlüssel und .env.

Was getestet ist und wo die Grenzen liegen

make check führt ruff, mypy --strict und die Tests aus, die kein Netzwerk, keinen Modell-Download und kein laufendes pacod-id oder Gateway brauchen: Die Tests der UI melden sich über den echten Ablauf gegen ein nachgebildetes pacod-id an. Snapshots der Routentabellen beider APIs fangen unbeabsichtigte Änderungen ab, und das CI führt bei jedem Push die Evaluierungssuite mit einem Ersatz-Embedder aus, um Drift im Korpus oder im Chunker zu erkennen.

  • Die Suchzahlen stammen von einem Entwicklungsrechner; make eval-docker wiederholt die Messung im Image der Pipeline, begrenzt auf eine vorgegebene Zahl von CPUs, wie auf dem Produktions-Host.
  • 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_URL lädt die Pipeline keinen.
  • Serbisch wird angenommen, aber die Spracherkennung hat kein serbisches Profil, sodass serbischer Text falsch erkannt oder abgelehnt werden kann.
  • kb hat keinen Metrik-Endpunkt. Das Diagramm der KI-Nutzung lädt Chart.js von einem CDN, das einzige externe Skript der UI.
  • 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 Passagen- 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, einschließlich des Umzugs auf pacod-net und pacod-admin-net.

Schnellstart

Mit Docker starten

make setup
make up
open http://localhost:8080/kb

Entwickeln und evaluieren

uv sync
make check
make eval

Funktioniert mit

  • pacod-id: Personen melden sich über pacod-id an; die Rollen Viewer, Editor und Admin werden dort vergeben, und die Mitgliederseite von kb nutzt dessen Admin-API.
  • pacod-gw: Der optionale LLM-Bereinigungsschritt und die optionale zweite Meinung des Scans laufen über das Gateway, das die Anbieterschlüssel hält, 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.
  • pacod-demo: Betreibt kb zusammen mit den anderen Produkten, auf dem gemeinsamen Qdrant, PostgreSQL und Embeddings-Dienst, hinter dem gemeinsamen Edge.