![LLM Sandbox: пример реализации агента с песочницей [часть 2, практика]](/images/cyber/49715169bf67bb87.jpg)
Статья посвящена практической реализации агента с изолированной средой исполнения кода. Рассказываю как устроен агент, который пишет и исполняет код в Docker песочнице.
Это вторая часть серии про LLM Sandbox. В первой части мы разобрали риски исполнения кода от LLM, ограничения песочницы, способы изоляции (Docker, Wasm, gVisor, microVM) и минимальную архитектуру агент+песочница.
Код реализации агента, skills, полные логи и артефакты примера — в открытом GitHub-репозитории .
Меня зовут Евгений. Я разработчик и лид ML-команды. На работе и в свободное время занимаюсь проектами, связанными с агентами, LLM и обработкой естественного языка в целом. В своём тг-канале делюсь практическим опытом и рассказываю про техническую часть AI и ML. Ссылка на пост-навигатор канала .
Реализация агента с песочницей
В этом разделе посмотрим как агент реализован в репозитории : оркестратор, субагенты, skills и Docker Sandbox.
Если кратко, агент работает так:
Получает задачу от пользователя.
Получает задачу от пользователя.
С помощью инструментов выполняет планирование и составляет набор подзадач.
С помощью инструментов выполняет планирование и составляет набор подзадач.
Назначает их субагентам.
Назначает их субагентам.
Субагент пишет код для решения подзадачи и исполняет его в песочнице.
Субагент пишет код для решения подзадачи и исполняет его в песочнице.
После выполнения подзадачи обновляется список задач.
После выполнения подзадачи обновляется список задач.
Артефакты, полученные в песочнице, сохраняются, а выходные файлы показываются в интерфейсе и доступны для скачивания.
Артефакты, полученные в песочнице, сохраняются, а выходные файлы показываются в интерфейсе и доступны для скачивания.
На финальном этапе оркестратор агрегирует решение всех подзадач и артефакты.
На финальном этапе оркестратор агрегирует решение всех подзадач и артефакты.
Выдаёт пользователю ответ.
Выдаёт пользователю ответ.
Основной паттерн работы агента - гибрид ReAct + Plan-Execute. Основной фреймворк - LangGraph.
Посмотрим подробнее на ключевые блоки.
Оркестратор — мозг агента. Обрабатывает запрос пользователя, планирует решение задачи, включая декомпозицию, выдаёт задачи субагентам и синтезирует финальный ответ.
Оркестратор — мозг агента. Обрабатывает запрос пользователя, планирует решение задачи, включая декомпозицию, выдаёт задачи субагентам и синтезирует финальный ответ.
Субагент — сессия с LLM и чистым контекстным окном. Оркестратор передаёт субагенту конкретную задачу и необходимый контекст. Так можно не перегружать основного агента, а выполнять подзадачи независимо.
Субагент — сессия с LLM и чистым контекстным окном. Оркестратор передаёт субагенту конкретную задачу и необходимый контекст. Так можно не перегружать основного агента, а выполнять подзадачи независимо.
Внутри оркестратора и субагента находится LLM с отдельными системными промптами.
Внутри оркестратора и субагента находится LLM с отдельными системными промптами.
Каталог навыков (skills) — набор подробных инструкций под конкретные задачи. Например, в skills/presentation в репозитории полностью описан процесс написания презентаций. Что такое skills, я писал в канале ( ссылка на пост ). Оркестратор и субагенты всегда видят название и описание навыка, а при необходимости подгружают полную инструкцию через инструмент read_skill .
Каталог навыков (skills) — набор подробных инструкций под конкретные задачи. Например, в skills/presentation в репозитории полностью описан процесс написания презентаций.
Что такое skills, я писал в канале ( ссылка на пост ). Оркестратор и субагенты всегда видят название и описание навыка, а при необходимости подгружают полную инструкцию через инструмент read_skill .
Что такое skills, я писал в канале ( ссылка на пост ). Оркестратор и субагенты всегда видят название и описание навыка, а при необходимости подгружают полную инструкцию через инструмент read_skill .
Инструменты для управления подзадачами. create_todo_list — создаёт набор подзадач. complete_todo — закрывает подзадачу при успешном решении. start_next_todo — начинает работу над подзадачей. revise_todo_list — пересобирает список подзадач, если текущий план перестал подходить. block_todo — блокирует подзадачу, если решение пошло не по плану.
Инструменты для управления подзадачами.
create_todo_list — создаёт набор подзадач.
create_todo_list — создаёт набор подзадач.
complete_todo — закрывает подзадачу при успешном решении.
complete_todo — закрывает подзадачу при успешном решении.
start_next_todo — начинает работу над подзадачей.
start_next_todo — начинает работу над подзадачей.
revise_todo_list — пересобирает список подзадач, если текущий план перестал подходить.
revise_todo_list — пересобирает список подзадач, если текущий план перестал подходить.
block_todo — блокирует подзадачу, если решение пошло не по плану.
block_todo — блокирует подзадачу, если решение пошло не по плану.
Инструменты управления файлами. read_skill — читает навык из файла и добавляет его в контекст. list_input_files — список файлов, отправленных пользователем. list_output_files — список файлов, созданных в ходе задачи. read_file_content — читает файл.
Инструменты управления файлами.
read_skill — читает навык из файла и добавляет его в контекст.
read_skill — читает навык из файла и добавляет его в контекст.
list_input_files — список файлов, отправленных пользователем.
list_input_files — список файлов, отправленных пользователем.
list_output_files — список файлов, созданных в ходе задачи.
list_output_files — список файлов, созданных в ходе задачи.
read_file_content — читает файл.
read_file_content — читает файл.
Инструменты исполнения кода. write_python_file — подготавливает полный Python-скрипт и сохраняет в /workspace/code . run_python_file — исполняет ранее подготовленный скрипт в песочнице.
Инструменты исполнения кода.
write_python_file — подготавливает полный Python-скрипт и сохраняет в /workspace/code .
write_python_file — подготавливает полный Python-скрипт и сохраняет в /workspace/code .
run_python_file — исполняет ранее подготовленный скрипт в песочнице.
run_python_file — исполняет ранее подготовленный скрипт в песочнице.
Docker Sandbox — песочница для изолированного исполнения кода. В режиме субагента создаётся отдельный Docker контейнер на время выполнения подзадачи и серии sandbox-вызовов. Команда запуска описана в теоретической части , флаги: --network=none , --read-only , --cap-drop=ALL , --security-opt=no-new-privileges , non-root user, лимиты CPU/RAM/PIDs, tmpfs для /tmp , read-only mount для /workspace/input и /workspace/code .
Docker Sandbox — песочница для изолированного исполнения кода. В режиме субагента создаётся отдельный Docker контейнер на время выполнения подзадачи и серии sandbox-вызовов. Команда запуска описана в теоретической части , флаги: --network=none , --read-only , --cap-drop=ALL , --security-opt=no-new-privileges , non-root user, лимиты CPU/RAM/PIDs, tmpfs для /tmp , read-only mount для /workspace/input и /workspace/code .
Важно: Docker с такими флагами — практичная изоляция для локального MVP, для продуктивной среды может понадобиться более серьезная изоляция в несколько уровней. См. теоретическую часть.
Важно: Docker с такими флагами — практичная изоляция для локального MVP, для продуктивной среды может понадобиться более серьезная изоляция в несколько уровней. См. теоретическую часть.
Инструменты работы с Bash умышленно не предоставляются агенту для прозрачной и контролируемой работы с заданными инструментами.
В интерфейсе показывается весь путь агента: текущий план, закрытые подзадачи, вызовы инструментов и их результаты. Полные логи — в подключённом Langfuse , а локально — в agent/runs/<run_id>/events.jsonl , agent/traces/<run_id>.jsonl и agent/web-artifacts/<run_id>/ .
Пример работы агента
Для наглядности посмотрим на конкретный запуск.
Задача (промпт)
Проанализируй набор данных и подготовь презентацию с ключевыми выводами EDA.
Набор данных
Датасет соревнования «Титаник» .
P.S. Модели видели этот датасет множество раз при обучении, и написать код аналитики несложно. Цель примера — показать взаимодействие агента с песочницей, а не способность LLM писать код.
Qwen3.5-27B — модель, которую можно развернуть на доступном железе. В репозитории это значение используется как дефолт ( Qwen/Qwen3.5-27B ), но его можно заменить через LLM_MODEL на любую OpenAI-совместимую модель.
Шаги выполнения задачи
Первым шагом агент создаёт список подзадач:
eda-analysis — первичный анализ данных.
eda-analysis — первичный анализ данных.
create-presentation — создание презентации.
create-presentation — создание презентации.
Для каждой подзадачи заданы требуемые навыки, входные/выходные файлы и критерии успеха.
Второй шаг — start_next_todo . Агент просматривает доступные входные файлы через list_input_files :
Читает фрагмент CSV через read_file_content , затем пишет код через write_python_file :
Результат:
Срабатывает валидация: файл не может быть больше 120 строк — агент делит его на два скрипта.
Исполняет в песочнице через run_python_file :
Аналогично создаётся /workspace/out/eda_summary.csv . Подзадача закрывается через complete_todo :
Следующая подзадача — презентация. Агент подгружает skills presentation и python-scripts . В процессе возможны ошибки, которые агент исправляет в следующих итерациях работы с кодом:
Итоговый ответ агента (сокращённо):
Полные логи и артефакты — в репозитории .
Пример выше — не только демонстрация песочницы, но и типичный агентский запуск с заметной «стоимостью» в токенах. Все LLM-вызовы, тайминги и usage отслеживались через Langfuse. Ниже — разбор run feec9b8b1e754c018d6de3f2f09d4f8c (Titanic + презентация, Qwen3.5-27B, OpenRouter/OpenAI-совместимый API).
Что считаем. На каждом шаге цикла модель получает промпт ( input_tokens ), генерирует ответ ( output_tokens ). Часть промпта может быть взята из KV-cache ( cached_input_tokens ). Песочница и файловые инструменты сами по себе токены не тратят, но результаты их работы возвращаются в контекст — и именно это раздувает входной запрос в модель на следующих шагах. В таблице ниже суммируются все вложенные LLM-вызовы оркестратора и субагентов из Langfuse.
Время работы
182,6 с (~3 мин)
29 (4 оркестратор + 8 субагент EDA + 17 субагент presentation)
Input tokens
359 913
Output tokens
16 551
Всего (input + output)
376 464
Запусков кода в песочнице
7 (~5 с суммарно)
Sandbox занял ~3% общего времени работы. Основная задержка — ожидание LLM и накопление контекста, а не исполнение Python.
Разбивка по фазам (данные Langfuse-трейса):
Всего токенов
create_todo_list — планирование
3 429
409
3 838
5,5 с
start_next_todo — делегирование EDA
3 895
3 168
3 972
1,6 с
файлы → код → sandbox → eda_summary.csv
68 810
3 527
36 960
72 337
39,7 с
start_next_todo — делегирование презентации
4 280
3 168
4 333
1,1 с
Субагент presentation
skills → код → sandbox → .pptx
274 700
11 832
204 864
286 532
127,6 с
финальный ответ пользователю
4 799
653
4 224
5 452
7,0 с
359 913
16 551
252 384
376 464
182,5 с
Что видно из цифр:
Оркестратор дешёвый — 17,6 тыс. токенов (4,7% от запуска). Он планирует, делегирует и синтезирует ответ, не таская в контекст логи.
Оркестратор дешёвый — 17,6 тыс. токенов (4,7% от запуска). Он планирует, делегирует и синтезирует ответ, не таская в контекст логи.
Субагент presentation — главный потребитель — 286,5 тыс. токенов (76%) и 128 с (70% времени). Причина: много итераций write_python_file / run_python_file , ошибки в коде ( slide_width ), повторные прогоны и растущий контекст.
Субагент presentation — главный потребитель — 286,5 тыс. токенов (76%) и 128 с (70% времени). Причина: много итераций write_python_file / run_python_file , ошибки в коде ( slide_width ), повторные прогоны и растущий контекст.
Контекст растёт по цепочке. Input на последнем LLM-вызове presentation-субагента — 24 706 токенов (против 4 445 на первом).
Контекст растёт по цепочке. Input на последнем LLM-вызове presentation-субагента — 24 706 токенов (против 4 445 на первом).
Самые дорогие вызовы — генерация кода , а не sandbox. write_python_file с output 1 777 токенов vs run_python_file с output 56 токенов.
Самые дорогие вызовы — генерация кода , а не sandbox. write_python_file с output 1 777 токенов vs run_python_file с output 56 токенов.
Cache помогает, но не спасает от роста контекста. Суммарно из cache обслужено 252 384 токенов input (~70% у presentation-субагента).
Cache помогает, но не спасает от роста контекста. Суммарно из cache обслужено 252 384 токенов input (~70% у presentation-субагента).
Практический вывод: песочница изолирует исполнение , но не стоимость . Чтобы удержать token budget, нужны те же приёмы, что и в инжиниринге контекста: субагенты с чистым окном, лимиты на размер tool output, сокращение stdout/stderr, skills по требованию ( read_skill ), а не в каждый промпт целиком.
На практике песочница встраивается в агентскую обвязку как один из инструментов агентского цикла — рядом с планированием, skills и файловыми операциями. Из этого запуска видно три вещи:
Docker Sandbox с жёсткими флагами — рабочий вариант для локального MVP: отдельная сессия для подзадачи/сессии исполнения кода в песочнице, изоляция исполнения, понятный обмен артефактами через /workspace/input и /workspace/out .
Docker Sandbox с жёсткими флагами — рабочий вариант для локального MVP: отдельная сессия для подзадачи/сессии исполнения кода в песочнице, изоляция исполнения, понятный обмен артефактами через /workspace/input и /workspace/out .
Оркестратор + субагенты помогают не смешивать планирование и «грязный» контекст исполнения кода.
Оркестратор + субагенты помогают не смешивать планирование и «грязный» контекст исполнения кода.
Основное время тратится на работу LLM. 7 запусков кода заняли ~5 с; основная стоимость — в генерации и накоплении контекста.
Основное время тратится на работу LLM. 7 запусков кода заняли ~5 с; основная стоимость — в генерации и накоплении контекста.
При этом успех песочницы означает, что код исполнился и артефакты создались, а не то, что результат автоматически стал правильным. Для настоящих пользовательских отчётов всё равно нужны валидация содержания, проверка визуального качества и фактической информации.
Теорию про риски, ограничения и альтернативы (Wasm, gVisor, microVM) смотрите в первой части . Код, логи и все артефакты смотрите в репозитории на GitHub .
P.S. Спасибо за прочтение. Приглашаю в тг-канал « В погоне за NLP » (@chasing_nlp) — там про новости из мира AI/ML, анонсы новых статей и опыт разработки проектов с LLM и агентами.
Серия «LLM Sandbox»
Часть 1. Теория — риски, ограничения, способы изоляции.
Часть 1. Теория — риски, ограничения, способы изоляции.
Часть 2. Практика — эта статья.
Часть 2. Практика — эта статья.
Другие мои статьи