docs: NordaGPT identity, memory & performance design spec

Four pillars: user identity awareness, persistent memory per user,
smart router for cost/speed optimization, streaming responses.
Target: 3,000 queries/day, 70-80% cost reduction, 3-5x faster responses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-28 05:00:34 +01:00
parent 13284ea005
commit 92780fe4b6

View File

@ -0,0 +1,286 @@
# NordaGPT Identity, Memory & Performance — Design Spec
**Data:** 2026-03-28
**Zgłaszający:** Jakub Pornowski (prędkość, tożsamość), Maciej Pienczyn
**Status:** Draft
## Cel
Przekształcić NordaGPT z anonimowego chatbota w spersonalizowanego asystenta, który:
1. Wie kim jest użytkownik i personalizuje odpowiedzi
2. Pamięta poprzednie rozmowy i buduje profil użytkownika
3. Odpowiada 3-5x szybciej dzięki smart routingowi i streamingowi
4. Kosztuje 70-80% mniej przy skali 3,000 zapytań/dzień
## Skala docelowa
- 100-150 aktywnych użytkowników dziennie
- 10-20 pytań na użytkownika
- 1,500-3,000 zapytań/dzień
- Budżet: sponsorowany przez INPI, w przyszłości przeniesiony na użytkowników
---
## Filar 1: Tożsamość użytkownika
### Dane wstrzykiwane do promptu
```
# AKTUALNY UŻYTKOWNIK
Rozmawiasz z: {user.name}
Email: {user.email}
Firma: {company.name} (ID {company.id}) — kategoria: {company.category}
Rola w firmie: {user.company_role}
Członek Izby: {tak/nie}
Rola w Izbie: {user.chamber_role lub "—"}
Powiązane firmy: {lista z UserCompanyPermissions}
Na portalu od: {user.created_at}
```
### Zmiany w API
- `send_message()` — nowy parametr `user_context: dict` (zamiast samego `user_id: int`)
- Route `chat_send_message()` buduje `user_context` z `current_user` + `current_user.company` + `UserCompanyPermissions`
### Zachowanie AI
- Pierwsza wiadomość w konwersacji: "Cześć {imię}, w czym mogę pomóc?"
- Na "co wiesz o mnie?": wypisuje dane z profilu + powiązania firmowe + fakty z pamięci
- Kontekstowe odpowiedzi uwzględniające firmę i rolę użytkownika
---
## Filar 2: Pamięć użytkownika
### Nowe tabele
#### `ai_user_memory`
| Kolumna | Typ | Opis |
|---------|-----|------|
| id | SERIAL PK | |
| user_id | FK users.id (NOT NULL) | Właściciel pamięci |
| fact | TEXT (NOT NULL) | Treść faktu |
| category | VARCHAR(50) | interests / needs / contacts / insights |
| source_conversation_id | FK ai_chat_conversations.id | Z której rozmowy |
| confidence | FLOAT DEFAULT 1.0 | Maleje z czasem |
| created_at | TIMESTAMP DEFAULT NOW() | |
| expires_at | TIMESTAMP | created_at + 12 miesięcy |
| is_active | BOOLEAN DEFAULT TRUE | Użytkownik może dezaktywować |
Indeksy: `(user_id, is_active, confidence DESC)`, `(expires_at)` dla cleanup crona.
#### `ai_conversation_summary`
| Kolumna | Typ | Opis |
|---------|-----|------|
| id | SERIAL PK | |
| conversation_id | FK UNIQUE ai_chat_conversations.id | 1:1 z konwersacją |
| user_id | FK users.id | |
| summary | TEXT | Podsumowanie 1-3 zdania |
| key_topics | JSONB | ["budowlane", "TERMO", "PEJ"] |
| created_at | TIMESTAMP DEFAULT NOW() | |
| updated_at | TIMESTAMP | |
### Generowanie pamięci (asynchroniczne)
1. Po wysłaniu odpowiedzi AI → Flash-Lite analizuje rozmowę w tle (nie blokuje response)
2. Wyciąga nowe fakty → INSERT do `ai_user_memory` (deduplikacja po treści)
3. Co 5 wiadomości w konwersacji → aktualizuje `ai_conversation_summary`
4. Przy opuszczeniu konwersacji → finalne podsumowanie
### Prompt do ekstrakcji faktów (Flash-Lite)
```
Na podstawie tej rozmowy, wyciągnij kluczowe fakty o użytkowniku.
Zwróć JSON array: [{"fact": "...", "category": "interests|needs|contacts|insights"}]
Zasady:
- Tylko nowe, nietrywialne fakty (nie "zapytał o firmę X")
- Fakty przydatne w przyszłych rozmowach
- Max 3 fakty na rozmowę
- Nie duplikuj istniejących faktów: {existing_facts}
```
### Wstrzykiwanie do promptu
- Top 10 najświeższych aktywnych faktów (~500 tokenów)
- Podsumowania ostatnich 5 konwersacji (~750 tokenów)
- Razem: ~1,250 tokenów
### UI
- Ustawienia czatu → "Co NordaGPT o mnie wie"
- Lista faktów z datą i źródłem (link do konwersacji)
- Przycisk "Usuń" przy każdym fakcie (soft delete: is_active=False)
- Lista podsumowań konwersacji
### RODO
- Pamięć prywatna — dostępna TYLKO dla właściciela (filtr user_id na każdym query)
- Użytkownik ma pełną kontrolę: podgląd, usuwanie
- Przy usunięciu konta → CASCADE DELETE pamięci
- Fakty nie zawierają danych wrażliwych (PESEL, konta bankowe) — ten sam filtr RODO co na wiadomościach
---
## Filar 3: Smart Router
### Przepływ zapytania
```
Użytkownik pisze pytanie
[1] Smart Router (3.1 Flash-Lite, ~1-2s)
Input: pytanie + tożsamość + pamięć + lista kategorii danych
Output JSON: {
"complexity": "simple|medium|complex",
"data_needed": ["companies:IT", "events", "user_memory"],
"model": "flash-lite|flash|flash-high"
}
[2] Context Builder
Ładuje TYLKO potrzebne dane z bazy
[3] Main Model (dobrany przez Router)
Prompt: tożsamość + pamięć + wybrane dane + historia (~15-25k tokenów)
[4] Streamed response → użytkownik
[5] Memory Extractor (Flash-Lite, async, w tle)
```
### Prompt routera
```
Jesteś routerem zapytań NordaGPT. Przeanalizuj pytanie użytkownika i zdecyduj:
Użytkownik: {name} z firmy {company}
Pamięć: {facts_summary}
Pytanie: {user_message}
Zwróć JSON:
{
"complexity": "simple|medium|complex",
"data_needed": ["categories from list below"],
"reasoning": "one sentence why"
}
Dostępne kategorie danych:
- companies_all: wszystkie 150 firm (30k tokenów) — porównania, przeglądy
- companies_filtered:{category}: firmy z danej kategorii (2-5k)
- companies_single:{slug}: jedna firma (0.5k)
- events: nadchodzące wydarzenia (2k)
- news: aktualności i PEJ (3k)
- classifieds: ogłoszenia B2B (2k)
- forum: tematy forum (5k)
- company_people: zarząd/KRS (5k)
- registered_users: użytkownicy portalu (3k)
- social_media: profile social media firm (2k)
- audits: SEO/GBP wyniki (2k)
ZAWSZE dodawane (nie musisz wybierać): tożsamość, pamięć, historia rozmowy.
Wybierz MINIMUM potrzebnych kategorii. Jeśli nie jesteś pewien, dodaj więcej.
```
### Kategorie danych i triggerowanie
| Kategoria | Triggery (słowa kluczowe) | ~Tokenów |
|-----------|---------------------------|----------|
| companies_all | "firmy", "porównaj", "wszystkie", "ile firm" | ~30k |
| companies_filtered | nazwa kategorii, "budowlane", "IT" | ~2-5k |
| companies_single | nazwa firmy, slug | ~0.5k |
| events | "spotkanie", "wydarzenie", "kalendarz" | ~2k |
| news | "aktualności", "nowości", "PEJ", "atom" | ~3k |
| classifieds | "ogłoszenie", "B2B", "zlecenie", "oferta" | ~2k |
| forum | "forum", "dyskusja", "temat", "wątek" | ~5k |
| company_people | "zarząd", "KRS", "właściciel", "udziały" | ~5k |
| registered_users | "kto jest", "użytkownicy", "profil" | ~3k |
### Fallback
Jeśli router zwróci błąd lub timeout → ładuj wszystkie dane (obecne zachowanie). Bezpieczne, wolniejsze.
### Dobór modelu
| Complexity | Model | Thinking | Oczekiwany czas |
|------------|-------|----------|-----------------|
| simple | 3.1 Flash-Lite | minimal | 2-3s |
| medium | 3 Flash | low | 4-6s |
| complex | 3 Flash | high | 8-12s |
---
## Filar 4: Streaming + UI
### Backend
- Nowy endpoint SSE: `POST /api/chat/<id>/message/stream`
- Używa `gemini_service.generate_text(stream=True)`
- Zwraca `text/event-stream` z chunkami: `data: {"type": "token", "content": "..."}\n\n`
- Events: `token` (tekst), `thinking` (model myśli), `done` (koniec + metadata), `error`
- Stary endpoint `/api/chat/<id>/message` pozostaje jako fallback (non-streaming)
### Frontend
- `fetch()` z `ReadableStream` (szersza kompatybilność niż EventSource dla POST)
- Tekst pojawia się słowo po słowie w bańce czatu
- Animacja "myślenia" gdy model przetwarza (pulsujące kropki)
- Po `done` → zapisz pełną odpowiedź do DOM, pokaż metadata (czas, model)
### Wskaźniki w UI
- Przy prostych pytaniach: brak wskaźnika myślenia, odpowiedź natychmiastowa
- Przy złożonych: "NordaGPT analizuje..." z animacją (2-3s), potem streaming tekstu
---
## Szacunek kosztów (3,000 zapytań/dzień)
| Składnik | Koszt/zapytanie | Dziennie | Miesięcznie |
|----------|-----------------|----------|-------------|
| Router (Flash-Lite) | ~$0.001 | $3 | $90 |
| Main model (mix) | ~$0.01-0.03 | $30-90 | $900-2,700 |
| Memory extraction (async) | ~$0.001 | $3 | $90 |
| **Suma** | | **$36-96** | **$1,080-2,880** |
vs. obecne podejście bez optymalizacji przy tej skali: ~$9,000-13,500/mies.
**Oszczędność: 70-80%**
---
## Szacunek prędkości po zmianach
| Metryka | Obecna | Po zmianach |
|---------|--------|-------------|
| Średni czas odpowiedzi | 20.5s | 3-6s |
| Perceived latency (streaming) | 20.5s | 1-2s |
| P95 | 34.5s | 8-12s |
| Najwolniejsze (complex) | 46s | 12-15s |
---
## Migracje SQL
1. `091_create_ai_user_memory.sql` — tabela + indeksy
2. `092_create_ai_conversation_summary.sql` — tabela + indeksy
## Pliki do zmiany
| Plik | Zmiana |
|------|--------|
| `database.py` | Nowe modele: AIUserMemory, AIConversationSummary |
| `nordabiz_chat.py` | Smart Router, Context Builder, Memory Extractor, user_context |
| `gemini_service.py` | Streaming support w generate_text (już częściowo jest) |
| `blueprints/chat/routes.py` | Nowy endpoint streaming, user_context budowanie |
| `templates/chat.html` | Streaming UI, animacja myślenia |
| `static/js/chat.js` lub inline | ReadableStream handler |
| Nowy: `smart_router.py` | Logika routera (prompt, parsing, fallback) |
| Nowy: `memory_service.py` | Ekstrakcja faktów, podsumowania, CRUD pamięci |
| Nowy: `context_builder.py` | Selektywne ładowanie danych na podstawie decyzji routera |
## Kolejność wdrażania
1. **Tożsamość użytkownika** — najprostsza, natychmiastowy efekt "wow"
2. **Smart Router + Context Builder** — redukcja kosztów i poprawa prędkości
3. **Streaming** — perceived latency drop
4. **Pamięć użytkownika** — wymaga nowych tabel, async pipeline, UI