jarvis-assist/docs/superpowers/specs/2026-09-12-knowledge-base-d...

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 per docker exec jarvis-ollama ollama pull nomic-embed-text zu laden.
  • Aufruf ueber Ollama's /api/embeddings HTTP-Endpoint (aiohttp, analog zum bestehenden Health-Check-Pattern in main.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):

  1. Dokument-Zeile in documents anlegen (title, content, document_type).
  2. Text in Chunks teilen: ~1000 Zeichen, 100 Zeichen Overlap (einfaches Character-Chunking, kein Tokenizer noetig - ausreichend fuer Business- Dokumente in diesem Umfang).
  3. Pro Chunk: Embedding via Ollama holen, Zeile in document_chunks einfuegen.
  4. Response: document_id, Anzahl Chunks, Status indexed.

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=...):

  1. Suchanfrage ueber Ollama embedden.
  2. document_chunks per Cosine-Distanz (<=>-Operator) sortieren, Top N (Parameter limit, Default 10).
  3. Pro Treffer: Chunk-Inhalt, Distanz-Score, zugehoeriger Dokumenttitel.

Fehlerbehandlung

  • Ollama nicht erreichbar oder Modell nicht gepullt -> 503 mit klarer Fehlermeldung (analog zum bestehenden CLAUDE_API_KEY-Check in chat()).
  • 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).