Czemu “wygląda dobrze” nie jest testem dla prompta
Dopracowujesz treść prompta, który klasyfikuje zgłoszenia, sprawdzasz go na trzech przykładach otwartych w notatniku i wdrażasz. Tydzień później reklamacje dotyczące rozliczeń, które wcześniej trafiały do właściwej kolejki, zaczynają trafiać do “pytania”. Nie te trzy przypadki, które sprawdziłeś, ale te pozostałe czterysta, których nie sprawdziłeś. Nic się nie wywala. Nie ma żadnego stack trace’u. Prompt przeczytany na głos wciąż brzmi dobrze.
Code review i kilka ręcznych sprawdzeń tego nie wychwytuje, bo ten sam prompt na tym samym wejściu nie zwraca dwa razy tego samego tekstu. Nie możesz porównać dwóch przebiegów bit po bicie i nazwać różnicę bugiem, tak jak przy czystej funkcji. Co możesz sprawdzić, to czy każda odpowiedź ma właściwości, na których ci zależy: właściwą kategorię, podsumowanie poniżej limitu długości, żadnego pola, którego źródło nigdy nie wspominało. Eval uruchamia te sprawdzenia na zbiorze prawdziwych danych wejściowych przy każdej zmianie prompta albo modelu, więc regresja wychodzi na jaw w twoim terminalu, a nie w zgłoszeniu klienta.
Z czego składa się LLM eval
Każdy eval, niezależnie od narzędzia, ma trzy części: dataset reprezentatywnych wejść, asercję, która sprawdza każdą odpowiedź względem tego, co dane wejście powinno wygenerować, oraz pass rate, który śledzisz z przebiegu na przebieg.
Myśl o tym jak o zestawie testów dla funkcji, która za każdym razem zwraca odrobinę inne zdanie. Asercja nie może sprawdzić dokładnej zgodności, więc sprawdza właściwość zdania: czy nazywa właściwą kategorię, czy liczba w nim się parsuje. Gdy zaakceptujesz, że test sprawdza “czy to się zgadza”, a nie “czy to jest identyczne”, napisanie go staje się zwykłym inżynierskim zadaniem.
Jak zbudować golden dataset dla LLM eval
Weźmy prompt, który zamienia surowy e-mail wsparcia w ustrukturyzowane zgłoszenie:
| |
Golden dataset to zbiór prawdziwych e-maili sparowanych z odpowiedzią, którą już znasz jako prawidłową. Prawdziwych, nie wymyślonych tylko, żeby prompt wyglądał dobrze:
| |
Pięć do dziesięciu przypadków wystarczy na start. Rozmiar liczy się mniej niż to, skąd pochodzą kolejne przypadki: z prawdziwych awarii produkcyjnych.
Jak pisać deterministyczne asercje dla odpowiedzi LLM
Większość tego, co może pójść źle w tym prompcie, nie ma nic wspólnego z gustem. Model zwraca błędny JSON, wymyśla czwartą kategorię albo pisze czterdzieści słów, gdy prosiłeś o dwanaście. Zwykły kod wychwytuje to wszystko, bez potrzeby drugiego modelu:
| |
Uruchom to na całym dataset i dostajesz pass rate, a nie proste tak albo nie:
| |
Te sprawdzenia nic nie kosztują i nigdy nie migoczą losowo: ta sama zła odpowiedź zawodzi tak samo każdym razem. Napisz je najpierw. Wychwytują większość prawdziwych regresji, jak zmianę schematu albo podsumowanie, które po cichu się podwoiło.
Kiedy używać LLM-as-judge i jak go skalibrować
Kategoria i pilność są obiektywne, a dataset już zawiera właściwą odpowiedź. Jakość podsumowania nie. “Potrzebny zwrot, podwójne obciążenie” i “Klient ma pytanie dotyczące rozliczenia” to oba poprawny JSON, oba poniżej 12 słów, i tylko jedno z nich faktycznie opisuje ten e-mail. Żaden regex tego nie złapie, więc drugi model ocenia to według rubryki:
| |
Zanim zaufasz temu werdyktowi na całym dataset, uruchom judge na 15-20 przypadkach już ocenionych ręcznie przez człowieka i policz, jak często się zgadzają. Judge, który nie zgadza się z ludźmi w co trzecim przypadku, jest słabą asercją przebraną za autorytet modelu.
Dwa obciążenia warto obserwować. Judge ma tendencję do wyżej oceniania dłuższych, bardziej wyszukanych odpowiedzi, nawet gdy krótsza jest równie poprawna. A model oceniający własne odpowiedzi ocenia je korzystniej, niż zrobiłby to niezależny model. Używaj innego modelu jako judge, kiedy to możliwe, i trzymaj prompt wąski: jedno pytanie tak/nie na wywołanie, nie pięć ocen w jednym żądaniu.
| Deterministyczna asercja | LLM-as-judge | |
|---|---|---|
| Koszt na przypadek | Darmowy | Jedno dodatkowe wywołanie modelu |
| Co wychwytuje | Schemat, format, fakty już obecne w dataset | Subiektywna jakość, trafność, ton |
| Powtarzalność | Ta sama odpowiedź, ten sam werdykt | Pewny dryf nawet w temperaturze 0 |
| Koszt wdrożenia | Napisz sprawdzenie i gotowe | Wymaga wcześniejszej kalibracji na przykładach ocenionych przez ludzi |
W kategoriach zestawu testów deterministyczne sprawdzenia to twoje testy jednostkowe: szybkie, dokładne, uruchamiane przy każdej zmianie. Judge jest bliższy sprawdzeniu akceptacyjnemu, temu, które inaczej wykonałby człowiek, czytając każdą odpowiedź. Te same kompromisy między poziomami opisano w testy jednostkowe vs integracyjne vs end-to-end.
Jak uruchamiać LLM eval w CI, żeby wychwytywać regresje
Eval, który działa tylko wtedy, gdy ktoś o nim pamięta, nic nie wychwytuje. Umieść skrypt obok prompta, który testuje (evals/triage.py razem z prompts/triage.py), i uruchamiaj go w CI przy każdej zmianie w jednym z tych plików, przed merge. Śledź pass rate w czasie i zablokuj build, gdy spadnie poniżej ustalonej przez ciebie bazowej wartości. Spadek z 95% do 88% to regresja, którą warto zablokować, nawet jeśli większość przypadków wciąż przechodzi.
Trzymaj w dataset zestaw regresyjny: każdy przypadek, który był kiedyś zgłoszonym bugiem, przypięty na trwałe po naprawie. Dla prompta daje ci to cykl opisany w czym naprawdę jest TDD. Zmień prompt, uruchom zestaw ponownie, zobacz, które asercje się przewróciły, popraw, powtórz. Zasilaj dataset nowymi awariami produkcyjnymi, gdy się pojawiają, żeby nie testować tylko wejść, dla których prompt został zaprojektowany.
Jak oceniać pipeline’y tool calling i RAG
Te same trzy części mają zastosowanie i tutaj. Dla pętli tool calling dataset paruje każde wejście z właściwą nazwą narzędzia i argumentami, a asercja sprawdza, czy model wybrał to narzędzie z wypełnionymi wymaganymi polami. Mechanika samej pętli jest opisana w function calling i tool use w LLM.
Pipeline RAG potrzebuje dwóch odrębnych ocen. Czy retrieval pobrał fragment zawierający odpowiedź? Czy generacja właściwie użyła tego fragmentu? Jeśli oceniasz to razem, nie wiesz, która połowa się zepsuła. Zobacz jak działa RAG, żeby zobaczyć, co właściwie dzielisz na dwa.
Częste błędy w LLM eval
Budowanie dataset z przypadków, które już przechodzą. Pięć przykładów wybranych, bo prompt dobrze je obsługuje, będzie przechodzić zawsze i nie powie ci nic o wejściu, które za miesiąc wszystko zepsuje. Każda prawdziwa awaria, która do ciebie dociera, powinna wejść do dataset tego samego dnia.
Sprawdzanie dokładnej zgodności na zmiennej odpowiedzi. summary == "Potrzebny zwrot za podwójne obciążenie" zawodzi w momencie, gdy model zmieni jedno słowo, nawet gdy nowe sformułowanie jest równie poprawne. Sprawdzaj właściwość, nie tekst: liczbę słów, kategorię, czy pojawia się wymagany fakt.
Pomijanie kalibracji judge. Judge, którego nigdy nie porównałeś choćby z tuzinem przykładów ocenionych przez ludzi, oddaje ci swoje własne obciążenia jako prawdę, w tym to, które sprzyja jego własnej odpowiedzi.
Ponowne uruchamianie tylko przypadku, który właśnie naprawiłeś. Tak stary bug wraca niezauważony. Zestaw regresyjny istnieje właśnie po to, żeby to uniemożliwić.
Kiedy nie warto budować zestawu LLM eval
Prompt, który uruchomisz raz, albo który człowiek sprawdza przy każdym wywołaniu, nie potrzebuje harnessu. Umieść sprawdzenie poprawności JSON inline, jeśli jest tanie, i idź dalej. Zestaw się opłaca, gdy prompt trafia do użytkowników wielokrotnie albo gdy planujesz go edytować więcej niż parę razy. To moment, w którym “wyglądało dobrze na moich trzech przykładach” przestaje wystarczać.
Na start: pięć prawdziwych wejść, jedna deterministyczna asercja na każdą właściwość, na której ci zależy, i judge LLM tylko dla wymiaru, którego kod nie może sprawdzić. Uruchom to przed następną zmianą prompta, nie po reklamacji.