Skip to content
← Wszystkie wpisy
6 min czytania Dawid Skłodowski

Ewaluacja wyjścia LLM: testy dla niedeterministycznego kodu

Nie możesz asertować dokładnej równości na wyjściu, które za każdym razem jest inne — więc jak w ogóle testować funkcję LLM? Deep-dive o evalach: budowaniu reprezentatywnego zbioru testowego, ocenianiu właściwości zamiast łańcuchów, łapaniu regresji i traktowaniu ewaluacji jako siatki bezpieczeństwa, która pozwala zmieniać prompty z pewnością.

Oto problem, który powstrzymuje większość zespołów przed testowaniem funkcji LLM w ogóle: wyjście jest za każdym razem inne. Model jest niedeterministyczny — zapytaj go to samo dwa razy i dostaniesz dwie inaczej sformułowane odpowiedzi, obie być może poprawne. Cały nasz instynkt testowania jako deweloperów jest zbudowany na assert_equal expected, actual, a ten instynkt jest tu bezużyteczny. Nie możesz asertować dokładnej równości na łańcuchu, który nigdy nie jest ten sam dwa razy. Więc wiele zespołów wnioskuje, że funkcji LLM nie da się testować, i wysyła na wibracje — zmień prompt, zerknij na jeden przykład, wdróż, miej nadzieję. To złe miejsce dla dowolnego kodu, a gorsze dla kodu, którego zachowania nie możesz przewidzieć. Jest dyscyplina na to, pożyczona z uczenia maszynowego, i nazywa się ewaluacją — w skrócie evale. Rozłóżmy, jak działa i dlaczego to siatka bezpieczeństwa, która czyni funkcję LLM utrzymywalną.

Dlaczego testowanie dokładnego dopasowania to zły narzędzie

Powód, dla którego assert_equal zawodzi, to nie technikalia — to że dokładne dopasowanie łańcucha testuje złą rzecz. W rzeczywistości nie obchodzi cię, czy model produkuje jeden konkretny łańcuch. Obchodzi cię, czy wyjście ma właściwości, których potrzebujesz: że klasyfikacja jest poprawna, że wyłuskane pole ma właściwą wartość, że streszczenie pokrywa kluczowe punkty, że „nie wiem” jest zwracane, gdy powinno. Te właściwości mogą zachodzić w wielu różnych dokładnych łańcuchach. Więc zmianą jest z „czy wyjście równa się temu łańcuchowi” na „czy wyjście ma te właściwości” — a gdy zrobisz tę zmianę, testowanie niedeterministycznego wyjścia staje się wykonalne.

Zbuduj reprezentatywny zbiór testowy

Wszystko zaczyna się od zbioru przypadków. Nie jednego przykładu, który wypróbowałeś ręcznie — zbioru, wybranego, by reprezentować zakres wejść, z którymi funkcja faktycznie się zmierzy, włącznie z niewygodnymi. Dla klasyfikatora to może być kilkadziesiąt oznaczonych wejść; dla streszczacza zbiór dokumentów z notatkami, co dobre streszczenie musi zawierać. Kluczowa dyscyplina:

  • Pokryj zakres, nie tylko łatwy środek. Włącz przypadki brzegowe, niejednoznaczne wejścia, te, które cię sparzyły. Zbiór testowy samych łatwych przypadków mówi ci tylko, że łatwe przypadki działają — co już wiedziałeś z dema.
  • Zachowuj przypadki, które pękły. Za każdym razem, gdy znajdziesz wejście, które funkcja obsługuje źle na produkcji, dodaj je do zbioru. To dokładnie testowanie regresji: błąd, który naprawiasz dziś, staje się przypadkiem gwarantującym, że zostaje naprawiony. Z czasem ten zbiór staje się najcenniejszym aktywem całej funkcji — zgromadzoną pamięcią wszystkiego, co kiedykolwiek poszło źle.
  • Zapisz oczekiwaną właściwość, nie oczekiwany łańcuch. Dla każdego przypadku zanotuj, co czyni odpowiedź poprawną — właściwa etykieta, wymagane fakty, wartość pola — nie jedno akceptowalne sformułowanie.

Ten zbiór to twój eval. To rzecz, wobec której uruchamiasz swój prompt, i to, co zamienia „zmieniłem prompt i wydaje się w porządku” w „zmieniłem prompt i 47 z 50 przypadków wciąż przechodzi, w górę z 44”.

Oceniaj właściwości, nie łańcuchy

Mając zbiór testowy, potrzebujesz sposobu, by ocenić każde wyjście wobec jego oczekiwanej właściwości. Metoda oceny zależy od tego, jaki rodzaj wyjścia masz, i wahają się od trywialnych do naprawdę trudnych:

  • Wyjście strukturyzowane / klasyfikacja jest łatwe. Jeśli model zwraca etykietę albo pole JSON, możesz sprawdzić je dokładnie — „kategoria równa się billing” to normalna asercja. To najłatwiejsze funkcje LLM do evaluacji, co jest dobrym powodem, by strukturyzować wyjście, gdzie możesz: czyni wyjście testowalnym.
  • Wyłuskiwanie jest sprawdzalne. Czy wyciągnęło właściwą datę, właściwą sumę, właściwe nazwisko? Porównaj wobec znanej-poprawnej wartości. Pozwól na trywialne różnice formatu, ale rdzeniem jest porównanie.
  • Tekst dowolnej formy potrzebuje sprawdzeń właściwości. Dla streszczenia albo odpowiedzi oceniaj właściwości, których potrzebujesz: czy zawiera kluczowe fakty (sprawdzenia słów kluczowych albo semantyczne)? Czy mieści się w granicy długości? Czy unika zakazanej treści? Sprawdzasz konieczne właściwości, nie oceniasz prozy.
  • Trudne przypadki: użycie modelu do oceny modelu. Dla naprawdę otwartego wyjścia jedną praktyczną techniką jest kazanie modelowi osądzić, czy odpowiedź spełnia rubrykę („czy ta odpowiedź poprawnie adresuje pytanie, tak/nie?”). Jest niedoskonała i sama niedeterministyczna, więc używaj jej jako sygnału obok innych, nie jedynej wyroczni — ale skaluje sprawdzanie, które inaczej potrzebowałoby człowieka na każdym przypadku.

Sedno jest takie, że jakaś ocena jest znacznie lepsza niż żadna. Nawet zgrubne sprawdzenie właściwości na 50 przypadkach mówi ci ogromnie więcej niż zerknięcie na jeden — bo próbkuje zachowanie w całym zakresie, zamiast w pojedynczym szczęśliwym punkcie.

Uruchamiaj na każdej zmianie i śledź liczbę

Gdy potrafisz ocenić zbiór, eval staje się rzeczą, którą uruchamiasz, gdy dotykasz funkcji — zwłaszcza gdy zmieniasz prompt. To jest wypłata. Wcześniej w tej serii argumentowałem, że prompty to kod, który powinien być wersjonowany i recenzowany; eval to to, co czyni to realnym, bo daje zmianie promptu obiektywny werdykt: ocena wzrosła, pozostała płaska albo spadła. Bez niego każda edycja promptu to hazard, którego nie możesz rozstrzygnąć. Z nim:

  • Zmiana promptu, która podnosi ocenę, to postęp, którego możesz bronić, nie przeczucie.
  • Zmiana, która obniża ocenę, jest złapana zanim trafi na produkcję, nawet jeśli jeden przykład, na który akurat spojrzałeś, wciąż działał. To regresja, którą inaczej odkryłbyś od zirytowanego użytkownika.
  • Możesz porównywać modele i ustawienia na tej samej stopie — nowa wersja modelu, zmiana temperatury, inna strategia pobierania, wszystkie mierzone wobec tego samego zbioru.

Śledź ocenę w czasie. „Jesteśmy na 92% na zbiorze eval, w górę z 88% w zeszłym miesiącu” to rodzaj zdania, które zamienia funkcję LLM z tajemnicy w coś, co faktycznie inżynierujesz. Liczba nie musi być doskonała ani naukowa, by była użyteczna — musi być spójna, by zmiany w niej coś znaczyły.

Evale to siatka bezpieczeństwa, nie ocena

Ujęcie, które ma znaczenie: zbiór eval to nie akademicki benchmark, który gonisz do 100%. To siatka bezpieczeństwa, która pozwala zmieniać rzeczy z pewnością. Tę samą rolę, którą zestaw testów gra dla zwykłego kodu — refaktoryzujesz swobodnie, bo testy złapią złamanie — eval gra dla funkcji LLM. Bez niego jesteś zamrożony: każde podkręcenie promptu jest ryzykowne, bo nie możesz powiedzieć, czy pomogło, czy zaszkodziło, więc przestajesz ulepszać funkcję ze strachu. Z nim iterujesz śmiało, bo każda zmiana dostaje natychmiastowy, obiektywny odczyt, czy uczyniła rzeczy lepszymi czy gorszymi w przypadkach, które mają znaczenie. To różnica między funkcją, która poprawia się z czasem, a taką, która kostnieje w dniu wysyłki.

Werdykt

Wyjście LLM jest niedeterministyczne, więc instynkt assert_equal, który ma każdy deweloper, to zły narzędzie — ale „nie da się dokładnie dopasować” nie znaczy „nie da się testować”. Dyscypliną jest ewaluacja: zbuduj reprezentatywny zbiór testowy, który pokrywa niewygodne krawędzie i rośnie za każdym razem, gdy coś pęka na produkcji; oceniaj każde wyjście pod kątem właściwości, których potrzebujesz (dokładne sprawdzenia dla wyjścia strukturyzowanego i wyłuskanego, sprawdzenia właściwości dla tekstu dowolnej formy, rubryki oceniane modelem dla naprawdę otwartego) zamiast pod kątem jednego dokładnego łańcucha; i uruchamiaj ten eval na każdej zmianie — zwłaszcza każdej zmianie promptu — by każda edycja dostawała obiektywny werdykt zamiast wibracji. Śledź ocenę w czasie jako miarę zdrowia funkcji. Zrobiony tak, zbiór eval to nie benchmark do gonienia, ale siatka bezpieczeństwa, która pozwala zmieniać prompty i modele z pewnością, dokładnie jak zestaw testów pozwala refaktoryzować kod. To pojedyncza rzecz, która zamienia funkcję LLM z czegoś, co wysyłasz na nadziei, w coś, co faktycznie możesz inżynierować i ulepszać.