Ocena GenAI i agentów: testowanie jednoetapowe vs wieloetapowe w CI
Porównaj oceny jednoetapowe oparte na śladach z ocenami wieloetapowymi sterowanymi symulatorem, obejmując standardy złote, sędziów typu LLM, metryki agentów, bramy CI oraz siedem praktyk pracy.
"Działa przy moim promptzie" nie jest standardem inżynieryjnym. Tradycyjny test jednostkowy albo się udaje, albo nie w sposób deterministyczny, natomiast systemy generatywne dają otwarte, probabilistyczne odpowiedzi. Pipeline RAG może być w większości poprawny, a agent może sfinalizować zadanie w sposób, który na papierze uznawany jest za sukces, ale w praktyce jest niebezpieczny. Dlatego właśnie oceny, w odróżnieniu od intuicji lub kanału czatowego pełnego stwierdzeń typu "wygląda dobrze", stanowią obecnie podstawę poważnej inżynierii ML w obszarze sztucznej inteligencji generatywnej i systemów agentowych. Poniżej omówiono, co należy mierzyć, w czym różnią się oceny jednoetapowe i wieloetapowe, jakie są ich wady oraz wzorce w Pythonie pasujące do pipeline CI.
Dlaczego oceny nie są opcjonalne
Trzy cechy aplikacji opartych na LLM sprawiają, że są one konieczne:
- Niedeterminizm. Identyczne prompty mogą dawać różne wyniki w zależności od kolejnych uruchomień, wersji modelu lub wartości temperatury.
- Kumulowanie błędów u agentów. Podczas gdy zwykły chatbot ma tylko jedną szansę na popełnienie błędu, agent łączy ze sobą od 10 do 50 wywołań modelu, wywołań narzędzi oraz decyzji. Niewielki wskaźnik błędów na krok przeradza się w częste problemy na całym etapie.
- Ciche regresje. Niewielka modyfikacja promptu, aktualizacja modelu lub ponowne indeksowanie magazynu wektorowego może pogorszyć jakość bez wywołania żadnego wyjątku.
Ewaluacja przekształca stwierdzenie „wydaje się lepsze” w liczbę, którą można śledzić z biegiem czasu, wykorzystywać do decyzji o wdrożeniu oraz porównywać pomiędzy różnymi wersjami.
Dwie formy ewaluacji
Prawie każda platforma, w tym DeepEval, Ragas, LangSmith, Braintrust i OpenAI Evals, opiera się na jednym z dwóch wzorców.
Jednoetapowe, oparte na śladach
Pętla jest prosta: bierze się każdy przykład, uruchamia aplikację raz i ocenia ślad.
Open a test run with trace-level metrics
for each golden:
yield golden
call app with golden.input
capture trace
build a test case from the trace
score test case with metrics
upload test run (traces + scores)
Złoty przykład to etykietowany zbiór danych: dane wejściowe, odpowiedź, którą powinien wygenerować, a często także kontekst lub wywołania narzędzi, które powinien uwzględnić. Ślad rejestruje całą historię jednego przebiegu, w tym które fragmenty zostały pobraane, jakie narzędzia zostały uruchomione, tok rozumowania pomiędzy nimi, opóźnienia i liczbę tokenów, a nie tylko tekst końcowy. Ocena tego zapisu zamiast samej odpowiedzi pozwala określić etap, w którym wystąpił błąd – czy to przy pobieraniu danych, generowaniu tekstu, czy przy wywołaniu narzędzia?
Wieloetapowy, oparty na symulatorze
To trudniejszy i bardziej realistyczny wzorzec dla agentów i asystentów czatowych.
def simulate(conversational goldens)
for each golden:
loop until expected outcome OR max turns:
simulator model generates a user turn
ask app for next reply
capture assistant turn
build a ConversationalTestCase from the full transcript
evaluate(test cases, metrics) # metrics run concurrently
-> pass/fail + reason per metric
Tutaj nie ma ustalonej pary wejścia i wyjścia. Model symulatora odgrywa rolę użytkownika z konkretnym celem, np. anulowania subskrypcji lub otrzymania zwrotu pieniędzy za określony zamówienie, i dostosowuje każdą wiadomość do odpowiedzi asystenta, podobnie jak robiłoby to człowiek. System oceniany jest na podstawie całej ścieżki działania. Czy trzymał się założonego zadania? Czy prawidłowo używał narzędzi w poszczególnych rundach? Czy ostatecznie osiągnął swój cel? Czy już po trzech rundach wymyślił szczegóły związane z zasadami?
Agenci, asystenci wieloetapowi oraz wszystko, co posiada pamięć lub stan, wymagają takiego podejścia. Ocena w ramach jednej rundy nie może wykryć sytuacji, gdy agent sprzeciwia się samemu sobie w czwartej rundzie lub używa jednego narzędzia pięć razy w pętli.
Wybór metryk
Metryki dzielą się na cztery grupy, a ich łączenie jest rozsądne, ponieważ żadna z nich nie przedstawia pełnego obrazu.
Praktyczny model mentalny: metryki jednoetapowe oceniają chwilowy stan, natomiast metryki oparte na agencji oceniają całokształt rozwoju.
LLM jako sędzia
Większość dzisiejszych metryk bez odniesień do zewnętrznych danych funkcjonuje według zasady „LLM jako sędzia”. Silny model, często inny i bardziej zdolny niż model testowany, ocenia wynik według określonych kryteriów i wyjaśnia uzyskany wynik.
from dataclasses import dataclass
JUDGE_PROMPT = """
You are evaluating an AI assistant's response for FAITHFULNESS.
A response is faithful if every factual claim it makes is supported
by the provided context. It is unfaithful if it adds unsupported
claims, contradicts the context, or fabricates details.
Context:
{context}
Response:
{response}
Return JSON: {{"score": <0.0-1.0>, "reason": "<short explanation>"}}
"""
@dataclass
class TestCase:
input: str
actual_output: str
retrieval_context: list[str]
def faithfulness_metric(test_case: TestCase, judge_llm) -> dict:
prompt = JUDGE_PROMPT.format(
context="\n".join(test_case.retrieval_context),
response=test_case.actual_output,
)
result = judge_llm.generate(prompt, response_format="json")
return {"score": result["score"], "reason": result["reason"], "passed": result["score"] >= 0.7}
Sędziowie mają swoje wady:
- Uprzedzenie na rzecz siebie. Modele oceniają swój własny wynik lub wynik w swoim stylu bardziej przychylnie. Gdzie to możliwe, używaj sędziego z innej rodziny modeli.
- Czułość na prompty. Małe zmiany w sformułowaniach kryteriów wpływają na oceny. Trzymaj prompty sędziów pod kontrolą wersji, tak jak kod.
- Koszt i opóźnienie. Korzystanie z sędziów również wiąże się z kosztami. Gdy budżet jest ograniczony, przetestuj ruch w warunkach rzeczywistych i nie oceniaj wszystkiego.
- Uprzedzenie na rzecz dłuższych odpowiedzi. Sędziowie skłaniają się ku dłuższym, bardziej pewnym siebie odpowiedziom, niezależnie od tego, czy są one poprawne. Od czasu do czasu kalibruj ich oceny na małej grupie danych oznaczonych przez ludzi.
Jedno kroku w praktyce
Złota baza może składać się z tak małej listy słowników zawierających dane wejściowe i oczekiwany wynik.
goldens = [
{
"input": "What's our refund policy for digital products?",
"expected_output": "Digital products are refundable within 14 days if unused.",
"expected_tools": [],
},
{
"input": "Cancel order #48213",
"expected_output": None, # open-ended, judged not matched
"expected_tools": ["lookup_order", "cancel_order"],
},
]
Program uruchamia się zgodnie z opisanym powyżej schematem: otwiera próbne uruchomienie, przechodzi przez każdy element typu „golden”, wywołuje aplikację, zapisuje ślady działania, ocenia wynik i przesyła go.
from deepeval import evaluate
from deepeval.metrics import FaithfulnessMetric, AnswerRelevancyMetric
from deepeval.test_case import LLMTestCase
def run_eval_suite(goldens, app_callback, metrics):
test_cases = []
for golden in goldens:
trace = app_callback(golden["input"]) # your traced LLM/RAG app
test_case = LLMTestCase(
input=golden["input"],
actual_output=trace.final_output,
retrieval_context=trace.retrieved_chunks,
expected_output=golden.get("expected_output"),
)
test_cases.append(test_case)
results = evaluate(test_cases, metrics) # runs metrics concurrently
return results
metrics = [FaithfulnessMetric(threshold=0.7), AnswerRelevancyMetric(threshold=0.7)]
results = run_eval_suite(goldens, my_rag_app, metrics)
Stwierdzenie przekształca wyniki w bramkę.
def assert_eval_gate(results, min_pass_rate=0.9):
passed = sum(1 for r in results if r.success)
pass_rate = passed / len(results)
assert pass_rate >= min_pass_rate, f"Eval pass rate {pass_rate:.2%}
below gate {min_pass_rate:.0%}"
Podłącz to do pipeline’u PR w taki sam sposób, jak podłączony jest pytest. Jeśli zmiana w promptzie lub procesie pozyskiwania danych obniży poziom wierności poniżej ustalonego progu, fuzja powinna zostać zablokowana i nic nie powinno zostać wydane bez żadnych ostrzeżeń.
Multi-turn w praktyce
Zamiast stałego wprowadzenia danych, symulator otrzymuje profil użytkownika oraz cel, a następnie kieruje rozmową.
class ConversationSimulator:
def __init__(self, simulator_model, max_turns=8):
self.simulator_model = simulator_model
self.max_turns = max_turns
def simulate(self, golden, chatbot_callback):
transcript = []
user_turn = self.simulator_model.generate_opening(golden["scenario"])
for _ in range(self.max_turns):
transcript.append({"role": "user", "content": user_turn})
assistant_turn = chatbot_callback(transcript)
transcript.append({"role": "assistant", "content": assistant_turn})
if self.simulator_model.is_goal_met(golden["expected_outcome"], transcript):
break
user_turn = self.simulator_model.generate_next_turn(transcript, golden)
return transcript
Następnie rozmowa otrzymana w wyniku jest oceniana za pomocą metryk agentowych.
from deepeval.test_case import ConversationalTestCase, Turn
from deepeval.metrics import (
TaskCompletionMetric,
ToolCorrectnessMetric,
ConversationCompletenessMetric,
)
def build_conversational_test_case(transcript, tools_called):
turns = [Turn(role=t["role"], content=t["content"]) for t in transcript]
return ConversationalTestCase(turns=turns, tools_called=tools_called)
metrics = [
TaskCompletionMetric(threshold=0.8), # did it accomplish the user's goal?
ToolCorrectnessMetric(threshold=1.0), # right tools, right args, right order
ConversationCompletenessMetric(threshold=0.7), # did it address everything raised?
]
test_case = build_conversational_test_case(transcript, tools_called=agent_trace.tool_calls)
result = evaluate([test_case], metrics)
Typowe sprawdzenia na poziomie agenta obejmują:
- Prawidłowość wywołania narzędzia: właściwe narzędzie, z odpowiednimi argumentami, w właściwej kolejności – to więcej niż tylko „jakieś narzędzie zostało wywołane”.
- Efektywność kroków i wykrywanie pętli: powtarzane wywołania tego samego narzędzia bez żadnego postępu.
- Przestrzeganie roli i zasad: zachowywanie się zgodnie z zdefiniowaną postacią i ograniczeniami przez całą rozmowę, a nie tylko w pierwszym kroku.
- Odbudowa po błędzie: rozsądna próba ponownego wywołania po nieudanym wywołaniu narzędzia, zamiast wymyślonego wyniku.
- Przenoszenie kontekstu: pamiętanie tego, co użytkownik powiedział trzy kroki wcześniej.
Wzajemne porównanie zalet i wad
| Jeden krok | Wielokrotne kroki | |
|---|---|---|
| Koszt | Jedno wywołanie na każdy krok, tanie i szybkie |
Ponieważ wyniki z wielu rund są zanieczyszczone, należy je interpretować statystycznie. Wykonaj zestaw testów N razy i analizuj rozkłady, a nie pojedynczą ocenę.
Praktyki, które się sprawdzają
- Zacznij od małego, czystego zbioru „złotych” przykładów liczącego od 30 do 100 egzemplarzy. Zanieczyszczony zbiór liczący 1000 przykładów jest gorszy od uporządkowanego zbioru 50 przykładów, ponieważ błędne etykiety psują jakość sygnału.
- Oceniaj funkcję wyszukiwania i generowanie oddzielnie w RAG. Dokładność oraz pamięć kontekstowa odpowiadają na różne pytania, a ich łączenie ukrywa prawdziwy problem.
- Oceniaj za pomocą silniejszego, niezależnego modelu i od czasu do czasu sprawdzaj jego wyniki w porównaniu z ocenami ludzkich ekspertów. Już 20 do 30 przykładów miesięcznie może ujawnić zmiany w sposobie oceny.
Główny wniosek
Oceny systemów sztucznej inteligencji generatywnej zaczynały się jako dodatek, komórka w notatniku uruchamiana przed demonstracją. Systemy agentowe dawno już przewyższyły ten poziom. Gdy oprogramowanie może działać kilka razy, wywoływać narzędzia i przechowywać stan między kolejnymi krokami, ocena przestaje być zwykłą kontrolą jakości. Staje się głównym narzędziem do analizy rzeczywistego zachowania systemu.
Oceny jednoetapowe oparte na śledzeniu dostarczają szybkich i tanich sprawdzeń regresji jakości wyników. Oceny wieloetapowe realizowane za pomocą symulatorów dają jedyną wiarygodną informację na temat tego, czy agent zachowuje się zgodnie z zamierzeniami podczas całej interakcji, a dla większości agentów produkcyjnych ta informacja ma największe znaczenie. Należy tworzyć oba rodzaje ocen i automatyzować je obie. Nie ufaj żadnemu z nich ślepo i zawsze zachowuj możliwość sprawdzenia ich przez człowieka.
Literatura pokrewna
- AutoSaddler: Umożliwienie przepisywania harnessu agenta AI przez ślady wykonywania — AutoSaddler diagnozuje nieudane uruchomienia agentów, naprawia ich harness, sprawdza, czy wprowadzone poprawki są uniwersalne, oraz przechowuje historię ewolucji. Wyjaśniono różnicę między patchami sterującymi a patchami poprawiającymi funkcjonalności.
- Gdzie narzędzia do programowania agencyjnego wykorzystują tokeny w praktyce — Okna kontekstowe, pętle narzędziowe oraz próby ponowne zwiększają zużycie tokenów — budżety i buforowanie pozwalają utrzymać koszty demonstracji na przystępnych poziomach.