From f43c83f218f134785142bb84d0ebcb2f65fa4dde Mon Sep 17 00:00:00 2001 From: oskar Date: Thu, 6 Aug 2026 13:19:00 +0200 Subject: [PATCH] eval(retrieval): przekwalifikuj N2 na mail_query M5, nowa kontrolka N3 Regresja po zamknieciu Etapu B fazy mailowej dala FAIL wylacznie na kryterium 3: kontrolka N2 "piaskownica plastikowa" spadla do 0.4924 (flat/hybrid) na mail przedszkolny "Materialy plastyczne" -- kolizja leksykalna ponizej progu 0.50. Rownoczesnie odrzucone w Etapie A zapytanie "piaskownica plac zabaw wspolnota" ma dzis realne odpowiedzi (maile administracji wspolnoty holc.waw.pl, 2023). To nie regresja retrievalu -- korpus urosl o tresc, ktorej w Etapie A nie bylo, wiec kontrolka negatywna stracila waznosc. - N2 wycofana; pelna historia decyzji (pilot, 2026-07-23, 2026-08-06) zachowana jako komentarz w queries.yaml, nie skasowana. - M5: "wymiana piasku w piaskownicy na placu zabaw wspolnoty", expected_envelope 6ab218df-...@holc.waw.pl, d1=0.2858, hit3=y. Wariant z "wymiana piasku" zamiast doslownego sformulowania operatora, bo tamto daje 0.4562/0.4597 -- oba nad HIT_THRESHOLD 0.45. - N3: "sterylizacja kota cennik kliniki weterynaryjnej", bar 0.50 bez zmian, 0.5257/0.5799/0.5257. Dwaj odrzuceni kandydaci i lista tematow majacych realna odpowiedz w korpusie udokumentowane w komentarzu. - retrieval_eval.py: tylko komentarz -- mail_hit nadal ocenia sie source-matchem, expected_envelope M5 jest dokumentacyjne. Weryfikacja: retrieval_eval.py --transport http --base-url http://192.168.31.5:8230 --gate-n 10 -> OVERALL PASS (exit 0), kryteria 1/2/3 PASS, kryterium 4 = 5/5 (wymagane >= 4). Co-Authored-By: Claude Opus 5 (1M context) --- jobs/documents-ingest/eval/queries.yaml | 101 ++++++++++++++++--- jobs/documents-ingest/eval/retrieval_eval.py | 5 + 2 files changed, 90 insertions(+), 16 deletions(-) diff --git a/jobs/documents-ingest/eval/queries.yaml b/jobs/documents-ingest/eval/queries.yaml index 223d391..bf5fada 100644 --- a/jobs/documents-ingest/eval/queries.yaml +++ b/jobs/documents-ingest/eval/queries.yaml @@ -63,32 +63,86 @@ queries: baseline_top1_dist: 0.6210 note: "Poprawny brak w pilocie; separacja od trafień wyraźna." - - id: "N2" - text: "piaskownica plastikowa" + # --- historia kontrolki borderline: N2 "piaskownica plastikowa" (WYCOFANA 2026-08-06) --- + # Nie kasować: to zapis decyzji, nie komentarz do kodu. Rolę kontrolki borderline przejmuje + # N3 poniżej, a samo zapytanie o piaskownicę żyje dalej jako pozytywne M5 w `mail_queries`. + # + # Pilot (kb-m5-eval-retrieval-pilot.md): kind negative_control_borderline, baseline_top1_dist + # 0.5533, no_answer_threshold 0.50. Poprawnie na granicy "brak" -- semantycznie sąsiednie + # dokumenty wspólnoty mieszkaniowej, nie odpowiedź na zapytanie. + # 2026-07-23 (faza mailowa, bramka po Etapie A): po dolaniu chunków mailowych top-1 = + # newsletter szkoły narciarskiej (Rossignol, rozmiary nart 155-181 cm, dist 0.5298) -- + # zweryfikowana treść pokazała kolizję semantyczną krótkich, liczbowych tekstów w + # przestrzeni wektorowej, NIE realny mail o piaskownicy; korpus mailowy Etapu A takiego + # trafienia nie zawierał (próbne zapytanie "piaskownica plac zabaw wspólnota" dało wtedy + # dist 0.5585, miss -- odrzucone jako wpis mail_queries, ówczesne "M5"). Próg obniżono + # wtedy z 0.55 do 0.50 właśnie z powodu tej znanej kolizji, żeby bramka nie płonęła co + # uruchomienie na nie-problemie. + # 2026-08-06 (zamknięcie Etapu B, pełny korpus mailowy w HNSW) -- ZWROT AKCJI: kontrolka + # unieważniona przez wzrost korpusu, w dwóch niezależnych miejscach naraz: + # a) "piaskownica plastikowa" spadła do 0.4924 (flat i hybrid, cascade 0.5533) na mail + # przedszkolny "Materiały plastyczne" -- znowu kolizja leksykalna (plastikowa / + # plastyczne), ale tym razem PONIŻEJ progu 0.50, więc kryterium 3 FAIL przy PASS + # kryteriów 1/2/4 -- czyli bramka płonęła dokładnie na tym, przed czym próg miał + # chronić; + # b) odrzucone w Etapie A "piaskownica plac zabaw wspólnota" ma dziś realne odpowiedzi: + # maile administracji wspólnoty (holc.waw.pl, 2023) "WM Targowa 2A - zabawy na terenie + # wspólnoty" (dist 0.4562) i "WM Targowa 2A - wymiana piasku" (dist 0.4597, + # envelope 6ab218df-ef21-dc78-d589-34f69a97aae6@holc.waw.pl). + # Wniosek: to NIE regresja retrievalu -- korpus urósł o treść, której w Etapie A nie było, + # a kontrolka negatywna z definicji traci ważność w chwili, gdy odpowiedź na nią wpada do + # korpusu. Stąd przekwalifikowanie na pozytywne M5 zamiast łatania progu w dół. + + # --- dobór następcy (kandydaci sprawdzeni na żywym kb-query 2026-08-06, --gate-n 10) --- + # Wymóg: temat sąsiadujący z korpusem (żeby kontrolka była borderline, nie oczywista), ale + # bez odpowiednika w Paperless ANI w mailach, dist > 0.50 we WSZYSTKICH torach. + # Uwaga metodologiczna z tego doboru: korpus mailowy jest po Etapie B znacznie szerszy niż + # "wspólnota / przedszkole / FLL / polisy / telco / narty" -- zawiera też archiwum zawodowe, + # newslettery, oferty pracy, Allegro, rezerwacje hoteli, PIT, najem, judo. Odrzucone jako + # kontrolki, bo korpus MA na nie realną odpowiedź (dist w nawiasie = min po torach): + # kominiarski/wentylacja (0.4277, paperless:105), hotel nad morzem (0.4166), PIT-37 (0.3994), + # judo dla dzieci (0.3469), najem mieszkania (0.3699), prawo jazdy kat. B (0.3606), + # opony zimowe (0.4098), gwarancja AGD (0.4498), karta rowerowa (0.4340). + # Kandydaci, którzy przeszli (flat / cascade / hybrid): + # A. "sterylizacja kota cennik kliniki weterynaryjnej" 0.5257 / 0.5799 / 0.5257 <- AKTYWNY + # B. "karta wędkarska pozwolenie na połów ryb" 0.5203 / 0.5223 / 0.5203 + # (odrzucony: cascade tylko 0.0223 nad progiem -- za ciasno na trwałą bramkę) + # C. "pasieka ul pszczeli zbiór miodu" 0.5985 / 0.6318 / 0.5985 + # (odrzucony jako aktywny: za łatwy, powiela rolę N "przepis na sernik"; trzymany jako + # zapasowy, gdyby korpus kiedyś unieważnił A tak jak unieważnił N2) + - id: "N3" + text: "sterylizacja kota cennik kliniki weterynaryjnej" kind: negative_control_borderline expected_envelope: null - baseline_top1_dist: 0.5533 + baseline_top1_dist: 0.5257 no_answer_threshold: 0.50 note: > - Poprawnie na granicy "brak" w pilocie -- semantycznie sąsiednie dokumenty wspólnoty - mieszkaniowej, nie odpowiedź na zapytanie. Faza mailowa, bramka po Etapie A - (2026-07-23): po dolaniu chunków mailowych top-1 = newsletter szkoły narciarskiej - (Rossignol, rozmiary nart 155-181cm, dist 0.5298) -- zweryfikowana treść pokazuje, że to - kolizja semantyczna krótkich, liczbowych tekstów w przestrzeni wektorowej, NIE realny - mail o piaskownicy -- korpus mailowy takiego trafienia nie zawiera (próbne zapytanie - "piaskownica plac zabaw wspólnota" dało dist 0.5585, miss; odrzucone jako mail_queries - wpis, patrz historia sesji). Próg tej kontroli obniżony do 0.50 (z 0.55) właśnie z powodu - tej znanej kolizji, żeby bramka nie płonęła co uruchomienie na nie-problemie. + Następca N2 (2026-08-06, patrz historia powyżej). Temat celowo sąsiedni wobec korpusu -- + usługa domowa z językiem cennika/rozliczenia, czyli rejestr, w którym korpus jest gęsty -- + ale zwierząt domowych nie ma w nim ani w Paperless, ani w mailach. Baseline zmierzony na + żywym kb-query (192.168.31.5:8230, --transport http, --gate-n 10), nie w pilocie: flat + 0.5257 / cascade 0.5799 / hybrid 0.5257. Top-1 we flat/hybrid to newsletter zakupowy z + ofertami -- brak realnej odpowiedzi, czyli kontrolka działa. Próg 0.50 przeniesiony z N2 + bez zmiany (margines ~0.026 nad progiem; przy kolejnym dużym doładowaniu korpusu należy + go zweryfikować, a nie obniżać próg -- lekcja z N2). # Faza mailowa (kb/phases/kb-m5-faza-mailowa.md, §8, Krok 5) -- bramka jakościowa dla # treści mailowej wprowadzonej w Etapie A (ostatnie 12 miesięcy, plan §7 Krok 4). Wypełniona # przez operatora 2026-07-23 (5 zapytań "wiem że to mam w mailach z ostatniego roku"; M5 -# odrzucone po weryfikacji, patrz N2 powyżej i historia sesji). expected_envelope celowo null -# dla wszystkich -- operator dostarczył treść zapytania, nie surowy Message-ID; kind: mail_hit -# ma inną semantykę hit@3 niż `queries:` powyżej: hit iff top-3 hybrid zawiera wynik z gałęzi +# odrzucone wtedy po weryfikacji, patrz historia N2 powyżej -- i PRZYWRÓCONE 2026-08-06 po +# Etapie B, gdy odpowiedź na nie realnie znalazła się w korpusie). expected_envelope jest null +# dla M1-M4 -- operator dostarczył treść zapytania, nie surowy Message-ID; kind: mail_hit ma +# inną semantykę hit@3 niż `queries:` powyżej: hit iff top-3 hybrid zawiera wynik z gałęzi # mailowej (envelope.source w summaryless_sources, dziś gmail) z dist < 0.45 # (retrieval_eval.py::mail_hit_at_3) -- identity-match na expected_envelope nie ma tu -# zastosowania, bo nie ma czego dopasować. +# zastosowania, bo dla M1-M4 nie ma czego dopasować. +# +# WYJĄTEK (2026-08-06): M5 ma znane, zweryfikowane curl-em expected_envelope. Uwaga przy +# czytaniu raportu -- retrieval_eval.py::summarize_query_result dla kind: mail_hit NIE używa +# expected_envelope (idzie ścieżką mail_hit_at_3, source-match), więc ten identyfikator ma +# dziś wartość wyłącznie dokumentacyjną/audytową: mówi, który mail uznaliśmy za poprawną +# odpowiedź, ale bramka go nie sprawdza. Jeśli kiedyś ma być egzekwowany, to zmiana w +# retrieval_eval.py (np. identity-match gdy expected_envelope != null), nie w tym pliku. # # Format wpisu (identyczny co do pól z `queries:` powyżej, minus baseline_top1_dist -- nie było # pilota mailowego przed tą bramką): @@ -118,3 +172,18 @@ mail_queries: kind: mail_hit expected_envelope: null note: "Wątki organizacyjne FLL 25-26." + - id: "M5" + text: "wymiana piasku w piaskownicy na placu zabaw wspólnoty" + kind: mail_hit + expected_envelope: "6ab218df-ef21-dc78-d589-34f69a97aae6@holc.waw.pl" + note: > + Dawna kontrolka negatywna N2, przekwalifikowana 2026-08-06 po Etapie B (pełna historia w + komentarzu w `queries:` powyżej). Odpowiedź: mail administracji wspólnoty "WM Targowa 2A + - wymiana piasku" (k.szrajner@holc.waw.pl, 2023-06-16, wymiana piasku w piaskownicy na + placu zabaw osiedla 19.06). Sformułowanie to naturalniejsza wariacja operatorskiego + "piaskownica plac zabaw wspólnota": samo "piaskownica plac zabaw wspólnota" daje dziś + top-1 0.4562 / top-2 0.4597 -- oba NAD progiem HIT_THRESHOLD 0.45, więc mail_hit_at_3 + liczyłoby to jako miss mimo poprawnych i zweryfikowanych trafień w top-2. Wariant z + "wymianą piasku" trafia expected_envelope na pozycji 1 z dist 0.2858 (flat i hybrid), + z zapasem wobec progu. To zarazem jedyne mail_query z niepustym expected_envelope -- + patrz WYJĄTEK w komentarzu nagłówkowym tej sekcji (bramka go nie egzekwuje). diff --git a/jobs/documents-ingest/eval/retrieval_eval.py b/jobs/documents-ingest/eval/retrieval_eval.py index e370413..333022a 100644 --- a/jobs/documents-ingest/eval/retrieval_eval.py +++ b/jobs/documents-ingest/eval/retrieval_eval.py @@ -217,6 +217,11 @@ def summarize_query_result(result: dict, envelope_sources: Optional[dict[str, st if query["kind"] == "mail_hit": # plan §8 Krok 5: no expected_envelope (operator gave query text, not a Message-ID) -- # graded on mail_hit_at_3's source-match semantics instead of hit_at_3's identity match. + # Since 2026-08-06 one mail query (M5, the requalified N2 control) *does* carry an + # expected_envelope, verified by hand against the live index. It stays documentation + # only: this branch deliberately keeps grading every mail_hit by source-match, so the + # id is never silently half-enforced. Enforcing it would be a change here (identity + # match when expected_envelope is not None), not in queries.yaml. flat_hit3 = None # flat never reaches summaryless (gmail) chunks -- not a meaningful axis hybrid_hit3 = mail_hit_at_3(hybrid_chunks, envelope_sources or {}) else: