Недавно релизнулся интересный open-source проект от Tencent — AutoCodeBenchmark. Это целый фреймворк, который автоматизирует создание, проверку и даже описание задач по программированию. По сути, это конвейер, где на входе — простейший кусок кода, а на выходе — полноценный бенчмарк с задачами, решениями и тестами на 20 языках.
Давайте заглянем под капот и разберем, как эта штука устроена. Тут вам и LLM, и куча Python-скриптов, и хитрая "песочница" для безопасного выполнения кода.
Общая архитектура: два столпа системы
Если посмотреть на репозиторий, то вся система стоит на двух китах:
- AutoCodeGen — это "фабрика" контента. Здесь происходит магия: из простых "семян" (seeds) кода с помощью LLM выращиваются полноценные задачи. Этот компонент отвечает за всю логику генерации.
- MultiLanguageSandbox — это "испытательный полигон". Безопасная среда, где сгенерированный код компилируется и выполняется. Без этой песочницы вся система была бы просто очередной говорилкой, а так у нее есть кулаки — возможность проверить, работает ли то, что она нагенерировала.
Взаимодействие этих двух компонентов и есть сердце AutoCodeBenchmark. AutoCodeGen
выступает в роли "креативного директора", который ставит задачи LLM, а MultiLanguageSandbox
— в роли "ОТК" (отдела технического контроля), который безжалостно отбраковывает нерабочий код. Этот цикл "генерация-проверка" и позволяет на выходе получать качественный контент.
А теперь разберем каждый компонент в деталях.
AutoCodeGen: Фабрика задач по программированию
Это мозг всей операции, настоящий конвейер, который превращает простейшие идеи в полноценные учебные материалы. Процесс разбит на четкие, последовательные шаги, где каждый скрипт в директории AutoCodeGen/src
выполняет свою конкретную функцию. Давайте пройдемся по всему этому пайплайну.

Шаг 1: Посев. Начинаем с простого
Все начинается с "семян" — seeds
. В директории AutoCodeGen/data/seeds/
лежат jsonl
файлы для нескольких языков. Внутри каждого — до смешного простые примеры кода.
Например, python.jsonl
содержит:
{"text": "def add_two_numbers(a, b):\n return a + b", "language": "python"}
Это наша отправная точка. Идея в том, что даже из такой примитивной функции можно "вырастить" что-то более интересное.
Шаг 2: Эволюция. Просим LLM усложнить задачу
Скрипт build_msg_for_solution.py
берет этот seed и, используя шаблон из templates/gen_code_solution_templates/
, формирует промпт для LLM.
[!INFO] Суть промпта: "Эй, LLM. Вот тебе простая функция. Преврати ее во что-то более сложное и осмысленное. А еще напиши для нее два набора тестов:
demo_testing()
с парой простых примеров иfull_testing()
с кучей кейсов, включая пограничные. Выдай все в трех отдельных блоках кода."
LLM (call_api.py
отправляет запрос) получает эту инструкцию и начинает творить. Например, из простого сложения двух чисел она может сделать функцию для суммирования элементов в сложных вложенных структурах данных или что-то в этом роде.
Затем extract_three_code_blocks.py
парсит ответ модели, извлекая из него три части:
canonical_solution
: Усложненная версия исходной функции.demo_test_func
: Простые демонстрационные тесты.full_test_func
: Полный набор тестов для проверки.
Шаг 3: Первая проверка в песочнице. Работает ли то, что получилось?
Теперь в дело вступает MultiLanguageSandbox
. Скрипт call_sandbox.py
берет сгенерированные canonical_solution
и оба тестовых набора (demo_test_func
и full_test_func
) и отправляет их на выполнение.
[!NOTE] Важный момент: На этом этапе тесты еще не являются классическими unit-тестами с
assert
. Они просто вызывают функцию с разными входами и печатают результат в консоль. Цель этой проверки — убедиться, что сгенерированный код в принципе запускается, не падает с ошибкой и выдает какой-то предсказуемый вывод.
Результат этого запуска (то, что было напечатано в консоль) сохраняется. Если код упал или отработал некорректно, вся заготовка (задача + решение + тесты) отправляется в брак.
Шаг 4: Генерация верифицированных тестов
И вот тут начинается самое интересное. Скрипт build_msg_for_test.py
берет успешно прошедшие проверку данные и формирует новый промпт для LLM.
[!TIP] Суть промпта: "Смотри, LLM. Вот функция (
canonical_solution
). Вот тестовые вызовы (demo_test_input
) и вот реальный результат их выполнения из песочницы (demo_test_output
). Сделай из этого нормальные unit-тесты сassert
."
Мы не просто просим нейросеть написать тесты из головы. Мы даем ей фактические, верифицированные данные о поведении кода и просим обернуть их в формат assert
. Это на порядок повышает качество и корректность финальных тестов.
После ответа модели скрипт extract_two_code_blocks.py
снова парсит результат, но на этот раз извлекает уже финальные demo_test_func
и full_test_func
в виде полноценных unit-тестов.
Шаг 5: Генерация человекочитаемой задачи
Когда у нас есть верифицированное решение и верифицированные тесты, остается последний шаг — придумать для всего этого осмысленное задание для человека.
Скрипт build_msg_for_question.py
формирует финальный промпт.
[!INFO] Суть промпта: "Окей, LLM, последний рывок. Вот тебе готовый код решения и тесты к нему. Придумай и напиши полноценное условие задачи, которое бы соответствовало этому коду. Опиши, что нужно сделать, формат ввода-вывода и приведи примеры из
demo_test
."
extract_question.py
извлекает из ответа модели текст задачи в тегах <question>
, и на этом конвейер завершает свою работу.
Итог конвейера AutoCodeGen
На выходе из этого многоступенчатого процесса мы получаем полный и, что самое главное, проверенный пакет данных:
question
: Текстовое описание задачи.canonical_solution
: Эталонное решение на одном из исходных языков.demo_test_func
: Демонстрационные тесты (часто для примеров в условии).full_test_func
: Полный набор тестов для проверки решения.language
: Язык программирования.
А дальше эти пакеты можно использовать как основу для перевода на другие языки (для этого есть build_msg_for_translation.py
) или для оценки моделей. Но прежде чем код куда-то пойдет, он должен пройти через "чистилище" — песочницу.
MultiLanguageSandbox: Безопасный полигон для кода
Если AutoCodeGen — это мозг, то MultiLanguageSandbox — это бронированная комната, где этот мозг проводит свои рискованные эксперименты. Главная задача этого компонента — выполнить потенциально небезопасный, сгенерированный нейросетью код так, чтобы он не навредил основной системе, и вернуть достоверный результат его работы.
Архитектура песочницы построена вокруг Flask-сервера (sandbox.py
), который принимает HTTP-запросы на выполнение кода. Внутри используется сложная система, чтобы обеспечить и безопасность, и поддержку множества языков.
Ключевые компоненты песочницы
code_config.yaml
: Это конфигурационный файл, который является сердцем мультиязычной поддержки. Для каждого языка здесь прописаны свои правила игры:compile_cmd
иcompile_flags
: Как компилировать код (если это компилируемый язык).execute_cmd
иexecute_flags
: Как запускать код.file_extension
иfile_name_template
: Как называть файлы.handler
: Указание на специальный обработчик для языков, требующих особого подхода (например,dotnet_handler
для C# илиrust_handler
для Rust).error_check
: Список строк, наличие которых в выводе будет считаться ошибкой теста (например, "AssertionError").
[!TIP] За счет этого файла добавление нового языка в песочницу сводится к описанию его правил в YAML, а не к переписыванию логики на Python. Очень гибко.
sandbox.py
(Flask App): Точка входа. Принимает JSON-запрос с кодом, языком и параметрами. Главный эндпоинт —/submit
. Он оркестрирует весь процесс внутри песочницы.code_splicer.py
: Умный "сшиватель" кода. Перед выполнением часто нужно объединить код решения (func_code
) и код тестов (main_code
). Этот модуль делает это не тупым сложением строк, а с учетом синтаксиса языка:- Для C# или Java он корректно объединяет
using
/import
директивы. - Для Go — обрабатывает
package main
иimport (...)
. - Для PHP — следит за тегами
<?php ?>
. - И так далее для десятка других языков.
- Для C# или Java он корректно объединяет
code.py
(CodeStore): Управляет временным рабочим окружением для каждого запроса. Он создает уникальную директорию, сохраняет туда исходный код, а для языков со сложной структурой проекта (вроде Rust с егоCargo.toml
или C# с.csproj
) разворачивает необходимый шаблон проекта.safe_subprocess.py
: Самая важная часть с точки зрения безопасности. Это не просто обертка над стандартнымsubprocess
. Этот модуль запускает код от имени специального, максимально ограниченного в правах пользователяsandbox
(uid=1000
,gid=1000
).- Изоляция пользователя: Процесс с кодом не имеет прав
root
. - Таймауты: Жестко контролирует время выполнения и убивает процесс, если он превышает лимит.
- Ограничение вывода: Чтобы избежать "логарифмической бомбы", ограничивает максимальный объем
stdout
иstderr
. - Убийство группы процессов: Важный нюанс. Запущенный код может порождать дочерние процессы.
safe_subprocess
убивает всю группу процессов (os.killpg
), чтобы ни один "потомок" не остался висеть в системе.
- Изоляция пользователя: Процесс с кодом не имеет прав
executor.py
: Непосредственный исполнитель. Он беретlanguage_config
из YAML, формирует команды для компиляции и запуска и передает их вsafe_subprocess
. После выполнения анализируетexit_code
,stdout
иstderr
, чтобы определить исход:PASSED
,COMPILATION_ERROR
,RUNTIME_ERROR
и т.д.
Особый случай: JVM Pool Manager для Java
Запуск JVM — процесс довольно медленный и ресурсоемкий. Если для каждого Java-теста запускать новую виртуальную машину, производительность сильно упадет. Разработчики AutoCodeBenchmark решили эту проблему элегантно.
В jvm_pool_manager.py
реализован пул постоянно работающих JVM-процессов.
- При старте песочницы запускается несколько (по умолчанию 8) JVM-процессов (
WorkerMain.java
), каждый из которых слушает свой порт. - Когда приходит запрос на выполнение Java-кода,
executor
не запускает новый процесс, а отправляет код по HTTP на свободный порт из пула. - JVM-воркер получает код, компилирует и выполняет его внутри себя, а затем возвращает результат.
- Это многократно ускоряет обработку Java-задач, так как нет накладных расходов на постоянный запуск и остановку JVM.
- Менеджер пула также следит за "здоровьем" воркеров и перезапускает их, если они упали или выполнили слишком много задач (для борьбы с утечками памяти).
Понравился материал?
Ваша поддержка — это энергия для новых статей и проектов. Спасибо, что читаете!
Заключение
AutoCodeBenchmark — это не просто очередной датасет. Это мощный и хорошо продуманный инструмент для генерации датасетов. Его архитектура показывает, как можно эффективно сочетать мощь генеративных моделей с надежностью и безопасностью изолированных сред выполнения.
Ключевые выводы, которые можно сделать из разбора этого проекта:
- LLM + Sandbox = Синергия: Сами по себе LLM склонны к "галлюцинациям" и генерации нерабочего кода. Песочница дает им мгновенную обратную связь, позволяя итеративно улучшать качество и отбраковывать мусор.
- Двухэтапная генерация тестов: Идея сначала получить фактический вывод кода, а потом на его основе генерировать
assert
'ы — ключевой элемент, обеспечивающий корректность тестов. Модель не фантазирует, а работает с фактами. - Автоматизация — это масштабируемость: Такой подход позволяет генерировать задачи для десятков языков, просто описывая их правила в конфиге. Сделать это вручную было бы на порядки дороже и дольше.