LLM Robot Control

Генератор синтетических датасетов для обучения моделей высокоуровневому планированию промышленной сборки. Создаёт структурированные сцены, строит планы, проверяет предусловия действий и достижение цели, экспортирует обучающие записи в JSONL. Для каждого примера сохраняются два варианта ответа: русский текст и JSON с навыками. Управление физическим роботом и расчёт траекторий не реализованы.

Установка

Требуются Python 3.13+, uv и Linux (для блокировки файлов используется fcntl).

uv sync

Запуск

Команды выполняются из корня репозитория:

# Проверить конфигурацию без запросов к модели и создания файлов
uv run python -m assembly_dataset --dry-run

# Создать 100 обучающих записей
uv run python -m assembly_dataset generate --count 100

# Продолжить корпус до 500 000 записей
uv run python -m assembly_dataset generate --count 500000

# Проверить сохранённые эпизоды и экспорт
uv run python -m assembly_dataset validate

--count задаёт точное суммарное число примеров по выбранным режимам. Каждый пример экспортируется в двух вариантах: --count 100 даёт 100 текстовых и 100 структурированных записей с одинаковыми ID. examples и прогресс считают пары один раз; examples_per_variant в manifest показывает размеры обоих вариантов. Без этого аргумента используется target_examples из конфигурации. Одна сцена может давать несколько записей; количество сцен выводится отдельно. Ноль допустим; отрицательные и дробные значения запрещены.

step_sampling: one выбирает один воспроизводимый шаг из каждого эпизода. all экспортирует каждый выполненный шаг. Общий лимит действует и в этом случае: последний эпизод может быть экспортирован частично. Если нужен корпус из полных планов для 500 000 исходных сцен, выберите modes: [full] и --count 500000. При трёх форматах число сцен отображается отдельно как scenarios.

Прогресс выполнения

При запуске CLI автоматически отображаются два индикатора: генерация записей и экспорт в JSONL. Каждый показывает количество записей относительно цели, процент выполнения, скорость, прошедшее и приблизительное оставшееся время. При продолжении корпуса генерация начинается с уже сохранённого количества.

Счётчик генерации обновляется после сохранения каждого проверенного эпизода, а не после каждого API-запроса. Во время вызовов модели отображается «ожидание LLM». Экспорт отдельно показывает запись строк и подготовку контрольных сумм. Оценка времени появляется после первых обновлений и может меняться из-за скорости модели, повторных попыток и длины планов.

В терминале полосы обновляются на месте; при перенаправлении вывода сохраняется итоговая строка каждого этапа. Индикаторы выводятся в stderr, итоговая статистика JSON — в stdout. Для отключения используйте --no-progress:

uv run python -m assembly_dataset generate --count 100 --no-progress

Конфигурация

Настройки находятся в config.yaml:

  • target_examples — размер итогового корпуса.
  • modes — форматы full, step, branch.
  • step_sampling — один шаг или все шаги эпизода.
  • seed, domains, min_components, max_components, blocked_fraction — параметры создания сцен.
  • split — доли train, validation и test; записи одной сцены не разделяются.
  • shard_size — максимальное число строк в одном файле JSONL.
  • output_dir — папка результатов относительно файла конфигурации.
  • teacher — провайдер, модель, доля сцен для LLM, параллельность и повторные попытки.

В поставляемом конфиге включён Codex для всех сцен. Для работы требуется доступ к указанной модели. При отсутствии файла учётных данных запускается авторизация через браузер. OpenAI-совместимые серверы настраиваются через teacher.llm; ключ и адрес сервера берутся из переменных окружения или корневого .env. Для генерации без API задайте teacher.enabled: false.

Другой конфиг передаётся через --config PATH, другая папка результатов — через --output-dir PATH. Изменение параметров сцен или модели требует отдельной папки корпуса. Лимит записей и размер шардов можно менять при продолжении запуска.

Где что менять

Задача Файл и параметр
Размер датасета config.yaml → target_examples или CLI --count
Модель и провайдер config.yaml → teacher.llm
Сложность и типы сцен config.yaml → domains, min_components, max_components, blocked_fraction
Инструкции генератору планов prompts.yaml → teacher_system
Текст запроса со сценой prompts.yaml → teacher_user
Запрос исправления ошибочного плана prompts.yaml → teacher_retry
Инструкции обучаемой модели для JSON prompts.yaml → structured_system.full, .step, .branch
Инструкции обучаемой модели для русского текста prompts.yaml → training_system.full, .step, .branch
Текст входа обучающей записи prompts.yaml → training_user
Формулировки заданий по типам сборки prompts.yaml → task_instructions
Названия объектов prompts.yaml → object_names
Русские описания для локального режима и просмотра сцены prompts.yaml → action_templates

В шаблонах teacher_user, teacher_retry и training_user нужно оставить ${payload}: сюда подставляется JSON с заданием и сценой. В запросе исправления JSON также содержит validation_error. Остальной текст можно менять свободно.

После изменения промптов используйте новую папку, например --output-dir dataset/prompt-experiment. Содержимое промптов сохраняется в настройках запуска и проверяется при возобновлении, чтобы не смешивать разные версии корпуса. Промпты меняют инструкции и формулировки, но не правила логической проверки.

Пример изменения запроса:

teacher_user: |
  Составь план выполнения следующего задания.
  Учитывай доступность деталей и зависимости операций.
  ${payload}

${payload} обязателен в трёх шаблонах user/retry. Он заменяется JSON с полями instruction и scene; при исправлении добавляется validation_error. В обучающем запросе JSON также содержит mode, а для шага — history. Обычные фигурные скобки разрешены; литеральный знак доллара записывается как $$. Системные инструкции и task_instructions — обычный текст без подстановок.

Пустые инструкции и неизвестные переменные шаблона отклоняются до API-запроса. Изменённые промпты генерации требуют отдельного output_dir. Исключение — structured_system: эти инструкции используются только при экспорте, поэтому их можно изменить и выполнить export в той же папке. Их полное содержимое сохраняется в настройках корпуса. Формат ответа Plan и правила проверки остаются заданными кодом: смена текста не добавляет новые навыки или поля JSON.

Форматы и результаты

dataset/assembly-ru/
  episodes.sqlite3              # сцены, планы, история состояний, графы, provenance
  sft/
    manifest.json               # точные размеры, распределения, SHA-256 файлов
    train/full/000000.jsonl              # ответ русским текстом
    train/full/000000.structured.jsonl    # ответ status/plan
    train/step/000000.jsonl
    train/branch/000000.jsonl
    validation/...
    test/...

Каждая строка содержит messages (system/user/assistant), record_id, scenario_id, mode, split, source, validation_level. В 000000.jsonl ответ assistant.content — обычный русский текст:

  • full — нумерованная последовательность действий.
  • step — одна русская фраза с названием объекта. Вход содержит состояние перед действием и историю выполненных действий.
  • branch — нумерованный план с условиями «если выполнено успешно» и «если произошла ошибка». После ошибки требуется уточнить сцену и перепланировать.

В 000000.structured.jsonl поле assistant.content содержит JSON в виде строки (стандартный формат messages для обучения):

{"status":"ready","plan":[{"skill":"pick","object":"деталь №1"},{"skill":"align","object":"деталь №1","target":"сборочная позиция №1"}]}

Это фрагмент примера; полный план включает остальные действия. Названия объектов и мест берутся из сцены. target есть у действий с местом назначения; у pick, release, inspect его нет. Навыки остаются на английском. return_home относится к объекту «робот» с целью «исходное положение».

В step массив plan содержит одно следующее действие со статусом ready. Без доступных действий он пуст; статус — ready при достигнутой цели или blocked при блокировке. В full и branch статус характеризует весь сценарий; при blocked массив может содержать выполнимую часть плана.

В branch у каждого действия дополнительно есть id, on_success, on_failure:

{"id":"step_1","skill":"pick","object":"деталь №1","on_success":"step_2","on_failure":"replan_required"}

Первым выполняется step_1. Успех ведёт к следующему ID, а у последнего действия — к терминальному исходу ready или blocked. Ошибка ведёт к replan_required: остановиться, обновить сцену и построить новый план. Для пустого плана итог задан полем status.

Две версии имеют одинаковые ID, порядок, разбиение и входные сцены, но разные системные инструкции. Для обучения одному формату выбирайте только соответствующие файлы; совместное обучение двум форматам требует сохранять их системные инструкции.

Пример для помощи оператору:

1. Определить положение объекта «распределительная коробка №1».
2. Переместиться к объекту «распределительная коробка №1».
3. Захватить объект «распределительная коробка №1».
4. Переместить объект «распределительная коробка №1» к оператору.
5. Удерживать объект «распределительная коробка №1» в неподвижном состоянии возле оператора.
6. Дождаться подтверждения завершения сборки объекта «распределительная коробка №1», продолжая удерживать его.
7. Переместить объект «распределительная коробка №1» в место «зона хранения №1».
8. Отпустить объект «распределительная коробка №1».
9. Вернуться в исходное положение.

Домен operator_assistance описывает удержание объекта у оператора. collaborative описывает передачу объекта оператору: это другая задача, с другим порядком отпускания. Чтобы получить только сценарии удержания, задайте domains: [operator_assistance].

Технические навыки, предусловия, эффекты и графы сохраняются внутри канонического эпизода для проверки. Обучаемая модель получает сцену с русскими названиями объектов, их положением, доступностью и зависимостями. Статусы находятся в метаданных записи; при невыполнимости текст ответа явно сообщает о блокировке.

Версия экспорта — 3, версия хранилища эпизодов — 2. Сохранённые эпизоды версии 2 можно переэкспортировать в оба варианта без запросов к LLM, сохранив настройки генерации. Корпуса версии 1 несовместимы: используйте новую папку. В конфигурации задана dataset/assembly-ru, чтобы не смешивать русские ответы с прежним техническим JSON.

SQLite служит транзакционным checkpoint и индексом уникальности. Он хранит полные эпизоды, включая trace и teacher-ошибки. Пакет в памяти ограничен размером очереди; Каждый готовый ответ сохраняется сразу, не ожидая остальных запросов пакета. При Ctrl+C, SIGTERM или ошибке генерации CLI экспортирует накопленные данные в JSONL/CSV; неполный экспорт отмечается partial: true в manifest. После исчерпания повторных попыток ошибки API останавливают генерацию. Невалидные планы, как прежде, заменяются проверенным символическим планом с отметкой ошибки. Для корпуса только из ответов LLM запускайте generate --teacher-only: при невалидном ответе генерация остановится с экспортом накопленных записей, а символическая замена не попадёт в базу. Флаг действует на новые записи; ранее сохранённые эпизоды он не удаляет. Для продолжения повторите команду с теми же настройками: сохранённые эпизоды не запрашиваются повторно, незавершённые запросы выполняются заново. После принудительного завершения (SIGKILL) данные остаются в SQLite; экспортировать их без запросов к LLM можно командой uv run python -m assembly_dataset export --allow-partial. Одновременные писатели в одну папку блокируются. Неизменённый процедурный запуск воспроизводим побайтно.

Полный экспорт пересобирается во временную папку и затем заменяет предыдущий. На время переэкспорта нужно место для обеих копий. Старый экспорт до завершения нового остаётся доступен. SQLite и SFT для 500 тысяч строк могут занимать десятки гигабайт: размер зависит от числа компонентов, режимов и step_sampling.

CSV для просмотра

Рядом с каждым шардом JSONL автоматически создаётся одноимённый CSV:

sft/train/full/000000.jsonl
sft/train/full/000000.csv
sft/train/full/000000.structured.jsonl
sft/train/full/000000.structured.csv

Одна строка CSV соответствует одной обучающей записи. Первые колонки: «Задание», «Сцена», «План», «Статус», «Формат», «Источник текста». В колонке «План» находится содержимое assistant.content: русский текст в обычном CSV и JSON в .structured.csv. Метаданные и полный вход находятся после основных колонок.

Кодировка — UTF-8 с BOM, разделитель — точка с запятой (;). При импорте в Excel или LibreOffice выберите этот разделитель и включите перенос текста в ячейках. Поля с кавычками, разделителями и переносами строк экранируются.

CSV включены в контрольные суммы manifest и проверяются командой validate. CSV не увеличивают examples: это представления тех же записей. Для добавления CSV к существующему корпусу выполните export с нужным лимитом; запросов к LLM при этом нет.

Продолжение и переэкспорт

После остановки повторите команду generate с тем же конфигом и папкой результатов. Для изменения количества строк используйте --count. Меньший лимит уменьшает экспорт, но сохраняет канонические эпизоды в SQLite.

# Повторно экспортировать 100 записей из сохранённых эпизодов
uv run python -m assembly_dataset export --count 100

Без --count команда export использует лимит из YAML. Если столько записей ещё не подготовлено, сначала выполните generate или укажите меньший лимит.

Модель мира и ограничения

В world.py находятся одиннадцать расширяемых рецептов: перемещение, механосборка, крепёж, прессовая посадка, электромонтаж, электроника, клей, сварка, совместная сборка, удержание объекта у оператора и контроль. Сцены различаются количеством компонентов, начальными положениями, уже завершёнными стадиями, отсутствующими деталями и DAG-зависимостями. Текущее положение задают предикаты at/holding в состоянии; поле components[].source_location обозначает исходное место подачи детали. Это каталог базовых абстрактных процессов, не исчерпывающая модель всей промышленности. Новый процесс требует рецепта, корректных предусловий/эффектов и тестов.

У каждой операции есть requires, forbids, add, delete. Полный план проверяется повторным исполнением; ветвящийся — обходом всех доступных путей. Состояния и эффекты задаёт генератор, teacher не может их переписать. blocked проверяется эталонным решателем для этого ограниченного ациклического домена. Это не универсальный PDDL-планировщик.

Структурная сигнатура строится из сцены с фиксированными каноническими ID. Номер кандидата, формулировка задания и ответ LLM не увеличивают разнообразие. Дедупликация выполняется уникальным индексом SQLite; при исчерпании малого пространства параметров генератор завершится с ошибкой. Варианты одного сценария получают один split по сигнатуре. Родственные, но различные сцены могут попасть в разные split — это не строгий тест переноса на неизвестные семейства изделий. Для маленьких корпусов присутствие validation/test не гарантируется.

validation_level: logical_only: нет проверки геометрии, столкновений, усилий, качества сварки/клея и реальной выполнимости навыков. VLM/VLA и разметка изображений не поддерживаются.

Teacher-модели

В поставляемом config.yaml включён Codex для всех сцен (enabled: true, fraction: 1.0). При enabled: false планы строятся локально. Для подключения OpenAI-совместимого сервера настройте блок:

teacher:
  enabled: true
  fraction: 0.15              # доля сцен; 1.0 — все сцены
  concurrency: 4
  timeout_seconds: 180
  retries: 2
  llm:
    provider: openai
    model: your-provider/your-model
    api_key_env: API_KEY
    base_url_env: BASE_URL
  generation:
    temperature: null
    max_tokens: 8192

Ключ и адрес сервера берутся из окружения или корневого .env. Для Codex замените llm:

llm:
  provider: codex
  model: <доступная вам модель>
  credential_path: .config/codex-credentials.json

Подключение выполняет llm.factory.LLMModelProvider. Teacher возвращает полный план: для каждого действия — внутренние skill/object/target и русское описание text. Код проверяет последовательность внутренних действий и сохраняет текст модели без замены шаблонной формулировкой. Затем из полного плана создаются пошаговые записи и условия ветвления; сами условия ошибки формирует код.

Логическая проверка подтверждает внутренние действия. Для текста проверяются наличие русского описания, количество шагов, названия объектов и отсутствие технических ID. Текст также должен совпадать с action_templates после нормализации пунктуации и ограниченного набора синонимов. Допускаются «Сначала/Затем/Далее», «взять/захватить», «перенести/переместить», «проконтролировать/проверить», «возвратиться/вернуться», «местоположение/положение». Другие формулировки отклоняются и запрашиваются заново: свободный текст не считается доказательством корректности. Для изменения основного стиля редактируйте action_templates в prompts.yaml.

После неудачных попыток используется символьный план и русские шаблоны из action_templates. Это помечается как source: symbolic, text_source: template и teacher_error. У успешного ответа модели text_source: teacher. В CSV это видно в колонке «Источник текста». Смена action_templates не переписывает сохранённые формулировки teacher.

Реальные API-вызовы не входят в локальные тесты. Для сравнения моделей используйте отдельные папки и проверяйте sources и teacher_failures в manifest.

Структура проекта

assembly_dataset/          # генерация сцен, валидация, teacher, хранение и CLI
config.yaml                # модель, размер, сцены и параметры генерации
prompts.yaml               # инструкции LLM и тексты обучающих записей
llm/                       # провайдеры моделей, авторизация и общие схемы настроек
tests/                     # тесты генератора и конвейера

Промпты редактируются в prompts.yaml, без изменения Python-кода. Параметр prompts_file в config.yaml позволяет выбрать другой файл промптов.

Проверки

uv run python -m unittest discover -s tests -v

Тесты используют локальную генерацию и тестовый агент без внешних API-запросов.

S
Description
Собственная библиотека для дальнейшего управления коллаборативным роботом
Readme
431 KiB
Languages
Python 100%