Проектирование харнесса агента: от задачи к архитектуре
Автоматический перевод
Эта статья была автоматически переведена с оригинальной английской версии.
Харнесс агента — это код приложения, который превращает решения модели в работу: он предоставляет контекст, выполняет разрешённые действия, поддерживает состояние и проверяет, когда задача завершена. Если вы создаёте агента для собственного приложения, проектировать этот код — значит решать, сколько свободы нужно задаче и что приложение должно гарантировать независимо от ответа модели.
Эти решения принимаются до выбора фреймворка. Ассистенту поддержки, coding-агенту и классификатору документов нужны разные способы действовать, восстанавливаться и доказывать успех. Если дать всем трём один и тот же цикл, одно хранилище памяти и один набор инструментов, требования, которые их различают, окажутся скрыты.
Здесь мы пройдём по этим решениям от описания задачи до первой реализации. Она продолжает статью Харнесс-инжиниринг для ИИ-агентов, где описаны обязанности харнесса. Теперь разберём, как выбрать их для конкретного проекта.
Зачем модели нужен харнесс
Модель может предложить возврат денег. Что-то другое должно определить аккаунт, загрузить заказ, решить, разрешена ли предложенная операция, вызвать платёжный сервис и записать, что произошло. Если сервис не ответил по таймауту, приложение также должно решить, безопасно ли повторить попытку. Эти обязанности существуют, даже когда модель очень хорошо выбирает следующий шаг.
LangChain использует широкое определение харнесса, которое включает код, конфигурацию и логику выполнения вокруг модели. На практике часть этого может уже принадлежать вашему приложению: аутентификация, доступ к базе данных, очередь задач или система согласований. Проектирование харнесса включает решение о том, как агент использует эти возможности. Оно не требует заново строить их внутри агентного фреймворка.
Минимальная версия может сделать один вызов модели, провалидировать её вывод и вернуть результат. Более крупная может позволить модели просматривать файлы, выполнять программы и дорабатывать результат на протяжении многих ходов. Обеим нужен ответ на один и тот же вопрос: какие части выполнения этой задачи можно делегировать и как мы узнаем, что они выполнены правильно?
Для конкретики рассмотрим ассистента поддержки клиентов, который обрабатывает возвраты, вопросы о доставке и изменения аккаунта. Мы будем использовать его как пример проектирования. Его требования приведут нас к определённой архитектуре; другая задача должна привести к другому набору решений.
Опишите результат до агента
Формулировка «обрабатывать запросы на возврат» оставляет большую часть инженерной работы неопределённой. Предположим, клиент просит вернуть деньги за доставленный заказ. Успешный результат требует возврата по правильному заказу и на правильную сумму, объяснения клиенту и отсутствия посторонних изменений в аккаунте. Если запрос выходит за рамки политики, правильным результатом может быть полезный отказ. Если заказ не удаётся определить, ассистент должен запросить недостающую информацию.
Это разные исходы. Если считать успехом каждый разговор, который закончился без ошибки, они сольются в один вводящий в заблуждение сигнал.
Нам также нужно знать, кто спрашивает. Идентификатор заказа в сообщении не доказывает, что заказ принадлежит этому человеку. Аутентифицированный аккаунт должен приходить от приложения, а сервис, выполняющий возврат, должен проверять, что операция относится к этому аккаунту. Модель может помочь интерпретировать запрос; она не может установить полномочия вызывающего, выдав правдоподобный идентификатор.
Так мы получаем начало контракта задачи: изменения, которые считаются успехом, запрещённые изменения и ситуации, требующие вопроса или передачи человеку. Он также показывает требования, которые промпт сам по себе выполнить не может. Максимальной сумме возврата нужен сервис, который её соблюдает. Целевому времени ответа нужен дедлайн. Требованию продолжить завтра нужно долговременное состояние.
Теперь информации достаточно, чтобы спросить, полезен ли агентный цикл вообще.
Какую часть процедуры мы уже знаем?
Если каждый запрос проходит одну и ту же последовательность — извлечь номер заказа, получить заказ, рассчитать право на возврат и объяснить результат, — эту последовательность можно написать напрямую. Модель может обрабатывать язык в начале и в конце. Ей не нужен ещё один вызов, чтобы решить, получать ли заказ, который процедура требует всегда.
Явный воркфлоу становится полезным, когда в процедуре есть обязательные ветвления или состояния ожидания. Возврат выше порога может требовать согласования. Изменение адреса может быть разрешено только до отправки. Эти этапы можно представить как машину состояний или граф, где код приложения решает, какие переходы разрешены.
Агентный цикл даёт модели больше свободы выбора. В смешанном разговоре с поддержкой клиент может начать с жалобы на доставку, сообщить, что пришёл не тот товар, а затем попросить обмен. Следующий полезный запрос зависит от того, что обнаружит ассистент. Цикл позволяет ему выбрать действие, посмотреть на результат и снова выбрать. За эту гибкость приходится платить большим числом возможных путей, которые нужно проверять, включая лишние вызовы, повторные запросы и преждевременное завершение.
Руководство LangGraph по воркфлоу и агентам проводит это различие через заранее определённые пути выполнения и динамически выбираемые шаги. Граф может выразить и то, и другое. «Основанная на графе» и «агентная» — не взаимоисключающие архитектуры.
Полезный компромисс для нашего ассистента поддержки — позволить агенту собрать информацию и подготовить предлагаемое изменение, а затем передать это предложение фиксированному шагу валидации и выполнения. Модель может исследовать ситуацию в рамках разговора, а обычный код отвечает за условия записи. Если мы уже знаем, что такое разделение необходимо, можно сначала построить внешний воркфлоу.
Или можно начать с ограниченного агентного цикла, инструменты которого соблюдают эти условия. Так оркестрация остаётся небольшой, пока мы узнаём, какие пути задаче на самом деле нужны. Выбор зависит от того, являются ли явные этапы, ожидание согласования и правила восстановления уже требованиями.
Не нужно вводить нескольких агентов только ради того, чтобы нарисовать больше блоков. Отдельные агенты становятся полезными, когда подзадачам нужен разный контекст или разные разрешения либо когда они могут выполняться независимо. Им также нужен способ объединять результаты и разрешать конфликтующие действия. Два исполнителя, каждый из которых может вернуть деньги за один и тот же заказ, создают проблему координации, которой не было у одного исполнителя.
На каком языке агент должен действовать?
Выбрав, кто управляет следующим шагом, нужно ещё выбрать, как выражается действие. Это независимые решения: воркфлоу может содержать агента, пишущего код, а открытый цикл может использовать лишь несколько узких инструментов.
Для ассистента поддержки естественный интерфейс — именованная операция, например issue_refund(order_id, amount_cents). Её схема делает запрошенное действие явным. Инструмент может вернуть идентификатор возврата при успехе или структурированное объяснение, почему запрос отклонён. Этот контракт можно проверить и протестировать, не прося модель сгенерировать реализацию возврата.
Некоторым задачам инструменты не нужны. Классификатор, у которого весь релевантный ввод находится в промпте, может вернуть провалидированную метку. Retrieval становится полезным, когда задаче нужна информация за пределами этого ввода. Запись становится полезной, только когда для выполнения задачи нужно что-то изменить. У каждой возможности должна быть причина существовать.
Сгенерированный код — другой язык действий. Представьте, что вы просите ассистента изучить таблицу, сгруппировать строки по клиенту и посчитать несколько сумм. Python может выразить эту работу одной программой, держа промежуточные значения внутри среды выполнения. Если выражать каждую маленькую операцию отдельным tool call, может понадобиться больше обменов с моделью.
smolagents от Hugging Face показывает оба подхода. ToolCallingAgent выдаёт структурированные tool calls. CodeAgent выдаёт Python, который может вызывать переданные инструменты, хранить значения и комбинировать операции. Code-агент по-прежнему использует инструменты; он выражает их комбинацию в виде программы.
Эта выразительность меняет то, что нам приходится поддерживать в эксплуатации. Теперь нужно обрабатывать синтаксические ошибки, лимиты выполнения, зависимости и доступ к файлам или сети. Окупает ли сокращение обменов с моделью эти издержки, зависит от задачи и модели. Это сравнение, которое нужно провести, а не автоматическое преимущество кода.
Несколько платформ поддерживают гибридный вариант: узкие бизнес-инструменты плюс инструмент выполнения кода в сэндбоксе. OpenAI’s Responses API, Claude API, Gemini API и AWS Bedrock AgentCore предлагают выполнение кода как встроенный инструмент, который работает рядом с вашими собственными функциями. Programmatic tool calling от Anthropic идёт на шаг дальше: код в сэндбоксе может вызывать разрешённые вами инструменты, а в модель возвращаются только выбранные результаты. Рекомендации Anthropic оставляют прямые tool calls вариантом по умолчанию и используют путь через код для больших результатов и цепочек зависимых вызовов. Существуют и дизайны только с кодом, например CodeAgent из smolagents и Code Mode от Cloudflare.
Для нашего ассистента по возвратам я бы начал с типизированных бизнес-операций. Если позже задаче понадобятся серьёзные вычисления, можно добавить ограниченные вычисления, не давая этому коду прямых полномочий над аккаунтами клиентов.
MCP отвечает на ещё один, отдельный вопрос: как приложение должно подключаться к инструментам? Локальной функции достаточно, когда реализацией владеет одно приложение. MCP может помочь, когда инструменты обслуживаются отдельно или используются несколькими клиентами. Он даёт протокол интеграции; валидация и контроль доступа остаются обязанностями участвующего приложения и сервера, как описано в спецификации инструментов MCP.
Ставьте ограничения там, где выполняются действия
Описание инструмента сообщает модели, когда операция полезна. Реализация должна решить, можно ли выполнить именно этот вызов. Для возврата это означает проверку аутентифицированного аккаунта, заказа, суммы, применимой политики и любого нужного согласования в сервисе, который выполняет запись.
Некоторые проверки требуют интерпретации
Предположим, клиент спрашивает: «Можно ли вернуть этот заказ?», а агент предлагает возврат денег. Заказ принадлежит клиенту, сумма корректна, окно возврата открыто. Все эти проверки могут пройти, а задуманное действие всё ещё остаётся неясным: клиент может спрашивать о праве на возврат, а не просить вернуть деньги сейчас. Прежде чем решать, что делать дальше, нужно интерпретировать сообщение.
Здесь удобно разделить три обязанности. Обычный код проверяет владельца, суммы и даты. Основная языковая модель ведёт разговор и объясняет результат. Отдельная модель принятия решений может оценить узкий семантический вопрос, например действительно ли клиент запросил предложенное изменение. Такое разделение даёт решение, которое можно тестировать независимо от остального разговора.
Jev, модель принятия решений от TypeSafe, — один из кандидатов на эту роль. Она принимает переданное состояние и типизированные вопросы и возвращает структурированные ответы. В этом примере её тип вопроса Noul возвращает вероятность для утверждения «да/нет». Я бы отдельно спросил, явно ли клиент запросил возврат и не отозвало ли этот запрос более позднее сообщение. TypeSafe рекомендует делать каждый вопрос конкретным и объединять ответы в коде; несколько независимых вопросов можно отправить в одном вызове API. Если держать два суждения раздельно, ошибочную интерпретацию легче найти.
Затем приложение объединяет эти результаты со своими обязательными проверками. Неуверенные или противоречивые ответы могут вести к уточнению или проверке человеком; таймауту классификатора тоже нужен явный путь обработки ошибки. Положительное семантическое суждение не может отменить проверку владельца или заменить обязательное подтверждение. Вероятность от Jev — это вывод модели, а не запись об авторизации и не независимая проверка правильности.
Для такой проверки я бы передал предложенное действие, релевантные сообщения клиента по порядку и любые результаты инструментов, нужные для решения, сохраняя различимыми их источники. Если передать только последнее сообщение, можно потерять отмену; если передать всю историю аккаунта, нужный запрос может затеряться. Вопрос, контекст и поведение при сбое — всё это части дизайна. Небольшая модель общего назначения со structured output — ещё один кандидат. Их полезность и стоимость нужно сравнить на одних и тех же решениях.
Ограничивайте доступ независимо от суждений модели
Источник инструкции важен. Сообщение клиента или найденный документ могут содержать инструкции. Этот текст не должен иметь возможности выдавать новые разрешения или заменять авторитетную политику приложения. Если документ велит агенту экспортировать все записи о клиентах, у системы не должно быть на это разрешения, как бы убедительно документ ни формулировал запрос.
Сюда же относится и решение о сэндбоксе. Если модель может вызывать только проверенные функции с узкими разрешениями, сэндбокс общего назначения для кода может дать мало. Функциям по-прежнему нужны авторизация, валидация, таймауты и безопасные операции с базой данных.
Как только мы разрешаем сгенерированный Python, shell-команды или изменения исполняемых файлов, нужно решить, к чему это выполнение имеет доступ, до того как его включить. Какие директории доступны для записи? Нужен ли доступ к сети? Какие учётные данные доступны? Сколько процессорного времени, памяти и вывода может потребить один запуск? Отдельный процесс сам по себе не отвечает на эти вопросы.
Руководство Hugging Face по безопасному выполнению описывает ограниченное локальное выполнение и более изолированные альтернативы. Их ограничения различаются. Контейнер с широкими монтированиями хоста и мощными учётными данными всё ещё может открыть доступ именно к тем ресурсам, которые мы хотели защитить. Тестируйте попытку запрещённого чтения, записи и сетевого запроса вместе с успешной программой.
Пока задаче не нужен исполнитель кода, его можно отложить. Его изоляция должна быть готова к моменту, когда мы разрешаем выполнение кода. И изоляция не может предотвратить злоупотребление API, который мы сознательно сделали доступным: сервис возвратов всё равно должен соблюдать собственные правила.
Что сохраняется, если запуск остановился на полпути?
Теперь предположим, что ассистент отправляет возврат, сервис его создаёт, а ответ теряется. Модель видит таймаут. Повтор вызова может создать ещё один возврат; объявление о сбое может сообщить клиенту то, что уже неверно.
Эта проблема связывает состояние, повторы и восстановление. Нам нужен идентификатор операции, которым владеет приложение: он сохраняется до отправки и используется повторно при восстановлении того же задуманного действия. Сервис должен распознавать этот идентификатор и возвращать существующий результат, а не выполнять действие снова. Рекомендации AWS по идемпотентным API объясняют этот паттерн. Если сервис не даёт такой гарантии, восстановлению нужен способ проверить, что произошло, или запросить решение человека перед следующей попыткой.
Сохранить разговор недостаточно. В нём может быть записано, что модель запросила возврат, но ничего не сказано о том, выполнил ли его внешний сервис. Чекпоинт помогает возобновить выполнение; он не откатывает внешние эффекты и не делает их повтор безопасным.
У этого ассистента три вида состояния с разными задачами. Разговор хранит запрос клиента и собранные к этому моменту факты. Записи о заказах и возвратах описывают бизнес-состояние. Долговременная запись о выполнении отслеживает незавершённые операции и согласования, чтобы перезапущенный воркер мог безопасно продолжить. Они могут использовать общую инфраструктуру, но не должны заменять друг друга.
Память между сессиями — отдельное решение. Если ассистент должен помнить предпочтение через неделю, нужны правила: чьё это предпочтение, кто может его изменить и как его можно удалить. Если каждая задача независима, сервис памяти может быть не нужен. Чем больше сохранённого текста, тем больше возможностей повторно использовать устаревшую или нерелевантную информацию.
То же рассуждение применимо к саммари. Когда разговор перерастает полезный контекст, может понадобиться retrieval или суммаризация. Саммари, которое теряет идентификатор заказа, условие согласования или незавершённую операцию, может сломать процедуру. Держите важное операционное состояние в явных полях и проверяйте, что сохраняется после сжатия, прежде чем на это полагаться.
Дайте циклу способ остановиться
Гибкий цикл может постоянно находить что-то ещё, что нужно проверить. Поэтому нужна политика остановки, которая охватывает успех, недостающую информацию, исчерпанные ресурсы и сбой. Достижение лимита должно давать правдивый статус: возврат может быть выполнен, не начат или всё ещё неясен. «Агент остановился» — недостаточная информация для клиента или следующего воркера.
Лимит на вызовы модели ограничивает только часть выполнения. Инструмент может зависнуть. Повтор может повторить работу. Суммаризатор или гард может сделать дополнительные вызовы модели. Приложению нужны лимиты на затраченное время, число попыток, размер вывода и расходы, которые учитывают компоненты, вызываемые на самом деле. Отмена тоже должна учитывать работу, уже отправленную во внешний сервис.
Выбор модели входит в этот дизайн, потому что от него зависит, что цикл может сделать в рамках этих лимитов. Проверьте нужный формат действий на репрезентативных задачах. Учтите доступный контекст, латентность, стоимость и то, куда можно отправлять данные задачи. Локально развёрнутая модель и хостинговый API могут реализовывать один и тот же интерфейс, но накладывать разные ограничения по мощности и эксплуатации.
Выберите начальную модель и эндпоинт, запишите их настройки, а затем не меняйте их, пока оцениваете изменение харнесса. Иначе более быстрый запуск может объясняться другим провайдером или моделью, а не тем изменением, которое мы хотели проверить.
Оценивайте завершение, включая случаи, в которых ничего делать не нужно
Теперь мы можем выполнить запрос, но ещё нужно решить, полезен ли результат. Для примера с возвратом есть как минимум три наблюдения: что сказал ассистент, какие действия он пытался выполнить и что изменилось в сервисе. Каждое ловит сбои, которые другие могут пропустить.
«Я вернул деньги за ваш заказ» может сопровождаться неизменённой базой данных. Правильный возврат может сопровождаться посторонним изменением адреса. Неизменённая база данных может означать правильный отказ, полезный уточняющий вопрос, падение или ассистента, который ничего не сделал. Одно только итоговое состояние не может различить эти последние четыре случая.
Симуляция поддержки, сопровождающая эту статью, наглядно показывает это ограничение. Среди её двенадцати собственных задач семь не ожидают изменений в базе данных. Поэтому если передать каждому проверяющему неизменённую исходную базу данных, она проходит 7 из 12 проверок состояния. Это свойство проверок, а не измеренная доля успеха поддержки. Определения задач проверяют различия итоговой базы данных; они не оценивают объяснение или промежуточные действия. Запись, которую позже отменили, тоже может исчезнуть из этого сравнения.
Включите в первый набор для оценки успешное действие, обоснованный отказ, запрос без важной информации и сбой во время выполнения. Определите ожидаемый ответ и разрешённые действия, а также итоговое состояние. Случай с уточнением должен требовать правильного вопроса; случай с отказом должен требовать полезного объяснения. Эти случаи показывают, понимает ли агент задачу и соблюдает ли её приложение.
Проверке намерения через Jev тоже нужны свои случаи. Дайте ей предложенные действия с ожидаемыми решениями, назначенными независимо: явный запрос на возврат, вопрос о праве на возврат, отозванный запрос и инструкцию, встроенную в результат инструмента. Последний случай также проверяет, сохраняет ли сборка контекста различие между намерением клиента и текстом из инструмента. Считайте неуместные действия, которые она пропускает, и легитимные действия, которые она блокирует, а затем проследите весь разговор после блокировки. Предотвращение записи решает только часть задачи, если ассистент после этого зацикливается или так и не просит клиента уточнить. Выбирайте пороги на примерах для разработки, зафиксируйте их до сравнения на отложенной выборке и записывайте версию вопроса, переданный контекст, возвращённые вероятности, применённое правило, ошибки и стоимость. Записанное демо ниже не проверяло отказ Jev, поэтому не может подтвердить эту пользу.
Записывайте достаточно, чтобы диагностировать сбой: задачу, версии модели и харнесса, предложенные действия, результаты выполнения, итоговое состояние, ошибки, латентность и стоимость. Не включайте в эту запись секреты и лишние персональные данные. Дайте кандидату входные данные задачи и нужные ему правила, но держите эталонные ответы и отложенные тестовые случаи недоступными. Эвалуатор и его доказательства тоже должны оставаться вне контроля кандидата. Если будущий оптимизатор сможет редактировать харнесс, у него не должно быть возможности переписать проверки или результаты, которые оценивают его изменения.
Эти доказательства дают необязательным компонентам цель. Добавляйте роутер, когда трейсы показывают, что выбору задачи нужен отдельный этап. Пробуйте саммари, когда длинные истории вызывают конкретный сбой. Тестируйте гард на запрещённых действиях, включая случаи, которые он должен пропускать. Обязательный контроль доступа следует из ограничений задачи; необязательным дополнениям нужны доказательства того, что их польза оправдывает их стоимость.
Превратите дизайн в первую реализацию
Для нашего примера с поддержкой у нас теперь есть обоснованная отправная точка: ограниченный цикл для интерпретации разнообразных запросов, узкие инструменты для операций с аккаунтом, авторитетные проверки в сервисах, выполняющих запись, и явные записи для незавершённых действий. Требования к сгенерированному коду или постоянной памяти разговоров у нас пока нет.
Сначала я бы построил один полный путь. Создайте небольшой тестовый аккаунт и ожидаемый исход возврата. Реализуйте операции поиска и возврата, включая пути отказа. Подключите модель к этим операциям. Затем проверьте случаи отказа, недостающей информации и прерванной записи. Так недостающие требования проявятся до того, как более крупный набор задач сделает их менее заметными.
На этом этапе может хватить цикла без фреймворка. Базовый поток такой: собрать контекст, запросить следующее действие, провалидировать и выполнить его, затем вернуть результат модели. Но если цикл ваш, то на вас и преобразование сообщений, отмена, состояние и трейсинг. Существующий фреймворк полезен, когда он экономит эту работу и не скрывает решений, которые нам нужно контролировать.
Для этой серии create_agent из LangChain — удобная отправная точка, потому что он даёт цикл «модель/инструменты» и хуки для изменения его поведения. Он уже работает на LangGraph. Переход к явному воркфлоу на LangGraph позже означает, что мы возьмём под контроль больше этапов вокруг этого агента; это не значит добавить граф в систему, где его раньше не было.
Первому дизайну не обязательно содержать все возможности, о которых шла речь выше. Ему нужно достаточно, чтобы выполнить свою задачу в рамках ограничений, плюс доказательства, которые показывают, где он ломается. Перед написанием реализации я бы собрал решения на одной странице:
| Вопрос | Решение, которое нужно записать |
|---|---|
| Какую задачу мы выполняем? | Вызывающий, ожидаемый результат, обоснованный отказ или передача человеку, запрещённые эффекты |
| Кто выбирает следующий шаг? | Известные этапы в коде; решения, делегированные модели; причина для каждого |
| Какие проверки требуют интерпретации? | Семантический вопрос, его контекст, модель-кандидат и поведение при неуверенности |
| Что модель может попросить сделать? | Без инструментов, типизированные операции, сгенерированный код или комбинация |
| Кто это авторизует и выполняет? | Разрешённые ресурсы, сервис, соблюдающий правила, правила согласования и ограничения сэндбокса |
| Что должно пережить прерывание? | Состояние разговора, бизнес-записи, незавершённые операции и поведение при восстановлении |
| Какие лимиты действуют? | Ограничения модели/провайдера, обработка данных, вызовы, время, расходы и отмена |
| Что доказывает завершение? | Проверки ответа, действий и состояния; кто владеет их доказательствами |
| Что мы не включили? | Необязательные компоненты и сбой или новое требование, которое оправдало бы каждый из них |
Полезный ответ достаточно конкретен, чтобы изменить реализацию. «Нужна память» — расплывчато. «Должен возобновить согласованный возврат после перезапуска воркера, не создав второй возврат» говорит, что сохранять и какой сбой тестировать.
Это часть 1 серии Building and Evaluating Agent Harnesses. Часть 2 развивает вопрос о потоке управления: когда явный воркфлоу улучшает обработку задачи настолько, что оправдывает свои дополнительные этапы? Мы построим минимальный обоснованный воркфлоу вокруг агента и сравним его с циклом. Роутеры, параллельные воркеры и саммари — кандидаты для исследования, а не заранее заданная цель. Следующие части отделят эвалуатор от кандидата и измерят разброс между повторными запусками.
Необязательно: изучите реализацию и её ограничения
Сопутствующее демо — небольшая запускаемая версия ассистента поддержки. Оно работает в двух средах:
- Собственные задачи поддержки: двенадцать задач, каждая на свежей базе данных SQLite с клиентами и заказами.
- τ³-bench retail: восемь задач из публичного бенчмарка, в котором симулированный клиент разговаривает с агентом, а проверки базы данных оценивают результат.
В этом разделе описано, что содержит демо, что в нём намеренно опущено, что показывают результаты и как его запустить. Его можно пропустить и всё равно использовать описанный выше процесс проектирования.
Что содержит демо
Вызовы выбирает один агент LangChain. У него пять инструментов для аккаунта (look_up_account, list_orders, issue_refund, update_address и set_preference) и MCP-сервер для поиска по политикам и FAQ. Чекпоинтер в памяти хранит состояние выполнения. Исполнителя кода и памяти между сессиями нет.
Демо записывает две конфигурации:
| Спецификация | Компоненты |
|---|---|
plain | Суммаризация, лимит 20 вызовов модели на запуск и 2 повтора при ошибке инструмента |
base | Всё из plain, плюс Schema-Guided Reasoning и гард записи на Jev |
Schema-Guided Reasoning (SGR) заставляет модель возвращать структурированный объект следующего шага вместо нативного tool call. Гард Jev запускается перед каждой записью и задаёт один составной вопрос: нарушит ли этот вызов политику или выйдет ли за рамки того, о чём просил клиент? Два отдельных вопроса из начала статьи, о запросе и его отзыве, — это предлагаемый редизайн. Записанные запуски их не использовали.
base добавляет SGR и Jev одновременно, поэтому собственные результаты не могут показать, какой из двух вызвал разницу.
Что в демо намеренно опущено
- Инструменты проверяют целостность данных, но оставляют бизнес-политику агенту, поэтому эксперимент может выявить запись, которую политика запрещает. Реальный сервис соблюдал бы политику при записи.
- Нет лимита на время или расходы для всей задачи, а у повторённых записей нет ключа идемпотентности.
- Эвалуатор работает в том же процессе, что и агент.
Поэтому эти результаты не проверяют соблюдение правил в продакшене, безопасность повторов или защиту доказательств от подделки.
Результаты на собственных задачах
Каждая строка — один запуск на задачу; модель и цены эндпоинта записаны вместе с результатами. Проверка состояния сравнивает итоговую базу данных с ожидаемой. Стоимость и латентность — средние на задачу; стоимость включает Jev там, где он работает.
| Спецификация | Модель | Проверки состояния | Стоимость | Латентность |
|---|---|---|---|---|
base | openai/gpt-6-luna | 12/12 | $0.00073 | 11.9 s |
plain | openai/gpt-6-luna | 12/12 | $0.00033 | 5.8 s |
base | z-ai/glm-5.3-flash | 12/12 | $0.00101 | 9.7 s |
base | xiaomi/mimo-v2.6-flash | 7/12 | $0.00087 | 18.6 s |
- С
gpt-6-lunaобе спецификации прошли все двенадцать задач.baseстоил примерно в 2,2 раза дороже и работал примерно вдвое дольше. - Jev пропустил каждую запись, которую проверял: двенадцать в запусках
gpt-6-lunaи GLM и четыре в запуске MiMo. Он ни разу не заблокировал запись, поэтому эти запуски не проверяют, остановит ли он плохую. - Пять провалов MiMo были ошибками формата вывода SGR. Запуска MiMo с
plainнет, поэтому мы не можем сказать, справился бы нативный tool calling лучше. - Суммаризация ни разу не запускалась. Самый большой запрос в шести записанных запусках (эти четыре и два запуска τ³-bench ниже) содержал 8 351 входной токен, что ниже порога срабатывания в 12 000 токенов. Эти результаты не могут показать, помогает ли суммаризация.
Результаты на τ³-bench
τ³-bench оценивает запуск, воспроизводя tool calls агента в свежей среде, поэтому бенчмарк должен выполнять инструменты сам. Адаптер ставит граф на паузу перед узлом инструментов, возвращает tool calls и записывает результаты бенчмарка обратно, прежде чем продолжить.
Из этого следуют два вывода. Во-первых, промпт, инструменты и политика берутся из τ³-bench, поэтому эти результаты нельзя сравнивать с собственными, даже при том же файле спецификации. Во-вторых, повтор инструментов и Jev здесь никогда не запускаются: единственная разница между base и plain — SGR.
Оба запуска используют openai/gpt-6-luna для агента и для симулированного клиента. Латентность симуляции включает работу симулятора. Стоимость агента и латентность симуляции — средние на задачу; стоимость симулятора — общая сумма за все восемь задач.
| Спецификация | Пройдено | Стоимость агента | Латентность симуляции | Стоимость симулятора, всего |
|---|---|---|---|---|
base | 6/8 | $0.00238 | 59.0 s | $0.00453 |
plain | 7/8 | $0.00136 | 38.9 s | $0.00471 |
- Восемь задач взяты из тестового сплита retail и не содержат утверждений на естественном языке, поэтому награду определяют только проверки базы данных.
- Обе спецификации завершили все восемь задач без ошибок адаптера. Каждый провал был пропущенной записью.
baseпровалил задачи 9 и 26.plainпередал задачу 27 человеку вместо того, чтобы завершить обмен. - Второй запуск
baseтоже прошёл 6/8, но провалил задачи 9 и 17. Проваленные задачи меняются от запуска к запуску, поэтому разница в одну задачу — не причина предпочестьplain. Эти запуски не измеряют этот разброс.
Проверьте записанные числа
Сохранённые отчёты и трейсы содержат исходы по задачам, количество токенов, конфигурации, версии пакетов и записанные цены. Для 393 чат-вызовов агента в шести записанных запусках стоимость, рассчитанная по токенам, совпала со списанной суммой в каждом ответе. Эта проверка не включает вызовы Jev и симулированного клиента. Цены исторические, это не расчёт стоимости вашего запуска.
Запустите сами
Установите uv и используйте тегированную версию ниже. После установки зависимостей тесты работают офлайн. Два запуска в средах делают платные вызовы моделей через OpenRouter и требуют API-ключа.
git clone https://github.com/slavadubrov/agent-harness-lab-public
cd agent-harness-lab-public
git checkout v0.1.2-a1
cp .env.example .env # set OPENROUTER_API_KEY
make test
make a1-custom SPEC=harness/spec/plain.yaml
make a1-tau3 SPEC=harness/spec/plain.yaml
Каждый запуск заменяет результаты в reports/article-a1/. Используйте git diff, чтобы сравнить их с закоммиченным базовым вариантом. Makefile устанавливает зависимости из замороженного uv.lock.
Чтобы попробовать другую модель или набор инструментов, скопируйте спецификацию из harness/spec/, измените эти поля и передайте её путь через SPEC. Настройки провайдера и собственных компонентов принимают произвольные ключи, поэтому валидация не может поймать каждую опечатку во вложенных полях.
Описанный в статье процесс проектирования можно использовать, не запуская демо. Демо делает один набор решений доступным для проверки; ваш собственный контракт задачи решает, какие из них нужны в вашем харнессе.