обновлен формат генерации датасета

This commit is contained in:
Даниил Грабарь
2026-10-22 10:07:05 +10:00
parent 9e551a2ca9
commit 1059b47978
29 changed files with 2230 additions and 468 deletions
+386
View File
@@ -0,0 +1,386 @@
# 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-запросов.