5.7 KiB
JARVIS Knowledge Base (Phase 3) - Design
Datum: 2026-09-12 Status: Approved, bereit fuer Implementierungsplan
Kontext
Phase 2 (Claude-Chat-Integration, Postgres-Persistenz, Health-Checks) ist live
auf dem VPS (72.61.186.98). Die JARVIS-API hat bereits Platzhalter-Endpunkte
fuer eine Knowledge Base (POST/GET /api/v1/documents), die aktuell nur
TODO-Stubs sind. Diese Spec beschreibt die echte Implementierung.
Die urspruengliche Architektur (siehe JARVIS_HANDOFF.md) sah Milvus als
Vector-DB vor. Das dafuer vorbereitete docker-compose.yml referenziert
milvusdb/milvus:v0.4.0, ein nicht existierendes Image, und ein veraltetes
Config-Format (nur etcd, kein MinIO) das mit modernem Milvus-Standalone nicht
kompatibel ist. Statt das nachzubauen, wird stattdessen pgvector in der
bereits laufenden Postgres-Instanz verwendet - siehe Entscheidung unten.
Entscheidung: pgvector statt Milvus
| pgvector (gewaehlt) | Milvus | |
|---|---|---|
| Neue Container | keine | etcd, minio, milvus (3) |
| Betriebsaufwand | keiner (nutzt laufenden Postgres) | hoch (3 zusaetzliche Services) |
| Konsistenz | Transaktional mit documents/conversations |
separates System, kein 2PC |
| Skalierung | ausreichend bis mehrere 100k Chunks | besser bei Millionen Vektoren |
Fuer eine Business-Knowledge-Base in diesem Umfang ist pgvector die
pragmatischere Wahl (YAGNI). Milvus bleibt als unbenutzter Platzhalter im
docker-compose.yml dokumentiert, falls spaetere Skalierung es noetig macht.
Embeddings: Ollama (nomic-embed-text)
Anthropic bietet keine Embedding-API an. Da Ollama bereits im Stack laeuft, wird lokal embedded statt ueber einen weiteren externen Anbieter (OpenAI/ Voyage) mit eigenem Key und laufenden Kosten:
- Modell:
nomic-embed-text(768 Dimensionen), einmalig perdocker exec jarvis-ollama ollama pull nomic-embed-textzu laden. - Aufruf ueber Ollama's
/api/embeddingsHTTP-Endpoint (aiohttp, analog zum bestehenden Health-Check-Pattern inmain.py).
Datenmodell
Neue Tabelle document_chunks:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE document_chunks (
id SERIAL PRIMARY KEY,
document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
embedding vector(768) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_document_chunks_document_id ON document_chunks(document_id);
CREATE INDEX idx_document_chunks_embedding ON document_chunks
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
Die bestehende documents-Tabelle speichert weiterhin Titel/Volltext/
Metadaten; embedding_id (bisher ungenutzt) bleibt vorerst unangetastet, da
die Beziehung jetzt ueber document_chunks.document_id laeuft.
Datenfluss
Upload (POST /api/v1/documents):
- Dokument-Zeile in
documentsanlegen (title, content, document_type). - Text in Chunks teilen: ~1000 Zeichen, 100 Zeichen Overlap (einfaches Character-Chunking, kein Tokenizer noetig - ausreichend fuer Business- Dokumente in diesem Umfang).
- Pro Chunk: Embedding via Ollama holen, Zeile in
document_chunkseinfuegen. - Response:
document_id, Anzahl Chunks, Statusindexed.
Passiert synchron im Request (kein Background-Queue-Mechanismus) - bei den erwarteten Dokumentgroessen (Business-FAQs, Anleitungen) unkritisch fuer die Latenz. Wird das spaeter zum Problem, ist ein Queue-basierter Ansatz ein separates Follow-up.
Suche (GET /api/v1/documents?query=...):
- Suchanfrage ueber Ollama embedden.
document_chunksper Cosine-Distanz (<=>-Operator) sortieren, Top N (Parameterlimit, Default 10).- Pro Treffer: Chunk-Inhalt, Distanz-Score, zugehoeriger Dokumenttitel.
Fehlerbehandlung
- Ollama nicht erreichbar oder Modell nicht gepullt ->
503mit klarer Fehlermeldung (analog zum bestehendenCLAUDE_API_KEY-Check inchat()). - Leerer/zu kurzer Query-String ->
422(Pydantic-Validierung). - Dokument ohne Inhalt ->
422.
Testing
- Unit-Test fuer die Chunking-Funktion (Grenzfaelle: leerer Text, Text kuerzer als Chunk-Groesse, Text mit exaktem Vielfachen der Chunk-Groesse).
- Manueller End-to-End-Test nach Deployment: Dokument hochladen, danach mit einer thematisch passenden Frage suchen und pruefen, dass der richtige Chunk als Top-Treffer kommt (analog zum manuellen Chat-Test aus Phase 2, da auf dem VPS kein automatisierter Test-Runner etabliert ist).
Nachtrag (gefunden bei der Umsetzung, 2026-09-12)
Der in diesem Dokument spezifizierte ivfflat-Index auf document_chunks.embedding
hat bei sehr wenig Daten (getestet mit 1 Zeile) eine so geringe Recall-Rate,
dass ORDER BY ... LIMIT Anfragen keine Treffer zurückgeben (bestätigt:
funktioniert erst mit SET ivfflat.probes = 10). Genau das kündigt Postgres
schon beim Anlegen des Index an ("This will cause low recall... Drop the
index until the table has more data").
Ruling: Index per Migration 003_drop_low_data_ivfflat_index.sql wieder
entfernt. Ein Sequential Scan ist bei der aktuellen Datenmenge korrekt und
schnell genug. Sobald document_chunks eine relevante Groessenordnung an
Zeilen hat (Richtwert pgvector-Doku: lists ~= sqrt(row_count)), sollte der
Index mit einem zur dann aktuellen Zeilenzahl passenden lists-Wert neu
angelegt werden - das ist ein eigenes Follow-up, keine sofortige Aufgabe.
Out of Scope (bewusst nicht Teil dieser Phase)
- RAG-Integration in den Chat-Endpoint (automatisches Anreichern von Claude-Antworten mit Suchtreffern) - eigenes Follow-up.
- Re-Embedding bei Dokument-Update/-Loeschung ausserhalb von
ON DELETE CASCADE. - Zugriffskontrolle pro Nutzer (kein Auth-System vorhanden, siehe Phase 3
in
JARVIS_HANDOFF.md).