LLM Sandbox: пример реализации агента с песочницей [часть 2, практика]. agents.. agents. ai.. agents. ai. DevOps.. agents. ai. DevOps. docker.. agents. ai. DevOps. docker. llm.. agents. ai. DevOps. docker. llm. mlops.. agents. ai. DevOps. docker. llm. mlops. Natural Language Processing.. agents. ai. DevOps. docker. llm. mlops. Natural Language Processing. sandbox.. agents. ai. DevOps. docker. llm. mlops. Natural Language Processing. sandbox. агент.. agents. ai. DevOps. docker. llm. mlops. Natural Language Processing. sandbox. агент. Информационная безопасность.. agents. ai. DevOps. docker. llm. mlops. Natural Language Processing. sandbox. агент. Информационная безопасность. искусственный интеллект.. agents. ai. DevOps. docker. llm. mlops. Natural Language Processing. sandbox. агент. Информационная безопасность. искусственный интеллект. Машинное обучение.
LLM Sandbox: пример реализации агента с песочницей [часть 2, практика] - 1

Введение

Статья посвящена практической реализации агента с изолированной средой исполнения кода. Рассказываю как устроен агент, который пишет и исполняет код в Docker песочнице.

Это вторая часть серии про LLM Sandbox. В первой части мы разобрали риски исполнения кода от LLM, ограничения песочницы, способы изоляции (Docker, Wasm, gVisor, microVM) и минимальную архитектуру агент+песочница.

Код реализации агента, skills, полные логи и артефакты примера — в открытом GitHub-репозитории.

Пара слов обо мне

Меня зовут Евгений. Я разработчик и лид ML-команды. На работе и в свободное время занимаюсь проектами, связанными с агентами, LLM и обработкой естественного языка в целом. В своём тг-канале делюсь практическим опытом и рассказываю про техническую часть AI и ML. Ссылка на пост-навигатор канала.


Реализация агента с песочницей

В этом разделе посмотрим как агент реализован в репозитории: оркестратор, субагенты, skills и Docker Sandbox.

LLM Sandbox: пример реализации агента с песочницей [часть 2, практика] - 2

Архитектура агента

Если кратко, агент работает так:

  1. Получает задачу от пользователя.

  2. С помощью инструментов выполняет планирование и составляет набор подзадач.

  3. Назначает их субагентам.

  4. Субагент пишет код для решения подзадачи и исполняет его в песочнице.

  5. После выполнения подзадачи обновляется список задач.

  6. Артефакты, полученные в песочнице, сохраняются, а выходные файлы показываются в интерфейсе и доступны для скачивания.

  7. На финальном этапе оркестратор агрегирует решение всех подзадач и артефакты.

  8. Выдаёт пользователю ответ.

Основной паттерн работы агента – гибрид ReAct + Plan-Execute. Основной фреймворк – LangGraph.

Посмотрим подробнее на ключевые блоки.

Содержимое агента

LLM Sandbox: пример реализации агента с песочницей [часть 2, практика] - 3
  1. Оркестратормозг агента. Обрабатывает запрос пользователя, планирует решение задачи, включая декомпозицию, выдаёт задачи субагентам и синтезирует финальный ответ.

  2. Субагент — сессия с LLM и чистым контекстным окном. Оркестратор передаёт субагенту конкретную задачу и необходимый контекст. Так можно не перегружать основного агента, а выполнять подзадачи независимо.

  3. Внутри оркестратора и субагента находится LLM с отдельными системными промптами.

  4. Каталог навыков (skills) — набор подробных инструкций под конкретные задачи. Например, в skills/presentation в репозитории полностью описан процесс написания презентаций.

    Что такое skills, я писал в канале. Оркестратор и субагенты всегда видят название и описание навыка, а при необходимости подгружают полную инструкцию через инструмент read_skill.

  5. Инструменты для управления подзадачами.

    • create_todo_list — создаёт набор подзадач.

    • complete_todo — закрывает подзадачу при успешном решении.

    • start_next_todo — начинает работу над подзадачей.

    • revise_todo_list — пересобирает список подзадач, если текущий план перестал подходить.

    • block_todo — блокирует подзадачу, если решение пошло не по плану.

  6. Инструменты управления файлами.

    • read_skill — читает навык из файла и добавляет его в контекст.

    • list_input_files — список файлов, отправленных пользователем.

    • list_output_files — список файлов, созданных в ходе задачи.

    • read_file_content — читает файл.

  7. Инструменты исполнения кода.

    • write_python_file — подготавливает полный Python-скрипт и сохраняет в /workspace/code.

    • run_python_file — исполняет ранее подготовленный скрипт в песочнице.

  8. 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, для продуктивной среды может понадобиться более серьезная изоляция в несколько уровней. См. теоретическую часть.

Инструменты работы с 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-совместимую модель.

Шаги выполнения задачи

Первым шагом агент создаёт список подзадач:

  1. eda-analysis — первичный анализ данных.

  2. create-presentation — создание презентации.

# Plan

- [>] eda-analysis: Exploratory data analysis of basic_dataset.csv **(active)**
    - skills: eda, python-scripts
    - inputs: /workspace/input/basic_dataset.csv
    - success: EDA script runs successfully and prints key statistics, distributions, correlations, and missing value summaries to stdout.
    - outputs: /workspace/out/eda_summary.csv
- [ ] create-presentation: Create PowerPoint presentation with key EDA findings
    - skills: presentation, python-scripts
    - inputs: /workspace/input/basic_dataset.csv, /workspace/out/eda_summary.csv
    - success: A .pptx file is generated with slides covering dataset overview, survival analysis, demographics, fare analysis, and key insights.
    - outputs: /workspace/out/titanic_eda_presentation.pptx

Для каждой подзадачи заданы требуемые навыки, входные/выходные файлы и критерии успеха.

Второй шаг — start_next_todo. Агент просматривает доступные входные файлы через list_input_files:

status: success
summary: Listed 1 input file(s).

- /workspace/input/basic_dataset.csv (61194 bytes)

Читает фрагмент CSV через read_file_content, затем пишет код через write_python_file:

import pandas as pd
import numpy as np

df = pd.read_csv('/workspace/input/basic_dataset.csv')

# === SHAPE ===
print("=== SHAPE ===")
print(df.shape)
print()

# === DTYPES ===
print("=== DTYPES ===")
print(df.dtypes)
print()
... <truncated>

Результат:

status: success
summary: Staged `eda_analysis.py` (131 lines). Next: run_python_file({"script_path": "eda_analysis.py"}).

Срабатывает валидация: файл не может быть больше 120 строк — агент делит его на два скрипта.

Исполняет в песочнице через run_python_file:

status: success
summary: success: Python execution succeeded (exit_code=0).

exit_code: 0
timed_out: False
succeeded: True
STDOUT:
=== SHAPE ===
(891, 12)

=== DTYPES ===
PassengerId      int64
Survived         int64
Pclass           int64
Name               str
Sex                str
Age            float64
SibSp            int64
Parch            int64
Ticket             str
Fare           float64
Cabin              str
Embarked           str
dtype: object
...<truncated>

Аналогично создаётся /workspace/out/eda_summary.csv. Подзадача закрывается через complete_todo:

{
  "id": "eda-analysis",
  "notes": "EDA script ran successfully, printed key statistics/distributions/correlations/missing values to stdout, and saved /workspace/out/eda_summary.csv with 25 summary metrics."
}

Следующая подзадача — презентация. Агент подгружает skills presentation и python-scripts. В процессе возможны ошибки, которые агент исправляет в следующих итерациях работы с кодом:

STDERR:
Traceback (most recent call last):
  File "/workspace/code/deck_build_part1.py", line 11, in <module>
    add_bg_rect(slide, DARK)
  File "/workspace/code/deck_helpers.py", line 17, in add_bg_rect
    width = slide.slide_width
            ^^^^^^^^^^^^^^^^^
AttributeError: 'Slide' object has no attribute 'slide_width'. Did you mean: 'slide_id'?

Итоговый ответ агента (сокращённо):

## Результаты анализа и презентация

### 📊 Ключевые выводы EDA (Titanic, 891 пассажир)

|Метрика|Значение|
|---|---|
|**Общий уровень выживших**|~38%|
|**Пропущенные данные**|Age: 20%, Cabin: 77.1%, Embarked: 0.22%|
...

### 📁 Созданные файлы

|Файл|Описание|
|---|---|
|`titanic_eda_presentation.pptx`|Презентация из 9 слайдов с графиками и выводами|
|`eda_summary.csv`|Сводка из 25 метрик EDA|
|`chart_survival.png`|Общая выживаемость|
...

Полные логи и артефакты — в репозитории.

Токен-экономика запуска

Пример выше — не только демонстрация песочницы, но и типичный агентский запуск с заметной «стоимостью» в токенах. Все 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 мин)

LLM-вызовов

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-трейса):

Шаг

Фаза

Действие

LLM-вызовов

Input

Output

Cached

Всего токенов

Время

1

Оркестратор

create_todo_list — планирование

1

3 429

409

0

3 838

5,5 с

2

Оркестратор

start_next_todo — делегирование EDA

1

3 895

77

3 168

3 972

1,6 с

3

Субагент EDA

файлы → код → sandbox → eda_summary.csv

8

68 810

3 527

36 960

72 337

39,7 с

4

Оркестратор

start_next_todo — делегирование презентации

1

4 280

53

3 168

4 333

1,1 с

5

Субагент presentation

skills → код → sandbox → .pptx

17

274 700

11 832

204 864

286 532

127,6 с

6

Оркестратор

финальный ответ пользователю

1

4 799

653

4 224

5 452

7,0 с

Итого

29

359 913

16 551

252 384

376 464

182,5 с

Что видно из цифр:

  1. Оркестратор дешёвый — 17,6 тыс. токенов (4,7% от запуска). Он планирует, делегирует и синтезирует ответ, не таская в контекст логи.

  2. Субагент presentation — главный потребитель — 286,5 тыс. токенов (76%) и 128 с (70% времени). Причина: много итераций write_python_file / run_python_file, ошибки в коде (slide_width), повторные прогоны и растущий контекст.

  3. Контекст растёт по цепочке. Input на последнем LLM-вызове presentation-субагента — 24 706 токенов (против 4 445 на первом).

  4. Самые дорогие вызовы — генерация кода, а не sandbox. write_python_file с output 1 777 токенов vs run_python_file с output 56 токенов.

  5. Cache помогает, но не спасает от роста контекста. Суммарно из cache обслужено 252 384 токенов input (~70% у presentation-субагента).

Практический вывод: песочница изолирует исполнение, но не стоимость. Чтобы удержать token budget, нужны те же приёмы, что и в инжиниринге контекста: субагенты с чистым окном, лимиты на размер tool output, сокращение stdout/stderr, skills по требованию (read_skill), а не в каждый промпт целиком.


Заключение

На практике песочница встраивается в агентскую обвязку как один из инструментов агентского цикла — рядом с планированием, skills и файловыми операциями. Из этого запуска видно три вещи:

  • Docker Sandbox с жёсткими флагами — рабочий вариант для локального MVP: отдельная сессия для подзадачи/сессии исполнения кода в песочнице, изоляция исполнения, понятный обмен артефактами через /workspace/input и /workspace/out.

  • Оркестратор + субагенты помогают не смешивать планирование и «грязный» контекст исполнения кода.

  • Основное время тратится на работу LLM. 7 запусков кода заняли ~5 с; основная стоимость — в генерации и накоплении контекста.

При этом успех песочницы означает, что код исполнился и артефакты создались, а не то, что результат автоматически стал правильным. Для настоящих пользовательских отчётов всё равно нужны валидация содержания, проверка визуального качества и фактической информации.

Теорию про риски, ограничения и альтернативы (Wasm, gVisor, microVM) смотрите в первой части. Код, логи и все артефакты смотрите в репозитории на GitHub.

P.S. Спасибо за прочтение. Приглашаю в тг-канал «В погоне за NLP» (@chasing_nlp) — там про новости из мира AI/ML, анонсы новых статей и опыт разработки проектов с LLM и агентами.


Серия «LLM Sandbox»

  1. Часть 1. Теория — риски, ограничения, способы изоляции.

  2. Часть 2. Практика — эта статья.

Другие мои статьи

  1. Agent Harness: одна LLM, разные результаты — в чем секрет?

  2. Второй мозг и LLM-Wiki: Теория и практический гайд по созданию и поддержке личной базы знаний

Автор: chasing_nlp

Источник