Deep Agents: open source-обвязка на LangGraph. gigachain.. gigachain. gigachat.. gigachain. gigachat. harness.. gigachain. gigachat. harness. harness engineering.. gigachain. gigachat. harness. harness engineering. langchain.. gigachain. gigachat. harness. harness engineering. langchain. langgraph.. gigachain. gigachat. harness. harness engineering. langchain. langgraph. агенты.. gigachain. gigachat. harness. harness engineering. langchain. langgraph. агенты. Блог компании Сбер.. gigachain. gigachat. harness. harness engineering. langchain. langgraph. агенты. Блог компании Сбер. искусственный интеллект.. gigachain. gigachat. harness. harness engineering. langchain. langgraph. агенты. Блог компании Сбер. искусственный интеллект. Машинное обучение.
Deep Agents: open source-обвязка на LangGraph - 1

Привет, на связи команда 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, — то в типичной обвязке их пять:

  1. Среда исполнения: файловая система, оболочка, выполнение кода. Агент перестаёт быть «говорящей головой с API-вызовами» и получает рабочее место, где результаты труда складываются в артефакты.

  2. Управление контекстом: агрегирование истории, выгрузка больших результатов в файлы, подгрузка знаний по требованию. Окно контекста перестаёт быть узким местом.

  3. Планирование: структурированный список задач как артефакт, а не абзац в рассуждениях.

  4. Делегирование: субагенты с изолированным контекстом для тяжёлых подзадач.

  5. Контроль: точки, где человек одобряет или отклоняет опасные действия.

Всё это — инженерия вокруг модели, и у неё уже есть имя: 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()):

Deep Agents: open source-обвязка на LangGraph - 2

Тот самый 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.

Deep Agents: open source-обвязка на LangGraph - 3

Один 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: тот же прогон там разворачивается в дерево спанов, где у каждого шага видны аргументы, результат, тайминг и токены.

Deep Agents: open source-обвязка на LangGraph - 4

Разберём, что здесь произошло:

  1. Изучение данных. Агент читает sales.csv через read_file — с пагинацией, чтобы понять структуру, не затягивая весь файл в контекст.

  2. Код. Через write_file создаёт analyze.py — обычный Python-скрипт с агрегацией выручки по регионам и месяцам.

  3. Первый запуск, поправка команды. execute с python analyze.py падает: python: command not found. Агент сам пробует python3 — уже рабочую команду.

  4. Второй запуск, исправление кода. Теперь падает сам скрипт: SyntaxError в незакрытой строке. Агент видит traceback, несколькими edit_file правит код (и в какой-то момент выносит его в отдельный run.py) и запускает снова — пока не получит exit code 0. Тот самый цикл «написал → запустил → исправил».

  5. Артефакт. Убедившись, что скрипт отработал, агент записывает его результаты в 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, утилиты и кукбуки с примерами агентов.

Полезные ссылки

Автор: trashchenkov

Источник