Оцінка GenAI та агентів: тестування в одному етапі проти багатоетапного тестування в CI
Порівняйте оцінювання одного етапу на основі слідів дій із оцінюванням кількох етапів за допомогою симулятора, враховуючи золоті стандарти, суддів з ШІ, метрики агентів, контрольні точки інтеграції та сім практик роботи.
„Це працює з моїм запитом“ — це не інженерний стандарт. Традиційний тест одиниці проходить або провалюється детерміновано, тоді як генеративні системи надають відкриті, ймовірнісні відповіді. Канал RAG може бути переважно правильним, а агент може виконати завдання так, що це на папері вважається успіхом, але на практиці є небезпечним. Саме тому оцінювання, на відміну від інтуїції чи чат-каналу, повного фраз типу „мені здається все гаразд“, тепер є основою серйозної роботи з машинним навчанням у сфері генеративного ШІ та агентних систем. У наступному розглядається, що потрібно вимірювати, у чому різниця між однокроковим та багатокроковим оцінюванням, які є компроміси кожного з них, а також шаблони Python, які підходять для інтеграції в CI-пайплайни.
Чому оцінювання не є опційним
Три характеристики додатків на основі LLM роблять оцінювання необхідним:
- Недетермінізм. Ідентичні запити можуть давати різні результати при різних запусках, версіях моделей чи значеннях температури.
- Кумулятивні помилки у агентах. У той час як звичайний чат-бот може помилятися лише один раз, агент поєднує від 10 до 50 викликів моделі, викликів інструментів та рішень. Незначний рівень помилок на кожному кроці призводить до частих проблем у всьому процесі.
- Тихі регресії. Зміна запиту, оновлення моделі чи переіндексація бази векторів можуть погіршити якість без видання жодних попереджень.
Оцінювання перетворює вислів „здається краще“ на число, яке можна відстежувати з часом, використовувати для контролю розгортання та порівнювати між версіями.
Два типи оцінювання
Майже кожна платформа, зокрема DeepEval, Ragas, LangSmith, Braintrust та OpenAI Evals, використовує один із двох підходів.
Однокроковий, з використанням сліду
Логіка проста: береться кожен елемент, один раз запускається додаток, оцінюється слід.
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)
Золотий зразок — це зразок із позначками: вхідні дані, відповідь, яку він має створити, а також контекст чи виклики інструментів, які мають бути використані. Хід виконання фіксує повну історію одного запуску, включаючи ті фрагменти даних, які були отримані, які інструменти використано, логіку між кроками, час виконання та кількість токенів, а не лише кінцевий текст. Оцінка цього запису, а не лише відповіді, дозволяє визначити етап, у якому виникла проблема. Чи це отримання даних, генерація відповіді чи виклик інструменту на певному етапі?
Багатокроковий, з використанням симулятора
Це складніший та більш реалістичний формат для агентів та чат-асистентів.
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
Тут немає фіксованої пари вхідних та вихідних даних. Модель симулятора виступає у ролі користувача з певною метою, наприклад скасувати підписку чи отримати повернення грошей за певний замовлення, та адаптує кожне повідомлення до відповідей асистента, так само, як це робив би людина. Систему оцінюють за повною траєкторією: чи залишалась вона зосередженою на завданні? Чи правильно використовувала інструменти протягом усіх етапів? Чи досягла мети в кінцевому підсумку? Чи вигадала деталі правил вже на третьому етапі?
Агентам, багатоетапним асистентам та будь-яким системам з пам’яттю чи станом потрібен саме такий підхід. Оцінка за один етап не може виявити, якщо агент суперечить сам собі на четвертому етапі або використовує один і той самий інструмент п’ять разів поспіль у циклі.
Вибір показників
Показники поділяються на чотири групи, і доцільно їх поєднувати, адже жоден з них не розповідає повної історії.
Корисна ментальна модель: метрики однокрокової оцінки дають зображення стану на момент оцінки, тоді як метрики агентської оцінки враховують динаміку змін.
LLM як суддя
Більшість сучасних метрик без використання зовнішніх даних працюють за принципом „LLM як суддя“. Сильна модель, часто інша та більш здатна, ніж модель, яку перевіряють, оцінює результат відповідно до критеріїв та пояснює свою оцінку.
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}
У суддів є підводні камені:
- Упередження через самопреференцію. Моделі оцінюють власні результати або результати у своєму стилі більш схвально. Якщо можливо, використовуйте суддю з іншої родини моделей.
- Чутливість до запиту. Невеликі зміни у формулюванні критеріїв впливають на оцінки. Тримайте запити до суддів під контролем версій, як код.
- Витрати та затримки. Використання суддів також коштує грошей. У разі обмеженого бюджету перевіряйте трафік на прикладах, а не оцінюйте все.
- Упередження через довжину та об’єм відповідей. Судді схильні давати довші, більш впевнені відповіді, незалежно від їхньої правильності. Час від часу калібруйте їхні оцінки за допомогою невеликого набору даних з позначками від людини.
Однокрокова обробка на практиці
Ідеальний набір може складатися лише зі списку словників, які містять вхідні дані та очікуваний результат.
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"],
},
]
Виконавець дотримується описаного вище алгоритму: запускає тестовий виконання, проходить кожен тест, запускає додаток, фіксує стеження за виконанням, оцінює результат та завантажує його.
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)
Твердження перетворює оцінки на бар’єр.
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%}"
Підключіть його до потоку PR так само, як підключений pytest. Якщо зміни у запитах чи отриманні інформації знижують рівень точності нижче допустимого порогу, злиття має бути заблоковане, і ніщо не повинно виходити у продакшн таємно.
Багатокрокові діалоги на практиці
Замість фіксованого вхідного даних симулятор отримує профіль користувача та мету, і сам керує розмовою.
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
Отриману розмову потім оцінюють за допомогою метрик агентства.
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)
Типові перевірки на рівні агента включають:
- Правильність виклику інструменту: правильний інструмент із правильними аргументами у правильному порядку, що є більшим, ніж просто „був викликаний якийсь інструмент“.
- Ефективність кроків та виявлення циклів: багаторазові виклики одного інструменту без досягнення прогресу.
- Дотримання ролі та правил: дотримання визначеної персони та обмежень протягом усієї розмови, а не лише на початковому кроці.
- Відновлення після помилки: розумна спроба повтору після невдалиого виклику інструменту замість вигаданого результату.
- Збереження контексту: пам’ятати те, що сказав користувач три кроки раніше.
Оцінка компромісів
| Однокрокова розмова | Багатокрокова розмова | |
|---|---|---|
| Витрати | Один виклик на кожен крок, дешево та швидко |
Оскільки результати багатокрокових процесів є шумними, їх слід інтерпретувати статистично. Запустіть набір тестів N разів та аналізуйте розподіли, а не один окремий показник.
Ефективні практики
- Почніть із невеликого, чистого набору з 30 до 100 прикладів. Шумний набір з 1 000 екземплярів гірший за організований набір з 50, оскільки некоректні мітки псують якість сигналу.
- Оцінюйте процес пошуку та генерації окремо в рамках технології RAG. Якість відтворення та контекстуальне запам’ятовування відповідають на різні запитання, а їх об’єднання приховує справжню причину проблем.
- Оцінюйте результати за допомогою більш надійної, незалежної моделі та час від часу перевіряйте її показники порівняно з оцінками людських експертів. Вже 20–30 зразків на місяць дозволяють виявити зміни в оцінках.
Висновок
Оцінка генеративних ШІ спочатку була лише додатковим елементом — коміркою в нотатках, яку хтось запускав перед демонстрацією. Агентні системи вийшли за межі цього підходу. Як тільки програмне забезпечення може діяти кілька разів, викликати інструменти та зберігати стан між кроками, оцінка більше не є частиною процесу контролю якості. Це основний інструмент для аналізу реальної поведінки системи.
Оцінка одного кроку на основі відстеження надає швидкі та недорогі перевірки якості результату. Оцінка кількох кроків за допомогою симулятора дає надійну інформацію про те, чи агент діє згідно з наміром протягом усього взаємодіючи, і для більшості продакшн-агентів саме ця інформація має найбільше значення. Створюйте обидва типи оцінок та автоматизуйте їх. Не довіряйте жодному з них сліпо та постійно перевіряйте результати людиною.