Реестр форм 1С для агента: scan_forms и первый агрегат поверх распаковки

13.07.26

Интеграция - Нейросети

Распаковать одну форму 1С мало: на реальной конфигурации их сотни, и LLM-агенту нужен не разбор отдельной формы, а карта всех форм сразу. В статье разобрано, как одним пакетным обходом собрать реестр форм в компактный агрегат, детектировать его дрейф и маршрутизировать запрос агента к нужной форме без запуска платформы. Материал для разработчиков и архитекторов, строящих агентные инструменты поверх кодовой базы 1С.

Статья продолжает серию о подготовке обычных форм 1С для агентного пайплайна. Разобран пакетный обход выгрузки конфигурации функцией scan_forms, сборка агрегата FormScanIndex и его сериализация в JSON, обобщение обхода на все контейнеры, чьё имя оканчивается на Form. Показана интеграция с детектом дрейфа через check_drift на основе хэшей содержимого, разделение сигналов modified и structure_modified, и строковая маршрутизация запроса через FormRouter без RAG и вызовов модели.

Первые две статьи закрыли форму как единицу. Сначала - как сделать Form.bin читаемым. Потом - как вытащить дерево элементов из elem.json. К концу этой статьи у агента на руках были три артефакта одной формы: модуль, структура, привязки.

А потом я подключил его к настоящей выгрузке. И понял, что смотрел в микроскоп, когда нужна была карта.

Одна форма - это хорошо для демонстрации. Но реальная типовая конфигурация 1С содержит сотни форм, распределённых по десяткам типов объектов. Агент не может «распаковать нужную форму»: он сначала должен узнать, что эта форма вообще существует, где она лежит и как называется её контейнер. Ему нужен реестр. Не разбор одной формы, а список всех - с адресами, чтобы было куда идти за деталями.

Эта статья - про переход от единичной формы к пакетному реестру. Про то, как я собрал первый агрегат scan_forms, как он сломался о реальную раскладку выгрузки, и почему я развёл два похожих индекса по разным классам, чтобы самому в них не запутаться.

 

Коротко: что делать

· Собирать реестр форм пакетно через scan_forms(cf_export_root), а не распаковывать формы по одной: агенту нужна карта всей конфигурации, а не отдельная форма.

· Обходить все контейнеры, чьё имя оканчивается на Form, а не только буквальный контейнер Form: на реальной раскладке форм в DocumentForm/CatalogForm в разы больше.

· Держать в голове два разных класса: FormsIndex - карточка распаковки одной формы, FormScanIndex - полный реестр всех форм. Это не два представления одного, это разные сущности.

· Детект дрейфа check_drift считать формы удалёнными по факту исчезновения .obj.bsl с диска, а не по флагу извлечения.

· Строить baseline дрейфа на содержимом (bsl_sha256), а не на времени изменения файла: mtime не переживает полную перераспаковку и даёт ложные срабатывания.

· Разделять два независимых сигнала: modified (изменился код формы) и structure_modified (изменилось нормализованное дерево элементов, elem_sha256).

· Маршрутизировать запрос агента к форме строковым FormRouter без вызова модели: приоритет form_name над object_name над object_type.

 

Рис. 1. Пайплайн реестра форм.

 

Зачем реестр поверх распаковки

Распаковка отвечает на вопрос «что внутри этой формы». Реестр отвечает на другой вопрос - «какие формы вообще есть и где они». Для агента это принципиально разные слои.

Пока форма одна, реестр не нужен: путь известен, распаковал, прочитал. Но агент, который помогает по конфигурации, начинает не с известного пути. Он начинает с запроса вроде «покажи форму списка номенклатуры». Чтобы этот запрос превратить в путь, нужен предварительный проход по всей выгрузке: собрать, что где лежит, и сложить в один компактный агрегат.

Именно поэтому scan_forms не распаковывает формы. Он их только находит и переписывает адреса. Тяжёлый разбор elem.json и Form.obj.bsl остаётся на потом, точечно, когда агент уже знает, к какой форме идти.

Реестр и распаковка - разные слои. Реестр отвечает «где форма», распаковка - «что внутри». Смешивать их в одной функции значит платить полным разбором за каждый обзорный запрос

Проблема: предыдущая статья остановилась на одной форме

К концу предыдущей статьи пайплайн умел обрабатывать конкретную форму по известному пути. Я честно считал, что осталось только «прогнать это в цикле по всем формам». Наивно.

Первая же попытка запустить обход показала, что я не знаю двух вещей. Во-первых, где в выгрузке лежат формы - реальная конфигурация 1С использует имена контейнеров по типу объекта (DocumentForm, CatalogForm), а не единственный Form. v8unpack воспроизводит эту структуру точно, но без интеграционных тестов на живой конфигурации это стандартная ловушка. Во-вторых, как отличить «форму, которую агент уже видел» от «формы, которая изменилась с прошлого прохода».

Обе проблемы решались не циклом. Первая - обобщением обхода. Вторая - отдельным механизмом сравнения check_drift. Про них дальше.

 

scan_forms.py: обход выгрузки

Функция намеренно принимает только два параметра и ничего лишнего не возвращает - это снижает связность. На вход - корень выгрузки, на выход - агрегат.

from pathlib import Path
from v8unpack_agent.scan_forms import scan_forms, FormScanIndex, FormEntry

index: FormScanIndex = scan_forms(cf_export_root=Path("cf_export/"))
# index.forms: list[FormEntry]
# index.total: int
# index.scanned_at: str          # метка времени в ISO-8601
# index.scan_warnings: list[str] # предупреждения по формам, которые не удалось прочитать

index.save(Path("forms_scan_index.json"))

Обход идёт в режиме best-effort. Ошибка на одной форме не роняет весь проход: проблема пишется в scan_warnings, обход продолжается. На сотнях форм гарантированно найдётся пара директорий с сюрпризом, и одна битая форма не должна обнулять реестр целиком.

Пакетный обход всегда best-effort. На реальном объёме одна нечитаемая форма - норма, а не авария. Пишите её в предупреждения и идите дальше.

Запуск из CLI

Обход доступен не только как функция, но и как модуль командной строки. Это удобно для разовой сборки реестра или для встраивания в скрипт CI.

python -m v8unpack_agent.scan_forms cf_export/ --mode config --save

вывод

Найдено форм: 14
Реестр сохранён: forms_scan_index.json

Позиционный аргумент - корень выгрузки; --mode {config,external} - режим обхода, по умолчанию config; --save - сохранить реестр в forms_scan_index.json. Запуск через python -m намеренно не тянет тяжёлые зависимости пакета: сам модуль scan_forms подгружается лениво через __getattr__.

 

FormScanIndex: структура, поля и два разных индекса

В пакете есть два индекса с похожими именами и совершенно разным смыслом.

 

Класс

Что это

Уровень

Ключевые поля

FormsIndex

Карточка распаковки одной формы (статьи 02–03)

Одна форма

Статус извлечения, метки времени распаковки

FormScanIndex

Полный реестр всех форм конфигурации (эта статья)

Вся выгрузка

forms, total, scanned_at, scan_warnings

 

FormsIndex отвечает на вопрос «в каком состоянии распакована вот эта форма». FormScanIndex отвечает на вопрос «какие формы вообще есть». Я намеренно держу их раздельно и называю файл реестра forms_scan_index.json.

 

@dataclass
class FormEntry:
    object_type: str          # "Catalog", "Document", ...
    object_name: str          # "Demo", "SalesOrder", ...
    container_name: str       # "CatalogForm", "DocumentForm", "Form", ...
    form_name: str            # "ФормаСписка", "ФормаЭлемента", ...
    form_path: str            # относительный путь к директории формы
    bsl_path: str             # путь к <ContainerName>.obj.bsl
    json_path: str            # путь к <ContainerName>.json
    bsl_sha256: str | None    # хэш содержимого .obj.bsl (baseline дрейфа кода)
    elem_sha256: str | None   # хэш нормализованного дерева элементов формы
    bsl_mtime: float          # вспом. метка времени изменения
    warnings: list[str]       # предупреждения по конкретной форме

 

{
  "object_type": "Catalog",
  "object_name": "Demo",
  "container_name": "CatalogForm",
  "form_name": "ФормаСписка",
  "form_path": "cf_export/Catalog/Demo/CatalogForm/ФормаСписка",
  "bsl_path": "cf_export/Catalog/Demo/CatalogForm/ФормаСписка/CatalogForm.obj.bsl",
  "json_path": "cf_export/Catalog/Demo/CatalogForm/ФормаСписка/CatalogForm.json",
  "bsl_sha256": "3b1f...e9a0",
  "elem_sha256": "7c42...01d5",
  "bsl_mtime": 1718450000.0,
  "warnings": []
}

 

Если в пакете два похоже названных индекса - разведите их по разным классам и разным именам файлов на диске.

Реальный layout: обобщение на контейнеры *Form

Когда я писал scan_forms на демо-конфигурации, раскладка выглядела так: cf_export/<Тип>/<Объект>/Form/<ИмяФормы>/. Контейнер называется Form, внутри - директории форм. Первый обход искал строго контейнер с именем Form.

Потом я прогнал его на конфигурации побольше - и получил реестр, в котором форм было подозрительно мало. Имя контейнера зависит от типа объекта конфигурации: DocumentForm, CatalogForm, ReportForm и так далее. Мой обход по буквальному Form их просто не видел.

Распределение по контейнерам после прогона scan_forms на тестовой выгрузке:

 

Контейнер

Кол-во форм

DocumentForm

693

CatalogForm

474

Form

417

ReportForm

166

CommonForm

162

InformationRegisterForm

148

ExchangePlanForm

34

ChartOfCharacteristicTypeForm

19

AccumulationRegisterForm

17

BusinessProcessForm

10

DocumentJournalForm

9

TaskForm

7

EnumForm

4

FilterCriterionForm

4

 

Итого: 2164 формы, из которых буквальный контейнер Form - меньше пятой части. Лечение - обобщить условие: вместо «имя равно Form» стало «имя оканчивается на Form». Заодно в FormEntry появилось поле container_name: без него две одноимённые формы в разных контейнерах сливались бы в одну запись.

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

Интеграция с детектом дрейфа

Реестр без детекта изменений живёт ровно до первой правки конфигурации. Поэтому рядом со scan_forms есть check_drift.

 

from pathlib import Path
from v8unpack_agent.drift_checker import check_drift, DriftReport

report: DriftReport = check_drift(
    cf_export_root=Path("cf_export/"),
    index_path=Path("forms_scan_index.json"),
)
# report.has_drift: bool
# report.added: list[str]              # формы на диске, которых нет в реестре
# report.removed: list[str]            # формы из реестра, которых нет на диске
# report.modified: list[str]           # изменился код формы (.obj.bsl)
# report.structure_modified: list[str] # изменилось дерево элементов формы
# report.stale_extractions: list[str]  # формы, чей .obj.bsl исчез с диска
# report.checked_at: str

 

 

Рис. 2. Детект дрейфа: сохранённый реестр и текущая выгрузка сравниваются в DriftReport

 

stale_extractions - это не «форма с флагом неуспешного извлечения». В реестре такого флага нет. stale_extractions - формы, которые были в реестре, но их файл .obj.bsl исчез с диска. Семантика именно «протухшая ссылка на извлечение», а не «извлечение не удалось».

Детект по mtime разваливается при полной перераспаковке: файловая система проставляет всем .obj.bsl новые времена изменения, хотя содержимое кода не поменялось ни на байт. Лечение - перевести baseline с файлового атрибута на содержимое: content-hash bsl_sha256.

structure_modified - отдельный и независимый от modified сигнал. modified отвечает «изменился ли код формы», structure_modified - «изменилась ли разметка, её дерево элементов». Хэшируется нормализованное дерево элементов: из него оставлены только структурные поля, вся косметика отброшена.

Сводка сигналов по сценариям:

 

Действие

modified

structure_modified

Правка кода (BSL)

да

нет

Добавление/удаление элемента, код не тронут

нет

да

Правка кода и разметки

да

да

Косметика (координаты/цвета/шрифты)

нет

нет

Повторная полная перераспаковка неизменённого .cf

нет

нет

 

stale_extractions - исчезнувший с диска .obj.bsl, а не «форма не извлеклась». Первая означает битую ссылку в индексе, вторая - неполноту контекста.

Baseline дрейфа стройте на содержимом, а не на mtime. Content-hash (bsl_sha256 для кода, elem_sha256 для нормализованной разметки) - устойчивый baseline.

Агент работает с реестром: FormRouter

Реестр собран. Теперь запрос агента надо превратить в конкретную форму. Этим занимается FormRouter.

from pathlib import Path
from v8unpack_agent.form_router import FormRouter, RouteResult

router = FormRouter(index_path=Path("forms_scan_index.json"))

result: RouteResult = router.route(query="ФормаСписка Catalog")
# result.matched: list[FormEntry]
# result.confidence: float   # 0.0-1.0
# result.warnings: list[str]

Маршрутизация версии 1 - чисто строковая. Никаких вызовов модели, никакого RAG. Просто сравнение строк запроса с полями записей, с приоритетом: точное совпадение по form_name весит больше, чем совпадение по object_name, а оно - больше, чем подстрочное совпадение по object_type.

reindex(changed_forms) обновляет записи реестра точечно, не пересобирая всё. Два ключевых требования к инкрементальному обновлению: (1) reindex обязан сохранять baseline дрейфа при записи; (2) ключ формы обязан включать container_name - иначе две одноимённые формы в разных контейнерах перезапишут друг друга. Ключ везде собирается одним хелпером из четырёх компонент: (object_type, object_name, container_name, form_name).

Строковый роутер без модели - нормальная первая версия. Инкрементальное обновление обязано сохранять baseline и составной ключ из четырёх компонент.

External-режим: внешние обработки и отчёты

Формы живут не только внутри выгрузки конфигурации. Внешние обработки и отчёты (.epf/.erf) при распаковке дают свою раскладку, и для них у scan_forms есть отдельный режим.

from pathlib import Path
from v8unpack_agent.scan_forms import scan_forms, FormScanIndex

index: FormScanIndex = scan_forms(
    Path("external_export/"),
    mode="external",
)

Раскладка внешнего объекта: External/<ИмяОбъекта>/Form/<ИмяФормы>/ и External/<ИмяОбъекта>/ReportForm/<ИмяФормы>/. Режим external изолирован от config по object_type: внешним объектам присваивается ExternalDataProcessor или ExternalReport. Эти типы не пересекаются с конфигурационными.

Внешние обработки и отчёты - не частный случай одного Form. Изолируйте их по object_type, храните реальный container_name и ищите артефакты по имени контейнера.

Что бы я сделал иначе

· Заложил бы обобщение на *Form-контейнеры с самого начала. Ставка на буквальный Form стоила реестра, в котором молча терялось четыре пятых форм.

· Сразу развёл бы FormsIndex и FormScanIndex по разным именам файлов - совпадение имён рано или поздно приводит к молчаливой перезаписи.

· Строил бы baseline дрейфа на содержимом с самого начала. Первая версия опиралась на mtime и разваливалась на первой же полной перераспаковке.

· Провёл бы baseline дрейфа через сохранение в роутере с самого начала - ранняя версия reindex его теряла.

· Сделал бы ключ формы составным везде одинаково сразу. Был период, когда check_drift уже включал container_name в ключ, а reindex роутера - ещё нет.

· Не завышал бы confidence роутера искусственно. Формула для совпадения по object_name фактически всегда даёт одно и то же значение - это вводит в заблуждение.

 

Чек-лист для применения

1. Собирать реестр через scan_forms(cf_export_root) пакетно, не распаковывая формы по одной.

2. Обходить все контейнеры, чьё имя оканчивается на Form, а не только буквальный Form: иначе потеряете большую часть форм.

3. Хранить в записи container_name, чтобы одноимённые формы в разных контейнерах не сливались.

4. Сохранять реестр в forms_scan_index.json, не в forms_index.json: второе имя занято карточкой распаковки из предыдущих статей.

5. Проверять именно пару <ContainerName>.obj.bsl (обязателен) и <ContainerName>.json (при отсутствии - предупреждение).

6. Трактовать stale_extractions как исчезновение .obj.bsl с диска, а не как флаг неуспешного извлечения.

7. Строить baseline дрейфа кода на content-hash bsl_sha256, а не на bsl_mtime: mtime не переживает полную перераспаковку.

8. Различать modified (изменился код формы) и structure_modified (изменилось нормализованное дерево элементов) как два независимых сигнала.

9. Маршрутизировать запрос строковым FormRouter с явным приоритетом form_name > object_name > object_type, без вызова модели.

10. При инкрементальном reindex проверять, что хэшевый baseline и составной ключ формы сохраняются.

 

Что дальше

Реестр даёт агенту карту всех форм, а строковый роутер — быстрый путь к форме по известным именам. Следующий шаг — собрать из модуля, дерева элементов и метаданных компактный контекст формы и проиндексировать его для смыслового поиска. FormRouter при этом останется первым, дешёвым уровнем маршрутизации, а RAG (Retrieval-Augmented Generation — дополнение запроса контекстом из поискового индекса) будет подключаться, когда точное имя формы неизвестно.

 

Ссылки

· Обычные формы 1С в агентном пайплайне: пошаговая распаковка - //infostart.ru/1c/articles/2721726/ - Первая статья про распаковку Form.bin

· СКД и дерево элементов обычной формы 1С: два некритичных шага в агентном пайплайне - //infostart.ru/1c/articles/2726561/ - Вторая статья про СКД и разбор elem.json

· v8unpack-agent - https://github.com/MRDK80/v8unpack-agent - Python-надстройка над v8unpack: scan_forms, drift_checker, form_router, CLI. MIT

· saby-integration/v8unpack - https://github.com/saby-integration/v8unpack - Python, MIT, pip install v8unpack

Вступайте в нашу телеграмм-группу Инфостарт

LLM RAG v8unpack FormScanIndex FormRouter content hash sha256 agent routing обычные формы агентный пайплайн

Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».

  • 0% комиссии — оплата напрямую исполнителю;
  • Исполнители любого масштаба — от отдельных специалистов до команд под проект;
  • Прямой обмен контактами между заказчиком и исполнителем;
  • Безопасная сделка — при необходимости;
  • Рейтинги, кейсы и прозрачная система откликов.

См. также

Инструментарий разработчика Чистка данных Свертка базы Инструменты администратора БД Системный администратор Программист Руководитель проекта 1С:Предприятие 8 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 Россия Платные (руб)

Инструмент представляет собой обработку для проведения свёртки или обрезки баз данных. Работает на ЛЮБЫХ конфигурациях (УТ, БП, ERP, УНФ, КА и т.д.). Поддерживаются серверные и файловые базы, управляемые и обычные формы, интерфейс 8.5. Может выполнять свертку одновременно в несколько потоков, а также без непосредственного участия пользователя. Решение в Реестре отечественного ПО.

24900 руб.

20.08.2024    74115    377    170    

322

Инструментарий разработчика Роли и права Запросы СКД Программист Руководитель проекта 1С:Предприятие 8 Платные (руб)

Инструменты для разработчиков 1С 8.3: Infostart Toolkit. Автоматизация и ускорение разработки на управляемых формах. Легкость работы с 1С.

16500 руб.

02.09.2020    266420    1483    421    

1177

Инструментарий разработчика Нейросети Платные (руб)

Первые попытки разработки на 1С с использованием больших языковых моделей (LLM) могут разочаровать. LLMки сильно галлюцинируют, потому что не знают устройства конфигураций 1С, не знают нюансов синтаксиса. Но если дать им подсказки с помощью MCP, то результат получается кардинально лучше. Далее в публикации: MCP для поиска по метаданным 1С, справке синтакс-помощника и проверки синтаксиса.

15250 руб.

25.08.2025    62117    128    36    

136

Пакетная печать Печатные формы Инструментарий разработчика Программист 1С:Предприятие 8 Платные (руб)

Расширение для создания и редактирования печатных форм в системе 1С:Предприятие 8.3. Благодаря конструктору можно значительно снизить затраты времени на разработку печатных форм, повысить качество и прозрачность разработки, а также навести порядок в многообразии корпоративных печатных форм. Обновление версии от 21.04.26

22570 руб.

06.10.2023    39736    111    48    

125

Инструментарий разработчика Разработка Администрирование веб-серверов Системный администратор Программист Бизнес-аналитик Руководитель проекта 1С 8.3 Платные (руб)

Analyzer 1C сводит выгрузку 1С — основную конфигурацию и все расширения — в единый граф знаний. Любой запрос по связям за доли секунды, с пометками «Доб.» / «Заимств.» / «Переопределено». Новое в 2.0 — обновление поставки: сравнение и объединение версий деревом «как в Конфигураторе» с выгрузкой плана решений; поиск конфликтов из-за перехватов расширений и висячих ссылок; загрузка из бинарных .cf/.cfe; циклические зависимости. Плюс анализ влияния, запросы BSL, роли и RLS, граф вызовов. Минута на развёртывание через Docker без необходимости подключения к Интернет. Любая 1С:Предприятие 8.3+.

14000 руб.

17.04.2026    8111    33    45    

49

Инструменты администратора БД Инструментарий разработчика Роли и права Программист 1С:Предприятие 8 1C:Бухгалтерия Россия Платные (руб)

Расширение позволяет без изменения кода конфигурации выполнять проверки при вводе данных, скрывать от пользователя недоступные ему данные, выполнять код в обработчиках. Не изменяет данные конфигурации, легко устанавливается практически на любую конфигурацию на управляемых формах.

17000 руб.

10.11.2023    26205    97    46    

104

Мастера заполнения Поиск данных Инструментарий разработчика Подбор и обработка объектов 1С 8.3 1С 8.5 Платные (руб)

Infostart MagicInput улучшает подбор в полях ввода 1С: ищет по любой части названия и по нескольким ключевым фрагментам, распознаёт ввод в другой раскладке и показывает иконки/статусы объектов прямо в списке. Поддерживает вставку навигационной ссылки/представления документа для автоподбора; для разработчиков доступны поиск по GUID и полному имени предопределённого. Работает в управляемых формах и подключается в большинстве конфигураций 1С 8.3/8.5.

6000 руб.

25.02.2026    4612    16    1    

20

Информационная безопасность Инструменты администратора БД Инструментарий разработчика Учет документов Системный администратор Программист Бизнес-аналитик Бухгалтер Пользователь Руководитель проекта 1С 8.3 1С 8.5 Розничная и сетевая торговля (FMCG) Платные (руб)

Контроль ввода данных в 1С: проверка заполнения реквизитов, обязательные поля, контроль перед записью и проведением, запрет проведения документа. Позволяет настраивать любые проверки данных в 1С 8.3/8.5 от обязательных полей до сложных условий – без открытия конфигуратора и написания кода. Готовое расширение, которое подключается и работает сразу.

6000 руб.

15.04.2026    2435    6    0    

21
Для отправки сообщения требуется регистрация/авторизация