
Привет, на связи команда GigaChain! Мы занимаемся агентными системами и развиваем набор open source-решений для подключения агентов к GigaChat API.
LangChain развивается с 2022 года и выросла в одну из самых популярных экосистем для агентов: у основной библиотеки более 140 тысяч звёзд на GitHub. Благодаря LangGraph — движку, который исполняет агент как граф с состоянием, — с её помощью собирают и простые цепочки вызовов, и сложные автономные агенты, ведущие задачу на сотни шагов. Поверх этого стека команда LangChain выпустила Deep Agents — готовую агентную обвязку (agent harness): рабочую среду с файловой системой, оболочкой, планировщиком, субагентами и памятью, в которую остаётся лишь поместить модель.
Сегодня мы подробно разберём эту обвязку: из чего она состоит и как собрать на ней агента, работающего на GigaChat.
Из чего состоит обвязка
Год назад Claude Code задумывали как агент для программирования, а потом случилось неожиданное: люди начали натравливать его на всё подряд — исследование, разбор инцидентов, аналитику, черновики документов. Оказалось, ценна не только способность писать код, но и среда, в которую эта способность помещена: рабочее место с файлами и оболочкой, планировщик задач, субагенты, память. Именно этот слой инженерной обвязки вокруг одной модели и называют агентной обвязкой. Он превращает модель из собеседника в агента: тот ведёт задачу на десятки и сотни шагов, работает в реальной среде и оставляет после себя реальные артефакты — кодовую базу, отчёт с таблицами, презентацию, да что угодно, что можно потом открыть и проверить.
В основе любой обвязки лежит всё тот же агентный цикл: модель получает историю сообщений и на каждом шаге решает, что делать дальше — вызвать инструмент или дать финальный ответ. Запрос на вызов инструмента (tool calling) модель возвращает в структурированном виде, обвязка исполняет инструмент и кладёт результат обратно в историю — и так до решения задачи. Как собрать такого ReAct-агента, мы подробно разбирали: на LangGraph — в отдельной статье, а на LangChain v1, через новый create_agent, — в докладе на AI Journey. Этот цикл никуда не делся, он внутри каждой обвязки.
Проблема в том, что в чистом виде цикл плохо переносит длинные задачи. Всё состояние агента живёт в одном месте — в списке сообщений, — и при длинной задаче это оборачивается сразу несколькими проблемами:
Контекст переполняется. Каждый вызов инструмента добавляет в историю результат, иногда на десятки тысяч токенов. Довольно быстро окно контекста забивается промежуточным мусором, и модель «забывает» начало задачи. У этого есть имя — context rot: чем длиннее контекст, тем хуже модель достаёт из него нужное.
План теряется. План действий существует только в тексте рассуждений модели — нет структуры, которая напоминала бы, что сделано, а что осталось. Поэтому в обвязках план выносят из рассуждений в отдельный артефакт — структурированный список задач со статусами.
Инструкции не масштабируются. Все процедуры и исключения под узкие сценарии приходится складывать в системный промпт; он раздувается, качество следования падает. Отсюда возникли навыки (skills) с прогрессивным раскрытием: в промпте постоянно висит только оглавление — имя и описание навыка, а полная инструкция подгружается, лишь когда навык понадобится.
Нет среды. Конечно, можно прикрутить отдельный инструмент — сохранить файл, дёрнуть API, — но целостного рабочего места у агента нет: ни файловой системы, куда складывать результаты, ни оболочки для выполнения кода; промежуточный результат кочует от шага к шагу только через контекст. А готовая файловая система снимает разом и это, и переполнение контекста.
Обвязка — это системный ответ на все четыре проблемы сразу. Если разобрать её на компоненты — примерно так их группируют и сами Deep Agents, — то в типичной обвязке их пять:
Среда исполнения: файловая система, оболочка, выполнение кода. Агент перестаёт быть «говорящей головой с API-вызовами» и получает рабочее место, где результаты труда складываются в артефакты.
Управление контекстом: агрегирование истории, выгрузка больших результатов в файлы, подгрузка знаний по требованию. Окно контекста перестаёт быть узким местом.
Планирование: структурированный список задач как артефакт, а не абзац в рассуждениях.
Делегирование: субагенты с изолированным контекстом для тяжёлых подзадач.
Контроль: точки, где человек одобряет или отклоняет опасные действия.
Всё это — инженерия вокруг модели, и у неё уже есть имя: harness engineering. Пять компонентов покрывают ядро обвязки, но не исчерпывают его: в более полных разборах компонентов ещё больше. Databricks, например, насчитывает восемь и добавляет к списку наблюдаемость и автоматическую проверку результата. Обе темы ещё встретятся: наблюдаемости посвящена отдельная глава, а к проверке результата мы вернёмся в последующих статьях.
Deep Agents: открытая обвязка
Deep Agents (пакет deepagents) — реализация обвязки от команды LangChain. Её место в стеке проще всего показать таблицей:
Слой |
Что даёт |
Deep Agents |
Готовая обвязка: планирование, файловая система, субагенты, память, навыки. |
LangChain |
Агенты, инструменты, middleware API. |
LangGraph |
Среда исполнения графа: состояние, checkpointing, streaming, прерывания. |
langchain-core |
Сообщения, модели, инструменты, Runnable. |
Читать стек удобно снизу вверх. В основании — langchain-core: общий словарь всей экосистемы (сообщения, модели, инструменты) и единый интерфейс Runnable, на котором всё держится. Над ним — LangGraph, среда исполнения, которая запускает агент как граф с состоянием. Это самый низкий уровень, где узлы, рёбра и сам цикл ты собираешь руками:
LangGraph за пять минут (если не сталкивались)
LangGraph описывает агент как граф состояний (StateGraph) из трёх сущностей: state — общая структура данных, которая течёт через весь граф и хранит всё накопленное (история сообщений, файлы, todos); узлы — функции-шаги (вызвать модель, выполнить инструмент); рёбра — переходы, причём условное ребро ветвит маршрут по состоянию.
from langgraph.graph import StateGraph, START, END builder = StateGraph(State) builder.add_node("model", call_model) builder.add_node("tools", run_tools) builder.add_edge(START, "model") builder.add_conditional_edges("model", route) # инструмент или выход builder.add_edge("tools", "model") graph = builder.compile(checkpointer=checkpointer)
LangGraph умеет сам нарисовать скомпилированный граф (graph.get_graph().draw_mermaid()):

Тот самый ReAct-цикл: модель либо зовёт инструмент и возвращается, либо выходит с ответом. Подробнее — в наших руководствах: ReAct-агент на LangGraph и агент на GigaChat + LangGraph от архитектуры до валидации.
Ещё выше — LangChain. Тут агент уже не нужно собирать из узлов: функция create_agent() возвращает готовый граф с ReAct-циклом — тем самым, что под спойлером, — в неё лишь передаёшь модель и инструменты. Чтобы вмешиваться в этот цикл, LangChain v1 даёт middleware — перехватчики, которые встраиваются между шагами агента и умеют проверить или подменить вызов инструмента, поправить промпт, изолировать контекст. Разбор механизма — в докладе «LangChain v1: укрощаем логику агентов через middleware». К самому middleware ещё вернёмся в конце, на нём построено почти всё, что делает Deep Agents.
На самом верху — Deep Agents. Сам по себе create_agent() — это только цикл; файловую систему, планировщик, субагенты и агрегирование пришлось бы городить поверх него вручную. Внутри себя create_deep_agent() вызывает тот же create_agent() и надстраивает над его циклом все компоненты обвязки из предыдущей главы, подключая их как middleware. Тебе остаётся только специализировать готовую обвязку содержимым: инструкциями, навыками, памятью. А LangGraph уже есть под капотом, так что при необходимости всегда можно уйти на уровень ниже.
У проекта две формы:
deepagents — библиотека (SDK) на Python: встраиваете агент в свой код, настраиваете модель, инструменты, бэкенд, память;
deepagents-code — готовый терминальный агент для программирования на той же обвязке, сделан по образцу Claude Code, но имеет открытый исходный код и работает с любой моделью.
В статье работаем с SDK — на нём виднее, как обвязка устроена изнутри.
Пройдёмся по этим компонентам, по тем же пяти, что и в главе «Из чего состоит обвязка»: среда исполнения, управление контекстом, планирование, делегирование, контроль. Теперь объясним на конкретных инструментах Deep Agents.
Среда исполнения: файловая система, бэкенды, shell
«Из коробки» deep-агент получает файловые инструменты: ls, read_file, write_file, edit_file, delete, glob, grep. Всё уже заточено под агент: read_file читает большие файлы порциями (offset и limit), edit_file правит точечно через замену строк, а grep и glob ищут по содержимому и по маске имён — писать это руками не нужно.
Важное уточнение: инструменты работают не напрямую с диском, а через бэкенд — подключаемую реализацию файловой системы. И бэкенд, и сами файловые инструменты даёт один и тот же FilesystemMiddleware. Подключать бэкенд вручную необязательно: не передашь ничего — встанет StateBackend из списка ниже, и инструменты продолжат работать как ни в чём не бывало, просто файлы лягут в состояние графа:
StateBackend(по умолчанию): виртуальные файлы, лежащие словарём (files) прямо в состоянии графа; на диск ничего не пишется, файлы живут в пределах одного треда. Знакомым с LangGraph: этот словарь чекпоинтится вместе со всем состоянием — сMemorySaverон, как и обычные чекпоинты, держится в памяти процесса.FilesystemBackend: реальные файлы под заданнымroot_dir. С флагомvirtual_mode=Trueпути нормализуются, и агент не может выйти за пределы корневой папки через..или абсолютные пути.LocalShellBackendрасширяетFilesystemBackendинструментомexecute: агент получает оболочку. Команды выполняются на вашей машине с рабочей директориейroot_dir; в инструкции кexecuteагенту велено адресоваться абсолютными путями и не использоватьcd, чтобы cwd не «плыла». При этомvirtual_modeоболочку не ограничивает — команды видят реальную ФС целиком, так что подход годится только для доверенной локальной среды.StoreBackendкладёт файлы в LangGraph Store: отдельное key-value-хранилище, общее для всех тредов агента (уStateBackendфайлы заперты в одном треде, а тут доступны отовсюду). Раскладываются они по namespace — ключу-кортежу вроде(user_id, "filesystem"), что даёт каждому пользователю свою изолированную «папку». Долговечность зависит от подключённой реализацииBaseStore:InMemoryStoreдержит данные в памяти, а реализация поверх Postgres — на диске, так что файлы переживают перезапуски. Отсюда и роль основы долговременной памяти.CompositeBackend— маршрутизатор: разные пути виртуальной ФС ведут в разные бэкенды.
Представим агент, который готовит вам еженедельный дайджест по нужным темам. Ему нужны три разных хранилища с разным сроком жизни. Пока идёт один прогон, он тянет кучу источников и складывает сырые выдержки — после сборки дайджеста этот ворох не нужен. Между запусками агент должен помнить контекст: какие темы вам интересны, в каком тоне вы любите выжимку, что уже присылал на прошлой неделе. Сам дайджест — это файл, который вы откроете и прочитаете. CompositeBackend разводит все три по одной виртуальной файловой системе:
from deepagents import create_deep_agent from deepagents.backends import ( CompositeBackend, StateBackend, StoreBackend, FilesystemBackend, ) agent = create_deep_agent( model=llm, backend=CompositeBackend( default=StateBackend(), # черновики прогона -- временно routes={ "/memories/": StoreBackend(), # память между запусками "/output/": FilesystemBackend( # готовый дайджест -- на диск root_dir="./output", virtual_mode=True, ), }, ), )
В коде это три адреса: default — временный StateBackend под черновики прогона; /memories/ ведёт в StoreBackend, переживающий перезапуск, — та самая память между запусками; /output/ ведёт в FilesystemBackend, где дайджест становится настоящим файлом в папке ./output.
Маршрутизация для агента невидима: агент пишет по обычным путям, а CompositeBackend в начале пути раскладывает файлы по хранилищам. Какой путь для чего — агент знает из инструкций: их задают в системном промпте или в AGENTS.md (память разберём в следующей главе).
Список бэкендов этим не исчерпывается — свой можно написать поверх чего угодно, хоть S3. Для продакшена особенно важны sandbox-бэкенды: безопасная замена LocalShellBackend, где execute выполняется в изоляции. В Deep Agents своя песочница — это подкласс BaseSandbox с реализованным execute, а изолированное окружение под ним может быть любым. Готовые партнёрские пакеты (Modal, Daytona, Vercel) зарубежные, но в наших реалиях подойдёт и self-hosted Docker с усиленной изоляцией (gVisor, Firecracker-microVM), и одноразовые контейнеры в любом российском облаке с serverless-контейнерами или управляемым Kubernetes.
Управление контекстом: агрегирование, выгрузка, память, навыки
Окно контекста ограничено, и на длинной задаче возникает вопрос: «Что держать в нём сейчас, а что подгружать по требованию?» Управление контекстом — то, что в индустрии зовут context engineering, — отвечает на первую из проблем, с которых мы начинали: переполнение контекста. В Deep Agents оно складывается из четырёх механизмов:
Агрегирование. Когда история разрастается, старые сообщения автоматически сжимаются в резюме. Порог настраивается параметрами
SummarizationMiddleware: trigger(когда сработать — по количеству токенов, сообщений или доле окна) иkeep(сколько свежих сообщений не трогать). Доля берётся от окна конкретной модели: LangChain читает его размер из профиля модели (max_input_tokens), поэтому 0,85 для окна в 200 тыс. — это около 170 тыс. токенов, а для 32 тыс. — около 27 тыс. Если профиля с размером окна у модели нет, то порог откатывается на фиксированные 170 тыс. токенов — для модели с меньшим окном его стоит задать вручную. Токены считаются приблизительно (count_tokens_approximately; можно подменить своимtoken_counter), точный токенизатор модели не нужен.Выгрузка (offloading). Делает
FilesystemMiddleware— тот, что раздаёт файловые инструменты: когда результат инструмента превышает порог (tool_token_limit_before_evict, по умолчанию 20 тыс. токенов; оценивается приблизительно — по длине текста, около 4 символов на токен), его тело уходит файлом в/large_tool_results/<tool_call_id>, а в контексте остаётся только превью (первые и последние 5 строк с пометкой, сколько пропущено в середине) и путь к файлу. Если понадобятся подробности, то агент дочитает файл черезread_fileпо частям или поищет нужное черезgrep.Память. У памяти два уровня. Небольшой необходимый контекст — инструкции и соглашения — задают параметром
memoryуcreate_deep_agent:MemoryMiddlewareгрузит перечисленные файлы (по стандарту agents.md, обычно AGENTS.md) в системный промпт при запуске, обёртывая их в блок<agent_memory>. Всё остальное — накопленные факты, заметки — живёт файлами в файловой системе (например, под/memories/вStoreBackend) и подтягивается агентом по требованию черезread_file; в промпт целиком оно не грузится. Пополняет память сам агент черезedit_file; соStoreBackendона переживает перезапуски.Навыки. Согласно стандарту agentskills.io это папки со SKILL.md-инструкциями и вспомогательными файлами. Работают по принципу прогрессивного раскрытия: в контексте постоянно живёт только короткий индекс — название, описание и путь к SKILL.md, — а полная инструкция подтягивается, только когда навык нужен. Это и есть тот механизм «специализации содержимым папки», о котором шла речь во введении. В deepagents этот индекс кладётся прямо в системный промпт (в других обвязках бывает иначе — в пользовательском промпте или в описании отдельного инструмента), а отдельного инструмента для чтения нет — агент берёт путь из индекса и открывает SKILL.md обычным
read_file.
Планирование: write_todos
Инструмент write_todos (middleware TodoListMiddleware) позволяет агенту вести структурированный список задач со статусами pending, in_progress, completed — он живёт в состоянии графа. План становится артефактом: агент отмечает пункты по мере выполнения и после каждого шага видит, что сделано.
Несколько подробностей реализации. Во-первых, план пересоздаётся целиком: каждый вызов write_todos перезаписывает весь список, поэтому за один ход инструмент вызывается максимум раз (параллельные вызовы создавали бы неоднозначность): агент шлёт свежую версию списка с обновлёнными статусами. Во-вторых, прогресс отражается статусами: выполненную задачу помечают completed и оставляют в списке (готовые пункты менять нельзя), а удаляют из плана только то, что стало неактуальным. В-третьих, для небольших задач план не нужен: в описании инструмента прямо сказано не использовать write_todos, если задача укладывается в три простых шага, чтобы не разводить бюрократию на ровном месте.
Делегирование: субагенты
Некоторые подзадачи тяжёлые сами по себе: чтобы решить одну, агент выполняет десятки шагов, перебирает источники, читает большие файлы, заходит в тупики. Если крутить всё это в основном контексте, то он забивается промежуточным шумом и теряет главную нить задачи. Субагент — способ вынести такую подзадачу в сторону: дочерний агент перемалывает всю возню в своём отдельном контексте и отдаёт наверх только чистый результат. Это называют карантином контекста (context quarantine): тяжёлую работу изолируют, а в контекст родителя попадает лишь выжимка.
Технически главный агент получает инструмент task (middleware SubAgentMiddleware) и запускает через него субагент нужного типа. «Из коробки» есть один — general-purpose: те же инструменты и возможности, что у главного, только в чистом контексте; его берут даже ради одной тяжёлой подзадачи, чтобы не топить основную ветку в подробностях. Свои субагенты добавляют декларативно: имя, описание (по нему главный решает, кого звать), системный промпт, набор инструментов, при желании — своя модель (provider:model, например подешевле для рутины), свой middleware и собственный interrupt_on. Можно и наоборот — подключить как субагент готовый скомпилированный граф.
Пара важных деталей. Субагент живёт только на время задачи и общается с родителем одним финальным сообщением — ни переспросить, ни дописать нельзя, поэтому задание должно быть самодостаточным, с явным указанием, что вернуть. Его результат не виден пользователю, родитель сам оформляет из него ответ. Несколько субагентов можно запустить параллельно. И тонкость изоляции: у субагента изолирован контекст сообщений, а файловая система общая — он работает с тем же бэкендом, что и родитель, так что тяжёлые данные удобно передавать через файлы, а не тащить в отчёт.
Контроль: human-in-the-loop
Контроль обеспечивает HumanInTheLoopMiddleware поверх механизма прерываний LangGraph. Параметр interrupt_on указывает, перед какими инструментами агент обязан остановиться и дождаться человека:
agent = create_deep_agent( model=llm, backend=backend, interrupt_on={"execute": True}, # каждая shell-команда -- только с одобрения checkpointer=checkpointer, )
Агент дойдёт до вызова execute, сохранит состояние в чекпоинт и остановится. Можно одобрить вызов, изменить аргументы или отклонить с комментарием — граф продолжит с того же места. Для агента с shell это не опция, а необходимость.
Под капотом: middleware
Прочитав про пять компонентов, вы наверняка заметили, что почти у каждого мы называли свой middleware — FilesystemMiddleware, TodoListMiddleware, SubAgentMiddleware и другие. Отойдём на шаг назад: всё это — один механизм, причём самого LangChain (мы отмечали его в ступени LangChain): deepagents им пользуется как есть. Middleware встраивают в граф агента через хуки двух типов: before_agent/before_model и after_model/after_agent — отдельные узлы вокруг вызова модели, и wrap_model_call/wrap_tool_call — обёртки самого вызова (узлов не создают, а перехватывают вызов модели или инструмента). create_deep_agent просто собирает готовый стек таких middleware поверх create_agent.

Один middleware не привязан к одному хуку, а может занимать сразу несколько (например, и вставлять узел before_model, и обёртывать вызов через wrap_model_call). Порядок в стеке важен: before_* выполняются сверху вниз по списку middleware, after_* — в обратном порядке, а wrap_* вкладываются луковицей: первый middleware обёртывает все остальные. Поэтому самые «внешние» проверки ставят в начало списка.
И в LangChain, и в Deep Agents уже есть целый набор готовых middleware — ограничение количества вызовов модели, повторы, фильтрация PII, выбор инструментов и другое; их каталог — в документации LangChain. Если нужного нет, то можно написать свой и поставить в стек рядом со встроенными.
Практика: агент действует в реальной среде
Компоненты разобрали, теперь соберём из них живого агента. На GigaChat поднимем агента-аналитика с доступом к файлам и оболочке: он читает данные с диска, пишет код, запускает его в bash, проверяет результат и собирает отчёт — тот самый воспроизводимый артефакт, о котором шла речь во вступлении.
Двигаться будем по нарастающей. Сначала поставим пакеты и запустим трассировку в Arize Phoenix, чтобы дальше видеть каждый шаг агента. Потом — минимальный агент, которому мы не даём ни одного своего инструмента (встроенные, из коробки, у него есть). Затем дадим ему файловую систему и оболочку и запустим на реальных данных. И под конец подключим навык.
Весь код этого раздела собран в воспроизводимый Jupyter-блокнот — он лежит в репозитории deepagents-gigachat, в папке examples/sales-analyst. В статье разберём его по частям.
Установка
Начнём с окружения. Понадобятся Python 3.12+ и три пакета: библиотека обвязки deepagents, интеграция GigaChat с LangChain langchain-gigachat и профиль deepagents-gigachat, о котором чуть ниже.
pip install "deepagents>=0.6.7" langchain-gigachat deepagents-gigachat
Профиль deepagents-gigachat подстраивает Deep Agents под особенности GigaChat: системный промпт, описания инструментов, дополнительный middleware. Он подключается автоматически, достаточно, чтобы пакет стоял в окружении, вызывать в коде ничего не нужно. Как он устроен и зачем — тема следующей статьи; здесь мы просто им пользуемся, чтобы примеры работали надёжно.
Ключ GigaChat API (как получить) и настройки кладём в файл .env рядом с кодом:
# .env GIGACHAT_CREDENTIALS=ваш_авторизационный_ключ GIGACHAT_SCOPE=GIGACHAT_API_PERS # физлицам; компаниям -- GIGACHAT_API_B2B / GIGACHAT_API_CORP GIGACHAT_MODEL=GigaChat-3-Ultra GIGACHAT_VERIFY_SSL_CERTS=False
Мы запускали примеры на модели GigaChat-3-Ultra. Вы можете подставить любую доступную вам модель с поддержкой функций — например, GigaChat-2-Max; актуальный список моделей и их идентификаторы — в документации GigaChat API.
В коде подхватим их через python-dotenv (pip install python-dotenv) — вызовом load_dotenv() в начале примера. langchain-gigachat сам возьмёт нужные переменные окружения (те, что с префиксом GIGACHAT_).
В примерах — deepagents 0.6.x (профиль GigaChat требует Python 3.12+ и deepagents ≥ 0.6.7). Обвязку быстро развивают: при воспроизведении сверяйтесь с changelog.
Наблюдаемость
Прежде чем запускать агент, стоит подготовить наблюдаемость (observability) и включить её до первого прогона, чтобы все шаги сразу записывались. У обвязки за одну задачу могут набегать десятки вызовов модели и инструментов; понять по «простыне» stdout, где агент свернул не туда, почти нереально, а в трассировке это видно сразу.
Под капотом у Deep Agents — обычный граф LangGraph, и LangChain предлагает смотреть трассы в своём облачном LangSmith. Но он проприетарный, с платными тарифами (бесплатный план ограничен), а мы обойдёмся open source: локальный Arize Phoenix с OpenInference-инструментированием LangChain собирает и хранит трассы целиком на вашей стороне. В них записи всего прогона, виден каждый шаг агента: вызовы модели и инструментов (каждый такой шаг называют спаном), содержимое контекста, расход токенов, ветки субагентов.
Трассы — не просто картинка в интерфейсе. Phoenix складывает их в базу (по умолчанию SQLite), а значит, они доступны как данные. С такой базой удобно работать кодинг-агенту: просишь его разобраться, почему прогон пошёл не так, — он сам запрашивает базу и собирает полную картину, вместо того чтобы вы вручную кликали по спанам.
Ставим пакеты и поднимаем Phoenix отдельным процессом — так он не зависит от нашего скрипта: переживает его перезапуски и продолжает копить трассы:
pip install arize-phoenix openinference-instrumentation-langchain phoenix serve # поднимет UI и коллектор на http://localhost:6006
В самом скрипте — только подключение трассировки (сервер уже поднят):
from phoenix.otel import register register( project_name="deepagents-gigachat", endpoint="http://localhost:6006/v1/traces", auto_instrument=True, # сам подключит OpenInference-обёртку для LangChain )
Теперь каждый запуск агента автоматически попадает в Phoenix, его видно в интерфейсе по адресу http://localhost:6006.
Минимальный агент: файловая система в памяти
Начнём с агента, которому не даём ни одного своего инструмента:
from dotenv import load_dotenv from deepagents import create_deep_agent from langchain_gigachat import GigaChat load_dotenv() # подхватит GIGACHAT_* из .env llm = GigaChat() # параметры модели берутся из окружения agent = create_deep_agent(model=llm, system_prompt="Ты полезный ассистент.") result = agent.invoke( {"messages": [{"role": "user", "content": "Составь план изучения LangGraph на неделю и сохрани его в файл plan.md"}]} ) print(result["files"].keys()) # dict_keys(['/plan.md'])
Мы не передавали инструменты, но агент справился — единственным вызовом встроенного write_file он сохранил план:
Трейс прогона
================================ Human Message ================================= Составь план изучения LangGraph на неделю и сохрани его в файл plan.md ================================== Ai Message ================================== Tool Calls: write_file (file_path: plan.md) ================================= Tool Message ================================= Updated file /plan.md ================================== Ai Message ================================== План изучения LangGraph на неделю успешно сохранен в файл plan.md.
Записан он не на диск, а в StateBackend — состояние графа. В result["files"] он под ключом /plan.md: агент просил сохранить plan.md, а виртуальная файловая система deepagents отсчитывает пути от своего корня /. Для агента это полноценная файловая система, для нас — просто запись в объекте состояния; на диске при этом ничего не создаётся.
Основной пример: файлы и оболочка
Теперь у агента есть файлы и оболочка, и мы дадим ему задачу посерьёзнее. В рабочей папке лежит CSV с продажами; агент должен сам разобраться в данных, написать скрипт анализа, выполнить его и оформить отчёт.
Сначала подготовим песочницу и данные:
import csv, random from pathlib import Path workspace = Path("workspace") workspace.mkdir(exist_ok=True) random.seed(42) with open(workspace / "sales.csv", "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["date", "region", "product", "qty", "price"]) for month in range(1, 7): for _ in range(50): writer.writerow([ f"2026-{month:02d}-{random.randint(1, 28):02d}", random.choice(["Москва", "СПб", "Казань", "Новосибирск"]), random.choice(["A", "B", "C"]), random.randint(1, 20), random.choice([990, 1490, 2490]), ])
Получилось полгода продаж (январь–июнь 2026) — по 50 строк на месяц, с регионом, товаром, количеством и ценой. Обычный CSV, каких много; никакой разметки «специально для агента» в нём нет.
Теперь создаём агент с LocalShellBackend — это файловые инструменты плюс оболочка:
from deepagents import create_deep_agent from deepagents.backends import LocalShellBackend from langgraph.checkpoint.memory import MemorySaver backend = LocalShellBackend(root_dir=str(workspace.resolve()), virtual_mode=True) agent = create_deep_agent( model=llm, backend=backend, checkpointer=MemorySaver(), system_prompt=( "Ты -- аналитик данных с доступом к файлам и shell. " "Работай итеративно: изучи данные, напиши код, запусти его, " "проверь результат. Используй относительные пути. " "Не выдумывай цифры -- бери их только из вывода своих скриптов." ), ) config = {"configurable": {"thread_id": "sales-analysis"}} result = agent.invoke( {"messages": [{"role": "user", "content": "В рабочей папке лежит sales.csv. Разберись в структуре данных, " "напиши Python-скрипт analyze.py, который считает выручку по регионам " "и по месяцам, запусти его и оформи отчёт report.md с таблицами и выводами."}]}, config=config, ) for msg in result["messages"]: msg.pretty_print()
Обратите внимание на «Используй относительные пути» в системном промпте — это сознательное отступление от стоковой инструкции execute, которая, наоборот, просит абсолютные пути и запрещает cd. Дело в том, что в virtual_mode=True абсолютные пути файловых инструментов и execute отличаются, а вот относительные — совпадают. Файловые инструменты (read_file, ls и другие) живут в виртуальной файловой системе, где корень / — это рабочая папка агента: для них /sales.csv — файл в этой папке. А execute передаёт команду настоящей оболочке, которая про виртуальную систему ничего не знает: для неё /sales.csv — файл в корне диска, и его там нет. Относительный же путь sales.csv в обоих случаях указывает на один и тот же файл, потому что оболочка выполняет команды прямо в рабочей папке.
Запускаем, и агент проходит всю задачу сам. Полная трасса с вызовами инструментов спрятана в спойлере (там интересно: скрипт с первого раза не завёлся, и видно, как агент чинит его по ошибкам). Ниже разберём ключевые шаги, так что заглядывать под спойлер необязательно.
Трейс прогона (сокращён — оставлены вызовы инструментов)
================================ Human Message ================================= В рабочей папке лежит sales.csv. Разберись в структуре данных, напиши Python-скрипт analyze.py, ... оформи отчёт report.md с таблицами и выводами. ================================== Ai Message ================================== Tool Calls: read_file (file_path: sales.csv) ================================== Ai Message ================================== Tool Calls: write_file (file_path: analyze.py) ================================== Ai Message ================================== Tool Calls: execute (command: python analyze.py) ================================= Tool Message ================================= [stderr] /bin/sh: python: command not found Exit code: 127 ================================== Ai Message ================================== Tool Calls: execute (command: python3 analyze.py) ================================= Tool Message ================================= [stderr] "| Регион | Выручка | SyntaxError: EOL while scanning string literal Exit code: 1 ================================== Ai Message ================================== Tool Calls: edit_file (file_path: analyze.py) ... ещё несколько edit_file ... ================================== Ai Message ================================== Tool Calls: execute (command: python3 run.py) ================================= Tool Message ================================= [Command succeeded with exit code 0] ================================== Ai Message ================================== Tool Calls: read_file (file_path: report.md)
Текстовый вывод удобен для чтения, но настоящую картину даёт Phoenix: тот же прогон там разворачивается в дерево спанов, где у каждого шага видны аргументы, результат, тайминг и токены.

Разберём, что здесь произошло:
Изучение данных. Агент читает sales.csv через
read_file— с пагинацией, чтобы понять структуру, не затягивая весь файл в контекст.Код. Через
write_fileсоздаёт analyze.py — обычный Python-скрипт с агрегацией выручки по регионам и месяцам.Первый запуск, поправка команды.
executeс python analyze.py падает: python: command not found. Агент сам пробует python3 — уже рабочую команду.Второй запуск, исправление кода. Теперь падает сам скрипт:
SyntaxErrorв незакрытой строке. Агент видитtraceback, несколькимиedit_fileправит код (и в какой-то момент выносит его в отдельный run.py) и запускает снова — пока не получитexit code 0. Тот самый цикл «написал → запустил → исправил».Артефакт. Убедившись, что скрипт отработал, агент записывает его результаты в report.md в виде таблиц и выводов.
Значения в отчёте агент посчитал реально выполненным скриптом и прочитал из его вывода. После прогона в workspace/ лежат настоящие файлы: sales.csv, analyze.py, report.md (плюс пара вспомогательных, которые агент создал по ходу работы). Скрипт можно перезапустить руками, отчёт можно открыть и проверить. Так работает агент в среде: после него остаются воспроизводимые артефакты, которые живут отдельно от диалога.
С флагом virtual_mode=True файловые инструменты агента работают внутри workspace/ как внутри корня: пути с .. и ~ отклоняются, а каждый итоговый путь проверяется на выход за пределы папки — наружу файловым инструментам не выбраться. execute так не ограничить: команды оболочки выполняются на вашей машине по-настоящему, со всеми правами вашего пользователя. Для локальных экспериментов это приемлемо, но в эксплуатации нужен sandbox-бэкенд с изоляцией.
Навыки (Skills)
Покажем механизм навыков. Допустим, у команды есть стандарт оформления отчётов — держать его в системном промпте не хочется: он большой, а нужен не в каждом запросе. Вынесем его в навык. И добавим второй, не относящийся к задаче, — проверку качества данных: с одним навыком выбирать не из чего, а на двух видно, как агент по короткому описанию берёт нужный и не трогает лишний.
Навык — это папка со SKILL.md (имя и описание во Front Matter, дальше инструкция) и, при желании, вспомогательными файлами рядом; наш sales-report ссылается на лежащий рядом шаблон template.md. Обе папки-навыка уже лежат в workspace/skills/ — агенту достаточно указать путь к этой директории, найти и раскрыть нужный он сумеет сам. Собираем агент с навыками:
from dotenv import load_dotenv from deepagents import create_deep_agent from deepagents.backends import LocalShellBackend from langgraph.checkpoint.memory import MemorySaver from langchain_gigachat import GigaChat load_dotenv() llm = GigaChat() # В workspace/skills/ заранее подготовлены две папки-навыка: # sales-report/ -- SKILL.md + template.md (стандарт оформления отчёта) # data-quality-check/ -- SKILL.md (проверка данных) backend = LocalShellBackend(root_dir="workspace", virtual_mode=True) agent = create_deep_agent( model=llm, backend=backend, skills=["/skills"], # виртуальный путь: корень бэкенда = workspace/ checkpointer=MemorySaver(), system_prompt=( "Ты -- аналитик данных с доступом к файлам и shell. " "Работай итеративно, не выдумывай цифры -- бери их из вывода своих скриптов." ), ) result = agent.invoke( {"messages": [{"role": "user", "content": "Подготовь отчёт по продажам из sales.csv по нашим правилам оформления."}]}, config={"configurable": {"thread_id": "sales-report-skill"}}, )
Запускаем — и в трассе видно, как агент выбирает нужный навык, раскрывает его и выполняет задачу:
Трейс прогона (сокращён)
Tool Calls: read_file (file_path: /skills/sales-report/SKILL.md) # выбрал нужный навык Tool Calls: read_file (file_path: /skills/sales-report/template.md) # подтянул шаблон по ссылке Tool Calls: read_file (file_path: sales.csv) Tool Calls: write_file (file_path: run.py) Tool Calls: execute (command: python run.py) [stderr] /bin/sh: python: command not found → Exit code: 127 Tool Calls: execute (command: python3 run.py) → exit code 0 Tool Calls: read_file (file_path: report.md)
Получив задачу «подготовь отчёт по продажам», агент из двух навыков выбрал релевантный — sales-report, — прочитал его SKILL.md, а следом и template.md, на который тот ссылается. Во второй навык (data-quality-check) он не полез: задача не про это. Полная инструкция заняла место в контексте только тогда, когда понадобилась, — в этом и есть прогрессивное раскрытие. Дальше — тот же цикл, что и в примере с аналитиком: агент пишет скрипт, сам чинит команду python→python3, получает report.md. Только теперь отчёт оформлен по стандарту из навыка.
На этом практика закончена. Из одного create_deep_agent() мы собрали агент, дали ему файловую систему, оболочку и навыки — и он прошёл реальную задачу от сырого CSV до готового отчёта, оставив после себя проверяемые артефакты. Ни одного специализированного агента писать не пришлось: вся настройка — содержимым среды. Перейдем к выводам.
Выводы
Обвязки стали де-факто стандартом разработки агентов в любой сфере: кодинг-агенты, персональные ассистенты, исследовательские и аналитические агенты. За разными задачами один узнаваемый набор: файловая система, планировщик, память, субагенты, навыки.
Deep Agents — открытая реализация этого стандарта на надёжном стеке: внутри тот же LangGraph с чекпоинтами и стримингом, снаружи —
create_deep_agent()с готовой обвязкой. Специализируешь его содержимым среды: инструкциями, навыками, памятью.Все примеры выше работают на GigaChat через langchain-gigachat: обвязка не привязана к конкретной модели — в этом её главная сила. Под Deep Agents GigaChat работает и без профиля, но с профилем deepagents-gigachat — заметно лучше: он подстраивает обвязку под особенности GigaChat и повышает её результаты на агентных задачах.
За этим профилем — отдельная работа. Как мы собирали deepagents-gigachat и как создали и регулярно обновляем открытый бенчмарк harness-bench-fast, на котором измеряем его эффект, расскажем в следующей статье.
Заходите в репозитории нашей команды на GitHub и GitVerse — там библиотеки интеграции GigaChat с экосистемой LangChain/LangGraph, утилиты и кукбуки с примерами агентов.
Полезные ссылки
Jupyter-ноутбук с примерами кода из статьи;
документация по Deep Agents;
профиль deepagents-gigachat;
документация по GigaChat API;
наши прошлые статьи: «Современный ReAct-агент» и агент Lean Canvas
Arize Phoenix — open source-фреймворк для сбора трасс из LLM-приложений.
мой TG-канал, где разбираю ИИ-агенты и их обвязки, делюсь материалами своих мастер-классов и полезными статьями экспертов.
gybson_63
А ACP вам чем не угодил? Уже как-то поздно велосипедить агентов на питоне, не?
trashchenkov Автор
Ничего против ACP не имею :) Но ACP сам по себе же не заменяет харнесс. Он про то, как агента подключить к IDE или еще какому-либо клиенту. А саму внутреннюю механику агента — цикл, работу с контекстом, планирование, субагентов и т. д. — все равно надо либо реализовать самому, либо взять готовую в виде Claude Code, OpenCode, Deep Agents и прочих харнессов.
Для этого у LangChain для этого уже есть отдельная интеграция
deepagents-acp: она позволяет агенту на их стеке работать как ACP-сервер.