Оценка генеративных ИИ и агентов: тестирование в одном этапе против многократного тестирования в 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%}"
Подключите его к пайплайну разработки так же, как подключен 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 примеров в месяц позволяют выявить смещения в оценках.
Вывод
Оценка генеративных ИИ-систем изначально была своего рода примечанием, записью в блокноте, которую кто-то делал перед демонстрацией. Агентные системы вышли за рамки этого подхода. Как только программное обеспечение может действовать несколько раз, вызывать инструменты и сохранять состояние между этапами взаимодействия, оценка больше не является частью процесса контроля качества. Она становится основным инструментом для понимания реального поведения системы.
Оценка в один этап на основе отслеживания предоставляет быстрые и недорогие проверки качества выводов. Оценка в несколько этапов с использованием симулятора дает надежную информацию о том, ведет ли агент себя так, как предусмотрено, на протяжении всего взаимодействия, и для большинства производственных агентов именно эта информация имеет наибольшее значение. Необходимо разрабатывать оба вида оценок и автоматизировать их. Не стоит слепо доверять ни одному из них — всегда должен быть человек, проверяющий результаты оценки.