Главная / Статьи

Оценка генеративных ИИ и агентов: тестирование в одном этапе против многократного тестирования в CI

Сравните оценки одного этапа на основе трейсов с оценками нескольких этапов, осуществляемыми с помощью симулятора, включая золотые стандарты, судей из числа больших языковых моделей, показатели агентов, контрольные точки интеграции и семь практик работы.

Издатель: REACTAPP.TOP8 мин чтения1800 слов

Источники: End-to-End Evals for Gen AI and Agentic AI: A Practitioner’s Guide to Single-Turn and Multi-Turn Evaluation

Фраза «У меня всё работает с моим запросом» не является инженерным стандартом. Традиционные тесты на единицу проходят или не проходят определённым образом, в то время как генеративные системы дают открытые, вероятностные ответы. Пайплайн RAG может быть в целом правильным, а агент может выполнить задачу таким образом, что на бумаге это считается успехом, но на практике это опасно. Именно поэтому оценки, в отличие от интуиции или чат-каналов, полных фраз вроде «Мне кажется, всё в порядке», сейчас служат основой для серьёзной разработки ИИ в области генеративных моделей и агентных систем. Далее рассматривается, что необходимо измерять, в чём разница между одноэтапными и многоэтапными оценками, каковы их преимущества и недостатки, а также шаблоны на Python, подходящие для использования в пайплайнах CI.

Почему оценки не являются опциональными

Три особенности приложений на базе LLM делают оценки необходимыми:

  1. Недетерминизм. Идентичные запросы могут давать разные результаты при разных запусках, версиях модели или значениях параметра температуры.
  2. Кумулятивные сбои у агентов. В то время как обычный чат-бот может ошибиться всего один раз, агент выполняет от 10 до 50 вызовов модели, инструментов и принимает решения. Небольшой процент ошибок на каждом этапе приводит к частым сбоям на всем протяжении работы.
  3. Тихие регрессии. Изменение запроса, обновление модели или переиндексация хранилища векторов могут ухудшить качество без возникновения каких-либо исключений.

Оценка превращает ощущение «всё стало лучше» в числовой показатель, который можно отслеживать со временем, использовать для контроля развертываний и сравнивать между версиями.

Два типа оценки

Почти все фреймворки, включая 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)

Типичные проверки на уровне агента включают:

  • Правильность вызова инструмента: использование правильного инструмента с правильными аргументами в правильном порядке, что выходит за рамки простого факта «был совершен вызов какого-либо инструмента».
  • Эффективность действий и обнаружение циклов: многократные вызовы одного и того же инструмента без достижения прогресса.
  • Соблюдение роли и правил: сохранение определенного образа персонажа и рамок поведения на протяжении всего диалога, а не только в первом ответе.
  • Восстановление после ошибок: разумная попытка повторного вызова после неудачи, вместо выдуманного результата.
  • Сохранение контекста: запоминание того, что сказал пользователь три ответа ранее.

Оценка компромиссов

Один ответ Множество ответов
Затраты Один вызов на каждый запрос, дешево и быстро
Каждая серия выполнений представляет собой многократный диалог, умноженный на количество ходов и показатели Набор данных Большие идеальные наборы легко получить из журналов событий или путем синтетического генерирования Симуляторы позволяют тестировать крайние случаи и агрессивных пользователей, которых никто не напишет вручную Сигнал Четко разделяет ошибки поиска и ошибки генерации, обеспечивает почти детерминированный результат для проверки в CI Выявляет кумулятивные сбои, приводящие к реальным инцидентам, и тестирует всю систему, включая инструменты, оркестрацию, состояние и механизмы контроля Слабые места Не выявляет смещения, противоречий и потери памяти между ходами, способствует переобучению на идеальном наборе Более высокая дисперсия из-за добавления случайности симулятором, сложнее отладка из-за того, что сбой на 6-м ходу может возникнуть уже на 2-м, а судьи теряются при работе с длинными текстами

Поскольку результаты многократных обработок содержат шум, их следует интерпретировать статистически. Проведите набор тестов N раз и анализируйте распределения, а не один единственный показатель.

Эффективные подходы

  1. Начните с небольшого, чистого набора из 30–100 примеров. Набор из 1 000 примеров с шумом хуже, чем аккуратный набор из 50, поскольку ошибочные метки портят качество данных.
  2. Оценивайте процессы поиска и генерации отдельно в рамках технологии RAG. Показатели точности и контекстуальной воспроизводимости отвечают на разные вопросы, и их слияние скрывает настоящую причину проблем.
  3. Оценивайте качество с помощью более надежной, независимой модели, время от времени проверяя ее показатели с помощью оценок людей. Даже 20–30 примеров в месяц позволяют выявить смещения в оценках.
  • Отслеживайте всё. Оценивайте данные, полученные в результате отслеживания (загруженные документы, вызовы инструментов, промежуточные шаги), а не только окончательный текст. Именно поэтому сбор таких данных должен происходить до создания тест-кейса.
  • Симулируйте действия агентов перед развертыванием. Проводите тестирование с использованием реалистичных профилей пользователей и конфликтных сценариев, таких как разозлённый пользователь, неоднозначная просьба или крайний случай согласно правилам, ещё до выпуска продукта, а не после возникновения инцидента.
  • Рассматривайте результаты оценки как динамические ресурсы. Перезапускайте полный набор тестов после каждой изменения запроса, обновления модели или пересоздания индекса, а также устанавливайте пороги процента успешных тестов в системах CI так же, как и пороги покрытия кода.
  • Наблюдайте за изменениями в распределениях со временем. Один запуск тестов — это лишь пробная выборка, а не истинные данные. Повторяйте критически важные наборы тестов и отслеживайте тенденции.
  • Вывод

    Оценка генеративных ИИ-систем изначально была своего рода примечанием, записью в блокноте, которую кто-то делал перед демонстрацией. Агентные системы вышли за рамки этого подхода. Как только программное обеспечение может действовать несколько раз, вызывать инструменты и сохранять состояние между этапами взаимодействия, оценка больше не является частью процесса контроля качества. Она становится основным инструментом для понимания реального поведения системы.

    Оценка в один этап на основе отслеживания предоставляет быстрые и недорогие проверки качества выводов. Оценка в несколько этапов с использованием симулятора дает надежную информацию о том, ведет ли агент себя так, как предусмотрено, на протяжении всего взаимодействия, и для большинства производственных агентов именно эта информация имеет наибольшее значение. Необходимо разрабатывать оба вида оценок и автоматизировать их. Не стоит слепо доверять ни одному из них — всегда должен быть человек, проверяющий результаты оценки.