Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
8.3 KiB
Context handover — 2026-08-31, seara
Continuarea sesiunii din 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 —
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 pekey.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.
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:
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 |
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)
systemctlpe LXC 110 are nevoie de--user. Serviciile lui Echo sunt unități de utilizator (moltbot, uid 1000). Ca root,systemctl is-active echo-corerăspundeinactivedeși botul rulează.- Nu adăuga documente în
~/.maria-bridge/documents/. E oglinda Drive-ului;rclone syncle șterge. Foloseștedocuments-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ă.