Dostępny na nowe rolePiotr Czerwiński

Blog · 25 czerwca 2026 · 11 min czytania

Evals przed merge: golden set za pipeline'em dopasowań AI

LLM evals · AI · dopasowania · testowanie

TL;DR: Zanim pozwoliłem sobie ruszyć pipeline dopasowań AI w produkcie łączącym kandydatów z ofertami pracy, który prowadzę, zbudowałem harness do ewaluacji i golden set: 193 pary kandydat i oferta ocenione przez człowieka, niemal wszystkie wzięte z decyzji akceptacji i odrzucenia, które wcześniej podjąłem w panelu admina. Każda zmiana w pipeline'ie (model, prompt, próg, waga, warstwa) musi teraz przed merge uruchomić to samo polecenie i pokazać te same metryki: P@10, strict P@10, NDCG@10, MRR, Recall@200 oraz violations@10, które musi wynosić zero. Trzy kolejne warianty refaktoru i retrievalu wylądowały w granicach szumu wokół punktu odniesienia, a eksperyment z kalibracją, co do którego byłem pewien, że zadziała, udowodnił, że nie może zadziałać. Harness to mały CLI, który napisałem sam, bo liczą się metryki związane z tym, jak produkt pokazuje wyniki, a etykiety i tak leżały już w mojej bazie.

Problem: pipeline, który mogłem zmieniać, ale nie mierzyć

Pipeline dopasowań bierze profil kandydata i znajduje oferty warte pokazania, a także w drugą stronę. Ma typowe warstwy nowoczesnego systemu wyszukiwania: retrieval wektorowy na embeddingach, twarde filtry na przykład na wykluczone technologie i poziom doświadczenia, przebieg LLM, który ocenia każdą parę, jaka przetrwała filtry, oraz wynik złożony, który ustala kolejność końcowej listy. Każda z tych warstw ma swoje pokrętła. Miałem plan dwuetapowej przebudowy z filtrem wstępnym, szerszym oknem retrievalu, rerankerem i ustrukturyzowanym promptem oceniającym, a każdy krok brzmiał jak oczywista poprawa.

I to była pułapka. Pipeline LLM jest niedeterministyczny w małej skali i ma swoje zdanie w dużej. Zmieniasz prompt, część par idzie w górę, część w dół, a te trzy, na które akurat patrzysz, wyglądają lepiej. Nie miałem jak stwierdzić, czy lista jako całość się poprawiła, a serwis z ofertami pracy stoi albo upada na pierwszych dziesięciu wynikach, które widzi człowiek. Dlatego plan przebudowy dostał kamień milowy zero, który nie dawał użytkownikom niczego: harness do ewaluacji, punkt odniesienia i regułę. Żadna zmiana w pipeline'ie nie trafia do merge bez dołączonego wyniku ewaluacji.

W branży nazywa się to LLM evals: powtarzalny pomiar systemu opartego na LLM na stałym zbiorze ocenionych przykładów, uruchamiany przed wypuszczeniem zmiany, tak jak zestaw testów uruchamia się przed deployem. Różnica względem testów jednostkowych polega na tym, że wynikiem jest liczba na skali, więc trzeba też wiedzieć, jak duża musi być różnica, żeby cokolwiek znaczyła.

Czym jest golden set i skąd biorą się etykiety?

Golden set (zbiór wzorcowy) to stały zbiór danych wejściowych z zaufanymi odpowiedziami przypisanymi przez człowieka, na którym ocenia się każdy wariant. W systemie rankingowym jeden wiersz to para (ten kandydat, ta oferta) i ocena. Użyłem skali trzystopniowej:

  • 2 - pokazałbym to bez wahania.
  • 1 - na granicy, do obrony, bez wstydu.
  • 0 - nie może się pokazać, z obowiązkowym powodem: wykluczona technologia, język, lokalizacja, brakująca umiejętność, poziom doświadczenia, wynagrodzenie, preferencje albo inny.

Najdroższą częścią golden setu jest zwykle etykietowanie. Miałem szczęście, a raczej opłaciła się wcześniejsza decyzja: panel admina od początku zapisywał każdą decyzję człowieka o akceptacji i odrzuceniu z datą i źródłem, właśnie po to, żeby kiedyś dało się to zmierzyć. Pierwszy eksport wyciągnął 189 decyzji ludzi plus garść słabych pozytywów z prawdziwych kliknięć, co dało 193 pary dla 26 profili kandydatów, podzielone mniej więcej po połowie na dobre dopasowania i odrzucenia. Automatyczne akceptacje celowo pominąłem. Decyzja podjęta przez zadanie w cronie to nie jest ocena.

Na luki harness zapisuje pulę do etykietowania: trzydzieści najwyżej ocenionych ofert dla każdego kandydata z obecnego pipeline'u, bez par, które już mają etykietę. Wpisuję ocenę w arkuszu i importuję go z powrotem. Plan zakładał godzinę albo dwie etykietowania. Ponieważ panel admina pokrył już większość par, pierwsza runda to było 25 par i około dziesięciu minut.

Dwa szczegóły sprawiają, że można to bezpiecznie commitować. Golden set przechowuje tylko nieprzezroczyste identyfikatory, oceny i powody, bez danych osobowych. Arkusz do etykietowania, który dla wygody pokazuje czytelne tytuły i dane kontaktowe, nigdy nie trafia do systemu kontroli wersji.

// illustrative shape - one golden row
{ "candidate": "<uuid>", "job": "<uuid>",
  "label": 0, "reason": "location",
  "source": "admin_decision", "labeled_at": "<iso>" }

Jakie metryki mają znaczenie w pipeline'ie rankingowym LLM?

Metryki dobrałem według tego, czego faktycznie doświadcza użytkownik, a potem dodałem jedną, która istnieje wyłącznie po to, żeby go chronić.

MetrykaNa jakie pytanie odpowiadaJak jej używam
P@10 (etykieta 1 lub 2)Czy to, co pokazujemy w pierwszej dziesiątce, jest do przyjęcia?Główna
Strict P@10 (tylko etykieta 2)Czy to jest naprawdę dobre, a nie tylko do obrony?Główna, czytana obok P@10
NDCG@10Czy najlepsze są na górze? P@10 nie widzi kolejności.Nie może spaść
MRRJak nisko jest pierwszy dobry wynik?Przybliżenie widoku od strony firmy
Recall@200Czy retrieval w ogóle wciągnął znane dobre pary?Diagnostyka zmian w retrievalu
violations@10Czy w pierwszej dziesiątce jest jakieś twarde wykluczenie?Bramka przed wdrożeniem: musi być 0
coverage@10Jaka część pierwszej dziesiątki w ogóle ma ocenę?Wskaźnik zaufania

Bramka przed wdrożeniem zasługuje na osobne zdanie. Precyzję można wymieniać na inne rzeczy. Oferty w złym kraju albo wymagającej technologii, którą kandydat wprost wykluczył, wymieniać nie można. Trzy powody odrzucenia liczą się jako twarde naruszenia i jedno takie naruszenie w pierwszej dziesiątce oblewa cały przebieg, niezależnie od tego, jak dobrze wyglądają średnie. Te same reguły żyją też w zestawie fixture'ów end-to-end (syntetyczne pary, na przykład kandydat, który wykluczył framework, zestawiony z ofertą opartą właśnie na nim). W trakcie przebudowy zestaw urósł z 22 do 30 i pilnuje filtrów bezpieczeństwa niezależnie od metryk rankingowych.

Coverage to metryka, o której ludzie zapominają. Jeśli nowy wariant wciąga do pierwszej dziesiątki nieocenione pary, pozostałe metryki liczą się tylko na ocenionej części i mogą wyglądać stabilnie, choć ukrywają zmianę. Niskie coverage oznacza: najpierw więcej etykiet, potem wiara w wyniki.

Jakie opcje harnessu rozważałem

Framework albo platforma do ewaluacji (promptfoo, LangSmith i podobne). Są dobre w tym, do czego powstały: przypadki testowe na poziomie promptu, wejście, wyjście i asercja albo sprawdzenie oceniane przez LLM, do tego dashboardy i historia. Moja jednostka ewaluacji była inna. Był nią cały ranking dla kandydata, tworzony łącznie przez retrieval, filtry, przebieg LLM i ważony wynik, oceniany metrykami rankingowymi względem etykiet, które już leżały w mojej bazie. Wciśnięcie tego w kształt testu promptu oznaczało eksport danych, pisanie własnych metryk i tak, oraz dołożenie kolejnego podmiotu przetwarzającego dane pochodzące z profili osobowych, a to wiąże się z papierologią, której unikam, gdy tylko mogę. Narzędziu zostawałby do zrobienia dashboard, a to załatwiał plik JSON z każdego przebiegu.

LLM-as-a-judge, czyli model oceniający wyniki zamiast człowieka. Jest tani i się skaluje, a jako szybki test regresji ma sens. Punkt odniesienia zostawiłem jednak ludzki, bo osoba akceptująca dopasowania zna rynek i kandydatów, a model sędziujący miałby wiele martwych pól wspólnych z modelem oceniającym, którego ocenia.

Testy A/B online na użytkownikach. Przy ruchu młodego produktu nie ma mocy statystycznej. Test trwałby miesiącami i nadal niczego by nie powiedział. Odłożyłem to. Pomiędzy ewaluacją offline a A/B jest tryb cienia (shadow mode): nowa wersja pipeline'u zapisuje wyniki oznaczone wersją, człowiek przegląda obie, a jego decyzje powiększają golden set.

Mały własny CLI. Cztery podpolecenia: eksport etykiet z decyzji ludzi, zapis puli do etykietowania, import etykiet i obliczenie punktu odniesienia pod danym tagiem. Metryki to czyste funkcje bez I/O, więc łatwo je testować. Harness działa tylko do odczytu: czyta bieżący ranking i golden set i nigdy nie zapisuje wyników, więc uruchomienie go na prawdziwych danych nie niesie żadnego ryzyka. Każdy przebieg zapisuje oznaczony tagiem i datą plik JSON ze stanem wyników, który trafia do commita, więc historia każdego wariantu leży w repozytorium obok kodu, który ją wytworzył. To wybrałem.

// illustrative shape - one committed result snapshot
{ "tag": "m2", "golden_rows": 193, "profiles": 26,
  "macro": { "P@10": 0.535, "strictP@10": 0.497,
             "NDCG@10": 0.836, "MRR": 0.682, "coverage@10": 0.59 },
  "violations_at_10_total": 0 }

Jak ocenić zmianę w rankingu LLM przed merge?

Przepuszczasz ten sam golden set przez punkt odniesienia i przez wariant, a różnicę czytasz względem progu szumu ustalonego z góry. Przy kilkuset etykietach i paru tuzinach profili przedziały ufności są szerokie, więc wszystko poniżej pięciu punktów procentowych traktuję jako szum. Celem dla prawdziwej poprawy było, orientacyjnie, dziesięć punktów. Oto, co dały pierwsze kamienie milowe przebudowy:

WariantP@10Strict P@10NDCG@10MRRCoverage@10Naruszenia
Punkt odniesienia0,5390,5000,8790,6941,000
M1: refaktor konsolidujący0,5350,4970,8330,6830,660
M2: filtr wstępny, szerszy retrieval0,5350,4970,8360,6820,590
M3: zmiany w ocenianiu0,5270,4890,8160,6630,590

Żaden wariant nie pobił punktu odniesienia. W przypadku pierwszego taki był cel: połączył trzy kopie logiki oceniania w jedną, naprawił normalizator umiejętności, który rozjechał się między dwiema mapami, i sprawił, że ponowne dopasowanie zachowuje decyzje ludzi. Bramka dla refaktoru to "nie gorzej" i refaktor ją przeszedł. Ciekawy sygnał był gdzie indziej. Coverage spadło z 1,00 do 0,66, bo naprawiony normalizator przestał odrzucać pary za rzekomo brakujące umiejętności, pula kandydatów urosła z 225 do 360 par, a mniej więcej jedna trzecia nowej pierwszej dziesiątki nigdy nie była oceniana. Metryki rankingowe były stabilne na ocenionej części i milczały o reszcie, a harness mi to powiedział, zamiast pozwolić odczytać stabilność jako sukces.

Drugi wariant poszerzył pulę jeszcze bardziej, do 519 par (+44%), a trzeci mieścił się w granicach szumu względem drugiego. Reranker w tym kamieniu milowym był nadal wyłączony flagą, więc jego wpływu w ogóle nie było w tych liczbach. To dokładnie ten rodzaj rzeczy, którą plik wyników z tagiem później czyni oczywistą. Bez harnessu wypuściłbym wszystkie trzy z historyjką o tym, dlaczego każdy jest lepszy.

Z każdym przebiegiem idzie jeszcze jedno sprawdzenie: rozkład surowego podobieństwa embeddingów dla wszystkich zapisanych par. Rozrzut od 10. do 90. percentyla wynosił od 0,40 do 0,65, co potwierdziło założenie z planu o tym, ile sygnału rankingowego niesie samo podobieństwo. O tym, dlaczego te surowe wyniki same w sobie znaczą tak mało, pisałem w tekście o strojeniu progów podobieństwa w pgvector.

Eksperyment, który się nie udał, i czego mnie nauczył

Przebudowa miała ambitny cel: dopasowania na tyle dobre, żeby automatycznie akceptować pewne pary i wysyłać do przeglądu przez człowieka tylko te niepewne. Plan zakładał dopasowanie regresji logistycznej do decyzji ludzi, z sygnałami pipeline'u jako cechami, i przesuwanie progu z priorytetem precyzji, aż automatyczna akceptacja osiągnie mniej więcej 95 do 98 procent precyzji.

Nawet się do tego nie zbliżyło. Na 149 parach z decyzją człowieka i oceną LLM precyzja stanęła na około 72 do 73 procent przy każdym progu. Prawdziwym odkryciem były wagi: większość sygnału niosło pokrycie słów kluczowych, a ogólna ocena LLM ledwo odróżniała pary zaakceptowane od odrzuconych. Część pipeline'u, za którą płaciłem od każdego wywołania, mówiła recenzentowi najmniej. Bramka została nieuzbrojona, a kolejne kroki stały się oczywiste: więcej etykiet, sygnał z rerankera i benchmark modelu oceniającego na tym samym golden secie, zanim zapłacę za większy.

Cały ten układ ma znane ograniczenia i trzymam je zapisane obok liczb. Etykiety pochodzą z puli tego, co pipeline już wyciągnął, więc Recall@200 z założenia wychodzi 1,0 i niewiele mówi, dopóki jakaś zmiana w retrievalu nie wciągnie par spoza puli. Punkt odniesienia jest trochę błędnym kołem, bo człowiek decydował o rankingach wytworzonych przez ten sam pipeline. Do tego zbiór jest mały. Nic z tego nie czyni go bezużytecznym jako punktu odniesienia dla wariantów. Czyni go instrumentem kierunkowym i tak go czytam.

Co powiedziałbym komuś, kto zaczyna to jutro

  • Zapisuj decyzje ludzi, zanim będą Ci potrzebne. Przycisk akceptacji albo odrzucenia, który zapisuje kto, kiedy i dlaczego, to golden set rosnący przy zwykłej pracy. U mnie zamienił dwie godziny etykietowania w dziesięć minut.
  • Dobieraj metryki według tego, co pokazuje ekran. Użytkownicy widzą dziesięć wyników, więc mierz pierwszą dziesiątkę. Kolejność (NDCG) i pierwszy dobry wynik (MRR) dodaj tylko dlatego, że odpowiadają na pytania, na które precyzja nie odpowie.
  • Z metryki wykluczeń zrób bramkę. Średnie można wymieniać między sobą, oferty w złym kraju nie. Zero albo nie wychodzi na produkcję.
  • Ustal próg szumu przed pierwszym przebiegiem. Inaczej każde drobne wahnięcie staje się historyjką. Przy małym zbiorze moim progiem było pięć punktów.
  • Pilnuj coverage. Wariant, który wciąga nieocenione wyniki do pierwszej dziesiątki, może wyglądać stabilnie, a jednocześnie ukrywać zmianę, na której Ci zależy.
  • Wersjonuj wyniki razem z kodem. Oznaczony tagiem zapis z każdego przebiegu jest tańszy niż platforma i po miesiącach odpowiada na pytanie "co wiedzieliśmy, kiedy to mergowaliśmy".
  • Spodziewaj się, że większość wariantów przegra. Trzy sensowne zmiany z rzędu wylądowały w granicach szumu. To harness, który robi swoją robotę.

Ta sama dyscyplina obowiązuje piętro niżej: ewaluacja jest tak dobra, jak ustrukturyzowane dane, które pipeline w ogóle wyciąga, i o tym jest tekst o tool use zamiast parsowania.

Pytania, na które odpowiada ten wpis

Jak ocenić zmiany w pipeline'ie rankingowym opartym na LLM?
Oceń punkt odniesienia i wariant na tym samym, stałym golden secie par ocenionych przez ludzi i porównaj metryki rankingowe, takie jak P@10, NDCG@10 i MRR. Próg szumu ustal przed pierwszym uruchomieniem, a merge zablokuj, jeśli w pierwszej dziesiątce pojawi się choć jedno twarde wykluczenie.
Jak zbudować golden set do LLM evals bez wielu godzin etykietowania?
Wyeksportuj decyzje, które ludzie już podejmują w produkcie, na przykład akceptacje i odrzucenia w panelu admina z datą i powodem, i pomiń automatyczne akceptacje. Potem etykietuj tylko luki, korzystając z puli najwyżej ocenionych par, które nie mają jeszcze etykiety.
Czy do oceny pipeline'u RAG albo dopasowań używać promptfoo lub LangSmith?
Dobrze sprawdzają się przy testach na poziomie promptu. Gdy jednostką oceny jest cały ranking tworzony przez retrieval, filtry i ocenę LLM, a etykiety są już w Twojej bazie, mały CLI tylko do odczytu z własnymi metrykami rankingowymi i wersjonowanymi plikami wyników bywa prostszy i trzyma dane u Ciebie.