145 lines
6.7 KiB
Markdown
145 lines
6.7 KiB
Markdown
# JARVIS Chatuebergreifendes Gedaechtnis - Design
|
|
|
|
**Datum:** 2026-09-13
|
|
**Status:** Approved, bereit fuer Implementierungsplan
|
|
|
|
## Kontext
|
|
|
|
Der Chat kennt bisher nur den Verlauf der eigenen `conversation_id`
|
|
(`main.py:754`, `get_messages(conversation_id)`). Startet man einen neuen
|
|
Chat (z.B. ueber den neuen "Neuer Chat"-Button), gehen alle Infos aus
|
|
frueheren Unterhaltungen verloren - JARVIS "vergisst" Namen, Vorlieben und
|
|
Kontext aus vorherigen Gespraechen. Ziel dieser Phase: ein Gedaechtnis, das
|
|
ueber einzelne Conversations hinweg besteht, kombiniert aus zwei Quellen:
|
|
|
|
- **Explizite Fakten** - Dinge, die der Nutzer ausdruecklich festhalten
|
|
laesst ("merke dir...", "vergiss, dass...")
|
|
- **Automatische Zusammenfassungen** - jede Conversation wird laufend
|
|
zusammengefasst und per Aehnlichkeitssuche wiedergefunden, wenn sie zum
|
|
aktuellen Thema passt
|
|
|
|
Die vorhandene Knowledge-Base-Infrastruktur (Phase 3a: `pgvector`, Ollama
|
|
`nomic-embed-text` Embeddings) wird fuer die Zusammenfassungen wiederverwendet
|
|
statt eine zweite Vector-Search-Loesung einzufuehren.
|
|
|
|
## Datenmodell (`migrations/006_memory.sql`)
|
|
|
|
```sql
|
|
CREATE TABLE memory_facts (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
content TEXT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE conversation_summaries (
|
|
conversation_id INTEGER PRIMARY KEY REFERENCES conversations(id),
|
|
summary TEXT NOT NULL,
|
|
embedding vector(768) NOT NULL,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
```
|
|
|
|
Kein `ivfflat`/`hnsw`-Index vorerst - gleiche Begruendung wie bei
|
|
`document_chunks` (Phase 3a): bei wenig Zeilen liefert der Index 0 Treffer,
|
|
Sequential Scan ist bei der zu erwartenden Groessenordnung (Dutzende bis
|
|
niedrige Hunderte Conversations) schnell genug. Nachtrag folgt, sobald genug
|
|
Daten vorhanden sind (gleicher offene Punkt wie bei den Dokumenten).
|
|
|
|
## Fakten erfassen: zwei neue Claude-Tools
|
|
|
|
Analog zu Kalender/E-Mail in `run_chat_completion()`:
|
|
|
|
- `remember_fact(fact: str)` - legt sofort einen Eintrag in `memory_facts`
|
|
an, **keine Rueckfrage noetig** (wie `create_calendar_event`) - ein
|
|
gespeicherter Fakt ist folgenlos und leicht wieder zu entfernen.
|
|
- `forget_fact(query: str)` - sucht per `ILIKE '%query%'` nach passenden
|
|
Fakten. Bei einem Treffer: loescht ihn erst **nach Bestaetigung im Chat**
|
|
(wie `delete_calendar_event`/`send_email`). Bei mehreren Treffern: gibt sie
|
|
Claude als Liste zurueck, damit es im Chat nachfragt, welcher gemeint ist,
|
|
statt blind zu loeschen. Bei keinem Treffer: klare Fehlermeldung an Claude
|
|
("kein passender Fakt gefunden").
|
|
|
|
Der System-Prompt bekommt eine kurze Ergaenzung, die Claude anweist, diese
|
|
Tools zu nutzen, wenn der Nutzer explizit um Merken/Vergessen bittet -
|
|
analog zu den bestehenden Anweisungen fuer Kalender-Loeschen.
|
|
|
|
## Automatische Zusammenfassung
|
|
|
|
Nach dem Speichern der Assistant-Antwort in `POST /api/v1/chat`
|
|
(nach `save_message(..., "assistant", ...)`) wird per `asyncio.create_task`
|
|
ein Hintergrund-Job gestartet, der die Chat-Antwort **nicht** blockiert:
|
|
|
|
1. Vollstaendigen Verlauf der Conversation laden (`get_messages`)
|
|
2. Kurzer zusaetzlicher Claude-Call (kleines `max_tokens`-Limit): "Fasse
|
|
dieses Gespraech in 2-3 Saetzen zusammen"
|
|
3. Zusammenfassung mit der vorhandenen Ollama-Embedding-Funktion (aus Phase
|
|
3a, bereits fuer `document_chunks` genutzt) embedden
|
|
4. `INSERT INTO conversation_summaries (...) VALUES (...) ON CONFLICT
|
|
(conversation_id) DO UPDATE SET summary = ..., embedding = ...,
|
|
updated_at = CURRENT_TIMESTAMP`
|
|
|
|
Laeuft nach **jeder** Turn-Antwort (kein Batching/Intervall-Logik noetig -
|
|
bei diesem Nutzungsvolumen vernachlaessigbare Zusatzkosten, ein Client
|
|
wartet nicht darauf). Ergebnis ist immer die Zusammenfassung des gesamten
|
|
bisherigen Gespraechs, nicht nur der letzten Nachricht.
|
|
|
|
## Einbindung in den Chat (`run_chat_completion`)
|
|
|
|
Vor dem eigentlichen Claude-Call wird der System-Prompt um zwei Abschnitte
|
|
ergaenzt:
|
|
|
|
1. **"Bekannte Fakten ueber den Nutzer"** - alle Zeilen aus `memory_facts`
|
|
werden vollstaendig geladen und als Liste angehaengt (kleine Tabelle,
|
|
kein Retrieval noetig - Vollstaendigkeit hat hier Vorrang vor
|
|
Relevanz-Filterung, damit kein gemerkter Fakt durch eine unguenstige
|
|
Formulierung der aktuellen Frage durchrutscht).
|
|
2. **"Relevante fruehere Gespraeche"** - die aktuelle Nutzernachricht wird
|
|
embedded (gleiche Ollama-Funktion), die Top-3 aehnlichsten Zeilen aus
|
|
`conversation_summaries` per Cosinus-Distanz geholt (`<=>`-Operator wie
|
|
bei der Dokumentensuche), **exklusive der aktuellen Conversation** (die
|
|
steht ja bereits vollstaendig in der History).
|
|
|
|
Beide Abschnitte werden nur angehaengt, wenn sie nicht leer sind.
|
|
|
|
## Fehlerbehandlung
|
|
|
|
- Ollama nicht erreichbar waehrend der Retrieval-Suche (Embedding der
|
|
aktuellen Nachricht schlaegt fehl): Abschnitt "Relevante fruehere
|
|
Gespraeche" wird einfach weggelassen, der Chat laeuft normal weiter
|
|
(gleiches Muster wie bestehende `check_ollama()`-Fehlerbehandlung).
|
|
- Hintergrund-Summary-Task wirft eine Exception (Claude-Call oder Embedding
|
|
schlaegt fehl): wird geloggt, hat **keinen** Einfluss auf die bereits an
|
|
den Nutzer gesendete Chat-Antwort (Task laeuft komplett nach dem Response
|
|
Return).
|
|
- `forget_fact` ohne Treffer: kein Fehler, sondern eine normale
|
|
Tool-Antwort an Claude, damit es im Chat mitteilt, dass nichts gefunden
|
|
wurde.
|
|
|
|
## Testing
|
|
|
|
- Unit-Tests (`tests/`, gemockte DB wie bei bestehenden Tool-Tests) fuer:
|
|
- `remember_fact`/`forget_fact` (Insert, ILIKE-Suche, Mehrfachtreffer,
|
|
kein Treffer)
|
|
- Prompt-Zusammensetzung: Facts-Abschnitt korrekt formatiert, leer wenn
|
|
keine Fakten vorhanden; Retrieval-Abschnitt korrekt formatiert, leer
|
|
wenn Embedding fehlschlaegt
|
|
- Manueller End-to-End-Test nach Deployment: in Chat A "merke dir, dass
|
|
mein Hund Bruno heisst" sagen, ueber den "Neuer Chat"-Button einen neuen
|
|
Chat B starten, dort nach dem Hundenamen fragen (Fakt-Pfad) und ueber ein
|
|
thematisch verwandtes, aber nicht identisches Thema aus Chat A fragen
|
|
(Zusammenfassungs-Retrieval-Pfad); anschliessend "vergiss, dass mein Hund
|
|
Bruno heisst" in einem dritten Chat testen.
|
|
|
|
## Out of Scope (bewusst nicht Teil dieser Phase)
|
|
|
|
- Automatisches Bereinigen alter/widerspruechlicher Fakten (z.B. wenn sich
|
|
eine Info spaeter aendert, muss der Nutzer explizit `forget_fact` +
|
|
`remember_fact` nutzen).
|
|
- `ivfflat`/`hnsw`-Index fuer `conversation_summaries` (Nachtrag, sobald
|
|
Datenmenge es rechtfertigt - siehe Phase 3a Follow-up).
|
|
- Mehrere Nutzer/Rollen (weiterhin ein einzelner `DEFAULT_USER_ID`, siehe
|
|
bestehende "Security"-Einschraenkung in der Handoff-Doku).
|
|
- UI zum Anzeigen/Verwalten der gespeicherten Fakten im Dashboard (aktuell
|
|
nur ueber Chat-Kommandos verwaltbar).
|