Минимальная рабочая платформа для экспериментов с ИИ-агентами: тесты типа A/B и тестирование в тени
Как определить, следует ли заменить версию агента A на версию B, с использованием определений конвейеров, локального назначения, событий экспозиции, трейсов, оценки в песочнице, тестов в тени и механизмов контроля.
Источники: A minimum viable experimentation platform for AI agents
Определить, превосходит ли новая версия агента ту, что используется в производстве, сложнее, чем кажется. На этапе разработки ситуация под контролем: достаточно иметь набор тестовых случаев, запустить обе версии, проанализировать несколько результатов, попросить другую модель оценить выходные данные и решить, стоит ли уделять дальнейшие усилия этим изменениям. Ситуация усложняется, когда целью является не просто запрос или один вызов модели, а система, компоненты которой меняются по собственным графикам и взаимодействуют способами, которые не всегда очевидны.
Представьте агента службы поддержки клиентов, собранного из этих компонентов:
agent runtime
memory and retrieval
system prompt
model
tools
guardrails
application logic
Предположим, что стратегия поиска изменяется, и процент завершения задач на тестовом наборе растет. Возможно, агент действительно стал лучше. Также возможно, что он выполняет больше вызовов модели, что увеличивает затраты на 20 процентов, его поведение с реальными пользователями отличается, или он получает преимущества от какого-то другого компонента, который случайно изменился в тот же период. В таком случае это уже меньше похоже на оценку модели, а скорее на эксперимент.
Далее приведен обзор минимальной платформы, на которую можно было бы полагаться для решения довольно простого вопроса:
should version B replace version A?
Это имеет смысл только при определенном масштабе. Один агент, двадцать тест-кейсов, проверенных вручную, и несколько внутренних пользователей не нуждаются в большинстве его функций. Платформа становится полезной тогда, когда несколько команд независимо модифицируют модели, промпты, память, инструменты и реализации агентов, эксперименты проводятся без остановки, и кто-то в конечном итоге должен утвердить изменения в производственной среде, предоставив доказательства, которые останутся читаемыми даже спустя месяцы. Платформа агентов для всей компании соответствует этому описанию.
Что такое B на самом деле
Первая проблема является базовой. Предположим, эксперимент указывает:
A = agent-v17
B = agent-v18
Что именно такое agent-v18? Если в ходе эксперимента меняется запрос, индекс поиска пересоздается, а шлюз модели заменяет свою стандартную модель, тогда метка почти ничего не говорит о том, что именно анализировалось. Это особенно сильно влияет на агентов, поскольку поведение в конце определяется всей цепочкой обработки, а не одним отдельным вызовом модели.
Поэтому объектом оценки является неизменное определение цепочки обработки. Оно может выглядеть так:
pipeline:
name: support-agent
version: 18
components:
runtime:
artifact: agent-runtime@sha256:...
memory:
artifact: memory-service@sha256:...
configuration:
strategy: hybrid
top_k: 8
model:
route: support
model: provider/model-x
prompt:
artifact: sha256:...
tools:
- artifact: customer-lookup@sha256:...
- artifact: refund-tool@sha256:...
Точный формат артефакта практически не имеет значения. Важно то, что вариант можно воссоздать в виде конфигурации системы, чтобы спустя месяцы кто-то мог сказать, что именно должен был запустить пользователь, назначенный к группе B. Это также позволяет сохранять платформу для экспериментов независимой от того, что в данный момент меняется. Один из экспериментов может сравнивать:
model X vs model Y
Кто-то другой может сравнить:
memory top_k=5 vs top_k=8
А третий вариант может противопоставить друг другу две совершенно разные реализации агентов. С точки зрения эксперимента каждый из них представляет собой лишь вариант обработки данных. Когда начинается эксперимент, эти определения становятся неизменными. Если требуется изменить подход, создается новая итерация эксперимента, так что историческое значение B никогда не переписывается тайно.
Задание не должно становиться зависимостью продакшена
Далее возникает вопрос о том, кто получит A, а кто — B. Привлекательным решением является то, при котором каждый запрос направляется в центральную службу экспериментов за определением подхода к обработке. Однако это вовлекает платформу для проведения экспериментов в расчеты, связанные с доступностью и задержками самого приложения, что является плохим компромиссом для системы, основная функция которой — измерение.
В первой версии используется детерминистическое хеширование. Плоскость управления передаёт свою текущую конфигурацию в SDK, находящийся внутри приложения, причём выбор архитектуры происходит непосредственно там, локально. Это обеспечивает полезное свойство при сбоях: если сервис экспериментов выходит из строя, платформа временно не может запускать новые эксперименты или изменять распределение ресурсов, однако приложение остаётся неприкосновенным, а SDK продолжает работать с его последней действительной конфигурацией.
Для MVP достаточно нескольких единиц генерации случайных значений:
request
user
session
workflow_instance
Последний вариант существует потому, что агенты не обязательно работают столько же времени, сколько веб-запрос. Агент может начать миграцию хранилища сегодня, дождаться одобрения извне и продолжить работу завтра. Если бы распределение задач пересчитывалось на основе конфигурации завтрашнего дня, один и тот же рабочий процесс мог бы начаться в одной реализации и завершиться в другой, что делало бы как операции, так и анализ ненужно сложными. Поэтому распределение задач остается неизменным:
workflow_instance = migration-8291
variant = B
pipeline = agent-v18
И он передается вместе с рабочим процессом каждый раз при его возобновлении.
Назначение отличается от выполнения
Ещё одно различие становится важным, когда обработка находится где-то внутри конвейера агента. Попадание в определённую группу обработок не гарантирует, что обработка действительно была выполнена.
Рассмотрим две реализации памяти, находящиеся на тестировании. Пользователь попадает в версию B — новую версию памяти, но агент отвечает, не выполняя никаких поисков в памяти. Назначение существует, однако тестируемый компонент так и не имел возможности повлиять на результат. Это должны быть два отдельных события:
ASSIGNMENT
user-18271 → B
EXPOSURE
memory-v2 retrieval started
Это также предоставляет информацию о состоянии эксперимента. Помимо количества пользователей, распределенных по каждой группе, можно просмотреть данные об уровне воздействия:
assigned 10,428
exposed 8,912
exposure rate 85.46%
Если у A и B наблюдаются неожиданно разные показатели экспозиции, этой разнице следует уделить первоочередное внимание.
Схожая проблема возникает, когда назначенное решение отличается от того, которое в итоге используется. Предположим, B выбирает модель Y, но она возвращает ошибку 503, и шлюз переходит на модель X. Запись будет выглядеть так:
assigned_variant = B
realized_model = X
fallback_reason = provider_unavailable
И выполнение остается в группе B.
Это соответствует правилу намерения к лечению, ITT. Каждый участник анализируется в той группе, к которой он был распределен случайным образом, даже если фактически выполненное лечение отличается. Этот вариант является частью опыта группы B, и удаление подобных запросов позже приведет к изменению состава группы с учетом событий, произошедших после распределения по группам. Данные о воздействии и фактически примененном лечении сохраняют свою ценность, в основном как сигналы, связанные со здоровьем и диагностикой, если только анализ не указывает на что-то иное.
Трейсинг объясняет, что произошло
Результат эксперимента указывает на то, изменился ли исход, но не говорит о причинах. Для выяснения причин более подходящим выбором является OpenTelemetry, а не новый протокол трейсинга. Слой экспериментов должен лишь добавить немного дополнительной информации к каждому интервалу работы:
experiment.id
experiment.version
experiment.variant
experiment.assignment_id
pipeline.id
pipeline.version
execution.purpose
При наличии этого условия суммарный результат будет выглядеть так:
B has a lower task completion rate
это можно превратить в гораздо более полезный запрос:
show failed B traces
Эти следы показывают, привлекает ли новый конвейер более слабый контекст, повторяются ли вызовы инструментов, переходится ли на другую модель, застревает ли процесс в циклах или просто используется более длинный путь для достижения того же результата.
След всё ещё не является записью эксперимента. Он хорошо объясняет, как проходила одна из экспекуляций, но результаты экспериментов могут существовать дольше. Клиент может поговорить с оператором сегодня и продлить подписку через неделю, уже после того как первоначальный след был завершён. Поэтому рядом с трейсингом работает более мелкий поток событий экспериментов:
assignment
exposure
outcome
feedback
evaluation
Лог записывает процесс выполнения. Поток событий отвечает за учет связи между процессом случайной генерации и результатами, поступающими позже.
Оценка в офлайн-режиме использует тот же пайплайн
Прежде чем B попадет к реальным пользователям, он должен пройти проверку на соответствие известным сценариям. Что касается агентов, сценарий не должен сводиться к:
prompt
expected answer
поскольку интересным результатом часто бывает не возвращаемый текст, а состояние среды после этого. Сценарий поддержки может быть описан следующим образом:
scenario:
id: duplicate-charge-001
input:
message: >
I was charged twice for order 9811.
environment:
fixture: duplicate-charge-customer
expected:
refund_count: 1
ticket_status: resolved
limits:
max_turns: 15
max_tool_calls: 20
max_cost: 0.50
Та же ситуация затем применяется к конвейеру A и конвейеру B, возможно, несколько раз, поскольку выполнения агентов являются стохастическими, и два запуска с одной и той же конфигурацией могут следовать разными траекториям из одного и того же начального состояния. MVP использует сбросимую песочницу с сервисами, основанными на фикстчерах, или тестовыми базами данных. Разработку универсальной функции воспроизведения многократных ходов лучше оставить на позже.
Воспроизведение работает только до тех пор, пока кандидат запрашивает взаимодействия, которые уже были записаны. Предположим, что B принимает иное решение на третьем ходу и вызывает инструмент, с которым оригинальная траектория никогда не взаимодействовала. Тогда ни один из записанных состояний мира не содержит эту ветвь. Честным результатом будет:
REPLAY_DIVERGED
Предоставление фиктивного ответа с притворством, что запуск всё ещё является повтором, — неверный шаг. Среда с состоянием позволяет обоим агентам естественным образом разветвляться, одновременно контролируя их начальные условия, что является более точным первоначальным приближением к оценке реальных агентов.
Теневая экспекуляция заполняет пробел
Между контролируемой средой и предоставлением пользователю доступа к B существует полезный этап: B выполняется асинхронно на копии реальных данных производства без вывода его результата. Записи и внешние побочные эффекты отключаются или перенаправляются в изолированную среду. Это обеспечивает данные, похожие на реальные из производства, и позволяет команде наблюдать:
latency
cost
tool usage
model fallbacks
guardrail failures
judge scores
trajectory differences
до того, как пользователь увидит результат обработки.
Тесты в теневом режиме не позволяют определить, улучшает ли B наиболее важный исход, поскольку пользователи никогда не видят B и поэтому не могут на это отреагировать. Они служат промежуточным этапом: реалистичные входные данные и реальное поведение во время выполнения, без всех рисков, связанных с прямыми экспериментами.
Не каждая проверка требует оценки от ИИ-судьи
Когда агент завершает сценарий, результат всё равно нуждается в оценке, поэтому первая версия содержит два типа оценщиков.
Первый тип — детерминистичный. Когда задача заключается в осуществлении одного возврата средств и решении проблемы, достаточно просто проверить окружение:
refund_count == 1
ticket_status == resolved
Запрос у другой модели о том, вероятно ли выполнение задачи, бесполезен, если можно проверить получившееся состояние.
Второй тип основан на моделях и применяется к свойствам, которые не поддаются проверке с помощью утверждений:
helpfulness
clarity
tone
quality of explanation
Судья-ИИ помогает в этом, но он становится ещё одним компонентом с версиями и никогда не является анонимным источником истины:
evaluator
evaluator version
judge model
judge prompt
rubric
Каждый сгенерированный балл сохраняет информацию о своем происхождении. Также ведется небольшой набор данных для калибровки с метками, добавленными людьми, и регулярно сравниваются результаты работы системы с этими данными. Система оценки на основе больших языковых моделей представляет собой измерительный инструмент, и когда этот инструмент меняется, важно, изменились ли также его показатели. Агентные системы оценки, способные просматривать информацию, вызывать инструменты или находить дополнительные доказательства, полезны и, вероятно, неизбежны в некоторых областях, но они не имеют места в первой версии.
Статистика: минимум деталей
Зрелые системы для экспериментов обладают высокой степенью сложности. Лучшей отправной точкой является небольшой набор правильно реализованных методов вместо обширного статистического меню, которое понимают лишь немногие пользователи. Первая версия поддерживает:
A/B allocation
binary metrics
continuous metrics
95% confidence intervals
sample ratio mismatch detection
fixed-horizon analysis
Этого достаточно, чтобы ответить на множество полезных вопросов, а краткое резюме результата может выглядеть так:
Experiment
support-agent-v18
Randomization
user
Primary metric
ticket resolution
A 81.4%
B 85.1%
Difference
+3.7 percentage points
95% CI
[...]
Experiment health
SRM PASS
Несоответствие пропорций выборки требует особого внимания, поскольку результаты никогда не должны выглядеть достоверными, если сам процесс случайной выборки нарушен. Если изначально планировалось равномерное распределение, а фактическое распределение сильно отличается, эксперимент считается несостоятельным, и на этом всё заканчивается — рядом с значком предупреждения не показывается никакого победителя.
Операционные ограничения также относятся к этому же уровню состояния эксперимента. Вариант может сорваться из-за проблем с затратами, не демонстрируя при этом технических сбоев. Представьте цикл запросов, который постоянно отправляет вызовы из:
4 model calls
В:
47 model calls
И ответ может по-прежнему быть HTTP 200, даже при наличии корректного результата. Поэтому необходимы верхние пределы:
max cost / execution
max tokens / execution
max agent turns
max tool calls
max variant spend / hour
Прерывание передачи может остановить эту процедуру или прекратить дальнейшее воздействие варианта. В отчете о состоянии здоровья тогда может быть указано:
Variant B
SRM PASS
Error rate PASS
Cost / request +312%
Circuit breaker TRIPPED
New exposure PAUSED
Механизм отключения цепи — это не статистический вывод. Это средство операционной безопасности, предотвращающее чрезмерную нагрузку на систему и бюджет во время проведения эксперимента, пока не собрано достаточно данных для статистических выводов.
Более сложные методы могут подождать: CUPED, последовательное тестирование, контрольные группы, уровни экспериментов, кластерная случайность, адаптивное распределение и многое другое. Модель данных должна предусматривать возможность их использования, но в первой версии достаточно правильно выполнять простые эксперименты типа A/B.
Что создавать в первую очередь
После удаления большинства элементов, которые можно отложить, платформа становится управляемой. Ей требуется сервис управления, содержащий:
- реестр экспериментов
- реестр потоков обработки данных
- реестр наборов данных
- реестр оценщиков
- определения метрик
В верхней части находится SDK, который загружает конфигурацию эксперимента и детерминированно применяет её на стороне клиента. Для этого требуется путь входа данных:
assignment
exposure
outcome
feedback
evaluation
Также принимаются трейсы OpenTelemetry. Разумными вариантами хранения являются PostgreSQL для состояния управляющей плоскости, NATS JetStream или Kafka для событий, ClickHouse для анализа экспериментов и трейсов, а объектное хранилище, совместимое с S3, — для крупных объемов данных. Офлайн-раннер обрабатывает наборы данных в средах типа A и B, которые можно сбросить.
Многие элементы намеренно отсутствуют. MVP не решает задачи произвольной виртуализации состояний среды, тонкой взаимосвязи между экспериментами, дрейфа оценщиков, автоматизированного анализа корневых причин, долгосрочных эффектов лечения или причинно-следственной атрибуции между несколькими агентами, и может обойтись без них. С самого начала важны только те аспекты, исправление которых затруднено, когда от них зависят исторические данные:
what was randomized
what treatment was assigned
what treatment actually ran
what pipeline version produced the execution
what outcome was measured
how that outcome was evaluated
Если эти идентичности сохранятся в первой версии, платформа сможет стать гораздо более функциональной, не меняя смысла её первоначальных экспериментов.
Полезный вывод заключается в том, что офлайн-оценка, теньовая экспекуляция и прямые эксперименты могут использовать общие определения процессов, записи хода выполнения и инструменты оценки, при этом каждый из них отвечает на свой собственный вопрос. Офлайн-запуски показывают, как ведут себя A и B в контролируемых сценариях. Теньовые запуски показывают, как B справляется с реальными данными в производственной среде, не влияя на пользователей. Прямые эксперименты показывают, какие изменения происходят при случайном разделении реальных пользователей. Записи хода выполнения фиксируют всё, что происходит внутри каждого запуска, и помогают интерпретировать результаты, однако диагностика на уровне записей никогда не должна заменять причинно-следственную оценку. Вместе они позволяют создать MVP, способный надежно отвечать на три вопроса:
What exactly did we run?
What happened when we ran it?
Did assigning users to B improve the outcome we care about?
Всё остальное может развиваться вокруг этого.