Trei probleme legate, gasite testand cu capturi reale. 1. RASPUNSURI GENERICE. Cand raspunsul nu era in documente, modelul primea oricum top-3 chunk-uri si compunea ceva plauzibil — utilizatorul pleca cu impresia ca a primit ajutor. Acum, daca nu avem acoperire, modelul nu mai e intrebat deloc: intrebarea pleaca la suport, cu referinta (M-260831-A1B2) si confirmare onesta — "am trimis" doar daca notificarea chiar a plecat, altfel "am inregistrat, dar nu am putut trimite". Jurnalul se scrie intotdeauna, in ~/.maria-bridge/escalations/, si se vede in dashboard. Daca a fost o captura, imaginea insasi se retrimite la suport (endpoint nou /send-image, limitat la MEDIA_DIR). 2. ORDONARE (rank.py). Un prag pe cosinus nu putea decide "avem raspunsul?": masurat pe indexul viu, intrebarile bune dau 0,600-0,816 si cele straine 0,534-0,685 — intervale care SE SUPRAPUN. Lipseau codurile: ORA-01722, D406, CIF sunt tokeni exacti pe care cautarea semantica ii topeste. Acum se ordoneaza de doua ori — embeddings si BM25 — si se fuzioneaza clasamentele (RRF), iar decizia de acoperire se ia pe dovezi: cosinus mare, sau cosinus de mijloc cu o fractiune din termenii distinctivi gasita chiar in chunk-uri. Fractiunea conteaza: cu un singur termen, "cum imi resetez parola de la Windows" trecea drept acoperita fiindca "parola" apare in documente. Pragurile sunt masurate, nu alese: ops/calibrate-rank.py ruleaza un set de cazuri pe indexul real si matura grila. 15/15 la strong=0.70 weak=0.58 ratio=0.5. 3. DICTIONAR DE ERORI ORACLE. 29 de erori uzuale, scrise pentru utilizatorul din fata aplicatiei: ce inseamna, ce poate incerca singur, cand sa sune. Fara nume de servere, IP-uri sau pasi de administrare — Maria e chatbot pentru clienti. Fisierul din git e SAMANTA: depozitul e oglinda Drive-ului, deci trebuie pus in document_store ca sa nu dispara la sincronizare. Pe drum, doua lucruri gasite in log: - fiecare mesaj din self-chat sosea de DOUA ori, pe @s.whatsapp.net si pe @lid, deci Maria raspundea de doua ori. Dedup pe key.id in punte. - reindexarea reface toate embeddings-urile (~7 s fiecare pe CPU): adaugarea unui document la 170 de chunk-uri insemna 20 de minute. Acum refoloseste vectorii chunk-urilor nemodificate din indexul precedent, fara vreun fisier nou de stare. Textul OCR se logheaza acum INTEGRAL, cu interogarea de cautare si sursele alese cu scorurile lor — fara asta un raspuns gresit nu se poate explica: nu stiai daca a citit prost captura sau a cautat prost in documente. 6 fisiere de test noi/extinse, 68 pass (de la 42). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
114 lines
4.2 KiB
Python
114 lines
4.2 KiB
Python
"""`consumer.prepare_query`: ce ajunge la model cand mesajul e o captura de ecran.
|
|
|
|
Nu porneste nici puntea, nici LLM-ul — se testeaza doar decizia dintre „raspund
|
|
pe baza imaginii", „raspund pe baza legendei" si „cer omului sa scrie textul".
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
import consumer
|
|
|
|
|
|
@pytest.fixture
|
|
def imagine(tmp_path, monkeypatch):
|
|
"""Un mesaj cu imagine, cu OCR-ul inlocuit de un text dat."""
|
|
def _face(ocr_text: str, caption: str = "", *, esueaza: bool = False):
|
|
fisier = tmp_path / "captura.png"
|
|
fisier.write_bytes(b"nu conteaza continutul, ocr e inlocuit")
|
|
|
|
def fals_run(path, langs=None):
|
|
if esueaza:
|
|
raise RuntimeError("tesseract lipseste")
|
|
return ocr_text
|
|
|
|
monkeypatch.setattr(consumer.ocr, "run", fals_run)
|
|
return {"text": caption, "media": {"path": str(fisier), "mimetype": "image/png"}}, fisier
|
|
return _face
|
|
|
|
|
|
def test_mesaj_text_simplu_trece_neatins():
|
|
intrebare, cautare, imediat = consumer.prepare_query({"text": "cum emit o factura?"})
|
|
assert intrebare == "cum emit o factura?"
|
|
assert cautare == "cum emit o factura?"
|
|
assert imediat is None
|
|
|
|
|
|
def test_captura_cu_eroare_cauta_doar_linia_de_eroare(imagine):
|
|
msg, _ = imagine("Meniu Fisier Editare\nORA-01722: invalid number\nRand 4 din 9")
|
|
intrebare, cautare, imediat = consumer.prepare_query(msg)
|
|
|
|
assert imediat is None
|
|
# cautarea in index merge pe eroare, nu pe toata fereastra
|
|
assert cautare == "ORA-01722: invalid number"
|
|
# modelului ii dam captura intreaga, marcata ca OCR
|
|
assert "OCR" in intrebare
|
|
assert "Meniu Fisier Editare" in intrebare
|
|
|
|
|
|
def test_legenda_intra_si_in_intrebare_si_in_cautare(imagine):
|
|
msg, _ = imagine("ORA-00942: table or view does not exist", caption="nu merge raportul")
|
|
intrebare, cautare, _ = consumer.prepare_query(msg)
|
|
assert intrebare.startswith("nu merge raportul")
|
|
assert cautare.splitlines()[0] == "nu merge raportul"
|
|
|
|
|
|
def test_captura_ramane_pe_disc_pana_e_tratat_mesajul(imagine):
|
|
# Nu se sterge la OCR: mai e nevoie de ea daca intrebarea pleaca la suport,
|
|
# unde se retrimite ca imagine.
|
|
msg, fisier = imagine("ORA-01722: invalid number")
|
|
consumer.prepare_query(msg)
|
|
assert fisier.exists()
|
|
|
|
|
|
def test_cleanup_media_sterge_captura(imagine):
|
|
# capturile pot contine date de client — nu raman pe disc dupa tratarea mesajului
|
|
msg, fisier = imagine("ORA-01722: invalid number")
|
|
consumer.prepare_query(msg)
|
|
consumer.cleanup_media(msg)
|
|
assert not fisier.exists()
|
|
|
|
|
|
def test_cleanup_media_tolereaza_lipsa_fisierului(imagine):
|
|
msg, fisier = imagine("ceva")
|
|
fisier.unlink()
|
|
consumer.cleanup_media(msg) # nu trebuie sa arunce
|
|
consumer.cleanup_media({"text": "fara imagine"})
|
|
|
|
|
|
def test_captura_ilizibila_cere_textul_erorii(imagine):
|
|
msg, _ = imagine("")
|
|
intrebare, _, imediat = consumer.prepare_query(msg)
|
|
assert intrebare is None
|
|
assert imediat == consumer.NO_TEXT_IN_IMAGE
|
|
|
|
|
|
def test_textul_ocr_ramane_pe_mesaj_pentru_jurnal(imagine):
|
|
# escalate() il scrie in jurnalul de escaladari; fara el, o intrebare trimisa
|
|
# la suport ar ajunge acolo fara continutul capturii
|
|
msg, _ = imagine("ORA-01722: invalid number")
|
|
consumer.prepare_query(msg)
|
|
assert msg["ocr_text"] == "ORA-01722: invalid number"
|
|
|
|
|
|
def test_ocr_esuat_dar_cu_legenda_raspunde_pe_legenda(imagine):
|
|
msg, _ = imagine("", caption="imi da eroare la salvare", esueaza=True)
|
|
intrebare, cautare, imediat = consumer.prepare_query(msg)
|
|
assert imediat is None
|
|
assert intrebare == cautare == "imi da eroare la salvare"
|
|
|
|
|
|
def test_imagine_nedescarcata_fara_legenda_cere_reincercare():
|
|
msg = {"text": "", "media": {"error": "imagine prea mare (12.0 MB > 8 MB)"}}
|
|
intrebare, _, imediat = consumer.prepare_query(msg)
|
|
assert intrebare is None
|
|
assert "scrie-mi" in imediat.lower()
|
|
|
|
|
|
def test_imagine_nedescarcata_cu_legenda_raspunde_pe_legenda():
|
|
msg = {"text": "eroare la export SAF-T", "media": {"error": "download failed"}}
|
|
intrebare, cautare, imediat = consumer.prepare_query(msg)
|
|
assert imediat is None
|
|
assert intrebare == cautare == "eroare la export SAF-T"
|