Перейти к содержанию
Публикация AiManual

Почему мультиагентные системы проваливаются, даже когда eval проходит: watchdog для промежуточных состояний

Финальный eval может пройти, даже если ошибка появилась на handoff между LLM-агентами. Разбираем слепую зону мультиагентного пайплайна, показываем Pydantic-схем

Коротко

Что будет в материале

  1. 01

    Короткий ответ: финальный eval проверяет результат, а сбой возникает раньше

  2. 02

    Где возникает слепая зона в мультиагентном пайплайне

  3. 03

    Что должен проверять watchdog перед handoff

  4. 04

    Pydantic-схема для промежуточного payload

Мультиагентная система может выдать приемлемый финальный ответ при сломанном промежуточном handoff. Причина проста: финальный eval видит последний текст, а пустой, усеченный или семантически неверный payload уже успевает пройти между агентами. Watchdog закрывает эту слепую зону: перед передачей он проверяет контракт, полноту и набор инвариантов, затем возвращает бинарное решение allowed или blocked с машиночитаемой причиной.

Условная цепочка выглядит так: планировщик передает задачу исполнителю, исполнитель формирует результат для проверяющего, а синтезатор собирает финальный ответ. Если исполнитель отправил пустой список результатов или указал неправильный task_id, следующий агент может заполнить пробелы предположениями. Последний текст получится связным, хотя исходный процесс уже нарушил контракт.

Watchdog проверяет пригодность состояния для следующего шага. Pydantic закрывает ошибки структуры и типов, инварианты обнаруживают противоречия между полями, а трассировка помогает найти место сбоя. Эти проверки не подтверждают истинность внешних данных и не заменяют end-to-end eval.

Короткий ответ: финальный eval проверяет результат, а сбой возникает раньше

Как выглядит сбой снаружи

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

  • финальный текст ссылается на результаты, которых не было в payload предыдущего агента;
  • ответ сообщает, что поиск завершен, хотя поле results содержит пустой список;
  • агент продолжает задачу после статуса failed, потому что ошибка передалась как обычная строка;
  • вместо свежего контекста используется устаревшая версия данных;
  • система отвечает на правильный тип запроса, но с идентификатором другой задачи;
  • повторный вызов исправляет результат случайно, и проблема исчезает до следующего запуска.

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

Почему такой пайплайн может пройти проверку

Финальная оценка отвечает на вопрос: приемлем ли последний ответ по заданному критерию. Она может не отвечать на вопросы: какие данные получил каждый агент, сохранился ли контракт, завершилась ли операция и кто впервые изменил состояние.

Например, evaluator сравнивает итоговый текст с ожидаемым набором фактов. Синтезатор видит неполный payload и достраивает пропущенный фрагмент по контексту задачи. Финальный ответ совпадает с ожидаемой формой, поэтому проверка проходит. В трассировке при этом остается промежуточное нарушение, которое никак не повлияло на итоговый балл.

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

Что именно нужно контролировать между агентами

Handoff стоит воспринимать как передачу состояния, а не как пересылку очередного сообщения. Для него нужен контракт с тремя уровнями контроля:

СлойГлавный вопросПримеры проверки
СтруктураМожно ли разобрать payload?Типы полей, обязательные ключи, версия схемы, формат идентификаторов
ПолнотаХватает ли данных следующему агенту?Результат, статус операции, ссылка на контекст, непустой список кандидатов
ПравдоподобиеНе противоречат ли поля друг другу?Идентификатор входит в список кандидатов, статус согласуется с результатом, маршрут совпадает с задачей

Watchdog не пытается оценить интеллект агента целиком. Его задача уже: определить, можно ли безопасно передать конкретное состояние следующему участнику цепочки.

Где возникает слепая зона в мультиагентном пайплайне

Логическая схема обычно выглядит так: входная задача -> планировщик -> handoff -> исполнитель -> handoff -> синтезатор -> eval. На каждом переходе меняются формат, набор полей, смысл данных, статус выполнения или источник контекста. Финальная проверка видит только правую часть схемы.

Пустой payload - не единственный опасный сбой

Пустой объект легко заметить в логах. Более опасны состояния, которые выглядят корректно для JSON-парсера и при этом нарушают смысл контракта.

СостояниеПочему оно выглядит допустимымЧто сломано
{}Объект корректен синтаксическиОтсутствуют задача, статус и результат
results: []Тип списка соблюденДля текущего типа задачи пустой результат запрещен
count: "3"Поле заполнено значениемЧисло передано строкой, арифметика или сортировка могут дать ошибку
Устаревший context_versionВсе ключи присутствуютАгент работает с предыдущей редакцией контекста
Чужой task_idИдентификатор имеет правильный форматРезультат относится к другому запуску
status: completed без результатаСтатус выглядит успешнымОперация объявлена завершенной без обязательного артефакта

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

Как ошибка меняется на следующем шаге

Следующий агент может обработать пропуск несколькими способами:

  1. принять пустой список за достоверное отсутствие результатов;
  2. восстановить данные по шаблону из промпта или общего контекста;
  3. передать незавершенный статус дальше, превратив его в нейтральное описание;
  4. создать новый результат без связи с исходным task_id.

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

Та же логика лежит в основе разбора надежности обвязки агентных проектов: сильная модель не исправляет потерю состояния, размытые ограничения и отсутствие наблюдаемости.

Почему логирование вызовов не всегда решает задачу

Логи сохраняют prompt, response, временные метки, идентификатор запуска и ошибки транспорта. Это полезно для расследования, но запись события сама по себе не отвечает на вопрос, пригодно ли состояние для следующего агента.

Логирование фиксируетWatchdog должен определить
Какой payload отправилиСоответствует ли он контракту
Какой статус вернул агентСогласуется ли статус с результатом
Когда произошел вызовАктуален ли контекст для текущей задачи
Какой агент вызванРазрешен ли этот маршрут
Какой текст сформированДостаточно ли данных для следующего шага

Для каждой проверки нужны статус pass или block, код причины, версия схемы и идентификатор handoff. Свободный текст в логе пригодится человеку, но оркестратору нужен машиночитаемый результат.

Что должен проверять watchdog перед handoff

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

Проверка структуры и обязательных полей

Базовые правила обнаруживают ошибки без вызова другой LLM:

  • обязательные ключи присутствуют;
  • строки не пусты, а числовые поля имеют числовой тип;
  • статус входит в перечисление допустимых значений;
  • коллекции имеют ожидаемый тип и нужную минимальную длину;
  • идентификатор задачи не пуст и соответствует текущему запуску;
  • версия схемы известна оркестратору;
  • вложенные объекты согласованы по типам.

Отсутствующее поле, null и пустая строка могут означать разные состояния. Например, отсутствие error при успешной операции допустимо, а пустая строка в поле причины при статусе failed не помогает ни автоматическому retry, ни расследованию.

Проверка полноты и готовности к следующему шагу

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

Для каждого перехода зафиксируйте минимальный набор данных. В условной задаче поиска это могут быть task_id, нормализованный запрос, results, версия контекста и признак завершения. Если поиск завершен, а results отсутствует, handoff блокируется. Если отсутствует необязательное поле пояснения, состояние может пройти с предупреждением.

Проверка готовности должна учитывать связь с текущей задачей. Один и тот же список результатов может быть полным по форме, но бесполезным, если его task_id или версия контекста не совпадает с активным запуском.

Проверка правдоподобия без иллюзии полной верификации

Простые инварианты ловят противоречия между полями:

  • количество элементов не может быть отрицательным;
  • выбранный идентификатор должен входить в список кандидатов;
  • при completed должен присутствовать обязательный результат;
  • при failed должна быть непустая причина;
  • статус внешней операции должен согласовываться с наличием ее результата;
  • target_agent должен совпадать с разрешенным маршрутом;
  • результат не должен ссылаться на другую задачу.

Более сложную смысловую проверку может выполнять отдельный LLM-критик. Он способен сравнить вывод агента с задачей и контекстом, но его ответ тоже остается вероятностным. Решение критика нужно хранить отдельно от детерминированной проверки и не выдавать за доказательство истинности.

Что возвращать при отказе

Отказ должен управлять потоком, а не оставаться строкой в журнале. Компактный ответ watchdog может выглядеть так:

{"allowed": false, "reasons": ["missing_result"], "severity": "error", "retryable": true, "handoff_id": "h-1842"}

Полезные поля:

  • allowed, итоговое бинарное решение;
  • reasons, список кодов нарушенных правил;
  • severity, например warning, error или critical;
  • retryable, возможность повторного вызова предыдущего агента;
  • handoff_id, связь решения с конкретной передачей;
  • normalized_payload, нормализованный объект после успешной проверки.

Оркестратор по этим полям выбирает остановку, повторный запрос, fallback или ручную проверку.

Pydantic-схема для промежуточного payload

Модель контракта handoff

Контракт должен описывать один конкретный переход между соседними агентами. В него входят только данные, которые нужны этому маршруту. Универсальный объект с десятками необязательных полей затрудняет контроль: любое значение можно объявить отсутствующим по проектным причинам.

Ниже приведен архитектурный шаблон для API Pydantic с методом model_validate. В реальном проекте нужно закрепить совместимую версию библиотеки в зависимостях и покрыть модель тестами на допустимые состояния.

from enum import Enum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator


class HandoffStatus(str, Enum):
    READY = 'ready'
    COMPLETED = 'completed'
    FAILED = 'failed'


class Handoff(BaseModel):
    model_config = ConfigDict(extra='forbid')

    handoff_id: str = Field(min_length=1)
    task_id: str = Field(min_length=1)
    source_agent: str = Field(min_length=1)
    target_agent: str = Field(min_length=1)
    task_type: str = Field(min_length=1)
    status: HandoffStatus
    schema_version: str = Field(min_length=1)
    payload: dict[str, Any] = Field(default_factory=dict)
    metadata: dict[str, str] = Field(default_factory=dict)

    @model_validator(mode='after')
    def check_status_payload(self):
        if self.status == HandoffStatus.COMPLETED and 'result' not in self.payload:
            raise ValueError('completed handoff requires result')

        if self.status == HandoffStatus.FAILED:
            error = str(self.payload.get('error', '')).strip()
            if not error:
                raise ValueError('failed handoff requires error')

        if self.task_type == 'search' and self.status == HandoffStatus.COMPLETED:
            results = self.payload.get('results')
            if not isinstance(results, list) or not results:
                raise ValueError('search task requires a non-empty results list')

        return self

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

Детерминированная валидация через model_validate

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

def parse_handoff(raw: dict[str, Any]):
    try:
        handoff = Handoff.model_validate(raw)
        return handoff, []
    except ValidationError as exc:
        reasons = []
        for item in exc.errors():
            path = '.'.join(str(part) for part in item['loc'])
            message = item.get('msg', 'invalid value')
            reasons.append('{}: {}'.format(path, message))
        return None, reasons

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

Кастомные валидаторы для инвариантов

Типы полей не описывают связи между ними. Кастомный валидатор нужен там, где допустимость значения зависит от соседних полей:

  • completed требует результата;
  • failed требует причины;
  • для типа search завершенный переход требует непустого списка;
  • выбранный кандидат должен находиться среди переданных кандидатов;
  • маршрут должен соответствовать ожидаемому target_agent.

Проверку маршрута лучше выполнять с учетом контекста оркестратора. Модель знает, какой target_agent записан в payload, но ожидаемого адресата может не знать. Поэтому watchdog передает модели параметр маршрута или проверяет его отдельным правилом.

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

Ответ watchdog в формате, удобном для оркестратора

class WatchdogDecision(BaseModel):
    allowed: bool
    reasons: list[str] = Field(default_factory=list)
    severity: str = 'info'
    retryable: bool = False
    handoff_id: str = Field(min_length=1)
    normalized_payload: dict[str, Any] | None = None

При успешной проверке normalized_payload содержит результат model_dump в согласованном формате. При отказе поле можно оставить пустым, чтобы downstream-агент не получил объект, который выглядит обработанным. Причины лучше хранить в виде кодов вроде missing_result, route_mismatch и stale_context, а человекочитаемое описание добавлять отдельным полем или в логе.

Бинарная проверка: пропустить, остановить или запросить исправление

Когда handoff нужно блокировать

Блокировка оправдана, когда следующий шаг не сможет работать предсказуемо или ошибка может повредить данные:

  • отсутствует результат, обязательный для текущего маршрута;
  • поле имеет несовместимый тип;
  • потерян task_id или найден идентификатор другой задачи;
  • невозможно определить следующий маршрут;
  • статус противоречит содержимому payload;
  • нарушен критический инвариант, например выбран объект, которого нет среди кандидатов;
  • внешняя операция помечена завершенной без подтвержденного артефакта.

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

Когда достаточно повторного запроса

Исправимый сбой возвращается предыдущему агенту с конкретным списком нарушений. Формулировка должна сообщать, какое поле нужно добавить или исправить, а не просить модель «попробовать еще раз».

decision = watchdog.inspect(raw, expected_target='reviewer')

if decision.allowed:
    dispatch(decision.normalized_payload)
elif decision.retryable and attempts < 2:
    request_repair(previous_agent, decision.reasons)
else:
    stop_pipeline(decision.reasons)

Лимит в две попытки здесь служит примером. Его выбирают по стоимости вызова, задержке и риску повторной ошибки. Исходный payload, обе версии ответа и решения watchdog нужно сохранять для диагностики. Бесконечные retries скрывают дефект контракта и расходуют бюджет токенов.

Почему warning не должен превращаться в block

СигналТип реакцииПричина
Отсутствует обязательное полеBlockСледующий агент не получает минимальный контекст
Несовместимый типBlockДанные нельзя надежно обработать
Пустое необязательное пояснениеWarningОсновной маршрут может продолжить работу
Низкая уверенностьWarning или ручная проверкаСигнал качества не всегда означает ошибку
Неправильный адресатBlockСостояние уйдет в неверный контекст

Допустимое неполное состояние нужно описывать в контракте явно. Например, промежуточный агент может передавать status: ready без финального результата, если следующий шаг как раз должен этот результат получить. Жесткое правило «каждый payload обязан содержать result» сломает такой маршрут.

Какие handoff проверять в первую очередь

Критические границы данных

Начинайте с переходов, где ошибка дорого распространяется:

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

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

Если отказ отправляет состояние человеку, полезно заранее разделить очередь ревью и пользовательский путь. Практика риск-ориентированной маршрутизации для human-in-the-loop без потери пропускной способности помогает выбрать случаи, где ручная проверка действительно нужна.

Легкие и тяжелые проверки

ПроверкаКогда запускатьСтоимость
Типы, обязательные поля, перечисленияНа каждом критическом handoffНизкая, детерминированная
Идентификаторы, статус, версии контекстаНа каждом переходе, который меняет состояниеНизкая
Связи между списками и выбранными объектамиДля маршрутов с несколькими сущностямиНизкая или средняя
LLM-критик для смысла и релевантностиВыборочно, при высоком рискеВысокая, добавляет токены и задержку
Ручная проверкаПеред дорогим или необратимым действиемВысокая по времени

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

Метрики для контроля watchdog

После запуска отслеживайте не один итоговый процент успешных задач:

  • доля заблокированных handoff: blocked / inspected;
  • распределение причин отказа по агентам, маршрутам и версиям схемы;
  • число повторных запросов и среднее число попыток;
  • добавленная задержка, включая p95 и p99 для критического пути;
  • доля ложных блокировок по выборочной ручной проверке;
  • число ошибок, найденных watchdog до финального eval;
  • время от сбоя до определения конкретного нарушенного поля;
  • стоимость семантических проверок и retries.

Целевые значения нельзя назначать без данных конкретного проекта. Смысл механизма виден по динамике: ошибки обнаруживаются раньше, расследование занимает меньше времени, а задержка и число ложных отказов остаются приемлемыми для workflow.

Ограничения watchdog и остаточные риски

Формально валидный payload может быть неверным по смыслу

Pydantic подтверждает форму, типы и правила, которые вы явно записали в модель. Он не знает, соответствует ли факт реальности, релевантен ли источник задаче и корректно ли агент интерпретировал пользовательский запрос.

Объект с непустым result, правильным task_id и допустимым статусом может содержать ошибочный вывод. Структурная проверка пропустит его, если контракт не содержит внешнего оракула, сравнения с эталоном, теста инструмента или другой проверки смысла.

Ложные блокировки и потеря пропускной способности

Жесткая схема отклоняет допустимые варианты, когда меняются промпт, модель или бизнес-логика. Например, новый агент может передавать результат в другом, но заранее разрешенном формате. Если валидатор знает только старую форму, он создаст отказ без реальной ошибки.

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

Ложные блокировки стоят пропускной способности: растет очередь retries, увеличивается время ответа, а ручная проверка получает лишние события. Поэтому warning и block должны иметь разные пороги и разные последствия.

Watchdog не заменяет end-to-end eval

Уровень контроляЧто он проверяетЧего он не подтверждает
Schema validationФорму и типы объектаИстинность и полезность данных
WatchdogПригодность handoff для следующего шагаКачество всей стратегии агентов
ТрассировкаПуть данных и последовательность событийКорректность каждого решения
Финальный evalПользовательский результатПричину промежуточного сбоя без дополнительной телеметрии

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

Практическая схема: как встроить watchdog в существующий пайплайн

Минимальная версия для первого этапа

  1. Составьте карту агентов и переходов между ними. Для каждого handoff сохраните источник, адресата, тип данных и обязательные поля.
  2. Выберите один критический маршрут, где ошибка уже приводит к дорогому retry, неверному действию или долгому расследованию.
  3. Опишите компактную Pydantic-модель и несколько инвариантов: идентификатор задачи, допустимый статус, обязательный результат и правильный адресат.
  4. Добавьте бинарный ответ watchdog, код причины, номер попытки и идентификатор handoff.
  5. Сохраняйте исходный payload, нормализованный объект и решение проверки. Через несколько запусков пересмотрите правила по фактическим отказам.

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

Чек-лист перед передачей данных агенту

ПунктПроверкаТип реакции
СхемаPayload проходит модель, версия схемы поддерживаетсяBlock при нарушении
Обязательные поляЕсть task_id, статус, результат или причина согласно маршрутуBlock
АктуальностьИдентификатор и версия контекста относятся к текущему запускуBlock
ГотовностьПредыдущая операция действительно завершенаBlock или retry
СвязиИдентификаторы и списки не противоречат друг другуBlock при критическом конфликте
КачествоУровень уверенности или семантическая оценка не ниже порога маршрутаWarning или ручная проверка
Маршрутtarget_agent совпадает с ожидаемым адресатомBlock

Как понять, что контроль действительно помогает

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

  • ошибки handoff появляются в телеметрии до финального ответа;
  • для отказа можно назвать конкретное поле и правило;
  • число невалидных переходов снижается после исправления источника;
  • retries ограничены и не образуют бесконечные циклы;
  • ложные блокировки отделены от критических ошибок;
  • задержка критического пути измеряется отдельно от времени ручного ревью.

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

Подписаться на канал