Files
ROMFASTSQL/CONTEXT_HANDOVER_20260831_reindexare.md
2026-08-31 22:15:55 +00:00

165 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Context handover — 2026-08-31, seara
Continuarea sesiunii din [`CONTEXT_HANDOVER_20260831.md`](CONTEXT_HANDOVER_20260831.md).
Tot ce e mai jos e comis și împins (`ROMFASTSQL`: `b447251..c514818`, `claude-agent`: două
comiteri noi).
---
## 1. Întrebarea care a declanșat handover-ul
> *„nu vreau ca reindexarea să refacă toate embeddings-urile de fiecare dată. de ce le face?"*
**Nu le mai face.** Reparat în `c514818` (parte din commit-ul `ca6b53e`), verificat pe viu:
```
[indexer] 12 documente, 169 chunk-uri (0 embeddings noi, 169 refolosite)
real 0.15s # înainte: ~20 de minute
```
### De ce le refăcea
`rag/indexer.py:build()` era scris ca o reconstrucție totală: pentru fiecare document, pentru
fiecare chunk, `embed(chunk)` — necondiționat. Nu exista nicio noțiune de „chunk-ul ăsta e
neschimbat". Indexul vechi era pur și simplu suprascris.
Costul: un embedding pe CPU (nomic-embed-text, fără GPU pe LXC 171) durează **~7 secunde**.
La 169 de chunk-uri asta înseamnă ~20 de minute — plătiți integral chiar și când se schimba
un singur document. Adăugarea dicționarului de erori Oracle (29 de chunk-uri noi) ar fi
costat 20 de minute în loc de 3.
Nu era o scăpare evidentă la citirea codului: `sync.py` avea deja o optimizare care *arăta*
ca și cum ar rezolva problema — amprenta pe nume+mtime+mărime, care sărea reindexarea când
nimic nu se schimbase. Dar aia decide **dacă** se reindexează, nu **cât** se reindexează.
Odată ce un singur fișier se atingea, se plătea totul.
### Cum se rezolvă acum
`rag/indexer.py:_vectori_existenti()` citește indexul precedent și îl transformă într-un
dicționar `text_chunk -> embedding`. La reconstrucție, fiecare chunk se caută întâi acolo.
Cheia e **textul chunk-ului**, nu numele documentului și nu mtime-ul. Motivul: dacă nu s-a
schimbat niciun caracter în chunk, vectorul e identic prin definiție. Un document reformatat
în Drive, ale cărui chunk-uri ies la fel, nu costă nimic. Un document în care s-a schimbat un
rând costă doar chunk-urile atinse.
**Sursa e chiar `rag_index.json`, nu un al doilea fișier de cache.** Deliberat: nu există un
al doilea fișier care să se poată desincroniza, iar dacă indexul lipsește se recalculează tot,
exact ca înainte. Logul spune de fiecare dată câte au fost calculate și câte refolosite.
### Când tot se recalculează (și e corect așa)
| Situație | Ce se întâmplă |
|---|---|
| `rag_index.json` lipsește sau e corupt | se recalculează tot — nu are de unde refolosi |
| Textul unui chunk s-a schimbat, oricât de puțin | acel chunk se re-embedează (corect: alt text = alt vector) |
| Se schimbă **strategia de chunking** (`indexer.py`) | toate chunk-urile ies altfel → se recalculează tot |
| Se schimbă `EMBED_MODEL` în env | **capcană: NU se recalculează**, deși ar trebui |
Ultima linie e singura problemă rămasă și e reală: cheia de cache e doar textul, fără modelul
care l-a produs. Dacă cineva schimbă `EMBED_MODEL`, indexul rămâne un amestec de vectori din
două modele, iar căutarea dă rezultate aiurea fără niciun mesaj de eroare. Nu s-a întâmplat
(modelul e `nomic-embed-text` de la început), dar merită reparat — vezi „De făcut" mai jos.
**Fișiere:** `rag/indexer.py` (`_vectori_existenti`, `build`), teste în
`tests/test_store_si_chunking.py` (`test_vectorii_din_indexul_vechi_se_refolosesc`,
`test_textul_modificat_se_reembedeaza`).
---
## 2. Ce s-a mai făcut în sesiune
Toate în `proxmox/lxc171-claude-agent/`. Documentația completă e în
[`maria-whatsapp-bridge/README.md`](proxmox/lxc171-claude-agent/maria-whatsapp-bridge/README.md) —
aici doar ce nu se vede din cod.
### Asociere WhatsApp prin cod, nu doar QR
`/pair` exista dar nu era folosibil: fără descriptor `browser` explicit WhatsApp refuză codul,
iar după introducerea lui corectă serverul cere un restart (515) care consuma din bugetul de
reîncercări și putea opri puntea la a cincea asociere. Cod valabil ~3 minute, cu marcarea
expirării în dashboard. Folosit pe bune pentru re-legarea lui Echo.
### Capturi de ecran cu erori (OCR)
tesseract `ron+eng`; modelul de răspuns e strict text, deci OCR nu e o opțiune de calitate.
**Căutarea în index folosește doar liniile care arată a eroare**, nu toată fereastra — un ecran
de meniuri diluează embedding-ul. Modelul primește captura întreagă, marcată ca OCR.
### Rank hibrid (`rag/rank.py`)
Embeddings + BM25, fuzionate prin RRF. Motivul, măsurat: intervalele de cosinus **se suprapun**
— întrebări bune 0,600–0,816, străine 0,534–0,685. Codurile (`ORA-01722`, `D406`, `CIF`) sunt
exact ce ratează căutarea semantică. Praguri calibrate cu `ops/calibrate-rank.py`, **19/19**.
### Escaladare la suport
Când nu există acoperire, modelul **nu mai e întrebat deloc**. Referință (`M-260831-FC34`),
confirmare onestă („am trimis" doar dacă notificarea chiar a plecat), captura se retrimite ca
imagine. Jurnal în `~/.maria-bridge/escalations/`, vizibil în dashboard.
### Dicționar de 29 de erori Oracle
`maria-whatsapp-bridge/knowledge/oracle-erori-uzuale.xml`, scris pentru clienți — fără nume de
servere sau pași de administrare.
### Două bug-uri găsite în log
- Fiecare mesaj din self-chat sosea **de două ori** (`@s.whatsapp.net` și `@lid`) → Maria
răspundea dublu. Dedup pe `key.id` în punte.
- Documentele adăugate din dashboard ajungeau în oglinda Drive și **dispăreau la următorul
`rclone sync`**. Acum există `~/.maria-bridge/documents-local/`.
### Echo re-legat la WhatsApp
Dispozitivul `:14`. S-a lămurit de ce nu răspund ambele punți în chatul „Eu": Echo aruncă tot
ce e `fromMe && !isGroup`, iar în self-chat tot ce scrii e `fromMe`. **Echilibrul e accidental** —
detalii în [`docs/chatboti-si-punti.md`](docs/chatboti-si-punti.md).
---
## 3. De făcut
### a) `EMBED_MODEL` în cheia de cache (mic, real)
Vezi tabelul de mai sus. Cea mai simplă variantă: pune modelul în fiecare intrare de index
(`"model": config.get("EMBED_MODEL")`) și refolosește doar intrările cu modelul curent.
Intrările vechi, fără câmp, se tratează ca „model necunoscut" → se recalculează o dată.
### b) `SUPPORT_JID` — decizia utilizatorului
E setat pe **propriul număr** (`40723197939@s.whatsapp.net`), pentru testare. De schimbat cu
numărul sau grupul echipei de suport, în `~/.maria-bridge/env`.
### c) Dicționarul Oracle trăiește doar pe container
E în `documents-local/`, deci supraviețuiește sincronizărilor, dar **nu-l vede nimeni altcineva**.
Locul lui pe termen lung e `document_store` din Drive. Când ajunge acolo, șterge copia locală —
altfel rămân două versiuni și câștigă tăcut cea din Drive.
### d) Recalibrare când se schimbă documentele
Pragurile din `env` sunt măsurate pe indexul de **acum**. La schimbări mari de conținut:
```bash
cd proxmox/lxc171-claude-agent/maria-whatsapp-bridge
MARIA_BRIDGE_DIR=$HOME/.maria-bridge ~/.maria-bridge/venv/bin/python ops/calibrate-rank.py
```
Adaugă în `CAZURI` (în capul scriptului) întrebările reale la care Maria a greșit — e singurul
mod în care calibrarea rămâne onestă.
### e) Rămase din handover-ul precedent
LXC 301 (nimic urgent), auditul de infrastructură, modelul de răspuns al Mariei.
---
## 4. Stare la finalul sesiunii
| | |
|---|---|
| Index RAG | 169 chunk-uri, 12 documente (din care `oracle-erori-uzuale.xml` e local) |
| Servicii | `maria-whatsapp`, `maria-rag`, `claude-discord-dashboard` — active |
| WhatsApp | Maria conectată (`40723197939`), Echo re-legat ca dispozitiv `:14` |
| Teste | **73 pass** (26 la începutul zilei) |
| `SUPPORT_JID` | propriul număr — de schimbat |
## 5. Capcane (în plus față de handover-ul precedent)
- **`systemctl` pe LXC 110 are nevoie de `--user`.** Serviciile lui Echo sunt unități de
utilizator (`moltbot`, uid 1000). Ca root, `systemctl is-active echo-core` răspunde
`inactive` deși botul rulează.
- **Nu adăuga documente în `~/.maria-bridge/documents/`.** E oglinda Drive-ului; `rclone sync`
le șterge. Folosește `documents-local/`.
- **Nu ridica pragurile de rank „ca să fie sigur".** Escaladarea prea agresivă e la fel de
proastă ca răspunsul generic — rulează calibrarea, nu intuiția.
- **Fiecare embedding costă ~7 secunde.** Orice schimbare în strategia de chunking înseamnă
recalcularea întregului index; nu e o schimbare de făcut în grabă.