Cheia de refolosire a embeddings-urilor era doar textul chunk-ului. La schimbarea
lui EMBED_MODEL indexul ar fi ramas un amestec de vectori din doua modele, iar
cautarea ar fi dat rezultate aiurea fara nici un mesaj de eroare. Acum fiecare
intrare poarta modelul cu care a fost calculata si se refolosesc doar cele cu
modelul curent; intrarile vechi, fara camp, se recalculeaza o singura data.
Indexul de pe container a fost stampilat manual cu `nomic-embed-text` (singurul
folosit pana acum), deci nu s-au recalculat cele 169 de chunk-uri.
`GET /groups` listeaza grupurile contului cu JID, nume si numar de participanti.
JID-ul unui grup ("120363...@g.us") nu se vede nicaieri in WhatsApp, iar el e
singurul mod de a scrie SUPPORT_JID pentru un grup de suport.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
429 lines
20 KiB
Markdown
429 lines
20 KiB
Markdown
# Punte WhatsApp + RAG pentru Maria (LXC 171)
|
|
|
|
> **Care chatbot e care?** Exista trei lucruri numite „Maria", doi boti pe Discord si
|
|
> doua punti WhatsApp legate la acelasi numar. Inainte de a depana, citeste
|
|
> [`docs/chatboti-si-punti.md`](../../../docs/chatboti-si-punti.md) — spune care instanta raspunde de fapt.
|
|
|
|
Bot de suport ROA pe WhatsApp: puntea WhatsApp (Baileys) primeste mesajele,
|
|
consumer-ul RAG raspunde folosind DOAR informatiile dintr-un depozit de
|
|
documente indexat cu embeddings (Ollama). Controlul (start/stop/restart,
|
|
documente, reindexare, sincronizare Google Drive) se face din **dashboard-ul
|
|
puntii Discord** (`../discord-bridge/dashboard/`), sectiunea "Maria — WhatsApp
|
|
+ RAG" — un singur panou comun pentru ambele punti de pe acest container, nu
|
|
un dashboard separat per serviciu.
|
|
|
|
Continua prototipul descris in
|
|
`claude-agent/docs/maria-whatsapp-rag-prototype.md` (construit initial in
|
|
`/tmp/maria-bridge/`) — aici e mutat in git, ca serviciu persistent.
|
|
|
|
Nu confunda cu:
|
|
- **Maria pe Flowise** (`vfp_roaauto/COMUN/utile/chatbot/`) — chatbot web separat.
|
|
- **Echo / `echo-whatsapp-bridge.service`** (LXC 110 moltbot) — alt bot, alta punte.
|
|
- **Punte Discord -> Claude Code** (`discord-bridge/`, acelasi container) — alt
|
|
proiect, alt scop (comanda Claude Code de pe Discord, nu suport RAG).
|
|
|
|
## ATENTIE: a existat o a doua instalare, pe LXC 104
|
|
|
|
Prototipul din care a plecat acest proiect a fost lasat sa ruleze pe **LXC 104
|
|
(Flowise)**, in `/root/maria-whatsapp-bridge/` — in afara git-ului, cu propriul
|
|
`consumer.py`, propriul `rag_index.json` si propriul `auth/` de WhatsApp.
|
|
|
|
Pana pe 2026-08-31, **aceea era Maria care raspundea de fapt pe WhatsApp**, iar cea
|
|
din git (LXC 171) nu fusese niciodata asociata. In acea zi rolul a trecut aici:
|
|
LXC 171 a fost asociat la acelasi numar, iar pe 104 serviciile
|
|
`maria-whatsapp-bridge` si `maria-whatsapp-consumer` au fost oprite si dezactivate.
|
|
`llama-qwen35.service` (LLM-ul de raspuns) si `flowise.service` raman pornite acolo. Indexul prototipului avea 24 de
|
|
chunk-uri dintr-un singur fisier (`roaauto_instructiuni.txt`), deci raspundea
|
|
inventand la orice intrebare din afara acelui subiect — inclusiv e-Factura.
|
|
|
|
Confuzia a costat timp de diagnostic: pe LXC 171 totul parea rupt (niciun LLM pe
|
|
8091, WhatsApp neasociat, `rag.log` fara activitate) desi utilizatorul primea
|
|
raspunsuri. Explicatia era ca ne uitam la containerul gresit.
|
|
|
|
**Daca depanezi Maria, verifica intai pe ce container ruleaza instanta conectata:**
|
|
|
|
```bash
|
|
# aici (LXC 171)
|
|
curl -s http://127.0.0.1:8099/status
|
|
# pe LXC 104, daca mai exista
|
|
ssh root@10.0.20.201 "pct exec 104 -- curl -s http://127.0.0.1:8099/status"
|
|
```
|
|
|
|
LLM-ul de raspuns (`llama.cpp` cu `Qwen3.5-2B-Q4_K_M`, port 8091) ruleaza tot pe
|
|
LXC 104 si e folosit prin retea: `LLM_URL=http://10.0.20.161:8091`. Pe LXC 171 nu
|
|
exista niciun model de chat — Ollama de aici are doar `nomic-embed-text`, pentru
|
|
embeddings.
|
|
|
|
## Arhitectura
|
|
|
|
```
|
|
WhatsApp (self-chat, sau numarul legat)
|
|
|
|
|
v
|
|
whatsapp/index.js (Baileys) -- API HTTP :8099 (/status /send /messages /react /qr /pair /groups)
|
|
| descarca imaginile primite in ~/.maria-bridge/media/
|
|
|
|
|
v
|
|
rag/consumer.py -- polling la /messages, RAG stateless (FARA memorie intre mesaje)
|
|
| vezi docs/maria-whatsapp-rag-prototype.md pentru motiv
|
|
v
|
|
rag/ocr.py -- capturile de ecran -> text (tesseract), inainte de RAG
|
|
|
|
|
v
|
|
rag/rank.py -- ordonare hibrida (embeddings + BM25) si decizia
|
|
| "avem raspunsul in documente?"; daca nu, mesajul pleaca la suport
|
|
v
|
|
rag/store.py -- depozit documente (.txt/.md) in ~/.maria-bridge/documents/
|
|
rag/indexer.py -- chunking + embeddings Ollama -> rag_index.json
|
|
rag/sync.py -- rclone pull din Google Drive + reindexare conditionata
|
|
|
|
../discord-bridge/dashboard/api.py -- panou comun; sectiunea Maria controleaza
|
|
maria-whatsapp.service + maria-rag.service, gestioneaza
|
|
documentele si declanseaza sincronizare/reindexare
|
|
```
|
|
|
|
Servicii `systemctl --user` (vezi `ops/`):
|
|
|
|
| Unitate | Ce face |
|
|
|---|---|
|
|
| `maria-whatsapp.service` | Puntea Baileys (Node), port 8099 |
|
|
| `maria-rag.service` | Consumer RAG (Python), polling + raspunsuri |
|
|
| `maria-sync.service` + `.timer` | Sincronizare Drive + reindexare, la 10 min |
|
|
|
|
## Instalare
|
|
|
|
```bash
|
|
cd maria-whatsapp-bridge
|
|
./ops/install.sh # creeaza ~/.maria-bridge/, venv, npm install, symlink-uri unit
|
|
```
|
|
|
|
Completeaza manual `~/.maria-bridge/env` (copiat din `ops/env.example` la prima
|
|
rulare): cel putin `LLM_URL` (backend-ul de chat) si, daca vrei sincronizare
|
|
automata cu Drive, `DRIVE_REMOTE`.
|
|
|
|
`OLLAMA_URL` (implicit `http://127.0.0.1:11434`, folosit pentru embeddings la
|
|
indexare) presupune un Ollama instalat local pe container — nu exista alt
|
|
Ollama documentat in infrastructura. Instalare (facuta deja pe LXC 171,
|
|
2026-08-31):
|
|
```bash
|
|
sudo apt-get install -y zstd # dependinta a instalatorului Ollama
|
|
curl -fsSL https://ollama.com/install.sh | sh
|
|
ollama pull nomic-embed-text # ~274 MB, CPU-only pe acest container
|
|
```
|
|
|
|
Pentru citirea capturilor de ecran primite (vezi „Imagini cu erori" mai jos):
|
|
```bash
|
|
sudo apt-get install -y tesseract-ocr tesseract-ocr-ron
|
|
```
|
|
|
|
Bridge-ul WhatsApp NU porneste automat la instalare — cere asocierea cu telefonul
|
|
(actiune manuala, o singura data):
|
|
|
|
```bash
|
|
./ops/install.sh --start
|
|
# apoi deschide dashboard-ul puntii Discord, sectiunea "Maria — WhatsApp + RAG"
|
|
# -> cardul "Conectare WhatsApp", si alege una din cele doua metode (mai jos)
|
|
```
|
|
|
|
## Asocierea cu telefonul: QR sau cod
|
|
|
|
Ambele duc la acelasi rezultat — puntea devine un *dispozitiv conectat* al contului.
|
|
Alegerea e practica, nu tehnica:
|
|
|
|
| | Cand se foloseste |
|
|
|---|---|
|
|
| **Cod QR** | Ai dashboard-ul deschis pe un ecran pe care telefonul il poate fotografia. |
|
|
| **Cod de asociere** (8 caractere) | Esti pe telefon, sau ecranul cu QR-ul e la distanta: scrii numarul in dashboard, primesti un cod si il tastezi pe telefon. |
|
|
|
|
Pentru codul de asociere, in dashboard: scrie numarul **in format international,
|
|
fara `+` si fara `00`** (ex. `40723197939`), apasa „Cere cod de asociere", apoi pe
|
|
telefon: WhatsApp -> Dispozitive conectate -> Conecteaza un dispozitiv ->
|
|
**Conecteaza cu numar de telefon** -> tastezi codul.
|
|
|
|
Codul e valabil **~3 minute**; dupa ce expira, dashboard-ul il marcheaza ca expirat
|
|
si trebuie cerut altul. Dupa introducerea corecta, WhatsApp inchide conexiunea cu
|
|
codul `restartRequired` (515) — puntea se reconecteaza singura, imediat; nu e o
|
|
eroare si nu consuma din bugetul de reincercari.
|
|
|
|
Din linia de comanda, aceleasi lucruri:
|
|
```bash
|
|
curl -s -X POST -H 'Content-Type: application/json' \
|
|
-d '{"phone":"40723197939"}' http://127.0.0.1:8099/pair
|
|
curl -s http://127.0.0.1:8099/status | python3 -m json.tool # cod + secunde ramase
|
|
```
|
|
|
|
Codul se poate cere doar cat timp sesiunea NU e inregistrata. Daca puntea raspunde
|
|
„sesiunea e deja inregistrata", opreste-o, sterge `~/.maria-bridge/whatsapp-auth/`
|
|
si porneste-o din nou — dar atentie, asta desface asocierea existenta.
|
|
|
|
## Imagini cu erori
|
|
|
|
Utilizatorii trimit aproape intotdeauna o captura cu fereastra de eroare, nu textul
|
|
ei. Puntea descarca imaginea in `~/.maria-bridge/media/`, iar consumer-ul o trece
|
|
prin **tesseract** (`ron+eng`) inainte de RAG. Modelul de raspuns (Qwen3.5-2B pe
|
|
LXC 104) e strict text, deci OCR-ul e singura cale — nu e o optiune de calitate.
|
|
|
|
Doua detalii care nu se vad din cod la prima citire:
|
|
|
|
- **Cautarea in index nu foloseste toata captura.** Un ecran intreg de meniuri,
|
|
coloane si totaluri dilueaza embedding-ul si scoate chunk-uri fara legatura. Se
|
|
cauta doar dupa liniile care arata a eroare (`ORA-…`, „eroare", „nu exista", …);
|
|
modelul primeste totusi fereastra intreaga, marcata explicit ca text OCR, ca sa nu
|
|
trateze greselile de recunoastere ca date exacte. Vezi `rag/ocr.py`.
|
|
- **Capturile se sterg imediat dupa citire.** Pot contine date de client si nu exista
|
|
niciun motiv sa ramana pe disc. Ce ramane dupa un restart in mijlocul procesarii se
|
|
curata la pornirea puntii (dupa 24h).
|
|
|
|
Legenda imaginii, daca exista, conteaza: intra si in intrebare si in cautare. Daca
|
|
OCR-ul nu gaseste nimic lizibil si nu exista legenda, Maria cere textul erorii in
|
|
loc sa inventeze un raspuns.
|
|
|
|
Limite: `MAX_MEDIA_MB` (implicit 8) pentru imaginea bruta, `OCR_MAX_CHARS`
|
|
(implicit 1500) pentru textul trimis modelului, `OCR_TIMEOUT_S` (60).
|
|
Videoclipurile, audio si documentele non-imagine sunt in continuare ignorate.
|
|
|
|
## Dashboard (comun cu puntea Discord)
|
|
|
|
Nu exista un dashboard separat pentru Maria. Controlul se face din dashboard-ul
|
|
puntii Discord — vezi `../discord-bridge/README.md` pentru URL si autentificare
|
|
(`DASHBOARD_TOKEN` din `~/.claude-discord/env`, tunel SSH sau Tailscale la
|
|
`/claude`). Acolo, sectiunea "Maria — WhatsApp + RAG":
|
|
- start/stop/restart pentru puntea WhatsApp si consumer-ul RAG
|
|
- starea conexiunii WhatsApp si asocierea (cod QR sau cod de 8 caractere), cand nu e conectat
|
|
- ultimele intrebari trimise la suport, cu textul citit din captura si motivul
|
|
- listare, adaugare si stergere documente din depozitul RAG
|
|
- reconstruire index manual, sau sincronizare Drive imediata
|
|
- ultimele linii din logurile fiecarui serviciu Maria (`whatsapp.log`/`rag.log`)
|
|
|
|
## Depozitul de documente
|
|
|
|
Fisiere `.txt`/`.md`/`.xml` (lista exacta: `store.DOC_EXTENSIONS`), in **doua
|
|
directoare care formeaza un singur spatiu de nume**:
|
|
|
|
| Director | Ce e acolo |
|
|
|---|---|
|
|
| `~/.maria-bridge/documents/` | **oglinda Google Drive.** `rclone sync` sterge de aici tot ce nu exista in Drive — nu pune nimic de mana. |
|
|
| `~/.maria-bridge/documents-local/` | documente care **nu** vin din Drive: dictionare proprii, ce se adauga din dashboard. Sincronizarea nu se atinge de ele. |
|
|
|
|
Cand acelasi nume apare in ambele, castiga cel din Drive (e sursa comuna a echipei;
|
|
copia locala ar putea fi o versiune veche uitata acolo). In dashboard, documentele
|
|
locale sunt marcate „local".
|
|
|
|
Directorul local exista fiindca altfel **tot ce se adauga din dashboard traia pana
|
|
la urmatorul tur de sincronizare** — maximum 10 minute. Acum adaugarea din dashboard
|
|
scrie direct acolo.
|
|
|
|
## Sincronizare cu Google Drive
|
|
|
|
Sursa: dosarul `document_store` din Drive-ul contului `mmarius28@gmail.com`
|
|
(pe Windows apare ca `D:\GoogleDrive\romfast\document_store`, prin Google Drive
|
|
Desktop). ID-ul dosarului: `1C4e75zgH1_7ZK-_oBP5ZZBvUPh3iEo1O`.
|
|
|
|
Containerul e headless (fara browser pentru OAuth), deci autorizarea se face pe o
|
|
masina cu browser si se muta aici ca token — **fara cont de serviciu si fara consola
|
|
Google Cloud**. rclone e deja instalat pe LXC 171 (`rclone v1.60.1`).
|
|
|
|
Pasi (o singura data):
|
|
|
|
1. **Pe Windows**, ia `rclone.exe` de la <https://rclone.org/downloads/> (arhiva
|
|
portabila, nu cere instalare) si ruleaza in acel dosar:
|
|
```
|
|
rclone.exe authorize "drive" --drive-scope=drive.readonly
|
|
```
|
|
Se deschide browserul; autentifica-te cu `mmarius28@gmail.com` si accepta.
|
|
In consola apare un token JSON intre `--->` si `<---`. Copiaza-l intreg.
|
|
2. **Pe container**, da tokenul scriptului de configurare:
|
|
```
|
|
ops/setup-drive.sh '<TOKEN_JSON>'
|
|
```
|
|
Face restul singur: creeaza remote-ul `gdrive`, verifica accesul la dosar,
|
|
scrie `DRIVE_REMOTE` in `~/.maria-bridge/env` (cu backup) si ruleaza prima
|
|
sincronizare cu reindexare. E idempotent — se poate rula din nou oricand.
|
|
|
|
Tinta scrisa in env e `gdrive,root_folder_id=<ID>:` — sintaxa „connection
|
|
string" a rclone, care fixeaza dosarul dupa ID, fara sa depinda de structura
|
|
de nume din My Drive.
|
|
3. Verifica in dashboard: sectiunea Maria trebuie sa arate „Drive: …" in loc de
|
|
„Sincronizare Drive dezactivata".
|
|
|
|
Tokenul se reimprospateaza singur (refresh token) cat timp aplicatia ramane
|
|
autorizata in contul Google. Daca expira, dashboard-ul arata sincronizarea ca
|
|
esuata — reia pasii 1-2.
|
|
|
|
### Ce se sincronizeaza
|
|
|
|
`rclone sync` aduce doar `*.txt`, `*.md` si `*.xml` (vezi `store.DOC_EXTENSIONS`).
|
|
Restul din dosar — chatflow-uri Flowise `.json`, scripturi `.ps1`, `.docx` — sunt
|
|
ignorate deliberat: nu sunt cunostinte de suport.
|
|
|
|
**`sync` sterge local ce nu mai exista in Drive**, deci `documents/` e o oglinda a
|
|
dosarului din Drive, nu o colectie care creste. De aceea exista `documents-local/`:
|
|
ce e adaugat de mana sau din dashboard sta acolo si nu e atins de sincronizare.
|
|
|
|
### Dictionarul de erori Oracle
|
|
|
|
`knowledge/oracle-erori-uzuale.xml` — 29 de erori Oracle uzuale (ORA-00001,
|
|
ORA-01722, ORA-12154, ORA-28040, ORA-12954 …), fiecare cu ce inseamna, ce poate
|
|
incerca utilizatorul singur si cand sa sune la suport. A fost scris fiindca
|
|
utilizatorii trimit capturi cu erori Oracle, iar restul documentelor sunt despre
|
|
e-Factura si SAF-T: modelul raspundea „ceva despre Oracle", dar nu despre eroarea
|
|
din captura.
|
|
|
|
Doua lucruri de stiut:
|
|
|
|
- **Fisierul din git nu ajunge singur in index.** Copia care ruleaza acum sta in
|
|
`~/.maria-bridge/documents-local/`, in afara oglinzii Drive. Asa supravietuieste
|
|
sincronizarilor, dar **exista doar pe acest container**. Locul lui pe termen lung
|
|
e `document_store` din Drive (pe Windows: `D:\GoogleDrive\romfast\document_store`),
|
|
unde il vede si restul echipei si de unde se poate edita fara acces la server.
|
|
Cand ajunge in Drive, sterge copia locala — altfel raman doua versiuni si castiga
|
|
tacut cea din Drive.
|
|
- **E scris pentru clienti, nu pentru administratori.** Nicio rezolvare nu contine
|
|
nume de servere, IP-uri, porturi sau pasi de administrare — la erorile care chiar
|
|
se rezolva doar tehnic (ORA-01652 spatiu plin, ORA-28000 cont blocat) textul spune
|
|
explicit „suna la suport", nu explica ce sa faca pe server. Cand adaugi erori noi,
|
|
pastreaza regula.
|
|
|
|
### Acelasi document in doua formate
|
|
|
|
Cand exista `X.xml` si `X.md`, **in index intra doar `.xml`** (ordinea de
|
|
preferinta: `.xml` > `.md` > `.txt`, in `rag/store.py`). Sursele `.xml` sunt
|
|
structurate pe probleme si de regula mai noi decat exporturile `.md` — la
|
|
`d406_saft_knowledge`, `.xml` era cu trei luni mai nou si cu 50% mai mare.
|
|
Fisierul umbrit ramane pe disc si apare in dashboard marcat „umbrit de …", ca sa
|
|
se vada de ce nu e indexat; daca ar fi indexate ambele, acelasi raspuns ar aparea
|
|
de doua ori in rezultatele RAG.
|
|
|
|
### Cum se taie XML-ul in chunk-uri
|
|
|
|
Un chunk per element de nivel 1 — adica **o problema = un chunk**, cu mesajul de
|
|
eroare si rezolvarea impreuna. Taierea pe linii goale (cea folosita la `.md`) le-ar
|
|
separa, iar cautarea ar gasi eroarea si ar returna un chunk fara raspuns.
|
|
Etichetele raman ca prefixe lizibile (`mesaj eroare: …`, `rezolvare: …`), fara
|
|
paranteze unghiulare. Un XML care nu se poate parsa nu opreste indexarea: cade pe
|
|
taierea obisnuita, cu o linie in log. Vezi `rag/indexer.py` si
|
|
`tests/test_store_si_chunking.py`.
|
|
|
|
Dupa configurare, `maria-sync.timer` trage la fiecare 10 minute; reindexarea
|
|
ruleaza DOAR daca s-a schimbat efectiv ceva in depozit (amprenta pe nume +
|
|
mtime + marime, vezi `rag/sync.py`), ca sa nu reface embeddings degeaba.
|
|
|
|
Iar cand chiar reindexeaza, **refoloseste vectorii chunk-urilor nemodificate** din
|
|
indexul precedent (`rag/indexer.py:_vectori_existenti`). Un embedding costa ~7
|
|
secunde pe CPU: fara refolosire, adaugarea unui singur document la 170 de chunk-uri
|
|
insemna 20 de minute de reconstruit tot. Cheia e chiar textul chunk-ului — daca nu
|
|
s-a schimbat niciun caracter, vectorul e acelasi — dar numai pentru **acelasi
|
|
`EMBED_MODEL`**: fiecare intrare din index poarta modelul cu care a fost calculata,
|
|
iar la schimbarea modelului indexul se reface intreg. Altfel ar ramane un amestec de
|
|
vectori din doua modele, iar cautarea ar da rezultate aiurea fara nici o eroare.
|
|
Logul spune de fiecare data cate au fost calculate si cate refolosite.
|
|
|
|
## Teste
|
|
|
|
```bash
|
|
cd /workspace/romfastsql/proxmox/lxc171-claude-agent/maria-whatsapp-bridge
|
|
python3 -m pytest # format, taierea XML, OCR, mesajele cu imagine, ordonarea
|
|
# hibrida si escaladarea — fara retea, fara Ollama, fara tesseract
|
|
```
|
|
|
|
## Cum se ordoneaza rezultatele (rank)
|
|
|
|
Cautarea are doua etape. Intai **recall**: primele `RANK_RECALL_N` chunk-uri dupa
|
|
cosinus (semantic) si primele dupa BM25 (lexical). Apoi **fuziune**: cele doua
|
|
clasamente se combina prin Reciprocal Rank Fusion — un chunk urcat de ambele metode
|
|
iese primul, unul urcat doar de una ramane in cursa.
|
|
|
|
De ce nu doar embeddings. Masurat pe indexul viu:
|
|
|
|
| Intrebare | Cel mai bun cosinus |
|
|
|---|---|
|
|
| „Nu exista nici un CIF pentru care sa aveti drept in SPV" (in documente) | 0,816 |
|
|
| „cum trimit declaratia D406 SAF-T" (in documente) | 0,689 |
|
|
| „imi da eroare la imprimanta HP LaserJet" (strain) | 0,641 |
|
|
| „cum schimb uleiul la masina" (strain) | 0,534 |
|
|
|
|
Marginea dintre o intrebare buna si una straina e de cateva sutimi. Ce lipseste din
|
|
cosinus e exact ce conteaza la suport: **codurile**. `ORA-01722`, `D406`, `CIF` sunt
|
|
tokeni exacti — cautarea lexicala ii prinde, cea semantica ii topeste in „ceva despre
|
|
facturi". La „bilant contabil" (cosinus 0,600) si „import de date" (0,635) partea
|
|
lexicala e singura care le tine in joc.
|
|
|
|
Fuziunea da mereu un clasament, si la o intrebare complet straina. De aceea decizia
|
|
**„avem sau nu acoperire"** se ia separat, pe dovezi:
|
|
|
|
1. cosinus ≥ `RANK_STRONG_COSINE` → raspundem;
|
|
2. cosinus < `RANK_WEAK_COSINE` → escaladam direct;
|
|
3. intre ele → raspundem doar daca cel putin `RANK_MIN_RARE_RATIO` din termenii
|
|
distinctivi ai intrebarii (coduri, cuvinte rare in corpus) apar chiar in
|
|
chunk-urile gasite.
|
|
|
|
Fractiunea din pasul 3 nu e cosmetica: cu un singur termen gasit, „cum imi resetez
|
|
parola de la Windows" trecea drept acoperita fiindca „parola" apare in documente.
|
|
|
|
### Recalibrarea, cand se schimba documentele
|
|
|
|
Pragurile sunt masurate, nu alese din burta — si se **muta** cand se schimba
|
|
depozitul. Exista un script pentru asta:
|
|
|
|
```bash
|
|
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
|
|
```
|
|
|
|
Primul ruleaza setul de cazuri cu pragurile din `env` si arata ce ar gresi; al doilea
|
|
matura o grila si listeaza combinatiile cu cele mai putine greseli. Embeddings-urile
|
|
intrebarilor raman in cache (`~/.maria-bridge/calibrare-cache.json`) — pe CPU costa
|
|
secunde fiecare. Setul de cazuri e in capul scriptului: **adauga acolo intrebarile
|
|
reale la care Maria a gresit**, e singurul mod in care calibrarea ramane onesta.
|
|
|
|
## Cand nu stie raspunsul: escaladarea la suport
|
|
|
|
Un raspuns generic e mai rau decat niciun raspuns — utilizatorul pleaca cu impresia
|
|
ca a primit ajutor. Cand `rank.assess` spune ca nu avem acoperire, Maria **nu mai
|
|
intreaba modelul deloc**: raspunde ca trimite intrebarea la suport, si chiar o
|
|
trimite.
|
|
|
|
Ce pleaca: cine a intrebat, textul (sau textul citit din captura), motivul deciziei
|
|
si scorurile. Daca a fost o captura de ecran, **imaginea insasi se retrimite** la
|
|
suport, cu rezumatul ca legenda — de aceea captura se sterge abia dupa ce mesajul e
|
|
complet tratat, nu imediat dupa OCR.
|
|
|
|
Destinatia e `SUPPORT_JID` din `env`: `<numar>@s.whatsapp.net` pentru o persoana sau
|
|
`<id>@g.us` pentru un grup. **JID-ul unui grup nu se vede nicaieri in WhatsApp**; se
|
|
citeste din punte, care le listeaza pe toate cu numele lor:
|
|
|
|
```bash
|
|
curl -s localhost:8099/groups | python3 -m json.tool | grep -B1 'ROMFAST'
|
|
```
|
|
|
|
Grupul trebuie sa contina si numarul Mariei (`40723197939`) — altfel JID-ul nici nu
|
|
apare in lista, iar trimiterea ar esua. **Nesetat = nimeni nu e anuntat**, dar escaladarea tot se
|
|
inregistreaza in `~/.maria-bridge/escalations/` si apare in dashboard, sectiunea
|
|
„Trimise la suport". Jurnalul se scrie intotdeauna, si cand notificarea esueaza:
|
|
altfel exact intrebarile fara raspuns ar disparea fara urma.
|
|
|
|
## Ce a citit Maria dintr-o captura
|
|
|
|
Textul OCR se logheaza **integral** in `~/.maria-bridge/logs/rag.log`, cu prefix
|
|
`|`, urmat de interogarea folosita la cautare si de sursele alese cu scorurile lor:
|
|
|
|
```
|
|
[consumer] OCR (703 caractere):
|
|
| Eroare la salvarea facturii:
|
|
| ORA-01722: invalid number
|
|
[consumer] caut dupa: 'Eroare la salvarea facturii:\nORA-01722: invalid number'
|
|
[consumer] rank: efactura_knowledge.md(0.771), d406_saft_knowledge.xml(0.62) | cosinus 0.771 >= 0.66
|
|
```
|
|
|
|
Fara asta un raspuns gresit nu se poate explica: nu se stie daca a citit prost
|
|
captura sau a cautat prost in documente. Aceleasi linii se vad in dashboard, la
|
|
logurile serviciului `rag`.
|
|
|
|
## Context conversational
|
|
|
|
`rag/consumer.py` nu retine memorie intre mesaje — fiecare intrebare e o
|
|
interogare RAG independenta (system prompt + top-K chunk-uri + intrebare).
|
|
Vezi `claude-agent/docs/maria-whatsapp-rag-prototype.md` pentru motiv si
|
|
comparatie cu celelalte punti (Discord: context nelimitat + `/new`; Maria pe
|
|
Flowise: fereastra fixa de 5 schimburi).
|