Files
ROMFASTSQL/CONTEXT_HANDOVER_20260901_termeni_distinctivi.md
2026-09-01 19:58:12 +00:00

155 lines
11 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-09-01, seara
> **REZOLVAT in `653f5cb`** (2026-09-01, noaptea). Pasul (1) facut: setul are 31 de
> cazuri. Pasul (2) facut, dar cauza s-a dovedit ALTA decat se banuia aici:
> politetea din ancora nu era ce strica verdictul, ci faptul ca termenii se
> comparau pe **cuvantul intreg** — documentul scrie „token"/„tokenuri", omul scrie
> „tokenul". Reparat prin potrivire pe primele 5 litere (codurile raman intregi) +
> varianta (a), `rank.assess(dovada=...)`. Varianta (b), lista de formule de
> politete, a fost scrisa si apoi **scoasa**: masuratoarea n-a justificat-o.
> 31/31 cu reparatia, 30/31 fara. Restul documentului se pastreaza ca istoric —
> §7 (eroarea „CUI cumparator incorect" lipsa din documente) e inca deschis.
**Ce e de făcut:** două lucruri legate între ele, în `proxmox/lxc171-claude-agent/maria-whatsapp-bridge/`:
1. adaugă **cazuri de continuare** în `ops/calibrate-rank.py` (azi setul n-are decât două, ambele cu cod de eroare);
2. repară **termenii distinctivi**: `rank.rare_terms()` judecă acoperirea pe *interogarea combinată* (ancora firului + mesajul nou), nu pe ce a întrebat omul acum.
(1) există ca să poți face (2) fără să ghicești. **Nu începe cu (2).**
Tot ce e mai jos e comis și împins: `ROMFASTSQL` `5e60394..af04d97`.
---
## 1. De unde vine
Sesiunea de azi a plecat de la: *„am trimis 2 mesaje pe grupul maria test whatsapp și ambele răspunsuri sunt eronate"*. Cauza reală s-a dovedit dublă și e reparată (vezi §5), dar reparația a scos la iveală problema care rămâne deschisă.
Din firul de test real (escaladarea `M-260901-06EB`, `~/.maria-bridge/escalations/1788291399-M-260901-06EB.json`):
```
[consumer] rank: efactura_knowledge.md(0.836), … | cosinus 0.836, iar termenii
distinctivi (buna, luna, august, trimitere, auto) nu apar in documente
```
**„buna" e tratat drept termen distinctiv.** E rar în corpusul de ERP, deci `rare_terms()` îl consideră discriminant, deși e o formulă de salut.
De ce contează acum și nu conta înainte: de azi, la o continuare se caută după **ancoră + mesajul nou** (`consumer.py:574` → `fir.interogare()`), iar ancora e primul mesaj al omului, cu tot cu „Buna . … Poti te rog sa ma ajuti?". Ancora nu se șterge cât trăiește firul (2h), deci **fiecare continuare de pe firul ăla e judecată și după „buna/luna/august"**, cuvinte care nu vor apărea niciodată în documente.
### Efectul
`assess()` cere ca cel puțin `RANK_MIN_RARE_RATIO` (0,5) din termenii distinctivi să apară chiar în chunk-uri. Cu 3 cuvinte de politețe care nu apar niciodată, raportul scade artificial la fiecare replică → **escaladare în plus**.
Direcția e sigură (un om vede întrebarea, nu primește un răspuns inventat), deci **nu e urgent**. Dar face Maria inutilă exact pe discuțiile lungi, unde ar trebui să fie cea mai bună.
În cazul testat n-a stricat nimic — răspunsul chiar nu era în documente, escaladarea a fost corectă. Deci **nu ai încă nicio dovadă că regresia se produce în practică**; asta e chiar ce trebuie măsurat la pasul (1).
---
## 2. Reparația propusă (nu implementată)
**Separă cele două roluri ale interogării:**
| Rol | Ce folosește azi | Ce ar trebui |
|---|---|---|
| **regăsire** (ce chunk-uri aduc) | ancoră + mesaj nou | rămâne așa — ancora chiar ajută, „da, mă blochează" singur nu găsește nimic |
| **acoperire** (răspund sau escaladez) | ancoră + mesaj nou | doar mesajul nou (+ OCR-ul capturii), adică ce a întrebat omul acum |
Concret, în `consumer.py:131 search()`, `rank.assess()` (`consumer.py:144`) primește azi același `query` ca și regăsirea. Ar trebui să primească separat interogarea de dovadă.
**Capcana, și motivul pentru care n-am făcut-o azi:** o continuare scurtă („da, mă blochează complet", „eram la salvarea unei facturi") **n-are termeni distinctivi proprii**. Judecată singură, escaladează — exact regresia pe care `fir.py` a fost scris ca s-o prevină (vezi tabelul din docstring-ul lui `rag/fir.py`, cazurile măsurate). Deci reparația poate strica mai mult decât repară, și nu se poate valida pe setul actual de cazuri.
Variante de luat în calcul, în ordinea preferinței:
- **a)** acoperirea se judecă pe mesajul nou, dar dacă acesta n-are termeni distinctivi proprii se cade înapoi pe interogarea combinată (continuările scurte se comportă ca azi);
- **b)** `rare_terms()` ignoră formulele de politețe — listă mică de stopwords („buna", „salut", "buna ziua", „multumesc", „va rog", „poti", „ajuti"). Whack-a-mole, dar o linie și zero risc;
- **c)** ancora se curăță la creare: se păstrează doar liniile care arată a eroare, cum face deja `ocr.retrieval_query()` pentru capturi (`rag/ocr.py`).
(b) e cel mai ieftin și poate fi făcut singur, imediat. (a) e cel corect. Nu le confunda: (b) ascunde simptomul măsurat azi, (a) rezolvă clasa.
---
## 3. Ce trebuie adăugat în `ops/calibrate-rank.py` — pasul (1)
Setul (`CAZURI`, o listă de `(intrebare, are_raspuns_in_documente)`) are azi **26 de cazuri, 26/26 corecte**. Continuări are doar două, și **ambele conțin un cod de eroare**, deci trec pe regula codurilor și nu spun nimic despre termenii distinctivi:
```python
("ORA-06550: line 1, column 7 PLS-00906 object invalid\nda, ma blocheaza complet", True),
("ORA-12154 TNS could not resolve the connect identifier\neram la salvarea unei facturi", True),
```
**Lipsesc continuările fără cod de eroare** — exact cazul în care termenii distinctivi decid. De adăugat, în forma pe care o produce `fir.interogare()` (ancoră `\n` mesaj nou):
- ancoră politicoasă + continuare scurtă, cu răspuns în documente → `True`
(ex. `"Buna ziua . Am o problema la trimiterea in SPV . Puteti sa ma ajutati?\nimi zice ca tokenul e expirat"`)
- ancoră politicoasă + continuare scurtă, fără răspuns în documente → `False`
- aceeași continuare **fără** formula de salut în ancoră → același verdict ca varianta cu salut
(dacă verdictele diferă, ai măsurat exact regresia)
- ancoră + a treia, a patra replică (fir lung) → verdictul nu trebuie să se degradeze cu lungimea
- continuare pe un fir deschis de o **captură** (ancora = OCR, plin de zgomot: „Total", „Lit", „Selecteaz!")
Scrie-le pornind de la fire reale, nu inventate: `~/.maria-bridge/escalations/*.json` are câmpul `fir` cu schimburile, iar `~/.maria-bridge/conversations/*.json` are ancora exactă.
**Atenție:** fiecare caz nou costă un embedding (~7s pe CPU, fără GPU pe LXC 171). Se pun în cache automat în `~/.maria-bridge/calibrare-cache.json`, deci doar prima rulare e lentă.
### Cum se rulează
```bash
cd /workspace/romfastsql/proxmox/lxc171-claude-agent/maria-whatsapp-bridge
MARIA_BRIDGE_DIR=$HOME/.maria-bridge ~/.maria-bridge/venv/bin/python ops/calibrate-rank.py
MARIA_BRIDGE_DIR=$HOME/.maria-bridge ~/.maria-bridge/venv/bin/python ops/calibrate-rank.py --sweep
python3 -m pytest -q # 103 teste; venv-ul n-are pytest, se ruleaza cu python3 de sistem
```
`--sweep` mătură `RANK_WEAK_COSINE` × `RANK_MIN_RARE_RATIO` (nu mai există prag „strong", vezi §5).
**Criteriu de acceptare pentru (2):** setul extins rămâne 100% corect *și* cu reparația, *și* fără ea nu e — altfel n-ai dovedit că reparația face ceva.
---
## 4. Fișiere care contează
| Fișier | De ce |
|---|---|
| `rag/rank.py:168 rare_terms()` | inima problemei: `len(t) >= 4 and df <= 25% din corpus` → „buna" trece |
| `rag/rank.py:212 assess()` | decizia acoperirii; docstring-ul explică de ce nu mai există prag pe cosinus |
| `rag/consumer.py:131 search()` | aici `assess()` primește același query ca regăsirea — punctul de separat |
| `rag/consumer.py:574` | `search_query = fir_mod.interogare(fir, search_query)` — unde se combină |
| `rag/fir.py:140 interogare()` | ancoră + mesaj nou |
| `rag/fir.py:126 extinde_ancora()` | ancora crește cu OCR-ul capturii (adăugat azi) |
| `rag/fir.py` docstring | tabelul cu răspunsurile greșite care justifică existența ancorei — citește-l înainte să scoți ancora din ceva |
| `ops/calibrate-rank.py` | setul de cazuri + `--sweep` |
| `tests/test_rank.py`, `tests/test_fir.py` | testele care prind regresiile |
| `README.md` §„Cum se ordonează rezultatele (rank)" | documentația de actualizat la final |
---
## 5. Ce s-a reparat azi (nu reface)
Trei comiteri, toate împinse, serviciile repornite, verificate pe viu în grupul „Maria Test".
**`45b3659`** — două răspunsuri inventate: mesaj vag (cosinus 0,768) și captură cu `errorMessage="CUI cumparator incorect"` (0,778, eroare inexistentă în documente). Cauza: `RANK_STRONG_COSINE=0,70` însemna „cosinusul singur e destul", dar toate chunk-urile eFactura seamănă între ele. Plus `LLM_TEMPERATURE=0` (llama.cpp folosea 0,8).
**`47110d7`** — două probleme de fond:
- gardul „mesaj prea vag" (`triaj.prea_vag`) cerea text sub 12 cuvinte; mesajul real avea 14. Acum nu mai numără cuvinte și se aplică **după căutare, doar când nu există acoperire**, o singură dată pe fir (`fir["detalii_cerute"]`);
- `fir.este_continuare()` rupea firul la **orice** imagine. Cazul frecvent e tocmai text + captură trimise una după alta. Acum o captură în `FIR_IMAGINE_MIN` (5 min) continuă firul, OCR-ul intră în ancoră, iar pe o escaladare deschisă captura pleacă la aceeași referință;
- **`RANK_STRONG_COSINE` a dispărut de tot.** Motivul e important pentru (2): *cosinusul crește cu lungimea interogării*. Aceeași captură: 0,778 singură, **0,836** împreună cu mesajul de dinainte. Orice prag fix de sus e trecut de o discuție destul de lungă. Acoperirea se sprijină acum exclusiv pe dovada lexicală — adică pe chiar `rare_terms()`, ceea ce face (2) mai important decât părea.
**`af04d97`** — găsite citind logul: `requests` nu ridică excepție la 4xx/5xx, deci escaladarea se scria `notified: true` chiar când puntea răspundea 503; puntea nu loga nimic la trimitere; mesajele primite se logau tăiate la 80 de caractere fără semn (m-a dus la o concluzie greșită în diagnostic).
---
## 6. Constrângeri de mediu
- **Modelul de chat e Qwen3.5-2B Q4** pe LXC 104 (`LLM_URL=http://10.0.20.161:8091`). Inventează dacă îl lași. Poarta de acoperire e singura apărare reală — de asta merită reparată bine.
- **Nu încerca un model mai mare pe LXC 171.** Testat azi: `qwen3:4b-instruct` prin Ollama, 4 nuclee, fără GPU → 600s timeout fără niciun token; un prompt de 15 tokeni a scos 6 tokeni în 10 min 20s. Modelul a fost șters. Fără GPU sau API extern (vezi `docs/supliment-glm-zai.md`, neimplementat) nu e altă variantă.
- **Ollama pe 171 are doar `nomic-embed-text`**, pentru embeddings. Un embedding ≈ 7s.
- Servicii: `systemctl --user restart maria-rag maria-whatsapp`; loguri în `~/.maria-bridge/logs/{rag,whatsapp}.log`.
- Grup de test: `120363409761730101@g.us` („Maria Test"), `SUPPORT_JID` e chiar numărul propriu, deci escaladările se văd.
- **Șterge firele vechi înainte de a testa** (`rm -f ~/.maria-bridge/conversations/*.json`), altfel primul mesaj e luat drept continuare a discuției precedente — TTL 120 min.
---
## 7. De reținut, separat de task
**„CUI cumparator incorect" nu există în niciun document** (`grep -ri "cumparator incorect" ~/.maria-bridge/documents*` → zero). Maria escaladează corect, dar ar putea răspunde. E o eroare eFactura reală și frecventă. N-am scris textul — nu inventez procedură ROA. De pus în `document_store` (Google Drive), de către cineva care știe procedura.