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

131 lines
5.7 KiB
Markdown

# 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`:
```sql
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`).