
Привет, Хабр! На связи команда GigaChain. В этой статье мы расскажем, как итеративно настраивали обвязку (harness) Deep Agents под особенности GigaChat: правили промпты и описания инструментов, а часть правил зашивали прямо в код, и как сделали для этого открытый бенчмарк harness-bench-fast, чтобы измерять прогресс. На зафиксированном сете из 391 задачи подключение профиля подняло долю решённых задач с 77,2% до 87,0%, а расход токенов сократило практически вдвое.
У каждой модели свои привычки
Модели похожи снаружи: принимают промпт, умеют вызывать инструменты. Но привычки у них разные, и вендоры сами их описывают: у каждого есть промпт-руководство, как формулировать инструкции, какие инструменты давать, что модель понимает плохо. Советы в этих руководствах расходятся.
OpenAI в руководстве по Codex-моделям просит давать модели инструмент apply_patch для правки файлов и поощрять батчинг — привычку модели отправлять несколько независимых tool-вызовов одним ходом, что экономит раунды диалога; обвязка должна это явно разрешить и в промпте, и на уровне API. Anthropic в руководстве по промптингу Claude рекомендует другое: просить модель после каждого результата инструмента оценить его качество и только потом выбирать следующий шаг — эту инструкцию предлагается вставлять в системный промпт дословно.
Обе рекомендации полезны своим моделям и бесполезны либо вредны чужим. Если обвязка таких особенностей не учитывает, то качество работы агента проседает.
Ярче всего это видно там, где под обвязку оптимизируют саму модель. Claude Code — по опросу Pragmatic Engineer самый любимый у разработчиков кодовый агент — работает так хорошо ещё и потому, что Claude дообучают прямо в этой обвязке: модель и обвязка развивались вместе. У этого есть и обратная сторона: в чужой обвязке та же модель может ошибаться заметнее. Армин Ронахер, создатель Flask и сооснователь Earendil, компании, которая развивает агент Pi, разбирает случай, на который наткнулся сам: свежий Claude в сторонней обвязке вызывает инструмент правки в том формате, к какому привык, а в чужой схеме нужных полей нет, вызов отклоняется, хотя правку модель задумала верно. Модель и обвязка — единая система; подтягивать их друг к другу можно с обеих сторон — настраивая модель под обвязку или обвязку под модель. В этой статье мы заходим со стороны обвязки.
Профили обвязок
Мы уже рассказывали о Deep Agents — открытом фреймворке компании LangChain для построения агентных обвязок на стеке LangChain/LangGraph. Файловая система, shell, планировщик, субагенты, память и навыки описаны в нём как абстракции: к каждой есть готовые реализации, при необходимости пишутся свои, а обвязку под задачу собирают из этих кубиков вызовом create_deep_agent(). На его основе компания LangChain собрала и готовый продукт — Deep Agents Code, терминальный кодинг-агент, которому достаточно указать модель.
LangChain активно развивает Deep Agents. В феврале 2026 команда показала на Terminal-Bench 2.0, что настройка обвязки под конкретную модель — в их случае GPT 5.2 Codex — поднимает результат агента с 52,8% до 66,5%, примерно с Top 30 в Top 5 лидерборда. Наблюдение оформили в механизм: в апреле в Deep Agents появились профили обвязки (HarnessProfile) — декларативный слой переопределений под конкретную модель: системный промпт, состав и описания инструментов, набор middleware — перехватчиков, которые вмешиваются в работу агента между моделью и инструментами (подробно о них расскажем ниже). Готовые профили под модели OpenAI и Anthropic команда собрала сама и включила в поставку Deep Agents — они подключаются автоматически, по имени модели: Deep Agents смотрит, какая модель выбрана, и подхватывает подходящий профиль. Причём у Anthropic под каждую модель свой профиль (Haiku, Sonnet и Opus идут порознь), а у OpenAI он один на все Codex-модели. На подмножестве tau2-bench из задач, где у сильных моделей ещё есть куда расти, профиль поднимает долю решённых задач на 10–20 процентных пунктов: например, GPT 5.3 Codex с профилем вырос с 33% до 53%. В июле по следам плейбука настройки профиля в поставке появился и профиль под открытую модель — NVIDIA Nemotron 3 Ultra; он вошёл в стабильную ветку Deep Agents 0.7.
Механизм открыт и для сторонних разработчиков: профиль можно оформить отдельным Python-пакетом, который подключается как плагин, и Deep Agents подхватит его без изменений в коде приложения. Мы воспользовались этой возможностью и выпустили deepagents-gigachat — профиль под GigaChat и, насколько нам известно, первый сторонний профиль, живущий вне репозитория Deep Agents.
Влияние профиля на поведение агента
Посмотрим, как профиль меняет поведение агента. Возьмём GigaChat-3-Ultra, под которую его и собирали, и прогоним одну задачу дважды — с профилем и без.
Задача нарочно простая: «переименуй файл oldname.txt в newname.txt». Вот трасса прогона без профиля — запись всех шагов агента:
[tool_call] write_file {'file_path': '/newname.txt', 'content': 'important content'} [tool_call] execute {'command': 'rm /oldname.txt'} [tool_call] ls {'path': '/'} # oldname.txt на месте?! [tool_call] execute {'command': 'rm -f /oldname.txt'} [tool_call] ls {'path': '/'} # всё ещё на месте [tool_call] execute {'command': 'mv /oldname.txt /newname.txt'} [tool_call] execute {'command': 'rm -f /newname.txt'} # сносит уже сделанное! [tool_call] execute {'command': 'find / -name "oldname.txt"'} ... ещё два десятка вызовов ... GraphRecursionError: Recursion limit of 80 reached
213 секунд, десятки тысяч токенов, задача провалена — а по пути агент ещё и уничтожил уже созданный newname.txt. Он зациклился: повторял одни и те же команды, пока не упёрся в лимит в 80 шагов. Та же модель, та же задача, но с профилем deepagents-gigachat:
[tool_call] execute {'command': 'mv oldname.txt newname.txt'} Файл oldname.txt успешно переименован в newname.txt.
Один вызов инструмента, 7 секунд. Модель в обоих прогонах одна и та же, различается только обвязка: где-то она сообщает модели то, чего та не знает про среду (через системный промпт и описания инструментов), где-то обеспечивает нужное поведение кодом там, где одних инструкций мало. Так устроен профиль deepagents-gigachat.
В прогоне без профиля агент зациклился не на пустом месте: обвязка не сообщила модели важной особенности среды, а сама она знать её не может. Дальше разберёмся, что именно ломалось в паре модели и обвязки и как мы это чинили.
Что обвязка без профиля недоговаривает модели
Разгадка истории с переименованием — лишь один из примеров того, как агент спотыкается о невидимые особенности среды. В ходе анализа трасс мы выделили пять системных классов таких сбоев. У всех них общая природа: модель физически не может знать специфику конкретной обвязки, этому веса не учат.
1. Абсолютные пути. У агента два разных способа работать со средой: файловые инструменты (read_file, write_file) и инструмент execute, запускающий команды в терминале. Они спроектированы на разных уровнях абстракции и по-разному понимают корень /. Файловые инструменты работают через абстрактный бэкенд и в режиме virtual_mode, видящий рабочую папку как изолированную песочницу с собственным корнем /. Инструмент execute передаёт команды настоящей ОС, где / — корень всего диска.
В задаче с переименованием агент видел файл как /oldname.txt и отправлял в терминал команду rm /oldname.txt. Для ОС этого файла в реальном корне диска не существовало: команда execute не удаляла рабочий файл, а файловые инструменты продолжали показывать его на месте. Модель попадала в тупик, пробуя всё более жёсткие команды, пока не исчерпывала лимит шагов.
Промптом это лечится плохо (в стоковой инструкции execute LangChain, наоборот, просит модель использовать абсолютные пути, окончательно её путая). Нам удалось закрыть это переписанными описаниями инструментов и подсказкой при зацикливании.
2. Однострочники python -c. Сталкиваясь с разовыми вычислениями, модель прибегает к запуску интерпретатора одной командой — python -c "...". Ловушка кроется в синтаксисе языка: через точку с запятой можно объединять только простые выражения, а управляющие конструкции (for, if, with) после ; вызывают синтаксическую ошибку:
python -c "s = 0; print(s)" # Работает python -c "s = 0; for v in xs: s += v" # SyntaxError: invalid syntax
Наткнувшись на SyntaxError, модель пытается исправлять однострочник на лету: переставляет кавычки, меняет пробелы или скобки, но продолжает ставить цикл после ;. Это приводит к серии одинаковых ошибок, пока не исчерпается лимит раундов.
3. Подстрочный поиск вместо regex в grep. Встроенный инструмент grep в Deep Agents выполняет только точный подстрочный поиск и не поддерживает регулярные выражения. Это осознанный компромисс разработчиков фреймворка: инструмент обязан работать одинаково на всех бэкендах, включая виртуальную память (StateBackend), где системной утилиты grep нет и поиск реализован на чистом Python.
Модель об этой особенности не знает. В задаче «посчитай строки, начинающиеся с import» она передаёт паттерн ^import, привычный по консольному grep. Инструмент ищет символ ^ как часть обычного текста и ожидаемо возвращает ноль совпадений:
[tool_call] grep {'pattern': '^import', 'path': 'src', ...} # 0 совпадений [tool_call] write_file {'file_path': 'count.txt', 'content': '0'} # уверенно пишет ноль
Наступает тихий сбой: инструмент не выдаёт синтаксическую ошибку, ведь ноль совпадений — формально корректный ответ. Модель принимает его за факт и фиксирует «0» в итоговом файле (при правильном ответе 39).
Мы переписали системное описание grep, разграничив сферы применения. Для точного поиска подстроки (например, имён переменных) модель использует встроенный grep. Но если требуется искать по регулярному выражению или считать строки, то описание прямо указывает использовать терминал через execute (grep -h '^import ' src/*.py | wc -l).
4. Утечка префикса нумерации. Инструмент правки файлов (edit_file) работает по принципу точечной замены: модель передаёт исходный фрагмент текста, который нужно найти в файле, и новый фрагмент ему на замену.
При чтении через read_file инструмент показывает содержимое с номерами строк в начале (например, 3\tHello world). Это служебная разметка для удобства модели, в самом файле этих номеров нет. Не зная об этом, модель копирует текст для замены прямо со служебным номером (3\tHello world). Инструмент пытается найти такой текст на диске, ожидаемо выдаёт ошибку «String not found», а модель уходит в серию бессмысленных повторов с той же строкой.
В описание инструмента мы добавили прямую инструкцию обязательно удалять префикс с номером строки перед передачей текста на замену, а также подсказку: если при правке возникла ошибка «String not found», то причина почти наверняка в скопированном номере строки.
5. Игнорирование динамической памяти. Память в Deep Agents устроена в два уровня. Первый уровень (инструкции из AGENTS.md) MemoryMiddleware подгружается напрямую в системный промпт. Но второй уровень — файлы динамической памяти с накопленными фактами (например, MEMORY.md или хранилище /memories/) — в промпт автоматически не попадает. Чтобы прочитать или записать туда данные, агент должен сам вызвать read_file или edit_file.
Базовая обвязка никак не стимулирует модель обращаться к этим файлам. Получив запрос вроде «Создай pyproject.toml, автора зовут Анна Петрова», агент просто создаёт файл проекта и завершает работу за четыре сообщения — имя автора остаётся в разовом контексте диалога и не записывается в память:
[tool_call] write_file {'file_path': 'pyproject.toml', 'content': '[build-system]...'} Готово, pyproject.toml создан. # Динамическая память не прочитана и не обновлена -- данные не сохранены
Мы добавили в перехватчик MemoryTaskMiddleware отслеживание наличия конфигурации памяти. Если конфигурация есть, то в диалог автоматически вставляется напоминание: перед выполнением задачи прочитай файлы динамической памяти через read_file, а перед завершением работы сохрани в них новые факты.
У всех пяти классов сбоев общая природа: это особенности конкретной среды, о которых модель заранее знать не может. Профиль объясняет их в промптах и описаниях инструментов, а там, где инструкций не хватает, подстраховывает логику агента кодом через middleware.
Устройство профиля: два слоя
Профиль deepagents-gigachat состоит из двух слоёв: системные промпты и описания инструментов задают инструкции для модели, а перехватчики (middleware) гарантируют нужное поведение на уровне кода Python. Такой же двухслойный подход можно увидеть и у самой команды LangChain в плейбуке по настройке обвязки под модель Nemotron.
Слой первый: промпт и описания инструментов
У базового Deep Agents системный промпт монолитный: это один длинный текст с общими правилами. Профиль заменяет его набором модульных коротких блоков, каждый из которых закрывает свой класс ошибок. Это позволяет точечно подстраивать промпт, добавлять или отключать инструкции под разные типы задач и не «размывать» внимание модели.
Базовый системный промпт профиля собран из десяти самостоятельных блоков:
ядро — читать задачу буквально, отвечать кратко, использовать только доступные инструменты;
память — правила чтения и обновления файлов памяти;
файлы — выполнять один
read_fileна файл, объединять все изменения в одинedit_file;shell — выполнять одну короткую команду за шаг, использовать относительные пути, избегать многострочного текста в кавычках;
обработка данных — агрегировать, суммировать и группировать данные через скрипты Python, а не в уме;
бюджет — учитывать лимит шагов; простые задачи укладывать в 3–6 вызовов инструментов;
формат вывода — точно соблюдать структуру и текст итогового ответа из задания.
Ещё три блока содержат узкие рецепты под специфические задачи: разбор логов и манифестов, работа с policy.md и разрешение git-конфликтов.
Второй компонент этого слоя — переписанные описания семи встроенных инструментов. Каждое описание подсвечивает конкретную ловушку среды, показывает её на примере и даёт инструкцию, как поступить при ошибке. Вот фрагмент реального описания инструмента edit_file:
**CRITICAL: STRIP the leading '<line_no>\t' prefix from read_file output before putting text into old_string or new_string.** Example: read_file shows ` 3\tHello world` -- you must pass old_string='Hello world', NOT old_string=' 3\tHello world'. <...> If edit_file says 'String not found' and you copied recently from read_file, the prefix leak is almost certainly the cause -- strip it and retry.
Все промпты и описания в профиле написаны на английском языке. Вся служебная обвязка Deep Agents (схемы инструментов, сообщения об ошибках, системные вставки) по умолчанию тоже на английском. Смешивание двух языков в одном системном промпте ухудшает следование инструкциям, а переводить на русский весь фреймворк избыточно.
Слой второй: middleware
Часть правил модель выполняет охотно, часть — как получится. Для второго случая в профиле есть middleware: компоненты, которые встраиваются в агентный цикл и получают управление в узловых точках: перед вызовом модели (хук before_model), вокруг вызова инструмента (wrap_tool_call) и в других. В эти моменты middleware может всё: подменить или дополнить сообщения, заблокировать вызов инструмента и вернуть модели своё сообщение вместо результата, вставить в диалог реплику-подсказку. Механизм мы подробно разбирали в прошлой статье — по сути, это перехватчик с полными правами на управление диалогом.
В профиле deepagents-gigachat задействованы пять основных middleware (шестой, ToolContractMiddleware, подключается по необходимости — он явно проговаривает модели, какие инструменты доступны в текущем прогоне):
ThinkToolMiddleware добавляет инструмент
think, который не выполняет действий в ОС: модель передаёт в него строку с рассуждением и получает её обратно в историю. Приём подсмотрели у Anthropic: модель без встроенного режима рассуждений может остановиться на сложной развилке, проговорить ход мысли и увидеть его в следующем шаге как часть диалога.ShellSafetyMiddleware проверяет команды до запуска: перехватывает вызовы
executeи блокирует те, что гарантированно упадут. Основные сценарии блокировки — многострочный текст внутри двойных кавычек и однострочникиpython -cс циклами после;. Вместо падения ОС модель получает ответ[SHELL-SAFETY] blocked unsafe execute commandс понятным объяснением, что не так и как исправить команду.PathNormalizerMiddleware — нормализация путей на уровне кода: в режиме virtual_mode результаты
globиgrepприходят с путями вида/src/foo.py. Модель копирует их дословно в итоговые файлы, а верификаторы ожидают относительный путьsrc/foo.py. Перехватчик автоматически срезает ведущий слэш в результатах инструментов. Правило «срезай слэш» в промпте работало не всегда, а исправление самих данных работает во всех случаях.MemoryTaskMiddleware — подсказка в момент необходимости. Если в рабочей среде подключена память, то перед первым ходом агента в диалог вставляется напоминание: «сначала прочитай файлы памяти, а в конце сохрани новые факты». Если спустя два раунда файлы памяти так и не тронуты, то вставляется второе, более настойчивое напоминание.
LoopBreakerMiddleware — перехватчик для борьбы с зацикливанием. Он анализирует историю вызовов и распознаёт пять сигнатур зацикливания:
трижды повторён идентичный вызов;
трижды подряд один инструмент вернул ошибку;
трижды подряд возникли ошибки одного семейства (от синтаксиса Python до промаха при редактировании);
два пустых
grepподряд на счётной задаче;больше 12 раундов инструментов на простой задаче.
Поймав зацикливание, middleware вставляет в диалог жёсткую подсказку с требованием сменить стратегию и конкретным рецептом исправления ошибки.
К похожему решению в своё время приходила и команда LangChain: в их февральском посте описан внутренний LoopDetectionMiddleware, боровшийся с зацикливанием GPT 5.2 Codex. В открытой поставке Deep Agents такого middleware тогда не было, поэтому для профиля GigaChat мы реализовали собственный LoopBreaker.
Важная подробность модельно-специфичной настройки: служебные вставки подсказок естественно оформлять системными сообщениями (SystemMessage), и наша первая версия так и делала. Но у GigaChat API есть жёсткое требование: SystemMessage должно быть строго одно и стоять самым первым в истории. Появление SystemMessage в середине диалога вызывает не просто снижение качества, а жёсткую ошибку 400 Bad Request. Поэтому все подсказки нашего профиля отправляются от лица пользователя (HumanMessage). Если вы пишете свой middleware под GigaChat, то учтите эту особенность: середина диалога принимает только HumanMessage и ToolMessage.
Так устроена архитектура профиля изнутри. Остаётся вопрос: как подключить весь этот набор промптов и перехватчиков к проекту, не переписывая код самого приложения?
Подключение
Технически профиль — это стандартный Python-пакет, который объявляет точку входа (entry point) в группе deepagents.harness_profiles:
[project.entry-points."deepagents.harness_profiles"] gigachat = "deepagents_gigachat:register_harness"
Точки входа — базовый механизм плагинов в экосистеме Python (аналогично тому, как pytest или flake8 подтягивают сторонние расширения). При запуске библиотека Deep Agents сканирует через importlib.metadata все установленные пакеты с этой группой точек входа и вызывает их функции регистрации.
Наша функция register_harness регистрирует объект HarnessProfile при передаче объекта GigaChat(). Сам HarnessProfile — это декларативная структура, описывающая полный набор настроек обвязки под конкретную модель:
префикс и суффикс системного промпта (
system_prompt_prefixиsystem_prompt_suffix);переопределения описаний инструментов (
tool_descriptions);список перехватчиков (
middleware);конфигурацию субагентов (
general_purpose_subagent).
В профиле deepagents-gigachat мы передали склеенные 10 блоков нашего промпта в system_prompt_prefix, переписали описания семи встроенных инструментов и подключили пять middleware, а субагенты оставили в стандартном режиме.
Благодаря этому интеграция происходит бесшовно: достаточно установить пакет — и любой вызов create_deep_agent() с моделью GigaChat автоматически задействует профиль без изменения кода самого приложения. Все замеры в этой статье и описанная выше конфигурация относятся к deepagents-gigachat==0.0.3, поэтому для точного воспроизведения зафиксируйте эту версию
uv add deepagents-gigachat==0.0.3
Если пакет не установлен, то обвязка работает в базовом режиме; если написать свой пакет с профилем, можно заменить нашу реализацию собственной. Более свежие версии deepagents-gigachat могут отличаться по составу middleware и поведению агента.
Из отладки — в бенчмарк: как мы измеряли прогресс
Любая правка в обвязке — будь то новый блок промпта или добавление очередного перехватчика — влечёт риск: починив один редкий сбой, можно случайно ухудшить поведение модели на десятке других сценариев. Чтобы не гадать, а измерять реальный эффект от каждого изменения, нам потребовался инструмент непрерывного регрессионного тестирования.
Всё началось в апреле 2026 года с локальной папки harness_bench/ внутри репозитория профиля. Мы стали собирать в неё короткие, конкретные тесты с автоматической проверкой: «Переименуй файл — старого быть не должно», «Посчитай строки с import — в count.txt должно лежать 39». Каждая задача — это промпт, начальные файлы и механический верификатор на Python, проверяющий состояние рабочей среды после прогона. Никаких субъективных LLM-судей, только детерминированный код: от простых проверок файлов и запуска pytest до SQL-запросов к SQLite и сверки таблиц XLSX.
По мере отладки профиля набор задач быстро разрастался. Когда их стало 200, измерительный инструмент отделили от профиля и перенесли в собственный открытый репозиторий — harness-bench-fast, предоставив версионирование наборов задач и самостоятельную жизнь. Вскоре набор вырос до 231 задачи, а к моменту публикации этой статьи — до 391 задачи (зафиксированный наборы v0.16.0).
Базу задач расширяли этапами, следуя за усложнением реальных агентных сценариев. Мы последовательно добавляли новые категории тестов:
агентные задачи — сложные многоходовые сценарии на написание и отладку кода (в духе Terminal-Bench, tau-bench и SWE-bench);
инфраструктурные тесты — разрешение git-конфликтов и дисциплина следования правилам из навыков (skills);
соревновательные (adversarial) тесты — сломанные системные окружения (например, отсутствующая команда python или повреждённые конфиги), файлы в редких кодировках, противоречивые инструкции в среде и логи на сотню мегабайтов, которые требуется стримить, а не читать целиком в контекст.
Результаты разных прогонов сравниваются строго внутри одной версии набора задач.
Почему мы не взяли готовый бенчмарк? Популярные агентные бенчмарки (Terminal-Bench, tau-bench) прекрасны, но у них есть три ограничения: они англоязычные (а нам хотелось замеряться на русскоязычных задачах), они требуют тяжёлые Docker-контейнеры на каждый шаг и не заточены под специфические классы ошибок обвязки. Наш бенчмарк запускается локально без Docker, полный прогон занимает меньше часа, а отдельная задача — секунды (слово fast в названии не случайно). При этом совместимость с индустрией сохранена: бенчмарк умеет экспортироваться в формат Harbor — универсального раннера агентных тестов, используемого командой LangChain.
Полезно сравнить наш набор с тем, как измеряет свои обвязки сама команда Deep Agents. Совсем недавно она перестроила свой eval-контур: место разрозненных тестов заняли три сквозных бенчмарка на платформе Harbor — Harbor-Index (82 сложнейшие задачи, отобранные из 6000+ кандидатов), 𝜏³-bench (30 задач на многоходовый диалог с симулятором пользователя) и ContextBench (30 задач на работу с длинным контекстом). Параллельно команда держит набор функциональных тестов (capability suite) — быстрые детерминированные проверки на ключевые механики обвязки: работу с файлами, памятью и выбором инструментов.
Наш набор из 391 задачи по духу ближе всего именно к этому набору функциональных тестов: каждая задача точечно измеряет поведение агента в конкретной ситуации среды. При этом три фундаментальных принципа оценки мы вывели независимо от LangChain:
Многократный прогон (pass@K): агент недетерминирован, поэтому делать выводы по одной случайной попытке нельзя — одну и ту же задачу нужно запускать несколько раз.
Быстрый срез для итераций: разворачивать тяжёлые среды на каждый эксперимент слишком долго; для ежедневной отладки нужен локальный и быстрый контур (что и вынесено в название harness-bench-fast).
Проверка кодом, а не моделью: результат должен оцениваться детерминированно (состоянием файлов, тестами, записями в БД), а не другим LLM-судьей.
Такое сопоставление подсвечивает и сильные, и слабые стороны нашего набора. Наш бенчмарк пока слабее покрывает многоходовый диалог с пользовательским симулятором. Зато в нём есть уникальные сценарии, которых нет у LangChain: разрешение git-конфликтов, сломанные системные окружения и — главное — полностью русскоязычные формулировки задач.
Оценка эффективности профиля
Результаты на актуальном зафиксированном наборе из 391 задачи (v0.16.0) наглядно демонстрируют эффект от профиля. Приведённая ниже таблица сопоставляет прогоны с базовой обвязкой и подключённым профилем deepagents-gigachat для моделей линейки GigaChat:
Модель | Без профиля | С профилем | Прирост качества | Расход токенов (без профиля и с ним) |
GigaChat 3.5 | 302/391 (77,2%) | 340/391 (87,0%) | +9,8 п.п. | 7,37 млн → 4,32 млн (−41%) |
GigaChat 3 Ultra | 312/391 (79,8%) | 340/391 (87,0%) | +7,2 п.п. | 7,99 млн → 4,41 млн (−45%) |
Профиль даёт прирост от 7 до 10 процентных пунктов качества и практически вдвое снижает расход токенов.
Агент решает больше задач за меньшее количество ходов и при ощутимо меньшем расходе токенов. Главная причина такой экономии — предотвращение зацикливаний и лишних вызовов инструментов. Попадая в цикл одинаковых ошибок, агент с каждым повторным шагом отправляет в модель всё более раздутый контекст диалога. В результате простая задача, которая должна решаться за пару вызовов и 1–2 тысячи токенов, сжигает десятки тысяч токенов на бессмысленные повторы. Профиль купирует такие циклы на ранних шагах и возвращает агент к решению.
С профилем модели линейки показывают следующие результаты: GigaChat 3.5 и 3 Ultra достигают 87,0%, GigaChat 3 Pro — 61,6% (241/391), GigaChat 3 Lightning — 45,5% (178/391).
Для масштаба: на том же наборе v0.16.0 в актуальном лидерборде Kimi CLI набирает 99,7%, Claude Code с Haiku 4.5 — 97,2%, а GigaChat 3.5 с описываемым здесь профилем 0.0.3 — 87,0%.
Пока статью готовили к публикации, вышел deepagents-gigachat 0.0.4. В него мы добавили ещё один middleware: если агент собирается записать вычисляемое значение, которое фактически не было посчитано, то запись блокируется и модели предлагается сначала получить результат программно. На GigaChat 3.5 эта и другие правки новой версии подняли средний результат с 87,5% до 90,2%.
Борьба с переобучением под бенчмарк
При итеративной отладке профиля на фиксированном наборе тестов всегда существует риск переобучения (overfitting), когда правила невольно подстраиваются под формулировки конкретных задач. Чтобы исключить такой эффект, мы руководствовались тремя правилами:
Исправление системных классов поведения. Мы следовали принципу, который команда LangChain сформулировала при настройке обвязки под модель Nemotron: «behavior-class fixes, not benchmark tricks». Каждую правку промпта или перехватчик проектируют под обобщённый класс сбоев (любой
read_fileс номерами строк или любойgrepбез поддержки regex), а не под специфику конкретного теста.Проверка на отложенных категориях (holdout). Значительную часть тестов — задачи на соблюдение регламентов из навыков (skills) и сломанные системные окружения — добавили в harness-bench-fast уже после того, как основной состав профиля сформировался. Успешное прохождение этих новых сценариев подтверждает переносимость профиля.
Регрессионный контроль. Бенчмарк используется не для подгонки результатов, а для проверки стабильности: он подтверждает, что устранение одной ошибки не ломает ранее отлаженное поведение.
Границы инжиниринга обвязок
Эффективный инжиниринг обвязок (harness engineering) требует дисциплины и чёткого понимания его границ. При проектировании профиля мы выделили три базовых принципа того, чего следует избегать:
1. Не пытаться исправлять обвязкой фундаментальные ограничения весов. При обработке небольших CSV-файлов модель стремится агрегировать напрямую в контексте, минуя запуск скриптов, и сбивается в подсчётах. Ни инструкции в промпте, ни перехватчики не способны гарантированно заставить модель писать Python-код, если входной текст кажется ей достаточно коротким для прямого ответа. Арифметика и склонность к упрощениям на коротких контекстах — это свойства весов самой модели; обвязкой их не изменить без жёстких запретов. Подобные вещи лечат обновлением весов, а не усложнением обвязки.
2. Не создавать вредные заплатки под частные случаи. Иногда модель выбирает формальный путь решения: например, вместо рефакторинга вызовов функции во всех файлах проекта она подставляет алиас вида from module import new_func as old_func. Написать в обвязке правило или перехватчик против такого обхода несложно, но это поломает нормальный Python-код в других задачах (вроде import pandas as pd). Попытки лечить каждую уловку модели жёсткими запретами превращают профиль в нагромождение вредных ограничений, поэтому от таких правок нужно сознательно отказываться.
3. Не накапливать правила в промпте вечно. Модель GigaChat развивается с каждым релизом, и инструкции, писавшиеся как подстраховка под ранние сборки, со временем становятся избыточными. Если только добавлять правила в промпт и никогда их не чистить, то системный промпт разрастается, расходует контекстное окно и размывает внимание модели. Бенчмаркинг необходим в том числе для того, чтобы вовремя убирать из профиля GigaChat устаревшие инструкции, от которых модель уже «выросла».
По этой же причине команда LangChain регулярно сокращает объём базовой обвязки Deep Agents: в релизе 0.7 из стандартной сборки исключили планировщик задач (write_todos), так как свежие модели научились удерживать план без отдельного инструмента. В обвязке должны оставаться только те компоненты, необходимость которых подтверждается актуальными измерениями.
Что в итоге?
Настройка обвязки позволяет выжать максимум возможностей из модели в реальной среде. В нашем случае подключение профиля deepagents-gigachat дало рост метрик на 7–10 процентных пунктов на 391 задаче и срезало расход токенов практически вдвое — без единого изменения в весах самой модели.
Главные практические уроки этой работы:
Сочетание промптов и кодовых перехватчиков (middleware). Инструкции в системном промпте задают ориентиры поведения модели, а перехватчики гарантируют их выполнение на уровне кода Python в критических точках (нормализация путей, безопасная оболочка, остановка зацикливаний).
Учёт ограничений целевого провайдера. Настройка профиля требует соблюдения правил формата сообщений API (например, требование единственного SystemMessage в начале диалога у GigaChat API) и учёта характерных зацикливаний модели.
Непрерывное регрессионное тестирование. Локальный бенчмарк с детерминированными верификаторами помогает проверять эффект от каждой правки в обвязке и своевременно удалять устаревшие инструкции по мере обновления модели.
Понимание границ метода. Сбои, обусловленные фундаментальными ограничениями весов самой модели (например, особенности вычислений на тексте), не следует закрывать частыми правилами в обвязке — такие вещи устраняются только с выходом новых версий модели.
Полезные ссылки
репозиторий профиля deepagents-gigachat;
открытый бенчмарк harness-bench-fast;
прошлая статья по теме: «Deep Agents: опенсорсная обвязка на LangGraph»;
официальная документация по Deep Agents;
документация по GigaChat API;
мой TG-канал, где разбираю ИИ-агенты и их обвязки, делюсь материалами своих мастер-классов и полезными статьями экспертов.

