Piszesz pipeline z trzech agentów. Działa. Chcesz go otoczyć testami, więc nagrywasz kilka przypadków ewaluacyjnych, uruchamiasz AgentEvaluator i dostajesz wyjątek o niezgodności liczby inferencji z liczbą tur konwersacji.
Pierwsza reakcja jest zawsze ta sama: coś źle skonfigurowałem. Druga, po godzinie: to bug frameworka.
Ani jedno, ani drugie. To jest konsekwencja tego, jak działa ewaluacja agentów, i kiedy się ją zrozumie, przestaje irytować. Ale trzeba przejść przez trzy rzeczy: dlaczego zwykły test tu nie działa, co go zastępuje i dlaczego pipeline jest dla tego mechanizmu przypadkiem szczególnym.
Klasyczny test jednostkowy stoi na założeniu, że poprawna odpowiedź jest jedna.
def add(a, b):
return a + b
assert add(2, 3) == 5
Zadaj to samo pytanie agentowi i dostaniesz coś z tego zbioru:
"Wynik to 5."
"5"
"Policzmy: 2 + 3 = 5"
"Około 5"
Wszystkie cztery są poprawne. Żadna nie przejdzie porównania na równość, a czwarta nie przejdzie nawet dopasowania po fragmencie, bo „około” zmienia znaczenie w sposób, który przy liczbach bywa istotny.
Problem nie polega na tym, że trzeba napisać sprytniejszego asserta. Polega na tym, że zbiór poprawnych odpowiedzi jest otwarty, a testy jednostkowe zakładają zbiór zamknięty. Można się z tym mocować przez pół dnia i skończyć z regexem, który przepuszcza rzeczy niepoprawne, a odrzuca poprawne.
ADK odpowiada na to dwiema metrykami, które trzeba rozumieć osobno, bo mierzą zupełnie inne rzeczy i mają różną wiarygodność.
Trajektoria narzędzi (tool_trajectory_avg_score). Czy agent wywołał właściwe narzędzia we właściwej kolejności. Domyślny próg to 1.0, czyli dopasowanie dokładne. To jest metryka najbliższa klasycznemu testowi: wywołania narzędzi są dyskretne, więc da się je porównać bez zgadywania. Jeśli masz zacząć od jednej rzeczy, zacznij od tej.
Zgodność odpowiedzi (response_match_score). Podobieństwo tekstu do wzorca, liczone metodą ROUGE, domyślny próg 0.8. Działa lokalnie, nie kosztuje nic i jest szybkie. Jest też tępe: mierzy pokrycie słowami, nie sens. Odpowiedź poprawna, ale sformułowana inaczej, dostanie niski wynik.
To rozróżnienie ma praktyczną konsekwencję. Trajektoria mówi, czy agent zrobił właściwe rzeczy. Zgodność odpowiedzi mówi, czy powiedział je podobnymi słowami. Pierwsze jest testem logiki, drugie testem stylu. Mylenie ich prowadzi do progów ustawianych na wyczucie i testów, które nic nie chronią.
Teraz sedno. Mechanizm ewaluacji zakłada prostą zależność: jedna tura konwersacji daje jedną finalną odpowiedź. Nagrywasz przypadek z trzema pytaniami, spodziewasz się trzech odpowiedzi, ewaluator porównuje je parami.
W pipeline wielostopniowym to założenie się rozpada. Każdy agent w sekwencji, który ma ustawiony output_key, kończy swoją pracę czymś, co framework traktuje jako odpowiedź finalną. Trzy etapy dają trzy takie odpowiedzi. Konwersacja ma jedną turę. Liczby się nie zgadzają i dostajesz wyjątek.
Że nie jest to moja lokalna przypadłość, widać w zgłoszeniach do projektu. Jest otwarte zgłoszenie o tym, że przy niezgodności liczby inferencji z liczbą tur kod wywala się na len() zamiast podać sensowny komunikat. Jest osobne o SequentialAgent opakowanym w AgentTool, gdzie ginie zdarzenie wywołania funkcji.
Wniosek, który oszczędza dzień
Ewaluacja w ADK jest zaprojektowana dla jednego agenta na raz, nie dla całego pipeline'u. To nie jest luka do obejścia, tylko granica narzędzia. Traktuj ją jak granicę testu jednostkowego: sprawdza jednostkę, a nie cały system.
Skoro ewaluator chce jednego agenta, daj mu jednego agenta. Ale nie rezygnuj przy tym z pipeline'u.
Układ, który u siebie ustandaryzowałem, wygląda tak:
moj_modul/
├── agent.py → pełny pipeline (produkcja)
├── agent_for_eval/
│ └── agent.py → jeden agent: ten etap, który testujesz
└── tests/
├── eval/
│ ├── podstawowe.test.json
│ └── test_config.json
└── test_e2e.py → całość, klasycznie
Pipeline zostaje pipeline'em. Obok stoi cienki moduł, który eksponuje pojedynczy etap, i to na niego wskazuje ewaluator. Każdy etap dostaje własny zestaw przypadków.
Brzmi jak obejście, ale po kilku tygodniach pracy w tym układzie uważam, że to po prostu dobra praktyka, na którą framework mnie wypchnął. Sprawdzanie całego pipeline'u jedną metryką i tak niewiele mówi: kiedy wynik jest zły, nie wiesz, który etap zawinił. Osobne zestawy dają odpowiedź od razu.
Całość zostaje pokryta testami end-to-end, pisanymi normalnie. One nie muszą mierzyć jakości odpowiedzi, tylko sprawdzać, czy przepływ się domyka i czy stan trafia tam, gdzie ma trafić.
Tu jedna rzecz do zweryfikowania, jeśli opierasz się na starszych materiałach, w tym na moich własnych sprzed kilku miesięcy.
Krąży przekonanie, że wszystko poza dwiema podstawowymi metrykami wymaga płatnej usługi w Google Cloud. Sprawdziłem aktualną listę kryteriów i to już nieprawda. Konta w chmurze wymagają dziś cztery metryki, wszystkie związane z oceną bezpieczeństwa albo z konwersacjami wieloturowymi.
| Metryka | Próg domyślny | Wymaga Vertex AI |
|---|
tool_trajectory_avg_score | 1.0 | Nie |
response_match_score | 0.8 | Nie |
final_response_match_v2 | 0.8 | Nie |
rubric_based_final_response_quality_v1 | 0.8 | Nie |
rubric_based_tool_use_quality_v1 | 1.0 | Nie |
hallucinations_v1 | 0.8 | Nie |
safety_v1 | 0.8 | Tak |
multi_turn_task_success_v1 | 0.8 | Tak |
Dwie pozycje z tej tabeli zmieniają sposób pracy.
final_response_match_v2 porównuje sens, nie słowa. To jest odpowiedź na tępotę metryki ROUGE opisaną wyżej i nie kosztuje osobnej usługi. Jeśli walczyłeś z progami przy response_match_score, zacznij od podmiany na tę metrykę, zanim zaczniesz stroić liczby.
hallucinations_v1 sprawdza, czy odpowiedź trzyma się tego, co zwróciły narzędzia. Automatyczne wykrywanie konfabulacji w zestawie darmowym to rzecz, o której mało kto wie, a przydaje się zwłaszcza w agentach opartych na dokumentach.
Jest też metryka oparta na rubrykach, przydatna w sytuacji, w której nie masz wzorcowej odpowiedzi. Zamiast wzorca definiujesz cechy dobrej odpowiedzi, na przykład „nie zawiera zdań o niczym” albo „każde twierdzenie ma odniesienie do źródła”, i model ocenia względem nich.
Warto znać wszystkie, bo służą do różnych rzeczy.
adk web, czyli interfejs przeglądarkowy. Przeprowadzasz rozmowę, zapisujesz ją jako przypadek ewaluacyjny jednym kliknięciem, uruchamiasz i oglądasz porównanie oczekiwanego z faktycznym. Najlepszy sposób na zbudowanie pierwszego zestawu, bo nie piszesz JSON-a ręcznie.
pytest, czyli wywołanie AgentEvaluator.evaluate() w teście. To jest wersja do potoku CI, bo wywala się wyjątkiem, gdy metryki spadną poniżej progu.
adk eval, z wiersza poleceń, z możliwością wskazania pojedynczych przypadków zamiast całego pliku.
adk conformance, czyli wykrywanie regresji względem nagranej wcześniej wzorcowej sesji. Najmniej znany z czwórki, a to jest dokładnie ten mechanizm, którego chcesz przed wydaniem.
Do tego dwa formaty danych. Pliki .test.json to szybkie przypadki jednosesyjne, do pracy bieżącej. Pliki evalset trzymają wiele długich sesji i nadają się do testów regresji, w tym z symulowanym użytkownikiem.
Bez wielkiego projektu i bez konta w chmurze.
- Wybierz jeden etap, który psuje się najczęściej. Nie zaczynaj od pokrycia całości. Zacznij od tego, który już Cię ugryzł.
- Nagraj trzy przypadki w
adk web. Rozmowa, zapis sesji jako przypadek, gotowe. Trzy wystarczą, żeby zobaczyć, jak to działa.
- Ustaw tylko
tool_trajectory_avg_score na 1.0. Zignoruj na razie metryki tekstowe. Sama trajektoria narzędzi wychwytuje zaskakująco dużo regresji, bo kiedy agent przestaje wywoływać właściwe narzędzie, reszta i tak jest bez znaczenia.
- Dopiero potem dołóż
final_response_match_v2. Kiedy trajektoria jest stabilna, dodaj ocenę sensu odpowiedzi. Nie odwrotnie, bo inaczej będziesz stroić progi tekstowe w sytuacji, w której agent robi po prostu nie to, co trzeba.
- Wepnij
pytest do potoku i zostaw progi wysoko. Test, który przepuszcza wszystko, jest gorszy od braku testu, bo daje fałszywe poczucie pokrycia.
Ewaluacja agentów bywa odkładana, bo wygląda na zadanie na później: najpierw niech działa, potem to opiszemy. Problem w tym, że bez niej nie da się odpowiedzieć na najprostsze pytanie, jakie pada po każdej zmianie promptu: czy jest lepiej, czy tylko inaczej.
Metryka trajektorii narzędzi odpowiada na to w kilka minut i nie wymaga niczego poza zapisaniem trzech rozmów. To jest najtańszy próg wejścia w cały temat i akurat ten warto przekroczyć wcześniej niż później.
Poprzednia część serii dotyczyła migracji na graf w ADK 2.0, gdzie opisałem cztery miejsca psujące się po cichu. Podstawowe pojęcia zebrałem w słowniczku.