fix(maria): acoperirea se judeca pe ce a intrebat omul acum, si pe radacina cuvantului
Doua reparatii pe poarta de acoperire, amandoua masurate pe setul de calibrare extins (31 de cazuri, de la 26). **Termenii distinctivi se comparau pe cuvantul intreg.** Documentatia si clientul nu folosesc aceleasi forme: documentul scrie „token" si „tokenuri", omul scrie „tokenul"; documentul „Generare", omul „generez". Trei continuari legitime din cinci escaladau desi chunk-ul cu raspunsul era chiar in context. Comparatia se face acum pe primele 5 litere; codurile raman intregi (ORA-01722 si ORA-01720 au aceleasi cinci). **Interogarea avea doua roluri amestecate.** Pe un fir, regasirea are nevoie de ancora („da, ma blocheaza" singur nu gaseste nimic), dar acoperirea nu: judecata pe ancora, ea cere sa apara in documente si salutul, si numele firmei, si luna, deci raportul scade la fiecare replica si firul lung escaladeaza degeaba. `rank.assess` primeste `dovada` — mesajul nou, plus OCR-ul capturii lui; cand acesta n-are termeni proprii, se cade inapoi pe interogarea intreaga. Setul de calibrare: 31/31 cu reparatia, 30/31 fara. Ce NU s-a facut, desi era propus in handover: lista de formule de politete in `rare_terms`. Masuratoarea nu o justifica — cu potrivirea pe radacina, cazurile cu ancora politicoasa trec si fara ea, iar intr-una din configuratii facea rau. Cauza reala nu era politetea din ancora, ci morfologia. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
This commit is contained in:
@@ -358,6 +358,22 @@ Fuziunea da mereu un clasament, si la o intrebare complet straina. De aceea deci
|
||||
distinctivi ai intrebarii (coduri, cuvinte rare in corpus) apar chiar in
|
||||
chunk-urile gasite.
|
||||
|
||||
Termenii se compara **pe primele 5 litere**, nu pe cuvantul intreg (codurile fac
|
||||
exceptie: `ORA-01722` si `ORA-01720` incep la fel). Documentatia si clientul nu
|
||||
folosesc aceleasi forme — documentul scrie „token" si „tokenuri", omul scrie
|
||||
„tokenul"; documentul „Generare", omul „generez". Comparate intregi, exact
|
||||
cuvintele care conteaza nu se gaseau: pe setul de calibrare, trei continuari
|
||||
legitime escaladau desi chunk-ul cu raspunsul era chiar in context.
|
||||
|
||||
**Pe un fir de discutie, interogarea are doua roluri diferite.** Regasirea se face
|
||||
pe ancora plus mesajul nou — ancora chiar ajuta, „da, ma blocheaza" singur nu
|
||||
gaseste nimic. Acoperirea se judeca insa pe **mesajul nou** (`dovada`, in
|
||||
`rank.assess`): altfel raspunde la ce a intrebat omul acum SI la formulele si
|
||||
numele proprii din primul mesaj („buna", „august", „AUTO SULEA" nu apar in niciun
|
||||
document), iar raportul scade la fiecare replica — firul lung escaladeaza degeaba.
|
||||
Cand mesajul nou n-are termeni distinctivi proprii, se cade inapoi pe interogarea
|
||||
intreaga, ca inainte.
|
||||
|
||||
Fractiunea din pasul 2 nu e cosmetica: cu un singur termen gasit, „cum imi resetez
|
||||
parola de la Windows" trecea drept acoperita fiindca „parola" apare in documente.
|
||||
|
||||
@@ -373,7 +389,7 @@ L-am scos, nu urcat, fiindca **cosinusul creste cu lungimea interogarii**: pe un
|
||||
de discutie se cauta dupa ancora plus mesajul nou, deci scorul urca la fiecare
|
||||
replica fara sa apara vreo dovada in plus. Aceeasi captura: 0,778 singura, 0,836
|
||||
impreuna cu mesajul de dinainte. Orice prag fix de sus e trecut de o discutie destul
|
||||
de lunga. `ops/calibrate-rank.py`: 26/26, si niciunul dintre cazurile cu raspuns in
|
||||
de lunga. `ops/calibrate-rank.py`: 31/31, si niciunul dintre cazurile cu raspuns in
|
||||
documente nu avea nevoie de scurtatura — toate trec pe dovada lexicala.
|
||||
|
||||
Modelul primeste `temperature=0` (`LLM_TEMPERATURE`). Fara ea, llama.cpp raspunde
|
||||
|
||||
@@ -24,6 +24,7 @@ siguranta: daca un prag nou strica un caz care mergea, se vede imediat.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
@@ -34,8 +35,10 @@ import config # noqa: E402
|
||||
import consumer # noqa: E402
|
||||
import rank # noqa: E402
|
||||
|
||||
# (intrebare, are raspuns in documente?)
|
||||
CAZURI: list[tuple[str, bool]] = [
|
||||
# (intrebare, are raspuns in documente?) sau, pentru continuari pe un fir,
|
||||
# (ancora, mesaj_nou, are raspuns in documente?): regasirea se face pe ancora plus
|
||||
# mesajul nou (exact ce produce fir.interogare), dovada doar pe mesajul nou.
|
||||
CAZURI: list[tuple] = [
|
||||
("token efactura expirat", True),
|
||||
("Nu exista nici un CIF pentru care sa aveti drept in SPV", True),
|
||||
("cum trimit declaratia D406 SAF-T", True),
|
||||
@@ -80,6 +83,31 @@ CAZURI: list[tuple[str, bool]] = [
|
||||
("vremea de maine in Bucuresti", False),
|
||||
("cum imi resetez parola de la Windows", False),
|
||||
("imi da eroare 0x80070005 la instalarea unui joc pe Steam", False),
|
||||
# --- continuari pe fir, fara cod de eroare: aici decid termenii distinctivi ---
|
||||
# Ancora politicoasa aduce in interogare „buna/ziua/puteti/ajutati", cuvinte care
|
||||
# nu apar niciodata in documente. Raspunsul (token expirat) e in
|
||||
# efactura_knowledge.md:118-134 (TOPIC token_anaf, expirare la 90 de zile).
|
||||
("Buna ziua . Am o problema la trimiterea in SPV . Puteti sa ma ajutati?",
|
||||
"imi zice ca tokenul e expirat", True),
|
||||
# Aceeasi continuare, ancora fara formule de politete: verdictul trebuie sa fie
|
||||
# acelasi. Daca difera, politetea din ancora e cea care escaladeaza.
|
||||
("Am o problema la trimiterea in SPV",
|
||||
"imi zice ca tokenul e expirat", True),
|
||||
# Aceeasi ancora politicoasa, dar raspunsul NU e in documente: „CUI cumparator
|
||||
# incorect" nu apare in niciun document (grep peste documents/, 2026-09-01).
|
||||
("Buna ziua . Am o problema la trimiterea in SPV . Puteti sa ma ajutati?",
|
||||
"imi da eroare CUI cumparator incorect la validare", False),
|
||||
# Fir lung: a patra replica, ancora crescuta cu schimburile de dinainte.
|
||||
# Verdictul nu are voie sa se degradeze cu lungimea (cosinusul urca oricum).
|
||||
("Buna ziua . Am o problema la trimiterea in SPV . Puteti sa ma ajutati?\n"
|
||||
"imi zice ca tokenul e expirat\n"
|
||||
"am apasat Actualizare in borderoul eFactura dar tot nu merge",
|
||||
"unde apas ca sa generez tokenul nou?", True),
|
||||
# Continuare pe un fir deschis de o captura (escaladarea M-260901-06EB): ancora
|
||||
# e mesajul omului plus OCR-ul, plin de zgomot de interfata. Eroarea din captura
|
||||
# nu e in documente, deci si continuarea trebuie sa plece la suport.
|
||||
("Buna . Am si eu o factura pe luna august cu eroare in spv la trimitere AUTO SULEA MOBILE SRL . Poti te rog sa ma ajuti?\nTotal\nLit\nO) Pers. Juridice (798) O Netrimise DI Trimise (fara raspuns) [5 Timis (cu raspuns Erori) (1). O Tamise (cu diferente Reg. V2.) (1)\nNr Articol Cantitate UM | Pret unitar Valoare fara TVA Discount fara TVA % TVA Descriere Detalii\n5 Detalii raspuns\n663| | <2xml version=\"1.0” encoding=\"UTF-8\" standalone=\"yes\"?>\n<header xmins=\"mfp:anaf-dgtefactura:mesajroriFactuta:v1” Index_incarcare=\"\nMesaj raspuns. | Factura cu id incarcare=6638295792 nu po] | | <Error errorMessage=\"CUI cumparator incorect\"/>\nTri\nMesaj trimitere | Transmis cu succes. Index incarcare\n1670404158\" Cif_emitent=\"\n3137200\">\n@ inclusiv Pers. Fizice\nSelecteaz!",
|
||||
"si acum ce fac cu factura asta?", False),
|
||||
]
|
||||
|
||||
CACHE = config.STATE_DIR / "calibrare-cache.json"
|
||||
@@ -92,14 +120,27 @@ def incarca_cache() -> dict[str, list[float]]:
|
||||
return {}
|
||||
|
||||
|
||||
def pregateste(cazuri: list[tuple[str, bool]]) -> tuple[consumer.Index, list[dict]]:
|
||||
def desfa(caz: tuple) -> tuple[str, str | None, bool]:
|
||||
"""(interogare de regasire, interogare de dovada, asteptat).
|
||||
|
||||
Un 3-tuplu e o continuare pe fir: se cauta dupa ancora plus mesajul nou (ca in
|
||||
fir.interogare), dar dovada e doar ce a intrebat omul acum.
|
||||
"""
|
||||
if len(caz) == 3:
|
||||
ancora, mesaj, asteptat = caz
|
||||
return f"{ancora}\n{mesaj}", mesaj, asteptat
|
||||
return caz[0], None, caz[1]
|
||||
|
||||
|
||||
def pregateste(cazuri: list[tuple]) -> tuple[consumer.Index, list[dict]]:
|
||||
index = consumer.load_index()
|
||||
if not len(index):
|
||||
sys.exit(f"index gol sau lipsa: {config.INDEX_FILE}")
|
||||
cache = incarca_cache()
|
||||
date = []
|
||||
nou = 0
|
||||
for intrebare, asteptat in cazuri:
|
||||
for caz in cazuri:
|
||||
intrebare, dovada, asteptat = desfa(caz)
|
||||
vec = cache.get(intrebare)
|
||||
if vec is None:
|
||||
print(f" embed: {intrebare[:50]}…", file=sys.stderr)
|
||||
@@ -112,6 +153,7 @@ def pregateste(cazuri: list[tuple[str, bool]]) -> tuple[consumer.Index, list[dic
|
||||
: config.get_int("TOP_K", 3)]
|
||||
date.append({
|
||||
"intrebare": intrebare,
|
||||
"dovada": dovada,
|
||||
"asteptat": asteptat,
|
||||
# ca in consumer.search: cosinusul chunk-urilor date modelului
|
||||
"best": max((cos[i] for i in ordine), default=0.0),
|
||||
@@ -124,13 +166,19 @@ def pregateste(cazuri: list[tuple[str, bool]]) -> tuple[consumer.Index, list[dic
|
||||
return index, date
|
||||
|
||||
|
||||
# rank.assess primeste `dovada` doar dupa reparatia termenilor distinctivi; pana
|
||||
# atunci se poate rula numai varianta de azi.
|
||||
ARE_DOVADA = "dovada" in inspect.signature(rank.assess).parameters
|
||||
|
||||
|
||||
def evalueaza(index: consumer.Index, date: list[dict], weak: float,
|
||||
min_ratio: float) -> tuple[int, list[str]]:
|
||||
min_ratio: float, dovada_separata: bool = False) -> tuple[int, list[str]]:
|
||||
corecte, gresite = 0, []
|
||||
for d in date:
|
||||
extra = {"dovada": d["dovada"]} if dovada_separata else {}
|
||||
ok, _ = rank.assess(
|
||||
d["intrebare"], d["best"], d["top_texts"], index.bm25,
|
||||
weak=weak, min_ratio=min_ratio,
|
||||
weak=weak, min_ratio=min_ratio, **extra,
|
||||
)
|
||||
if ok == d["asteptat"]:
|
||||
corecte += 1
|
||||
@@ -147,7 +195,7 @@ def main() -> None:
|
||||
|
||||
cazuri = CAZURI
|
||||
if args.cases:
|
||||
cazuri = [(q, bool(a)) for q, a in json.loads(pathlib.Path(args.cases).read_text())]
|
||||
cazuri = [tuple(c) for c in json.loads(pathlib.Path(args.cases).read_text())]
|
||||
|
||||
index, date = pregateste(cazuri)
|
||||
print(f"index: {len(index)} chunk-uri | cazuri: {len(date)}\n")
|
||||
@@ -156,9 +204,11 @@ def main() -> None:
|
||||
rezultate = []
|
||||
for weak in (0.50, 0.54, 0.58, 0.60, 0.64):
|
||||
for ratio in (0.0, 0.34, 0.5, 0.67, 1.0):
|
||||
corecte, _ = evalueaza(index, date, weak, ratio)
|
||||
corecte, _ = evalueaza(index, date, weak, ratio, ARE_DOVADA)
|
||||
rezultate.append((corecte, weak, ratio))
|
||||
rezultate.sort(key=lambda r: (-r[0], r[1]))
|
||||
mod = "cu dovada separata" if ARE_DOVADA else "fara separare (rank.assess nu are inca `dovada`)"
|
||||
print(f"mod: {mod}\n")
|
||||
print(f"{'corecte':>8} weak ratio")
|
||||
for corecte, weak, ratio in rezultate[:15]:
|
||||
print(f"{corecte:>5}/{len(date)} {weak:.2f} {ratio:.2f}")
|
||||
@@ -171,10 +221,14 @@ def main() -> None:
|
||||
semn = "OK " if ok == d["asteptat"] else "GRESIT"
|
||||
stare = "ACOPERIT " if ok else "ESCALADAT"
|
||||
print(f"{semn} [{stare}] {d['intrebare'][:44]:46} {motiv}")
|
||||
corecte, gresite = evalueaza(index, date, weak, ratio)
|
||||
print(f"\n{corecte}/{len(date)} corecte")
|
||||
for g in gresite:
|
||||
print(f" - {g}")
|
||||
for eticheta, separata in (("fara separare (azi)", False), ("cu dovada separata ", True)):
|
||||
if separata and not ARE_DOVADA:
|
||||
print("\ncu dovada separata : rank.assess nu accepta inca `dovada`, sarit")
|
||||
continue
|
||||
corecte, gresite = evalueaza(index, date, weak, ratio, separata)
|
||||
print(f"\n{eticheta}: {corecte}/{len(date)} corecte")
|
||||
for g in gresite:
|
||||
print(f" - {g}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -128,8 +128,13 @@ def cosine(a: list[float], b: list[float]) -> float:
|
||||
return dot / (na * nb) if na and nb else 0.0
|
||||
|
||||
|
||||
def search(index: Index, query: str, top_k: int) -> dict:
|
||||
"""Cauta si decide daca avem acoperire. Intoarce si dovezile, pentru log."""
|
||||
def search(index: Index, query: str, top_k: int, dovada: str | None = None) -> dict:
|
||||
"""Cauta si decide daca avem acoperire. Intoarce si dovezile, pentru log.
|
||||
|
||||
`query` regaseste, `dovada` judeca. Pe un fir, `query` e ancora plus mesajul
|
||||
nou (ancora chiar ajuta regasirea), dar acoperirea nu se judeca pe ea — vezi
|
||||
`rank.assess`.
|
||||
"""
|
||||
if not len(index):
|
||||
return {"chunks": [], "covered": False, "reason": "index gol",
|
||||
"best_cosine": 0.0, "top": []}
|
||||
@@ -141,7 +146,7 @@ def search(index: Index, query: str, top_k: int) -> dict:
|
||||
# cosinusul chunk-urilor CHIAR date modelului, nu cel mai bun din tot indexul:
|
||||
# altfel acoperirea se judeca pe o dovada care nu ajunge in context.
|
||||
best = max((cosines[i] for i in order), default=0.0)
|
||||
covered, reason = rank.assess(query, best, texts, index.bm25)
|
||||
covered, reason = rank.assess(query, best, texts, index.bm25, dovada=dovada)
|
||||
return {
|
||||
"chunks": texts,
|
||||
"covered": covered,
|
||||
@@ -558,6 +563,7 @@ def handle(index: Index, msg: dict) -> None:
|
||||
if not question:
|
||||
return
|
||||
|
||||
dovada = None
|
||||
if continuare:
|
||||
fir_mod.adauga(fir, "client", text or "[captura de ecran]")
|
||||
fir_mod.extinde_ancora(fir, msg.get("ocr_text") or "")
|
||||
@@ -571,6 +577,9 @@ def handle(index: Index, msg: dict) -> None:
|
||||
send_reply(sender, raspuns)
|
||||
return
|
||||
fir["ref"] = None # escaladarea nu mai exista; tratam mesajul normal
|
||||
# ce a intrebat omul ACUM (mesajul nou, sau textul retinut din captura lui):
|
||||
# acoperirea se judeca pe asta, nu pe ancora care ramane in interogare.
|
||||
dovada = search_query
|
||||
search_query = fir_mod.interogare(fir, search_query)
|
||||
print(f"[consumer] continuare pe firul deschis; caut dupa {search_query[:90]!r}",
|
||||
file=sys.stderr)
|
||||
@@ -579,7 +588,7 @@ def handle(index: Index, msg: dict) -> None:
|
||||
fir_mod.adauga(fir, "client", text or "[captura de ecran]")
|
||||
|
||||
try:
|
||||
result = search(index, search_query, config.get_int("TOP_K", 3))
|
||||
result = search(index, search_query, config.get_int("TOP_K", 3), dovada)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"[consumer] cautare esuata: {exc}", file=sys.stderr)
|
||||
send_reply(sender, "Scuze, am o problema tehnica momentan. Cineva din echipa te va contacta.")
|
||||
|
||||
@@ -17,6 +17,11 @@ escaladarii deschise si ultimele schimburi. Ancora intra in interogarea de cauta
|
||||
la fiecare continuare — asa „da, ma blocheaza" se cauta impreuna cu eroarea, nu
|
||||
singur.
|
||||
|
||||
Ancora ajuta REGASIREA, nu acoperirea. Decizia „raspund sau escaladez" se ia pe
|
||||
mesajul nou (`rank.assess(..., dovada=...)`), fiindca ancora poarta si salutul, si
|
||||
numele firmei, si luna — cuvinte care nu apar in niciun document si care trageau
|
||||
raportul de termeni distinctivi in jos la fiecare replica.
|
||||
|
||||
Ce rupe firul, necontestat: o captura noua sau un cod de eroare diferit. Sunt
|
||||
singurele semne sigure ca omul a trecut la altceva; restul (schimbarea subiectului
|
||||
in cuvinte) e prea usor de confundat cu o continuare, iar greseala aia produce
|
||||
|
||||
@@ -209,6 +209,31 @@ def rank(
|
||||
return sorted(fused, key=lambda i: (fused[i], cosines[i]), reverse=True)
|
||||
|
||||
|
||||
RADACINA = 5 # cate litere din inceputul unui cuvant se compara cu documentele
|
||||
|
||||
|
||||
def _apare(term: str, blob: str) -> bool:
|
||||
"""Termenul e in text — si la alta forma gramaticala decat a scris-o omul.
|
||||
|
||||
Romana articuleaza si conjuga, documentatia si clientul nu folosesc aceleasi
|
||||
forme: documentul scrie „token" si „tokenuri", omul scrie „tokenul"; documentul
|
||||
„Generare", omul „generez"; documentul „trimise", omul „trimiterea". Comparat
|
||||
pe cuvantul intreg, exact cuvantul care conteaza nu se gaseste — masurat pe
|
||||
setul de calibrare (2026-09-01), trei continuari legitime din cinci escaladau
|
||||
numai din cauza asta, desi chunk-ul cu raspunsul era chiar in context.
|
||||
|
||||
Codurile se compara intregi: `ora-01722` si `ora-01720` au aceleasi 5 litere
|
||||
de la inceput si n-au nimic in comun.
|
||||
|
||||
# ponytail: prefix fix, nu stemmer. Poate potrivi gresit cuvinte scurte cu
|
||||
# aceeasi radacina („capitala" ~ „capitaluri"); daca apare in practica, aici
|
||||
# se pune un stemmer romanesc, nu un prag mai mare.
|
||||
"""
|
||||
if _COD.match(term):
|
||||
return term in blob
|
||||
return term[:RADACINA] in blob
|
||||
|
||||
|
||||
def assess(
|
||||
query: str,
|
||||
best_cosine: float,
|
||||
@@ -217,6 +242,7 @@ def assess(
|
||||
*,
|
||||
weak: float | None = None,
|
||||
min_ratio: float | None = None,
|
||||
dovada: str | None = None,
|
||||
) -> tuple[bool, str]:
|
||||
"""(avem acoperire in documente?, motivul) — decizia de a raspunde sau de a escalada.
|
||||
|
||||
@@ -232,6 +258,19 @@ def assess(
|
||||
deci un chunk despre ORA-12541 sta la 0,74 de o intrebare despre ORA-06550.
|
||||
Fara asta, Maria raspundea sigur pe ea cu eroarea gresita, in loc sa escaladeze.
|
||||
|
||||
`dovada` desparte cele doua roluri ale interogarii pe un fir de discutie.
|
||||
Regasirea are nevoie de ancora („da, ma blocheaza" singur nu gaseste nimic),
|
||||
dar acoperirea nu: judecata pe ancora + mesaj nou, ea raspunde la ce a intrebat
|
||||
omul acum SI la formulele si numele proprii din primul mesaj — „buna", „august",
|
||||
„AUTO SULEA" nu apar in niciun document, deci raportul scade la fiecare replica.
|
||||
Cand `dovada` (mesajul nou, plus OCR-ul capturii lui) are termeni distinctivi
|
||||
proprii, acoperirea se judeca pe ei. Cand n-are, se cade inapoi pe interogarea
|
||||
intreaga — comportamentul de dinainte; altfel o continuare fara niciun cuvant
|
||||
al ei ar escalada.
|
||||
|
||||
Codurile (cazul 0) raman pe interogarea intreaga: codul din ancora e chiar
|
||||
subiectul firului, iar continuarea nu-l repeta.
|
||||
|
||||
A existat si un prag „cosinus mare -> raspundem, indiferent de lexical"
|
||||
(`RANK_STRONG_COSINE`, 0,70). L-am scos, nu urcat, si nu doar fiindca a lasat
|
||||
sa treaca doua raspunsuri inventate pe 2026-09-01: **cosinusul creste cu
|
||||
@@ -256,17 +295,19 @@ def assess(
|
||||
)
|
||||
if best_cosine < weak:
|
||||
return False, f"cosinus {best_cosine:.3f} < {weak}"
|
||||
rare = bm25.rare_terms(query)
|
||||
rare = bm25.rare_terms(dovada) if dovada else []
|
||||
if not rare:
|
||||
rare = bm25.rare_terms(query)
|
||||
if not rare:
|
||||
return False, f"cosinus {best_cosine:.3f}, iar intrebarea nu are termeni distinctivi"
|
||||
blob = fold("\n".join(top_texts))
|
||||
gasiti = [t for t in rare if t in blob]
|
||||
gasiti = [t for t in rare if _apare(t, blob)]
|
||||
if gasiti and len(gasiti) / len(rare) >= min_ratio:
|
||||
return True, (
|
||||
f"cosinus {best_cosine:.3f} + {len(gasiti)}/{len(rare)} termeni distinctivi "
|
||||
f"in context: {', '.join(gasiti)}"
|
||||
)
|
||||
lipsa = [t for t in rare if t not in blob]
|
||||
lipsa = [t for t in rare if not _apare(t, blob)]
|
||||
return False, (
|
||||
f"cosinus {best_cosine:.3f}, iar termenii distinctivi "
|
||||
f"({', '.join(lipsa[:5])}) nu apar in documente"
|
||||
|
||||
@@ -45,6 +45,45 @@ def test_rare_terms_prinde_codurile_si_ignora_cuvintele_comune():
|
||||
assert "ora-01722" in rare
|
||||
|
||||
|
||||
def test_assess_gaseste_termenul_la_alta_forma_gramaticala():
|
||||
# Documentul scrie „token"/„tokenuri", omul scrie „tokenul". Comparat pe cuvantul
|
||||
# intreg, singurul cuvant care conteaza nu se gaseste si intrebarea escaladeaza.
|
||||
bm = rank.Bm25(DOCS)
|
||||
covered, motiv = rank.assess("imi zice ca tokenul a expirat", 0.64, DOCS[1:2], bm)
|
||||
assert covered
|
||||
assert "tokenul" in motiv
|
||||
|
||||
|
||||
def test_assess_codurile_se_compara_intregi_nu_pe_radacina():
|
||||
# „ora-01722" si „ora-01720" au aceleasi cinci litere de la inceput.
|
||||
bm = rank.Bm25(DOCS)
|
||||
covered, _ = rank.assess("eroarea ORA-01720", 0.64, DOCS[:1], bm)
|
||||
assert not covered
|
||||
|
||||
|
||||
def test_assess_dovada_judeca_mesajul_nou_nu_ancora():
|
||||
# Continuare pe un fir: ancora e salutul plus reclamatia vaga, mesajul nou e
|
||||
# chiar intrebarea. Fara dovada separata, „buna/august" (care nu apar nicaieri)
|
||||
# trag raportul sub prag si escaladeaza degeaba.
|
||||
bm = rank.Bm25(DOCS)
|
||||
ancora = "Buna ziua . Am o problema pe luna august la firma AUTO SULEA . Ma ajutati?"
|
||||
query = f"{ancora}\ntokenul a expirat"
|
||||
fara, _ = rank.assess(query, 0.64, DOCS[1:2], bm)
|
||||
cu, motiv = rank.assess(query, 0.64, DOCS[1:2], bm, dovada="tokenul a expirat")
|
||||
assert not fara
|
||||
assert cu and "expirat" in motiv
|
||||
|
||||
|
||||
def test_assess_continuare_scurta_cade_inapoi_pe_interogarea_intreaga():
|
||||
# „da, ma blocheaza complet" n-are termeni distinctivi proprii. Judecata singura
|
||||
# ar escalada orice continuare scurta — exact regresia pentru care exista ancora.
|
||||
bm = rank.Bm25(DOCS)
|
||||
query = "token efactura expirat\nda"
|
||||
covered, _ = rank.assess(query, 0.64, DOCS[1:2], bm, dovada="da")
|
||||
fara, _ = rank.assess(query, 0.64, DOCS[1:2], bm)
|
||||
assert covered is fara is True
|
||||
|
||||
|
||||
def test_rank_urca_chunkul_confirmat_de_ambele_metode():
|
||||
# cosinusul favorizeaza documentul 3, dar lexical se potriveste documentul 0;
|
||||
# documentul 0 e sustinut de amandoua (cosinus decent + BM25 mare) si iese primul
|
||||
|
||||
Reference in New Issue
Block a user