docs: context handover — de ce reindexarea refacea toate embeddings-urile
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
This commit is contained in:
164
CONTEXT_HANDOVER_20260831_reindexare.md
Normal file
164
CONTEXT_HANDOVER_20260831_reindexare.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# 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ă.
|
||||||
Reference in New Issue
Block a user