387 lines
28 KiB
Markdown
387 lines
28 KiB
Markdown
# LLM Robot Control
|
||
|
||
Генератор синтетических датасетов для обучения моделей высокоуровневому
|
||
планированию промышленной сборки. Создаёт структурированные сцены, строит планы,
|
||
проверяет предусловия действий и достижение цели, экспортирует обучающие записи
|
||
в JSONL. Для каждого примера сохраняются два варианта ответа: русский текст и JSON с навыками.
|
||
Управление физическим роботом и расчёт траекторий не реализованы.
|
||
|
||
## Установка
|
||
|
||
Требуются Python 3.13+, uv и Linux (для блокировки файлов используется `fcntl`).
|
||
|
||
```bash
|
||
uv sync
|
||
```
|
||
|
||
## Запуск
|
||
|
||
Команды выполняются из корня репозитория:
|
||
|
||
```bash
|
||
# Проверить конфигурацию без запросов к модели и создания файлов
|
||
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`:
|
||
|
||
```bash
|
||
uv run python -m assembly_dataset generate --count 100 --no-progress
|
||
```
|
||
|
||
## Конфигурация
|
||
|
||
Настройки находятся в [config.yaml](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`. Содержимое промптов сохраняется в настройках
|
||
запуска и проверяется при возобновлении, чтобы не смешивать разные версии корпуса.
|
||
Промпты меняют инструкции и формулировки, но не правила логической проверки.
|
||
|
||
Пример изменения запроса:
|
||
|
||
```yaml
|
||
teacher_user: |
|
||
Составь план выполнения следующего задания.
|
||
Учитывай доступность деталей и зависимости операций.
|
||
${payload}
|
||
```
|
||
|
||
`${payload}` обязателен в трёх шаблонах user/retry. Он заменяется JSON с полями
|
||
`instruction` и `scene`; при исправлении добавляется `validation_error`.
|
||
В обучающем запросе JSON также содержит `mode`, а для шага — `history`.
|
||
Обычные фигурные скобки разрешены; литеральный знак доллара записывается как `$$`.
|
||
Системные инструкции и `task_instructions` — обычный текст без подстановок.
|
||
|
||
Пустые инструкции и неизвестные переменные шаблона отклоняются до API-запроса.
|
||
Изменённые промпты генерации требуют отдельного `output_dir`.
|
||
Исключение — `structured_system`: эти инструкции используются только при экспорте,
|
||
поэтому их можно изменить и выполнить `export` в той же папке. Их полное содержимое сохраняется
|
||
в настройках корпуса. Формат ответа `Plan` и правила проверки остаются заданными
|
||
кодом: смена текста не добавляет новые навыки или поля JSON.
|
||
|
||
## Форматы и результаты
|
||
|
||
```text
|
||
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` для обучения):
|
||
|
||
```json
|
||
{"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`:
|
||
|
||
```json
|
||
{"id":"step_1","skill":"pick","object":"деталь №1","on_success":"step_2","on_failure":"replan_required"}
|
||
```
|
||
|
||
Первым выполняется `step_1`. Успех ведёт к следующему ID, а у последнего действия —
|
||
к терминальному исходу `ready` или `blocked`. Ошибка ведёт к `replan_required`:
|
||
остановиться, обновить сцену и построить новый план. Для пустого плана итог задан
|
||
полем `status`.
|
||
|
||
Две версии имеют одинаковые ID, порядок, разбиение и входные сцены, но разные
|
||
системные инструкции. Для обучения одному формату выбирайте только соответствующие
|
||
файлы; совместное обучение двум форматам требует сохранять их системные инструкции.
|
||
|
||
Пример для помощи оператору:
|
||
|
||
```text
|
||
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-ошибки. Пакет в памяти ограничен размером очереди;
|
||
SFT экспортируется потоково по шардам. После прерывания повторите команду:
|
||
принятые эпизоды сохраняются, незавершённый пакет повторяется. При teacher-режиме
|
||
запросы незавершённого пакета могут повториться. Одновременные писатели в одну папку
|
||
блокируются. Неизменённый процедурный запуск воспроизводим побайтно.
|
||
|
||
Полный экспорт пересобирается во временную папку и затем заменяет предыдущий.
|
||
На время переэкспорта нужно место для обеих копий. Старый экспорт до завершения
|
||
нового остаётся доступен. SQLite и SFT для 500 тысяч строк могут занимать десятки
|
||
гигабайт: размер зависит от числа компонентов, режимов и `step_sampling`.
|
||
|
||
## CSV для просмотра
|
||
|
||
Рядом с каждым шардом JSONL автоматически создаётся одноимённый CSV:
|
||
|
||
```text
|
||
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.
|
||
|
||
```bash
|
||
# Повторно экспортировать 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-совместимого сервера настройте блок:
|
||
|
||
```yaml
|
||
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`:
|
||
|
||
```yaml
|
||
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.
|
||
|
||
## Структура проекта
|
||
|
||
```text
|
||
assembly_dataset/ # генерация сцен, валидация, teacher, хранение и CLI
|
||
config.yaml # модель, размер, сцены и параметры генерации
|
||
prompts.yaml # инструкции LLM и тексты обучающих записей
|
||
llm/ # провайдеры моделей, авторизация и общие схемы настроек
|
||
tests/ # тесты генератора и конвейера
|
||
```
|
||
|
||
Промпты редактируются в [prompts.yaml](prompts.yaml), без изменения Python-кода.
|
||
Параметр `prompts_file` в `config.yaml` позволяет выбрать другой файл промптов.
|
||
|
||
## Проверки
|
||
|
||
```bash
|
||
uv run python -m unittest discover -s tests -v
|
||
```
|
||
|
||
Тесты используют локальную генерацию и тестовый агент без внешних API-запросов.
|