Метод, которого не существовало: как я собрал локальный RAG для CAD API

от автора

Локальный RAG для Smart3D API

Локальный RAG для Smart3D API

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

Логично было начать с автоматизации САПР — в моём случае со Smart3D. И здесь возникает парадокс: автоматизация под Smart3D сама по себе оказывается дорогой и небыстрой. Разработчиков, хорошо знакомых с этим API, немного, их работа стоит дорого, а задачи постепенно выстраиваются в очередь. Получается замкнутый круг: процессы нужно оптимизировать, но сам инструмент оптимизации становится узким местом.

Smart3D

Smart3D (Intergraph Smart 3D) — промышленная система автоматизированного проектирования компании Hexagon, ранее Intergraph. Она используется при создании крупных промышленных объектов в нефтегазовой отрасли, энергетике и судостроении. Проектирование ведётся в общей базе данных, а модель содержит большое количество атрибутов, связей и инженерных правил. Расширять возможности системы можно через программные интерфейсы — COM и .NET API. В работе я использую версию Smart3D 2016 года.

При этом основная сложность не в языке программирования. Разрабатывать расширения можно и на C#, и на VB.NET, а сам код типовой утилиты обычно не слишком сложный. Дорогим и медленным процесс делает другое — знание конкретного API. Какие классы действительно существуют? Какая перегрузка работает именно в этой версии? Где искать нужный метод, какие у него ограничения и в каком порядке нужно вызывать связанные методы? Именно ответы на эти вопросы занимают основное время.

В январе 2026 года, во время новогодних праздников, у меня появилось немного свободного времени. Я решил проверить простую гипотезу: можно ли снизить порог входа в Smart3D API с помощью языковой модели и локального RAG-контекста? Для меня это был ещё и способ самому глубже погрузиться в тему, потому что практика по-прежнему остаётся одним из лучших способов обучения.

Первая попытка: спросить модель в лоб

Первый заход был наивным. Я просто спросил публичную модель, как выполнить типовую операцию через Smart3D API.

Ответ выглядел убедительно: аккуратный C#-код, классы похожи на настоящие, название метода звучит правдоподобно, а сигнатура выглядит логично.

Подвох обнаружился только на этапе компиляции: такого метода в этой версии API просто не существовало.

Но модель здесь не виновата — ей действительно неоткуда было это узнать. Smart3D-кода в открытых источниках исчезающе мало: API проприетарный и ограниченно публичный, форумы почти молчат, практических примеров в интернете единицы, а основная часть знаний остаётся внутри проектных команд.

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

Значит, модели нужен не широкий кругозор, а проверяемый локальный контекст.

В этой статье RAG — это не чат-бот поверх набора документов. Это способ дать модели локальный справочник по API и заставить её опираться на реально найденные сущности, а не пытаться восстановить API по общей памяти. Для каждого результата можно увидеть, на какие сущности и исходные материалы он опирается.

Забегая вперёд, основные точки риска удобно разложить по слоям будущей системы. К этой карте мы ещё вернёмся — она показана на первом изображении.

Изображение 1. Карта типовых рисков локального RAG по API

Изображение 1. Карта типовых рисков локального RAG по API

Раскопки: что вообще есть внутри

Раз интернет и публичные модели молчали, оставалось одно место, где нужное знание точно было, — сама система. Я полез в установку Smart3D и провёл ревизию: скомпилированные сборки — есть; XML-документация — тоже есть, но только для части сборок; нашлось немного примеров кода и несколько готовых решений, написанных до меня. Негусто, но это уже были данные.

Правда, XML-документация быстро показала свой предел. Она описывает API снаружи: имя метода, параметры и короткое пояснение. Но при разработке возникают другие вопросы: какая перегрузка действительно работает, с какими классами этот метод используется, какие исключения он выбрасывает при некорректных аргументах и, главное, как выглядит типовой сценарий применения не в теории, а в реальном коде? Ответы на эти вопросы находятся внутри — в телах методов скомпилированных сборок.

Раз нужное знание нельзя найти в открытых источниках, его можно восстановить из самих библиотек: декомпилировать сборки, разобрать API и описать найденные сущности. Вручную это долго и довольно муторно — в библиотеках тысячи типов и методов. И здесь я второй раз вернулся к языковой модели, но уже в другой роли: не «напиши мне код», а «помоги описать API, опираясь только на факты».

Здесь нужна важная оговорка: всё это создавалось как внутренний инструмент для разработки собственных расширений. Декомпилированный код, база сущностей и поисковый индекс никуда не публикуются и не распространяются — они остаются внутри того же внутреннего контура, где работает сама система.

Дальше проект начал расти сам собой, модуль за модулем, и с каждым новым шагом становился всё интереснее.

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

Затем появился модуль обогащения. Языковая модель получала эту выжимку и на её основе формулировала сценарии использования и поисковые подсказки. Тогда же появилось главное правило: модель должна описывать только подтверждённые факты, то есть только то, что действительно есть в переданных ей данных, а каждый вывод должен сохранять связь со своим источником. Без этого правила обогащение быстро превращалось бы в те же фантазии, только теперь они уже записывались бы в базу.

Следующим возник вопрос: что считать чанком для эмбеддинга? Просто резать код на отдельные куски не хотелось, потому что фрагмент без контекста редко объясняет, когда и зачем применять конкретный метод. Поэтому чанком стала не случайная часть текста, а карточка сущности: в неё вошли сигнатура, описание, извлечённые факты и поисковые подсказки, собранные в единый текст.

Отдельной задачей стал выбор модели эмбеддинга. Я изучил сравнения и попробовал несколько вариантов, начиная с компактных англоязычных моделей. Но запросы у нас двуязычные: инженер формулирует вопрос по-русски, а названия классов, методов и сам API остаются английскими. На таких запросах устойчивее всего показала себя мультиязычная модель bge-m3 — на ней я и остановился.

Потом появилась верификация данных, затем поиск, а после этого захотелось получить уже не просто поисковую систему, а агента, который сам определяет, какой информации ему не хватает. Потом появился проверяющий слой поверх генерации, тесты промптов и трассировка. Каждый новый модуль рождался примерно из одного вопроса: «А что будет, если добавить ещё вот это?» И в какой-то момент стало понятно, что бросать проект на полпути уже не хочется — стало интересно самому довести весь контур до конца.

Но у проекта была и вполне практическая причина. Smart3D у нас — не лабораторный стенд: команда сопровождает рабочую инженерную среду крупного промышленного проекта, в которой около 5 000 типов элементов каталога, порядка 100 000 типоразмеров и 120 трубопроводных классов. Есть параметрические объекты, собственные утилиты и интеграции.

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

И почти каждая такая задача упирается не в знание C#, а в знание самого API. Нужный класс, подходящую перегрузку или правильный порядок вызовов приходится искать вручную либо ставить задачу в очередь к специалисту. Поэтому бизнес-задача здесь довольно простая: сократить путь от инженерной потребности до проверяемого решения, разгрузить очередь к экспертам и не терять уже найденную техническую логику.

Для эксперимента у нас уже была подходящая инфраструктура. В компании есть департамент Цифровой трансформации бизнеса, который работает с заказчиком в закрытом контуре. Для этих задач внутри периметра уже были развёрнуты языковая модель, система распознавания документов и сервис распознавания речи. Отдельный сервер разработки был доступен для внутренних экспериментов — его я и использовал. Код и данные не покидали внутренний контур, а обращения к модели не требовали внешнего API, поэтому разные варианты можно было спокойно проверять без оплаты каждого отдельного запроса.

И ещё одна оговорка о моей роли. Я не штатный разработчик утилит для Smart3D: моя зона ответственности — инженерная среда, данные и логика процессов проектирования. Этот прототип пока нельзя назвать готовым продуктом, но отдельные сценарии уже используются на практике — от проверки технических решений до написания кода.

От памяти объекта к памяти API

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

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

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

Это показано на втором изображении.

Изображение 2. Два контура разработки под Smart3D API

Изображение 2. Два контура разработки под Smart3D API

Дальше я намеренно называю результат работы системы черновиком. Задача не в том, чтобы сразу получить код и без проверки передать его в работу, а в том, чтобы быстрее собрать проверяемую заготовку. В ней уже есть найденные сущности API, ссылки на исходные данные и журнал трассировки, поэтому специалист может проверять не всё решение целиком, а конкретные места, на которых оно построено.

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

Дальше разберём, как устроен этот контур. Сначала посмотрим на систему целиком — какие слои в неё входят и как данные проходят путь от DLL-файлов до черновика кода, а после этого отдельно разберём каждую стадию. Если вас больше интересует практический результат, можно сразу перейти к разделу «Живая сессия»: там видно, как система работает с реальными запросами инженера.

Общая архитектура

Всю систему можно описать одной последовательностью:

source layer → entity cards → vector index → agent loop → C# draft + trace

На выходе получается черновик C#-кода вместе с трассировкой. Если упростить, сначала система строит локальный справочник по API, затем агент использует этот справочник как навигацию и каждый раз проверяет, откуда именно получено найденное знание и к какому исходнику оно ведёт.

В развёрнутом виде весь процесс состоит из нескольких последовательных стадий:

DLL / XML  → DllRoslynDump: decompile в .cs  → Source layer (Release/_DecompOut)  → Stage1.py: parse + enrich  → entities.enriched.jsonl  → Stage2.py: bge-m3 embeddings  → meta.jsonl + vectors.npy  → Stage3.py: history-aware agent  → C# draft + used_entity_keys + trace

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

Второй контур работает уже во время запроса инженера. Это агентная часть системы: на каждый запрос агент обращается и к векторному индексу, и непосредственно к исходным данным. Общая схема двух контуров показана на третьем изображении.

Изображение 3. Архитектура: build-time и runtime контуры

Изображение 3. Архитектура: build-time и runtime контуры

В рабочем прототипе все этапы собраны в обычное консольное меню, которое запускается через файл RUN.bat. Отдельного пользовательского интерфейса пока нет. Скриншот меню нужен только для того, чтобы показать: весь пайплайн запускается как воспроизводимая локальная процедура.

Изображение 4. Консольное меню запуска пайплайна

Изображение 4. Консольное меню запуска пайплайна

Теперь разберём систему по отдельным стадиям.

Stage 0. Декомпиляция

Декомпиляцией занимается DllRoslynDump — отдельная консольная утилита на .NET, которая работает до всех Python-стадий. Основной движок — ICSharpCode.Decompiler. Roslyn используется во вспомогательном semantic-режиме для построения дополнительных API-документов; основной enriched-пайплайн Stage1 читает декомпилированный source-слой из Release/_DecompOut.

Режим decompile берёт DLL из Release и раскладывает декомпилированный C# по сборкам и namespace в Release/_DecompOut. При этом он отбрасывает служебные типы, сгенерированные компилятором, и записывает каждый верхнеуровневый тип в отдельный .cs-файл.

Ключевая настройка — ShowXmlDocumentation = true. Благодаря ей XML-документация не отбрасывается, а переносится прямо в декомпилированный код, чтобы на следующей стадии её можно было извлечь вместе с остальными данными.

После декомпиляции исходный слой содержит 2366 .cs-файлов из 6 сборок Smart3D. Из этих файлов извлекаются API-сущности, а при необходимости агент может вернуться к конкретному фрагменту исходного кода.

Stage 1. Карточка API-сущности вместо chunking’а

Главное отличие от обычного RAG начинается именно здесь — с того самого решения из раскопок. Stage1.py не режет код на обычные текстовые чанки, а превращает каждую найденную парсером API-сущность в структурированную карточку. В неё входят сигнатура, тип сущности, XML-summary, проверяемые факты из кода, поисковые подсказки, embedding_text и стабильный entity_key.

Структура карточки показана на пятом изображении.

Изображение 5. Карточка API-сущности

Изображение 5. Карточка API-сущности

Парсер извлекает сущности эвристически прямо из декомпилированных .cs-файлов. Он получает сигнатуры и XML-doc, включая summary, params, returns и exceptions, а отдельная функция extractfacts() извлекает из тела метода проверяемые факты: какие методы он вызывает, какие исключения бросает и какие объекты создаёт.

Поверх этих данных работает LLM-обогащение, но действует оно по уже заданному правилу: только подтверждённые факты. Сценарии использования и поисковые подсказки формируются только на основе переданных сигнатур, XML и фактов из кода. Модель не должна опираться на общие представления об API, а у каждого вывода сохраняется след происхождения, поэтому результат обогащения можно проверить и аудировать.

Обогащение — самая затратная по времени стадия всего пайплайна, поэтому для неё используется кэш. Его ключ рассчитывается по сигнатуре, XML, фактам из кода и версии промпта. При повторном запуске LLM вызывается только для тех сущностей, в которых что-то изменилось, а все остальные данные загружаются из кэша.

Каждая сущность получает детерминированный ключ:

entity_key = {type}::{kind}::{sha1(нормализованная сигнатура)}

Например: SymbolGeometryHelper::method::624b24cc.... Этот ключ проходит через все следующие стадии, и по нему агент показывает, на какую сущность опирался при генерации. Проще говоря, у каждого метода API появляется собственный паспорт, на который можно сослаться.

На выходе Stage1 в этом снимке получилось 6484 API-сущности: 2094 класса, 2736 конструкторов, 1575 методов и 79 свойств.

Stage 2. Векторный индекс

Это самая короткая стадия. Stage2.py берёт embedding_text каждой карточки, рассчитывает эмбеддинги через SentenceTransformer с моделью BAAI/bge-m3 и сохраняет их в файловую матрицу. Размерность векторов — 1024, формат — float32; используется L2-нормализация. Устройство выбирается автоматически: при наличии используется CUDA, в противном случае — CPU. На текущем масштабе поиск выполняется полным сравнением вектора запроса со всей матрицей, поэтому отдельная векторная база или ANN-индекс пока не нужны.

В индекс попадает не сырой код, а embedding_text. Сигнатура API сохраняется, но дополняется кратким описанием, контекстом владельца, сценариями использования и поисковыми подсказками на двух языках — то есть теми формулировками, которыми инженер действительно описывает задачу.

Например, «создать коробку 2×2×3 м» — для работы с геометрией или «выбрать все элементы типа RUN» — для работы с бизнес-объектами. Поэтому поиск опирается не только на синтаксис сигнатуры, но и на смысловое описание сущности и возможных сценариев её применения.

Контракт этой стадии состоит из двух файлов. В meta.jsonl хранятся метаданные в том же порядке, в котором расположены векторы, а в vectors.npy — матрица размером 6484×1024. Вместе они образуют стабильную границу между слоем извлечения данных и агентным слоем.

Три основных артефакта — enriched-база, метаданные и матрица векторов — вместе занимают около 77 МБ, то есть основная инженерная память API фактически помещается на обычную флешку. Для экспериментов это оказалось удобным: файлы хранятся рядом с проектом, поэтому не нужна отдельная внешняя векторная база и не нужен облачный поиск

Stage 3. Агентный цикл

Первый прототип был устроен проще: top-k поиск и один запрос к модели. Но такой подход быстро показал свои ограничения.

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

Поэтому Stage3 работает в несколько последовательных шагов и не сводится к одному запросу к модели. Сначала агент разбирает задачу и формирует поисковые запросы, затем находит подходящие API-сущности. При необходимости он дочитывает декомпилированный C# и собирает дополнительный контекст. Только после этого модель формирует C#-черновик, а отдельный проверяющий слой оценивает, достаточно ли найденного контекста и не появились ли в коде неизвестные символы.

Полный runtime-цикл состоит из нескольких повторяемых шагов, которые показаны на шестом изображении.

Изображение 6. Runtime-цикл Stage3

Изображение 6. Runtime-цикл Stage3

В этом цикле я оставил несколько предохранителей от типичных ошибок LLM.

Первый — не передавать генератору найденный фрагмент кода сразу в исходном виде. Сначала поиск возвращает карточку API-сущности, в которой есть сигнатура, описание, проверяемые факты, путь к файлу и номера строк. Если нужно увидеть реализацию, агент отдельно дочитывает декомпилированный .cs. В результате векторный поиск работает как навигация, а детали проверяются через исходный слой.

Второй предохранитель — возможность честно сказать, что контекста недостаточно. Для этого предусмотрен режим need_more: агент может запустить дополнительный поиск или запросить чтение исходного кода, то есть не дорисовывать API из головы, а сначала получить недостающие данные.

Третий предохранитель — сохранение следа результата. В каждом ответе остаются used_entity_keys, по которым можно открыть карточки и проверить, откуда появились конкретные классы, методы и сигнатуры.

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

Пятый предохранитель — история между запросами. Агент сохраняет предыдущие шаги, поэтому уточняющий запрос, например «поверни созданный элемент», может продолжить уже сформированный код, а не начинать задачу заново. Но важно понимать ограничение: это память текста и кода, а не живое состояние открытой модели Smart3D.

Часть этих правил встроена прямо в системный промпт генератора. Промпты в проекте написаны на английском, поэтому далее фрагмент приведён без перевода.

Промт
Rules:- Do NOT invent API details. If you lack facts, request more.- Prefer minimal, compilable C# examples.- Always include used_entity_keys in your response.Output schema:{ "status": "ok" | "need_more", "used_entity_keys": [...],  "more_queries": [...], "tool_calls": [...], "code": ..., "notes": [...] }

Правила «не изобретай API» и «всегда возвращай использованные сущности» — это не просто рекомендации, а часть формата взаимодействия с моделью. Модель должна вернуть валидный JSON с ожидаемыми полями; оркестратор использует статус ответа и сохраняет used_entity_keys, по которым специалист может адресно проверить использованные сущности. Полной JSON Schema-валидации и автоматической проверки каждого ключа в текущей версии пока нет.

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

Весь процесс генерации трассируется. Журнал сохраняет события поиска, вызовы инструментов, ответы модели, оценки и результаты диагностики, поэтому ошибки можно разбирать постфактум по журналу, а не по ощущениям.

Во всех LLM-ролях используется GPT-OSS-120B. Модель работает через локальный OpenAI-совместимый интерфейс и полностью остаётся внутри периметра. Температуры выбраны консервативные: 0.05 — для обогащения, 0.1 — для планирования, генерации и оценки.

Живая сессия

Теперь посмотрим, как система работает в живой цепочке запросов. В сохранённой сессии агента было 10 запросов: генерация простой геометрии, последующие повороты созданных объектов, выбор бизнес-объектов, фильтрация и изменение атрибутов. 9 запросов завершились успешно, 1 — неудачно.

Но здесь важно уточнить, что означает слово «успешно». Агент вернул черновик со статусом ok, а при просмотре кода была видна правильная API-идея. Это не означает, что код был скомпилирован или запущен в Smart3D. И эти результаты не стоит воспринимать как benchmark: это данные одной демонстрационной сессии, не больше.

Самым показательным оказался сценарий смены правила именования для трубопроводных RUN. Инженер задаёт предметный контекст: RUN в модели соответствует классу CPMPipeRun, а активное правило именования нужно заменить на RunName. После этого агент обращается к локальной базе API и находит подходящий механизм:

DLL / XML  → DllRoslynDump: decompile в .cs  → Source layer (Release/_DecompOut)  → Stage1.py: parse + enrich  → entities.enriched.jsonl  → Stage2.py: bge-m3 embeddings  → meta.jsonl + vectors.npy  → Stage3.py: history-aware agent  → C# draft + used_entity_keys + trace

Ниже приведён C#-черновик для этого сценария в том виде, в котором его вернул агент. Функционально код не исправлялся; в комментарии лишь убрана неподтверждённая привязка к DefaultNameRule. В черновике заложена правильная API-идея: правило именования ищется через CatalogBaseHelper.GetNamingRules, затем назначается через NameRuleHelper.ActiveNameRule, после чего фиксируется транзакция. Но это ещё не готовая команда Smart3D, а только тело операции, которое необходимо встроить в штатный командный класс.

C#-черновик
using System;using Ingr.SP3D.Common.Client.Services;using Ingr.SP3D.Common.Middle;using Ingr.SP3D.ReferenceData.Middle.Services;public static void SetRunNameRule(){    // Ищем правило именования RunName среди правил,    // доступных для класса Pipe Run: CPMPipeRun.    BusinessObject targetRule = null;    foreach (BusinessObject rule in new CatalogBaseHelper().GetNamingRules("CPMPipeRun"))    {        string ruleName = (string)rule.GetPropertyValue("IJDNameRuleHolder", "Name");        if (ruleName == "RunName")        {            targetRule = rule;            break;        }    }    if (targetRule == null)    {        throw new Exception("RunName not found for CPMPipeRun");    }    // Берём все выделенные в модели объекты.    foreach (object obj in ClientServiceProvider.SelectSet.SelectedObjects)    {        BusinessObject bo = obj as BusinessObject;        if (bo == null)        {            continue;        }        // Оставляем только RUN. В Smart3D Pipe Run имеет класс CPMPipeRun.        if (bo.ClassInfo.Name != "CPMPipeRun")        {            continue;        }        // Меняем активное правило именования RUN.        // Это не прямое изменение IJNamedItem.Name,        // а смена текущего Name Rule на RunName.        NameRuleHelper helper = new NameRuleHelper(bo);        helper.ActiveNameRule = targetRule;    }    // Фиксируем изменения в транзакции Smart3D.    ClientServiceProvider.TransactionMgr.Commit("Set RunName for selected runs");}

Этот фрагмент показателен именно как черновик. Агент нашёл правильный предметный механизм, но не собрал полностью компилируемую команду: метод не обёрнут в класс, а прямое приведение результата GetPropertyValue к string требует ручной корректировки. Это не только вопрос знания отдельных методов API. Здесь не хватило проверенного шаблона команды и обратной связи от компилятора. Если добавить в контекст рабочие каркасы BaseModalCommand, типовые способы чтения свойств и цикл compile-check, агент сможет выдавать результат, который потребует меньше ручной доводки.

Разница хорошо видна на рабочем варианте. Для запуска в Smart3D код должен быть оформлен как команда, унаследованная от BaseModalCommand. Точкой входа становится OnStart, внутри которой выполняются обработка исключений, откат транзакции, вывод информации в строку состояния и завершение команды. Сборка DLL, регистрация команды и её подключение к интерфейсу Smart3D здесь опущены: показаны только класс команды и предметная логика.

При этом сама предметная логика остаётся прежней: получить выделенные объекты, оставить только CPMPipeRun, найти целевое правило именования — в коде оно задаётся через TargetRuleName, а в этом примере используется RunName — и затем назначить найденное правило через NameRuleHelper.ActiveNameRule.

Рабочий вариант
using System;using Ingr.SP3D.Common.Client;using Ingr.SP3D.Common.Client.Services;using Ingr.SP3D.Common.Middle;using Ingr.SP3D.ReferenceData.Middle.Services;public class Filter01 : BaseModalCommand{    private const string RunClassName = "CPMPipeRun";    private const string TargetRuleName = "RunName";    public override void OnStart(int commandID, object argument)    {        base.OnStart(commandID, argument);        try        {            SetRunNameRule();            WriteStatusBarMsg("Name Rule для выбранных RUN изменен на " + TargetRuleName);        }        catch (Exception ex)        {            ClientServiceProvider.TransactionMgr.Abort();            WriteStatusBarMsg("Ошибка изменения Name Rule: " + ex.Message);            throw;        }        finally        {            StopCommand();        }    }    private void SetRunNameRule()    {        BusinessObject targetRule = FindNameRule(RunClassName, TargetRuleName);        if (targetRule == null)        {            throw new Exception(TargetRuleName + " not found for " + RunClassName);        }        int changedCount = 0;        foreach (object obj in ClientServiceProvider.SelectSet.SelectedObjects)        {            BusinessObject bo = obj as BusinessObject;            if (bo == null)            {                continue;            }            if (bo.ClassInfo == null || bo.ClassInfo.Name != RunClassName)            {                continue;            }            NameRuleHelper helper = new NameRuleHelper(bo);            helper.ActiveNameRule = targetRule;            changedCount++;        }        if (changedCount == 0)        {            throw new Exception("Среди выделенных объектов нет RUN класса " + RunClassName);        }        ClientServiceProvider.TransactionMgr.Commit("Set " + TargetRuleName + " for selected runs");    }    private BusinessObject FindNameRule(string className, string ruleName)    {        CatalogBaseHelper catalog = new CatalogBaseHelper();        foreach (BusinessObject rule in catalog.GetNamingRules(className))        {            string currentName = Convert.ToString(                rule.GetPropertyValue("IJDNameRuleHolder", "Name")            );            if (string.Equals(currentName, ruleName, StringComparison.OrdinalIgnoreCase))            {                return rule;            }        }        return null;    }}

Главный предметный нюанс при этом не меняется: код не изменяет строковое имя RUN напрямую, а меняет Name Rule. Именно этот параметр проектировщик видит в интерфейсе Smart3D как выпадающий список правил именования. На таких деталях хорошо видно, зачем системе нужен предметный контекст. Формально задачу «изменить имя» можно понять буквально, но штатная операция в Smart3D устроена иначе — она заключается в смене активного правила именования. В этом конкретном примере фильтр намеренно использует точное имя класса CPMPipeRun; если в другой конфигурации нужно учитывать производные или пользовательские классы, проверку типа придётся расширить с учётом фактической BOC-иерархии.

Проверка результата при этом остаётся адресной. По найденным сущностям видно, откуда появились GetNamingRulesActiveNameRule и TransactionMgr, поэтому не нужно заново просматривать весь API.

Рабочий командный класс показывает текущую границу прототипа. RAG уже сокращает путь к правильной API-идее, но финальную форму команды пока доводит специалист: он добавляет жизненный цикл BaseModalCommand, обработку ошибок и запуск через OnStart. При этом такая граница не выглядит принципиальным ограничением. Если добавить в контекст проверенные шаблоны команд, объём ручной доработки можно сократить.

Единственный неудачный запрос система не стала исправлять угадыванием. Planner сформировал поисковые запросы, а retrieval нашёл релевантные сущности, но агент не смог подтвердить корректный способ применить поворот к найденной геометрии и после десяти раундов так и не дошёл до статуса ok. После более точной формулировки — «повернуть созданный элемент по оси» — агент нашёл Projection3d, конструктор Matrix4X4 и собрал операцию поворота. В этом случае честный незавершённый результат оказался полезнее уверенной галлюцинации.

Накопленный trace-файл за несколько серий запусков показывает, сколько работы скрывается за ответами агента. В нём было зафиксировано 412 событий: 71 поисковая выдача, 54 обращения к инструментам, 26 проверок кода и 13 диагностик неизвестных символов.

Агент действительно работает в несколько раундов: повторно выполняет поиск, дочитывает исходный код и проверяет собственный результат, а не ограничивается одним поиском и одним запросом к модели.

Где RAG может ошибиться

После живой сессии стоит вернуться к карте рисков из начала статьи. Теперь уже видно, какие слои действительно нужно контролировать. Важно понимать: схема показывает типовые риски самого подхода, а не перечень сбоев именно этого проекта.

Для части рисков уже предусмотрены архитектурные меры. Журнал трассировки и список использованных сущностей позволяют разобрать происхождение ответа, read_source и карточки сущностей снижают вероятность выбора неправильной перегрузки, а режим need_more помогает не заполнять пробелы вымышленными данными.

Отдельный риск — версионирование снимка знаний. В RAG по API нельзя хранить индекс отдельно от исходных данных: карточки сущностей, метаданные, векторы и декомпилированный исходный слой должны относиться к одной версии DLL и XML. Если эти части расходятся, агент может найти подходящую карточку, но затем не сможет корректно прочитать исходный код или проверить детали вызова.

Поэтому для такого проекта нужен ещё один обязательный слой — паспорт снимка, или manifest. Он должен фиксировать, какие сборки, XML-документация, декомпилированные исходники, обогащённая база и векторный индекс образуют единый снимок API.

Главный незакрытый риск — проверка результата. Сейчас черновик проверяют только внутренние механизмы самого агента: проверяющий слой и диагностика неизвестных символов. По сути, оба механизма остаются эвристиками. Обязательной компиляции пока нет, как нет прогона в Smart3D и интеграционной проверки. Именно поэтому результат системы я называю черновиком, без попытки представить его чем-то большим.

Что дальше

Ближайшие шаги связаны с проверкой. Сгенерированный C# должен обязательно проходить компиляцию, после чего нужен прогон в Smart3D — сначала на тестовой модели, а затем в интеграционном сценарии. Сейчас черновик оценивает только сама модель, и это именно тот риск, о котором шла речь в предыдущем разделе.

К этому же относится паспорт снимка знаний из предыдущего раздела: все составляющие снимка — от сборок до векторного индекса — должны существовать как один версионированный артефакт.

После проверки нужно будет усилить поиск. Поверх векторного поиска потребуется точный и структурный: по именам классов и методов, а также по связям между сущностями. Это должно снизить вероятность того, что система найдёт похожее вместо точного. И нужно сделать жёстче правило «не хватает данных»: чем реже агент пытается угадывать, тем дешевле последующая проверка.

Ещё одно направление — проверенные практические паттерны. Для геометрии недостаточно найти нужный класс: нужно соблюдать порядок вызовов, структуру метода, параметры, логику преобразований и синтаксис конкретного символа. Поэтому для коробки, цилиндра, опоры, патрубка или другого типового элемента полезнее заполнять проверенный каркас, чем каждый раз генерировать код с нуля. В такой схеме RAG отвечает за выбор API-сущностей и проверку контекста, а шаблон — за устойчивую структуру C#-кода.

И здесь пригодятся находки из самых первых исследований — примеры кода и готовые решения, найденные в установке. Их можно использовать не для прямого копирования, а для извлечения практических паттернов: какие классы обычно применяются вместе, в каком порядке вызываются методы и какие проверки выполняются перед вызовом.

Отдельную ценность представляют собственные или разрешённые плагины. По ним видно, как в реальной системе устроена команда: откуда она получает контекст, как обрабатывает ошибки и куда передаёт результат. Это уже не просто RAG по справочнику API, а восстановление практических шаблонов разработки для конкретного САПР.

Дальняя цель — графическое программирование. Если типовые процессы оформить в виде нод, инженер сможет сам собирать цепочку из готовых блоков: выбрать элементы, отфильтровать их, изменить атрибут и построить геометрию. RAG-контур при этом уйдёт под капот: он будет подбирать API-сущности и проверять контекст внутри каждой ноды, а человек сможет работать на уровне самого процесса, не переходя к C#.

Последний риск — устаревание оркестрации. Агентные фреймворки и протоколы вроде MCP развиваются быстро, поэтому Stage3 со временем может быть пересобран или заменён готовым решением. Хотя ручная сборка агентного цикла изначально была частью замысла: так можно понять, как агент устроен изнутри, а не просто настроить готовый инструмент.

Но основную часть работы это не обесценивает. Исходный слой, карточки сущностей и индекс остаются знаниями о конкретной версии API, и их можно использовать с другим агентным движком. Именно поэтому build-time и runtime изначально разделены.

Вывод: инженерная память API

Первая статья была о том, что инженерная память объекта — это не только данные, но ещё и логика их создания. Эта статья показывает, что тот же принцип работает уровнем ниже — на уровне кода и автоматизации.

Главное изменение за время проекта связано не с самой моделью. Изменилась судьба знания. Редкое знание API перестало существовать только как устная традиция: у него появились карточки и ключи, исходные данные, журнал трассировки и проверяемый путь от запроса инженера до черновика кода. При этом весь процесс остаётся внутри рабочего периметра.

Если перевести результат с технического языка на язык компании, ценность пока не в том, что появилась «нейросеть, которая пишет плагины». Появился общий слой знаний: он сокращает повторный поиск, помогает быстрее подключать новых специалистов и сохраняет найденные API-решения внутри команды. Чем больше в этот слой попадёт проверенных шаблонов и рабочих сценариев, тем меньше типовых задач придётся каждый раз начинать с нуля.

Эксперт при этом никуда не исчезает. Он по-прежнему проверяет код, разбирает сложные случаи и решает, что можно передавать в работу. Но теперь эксперт перестаёт быть единственной точкой входа в API. Управляемость цифровой инженерной среды, как и управляемость модели объекта, появляется тогда, когда логика перестаёт зависеть от того, кто сегодня находится на месте.

ссылка на оригинал статьи https://habr.com/ru/articles/1057612/