Последние годы инжиниринг живёт под одним лозунгом: то же самое, но дешевле и быстрее. Заказчики сокращают бюджеты и сроки, подрядчики ищут, какие процессы можно оптимизировать, и автоматизация проектирования становится одним из первых кандидатов. Рутинных операций в проектировании много, и значительную часть из них можно передать скриптам и небольшим программным утилитам.
Логично было начать с автоматизации САПР — в моём случае со 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 по общей памяти. Для каждого результата можно увидеть, на какие сущности и исходные материалы он опирается.
Забегая вперёд, основные точки риска удобно разложить по слоям будущей системы. К этой карте мы ещё вернёмся — она показана на первом изображении.
Раскопки: что вообще есть внутри
Раз интернет и публичные модели молчали, оставалось одно место, где нужное знание точно было, — сама система. Я полез в установку Smart3D и провёл ревизию: скомпилированные сборки — есть; XML-документация — тоже есть, но только для части сборок; нашлось немного примеров кода и несколько готовых решений, написанных до меня. Негусто, но это уже были данные.
Правда, XML-документация быстро показала свой предел. Она описывает API снаружи: имя метода, параметры и короткое пояснение. Но при разработке возникают другие вопросы: какая перегрузка действительно работает, с какими классами этот метод используется, какие исключения он выбрасывает при некорректных аргументах и, главное, как выглядит типовой сценарий применения не в теории, а в реальном коде? Ответы на эти вопросы находятся внутри — в телах методов скомпилированных сборок.
Раз нужное знание нельзя найти в открытых источниках, его можно восстановить из самих библиотек: декомпилировать сборки, разобрать API и описать найденные сущности. Вручную это долго и довольно муторно — в библиотеках тысячи типов и методов. И здесь я второй раз вернулся к языковой модели, но уже в другой роли: не «напиши мне код», а «помоги описать API, опираясь только на факты».
Здесь нужна важная оговорка: всё это создавалось как внутренний инструмент для разработки собственных расширений. Декомпилированный код, база сущностей и поисковый индекс никуда не публикуются и не распространяются — они остаются внутри того же внутреннего контура, где работает сама система.
Дальше проект начал расти сам собой, модуль за модулем, и с каждым новым шагом становился всё интереснее.
Сначала появились сырые данные: я декомпилировал сборки и написал парсер. Он извлекал из декомпилированного C# сигнатуры, XML-документацию и проверяемые факты из тел методов, например вызовы других методов, исключения и создаваемые объекты. На этом этапе никакой языковой модели ещё не было, только структурированная выжимка фактов.
Затем появился модуль обогащения. Языковая модель получала эту выжимку и на её основе формулировала сценарии использования и поисковые подсказки. Тогда же появилось главное правило: модель должна описывать только подтверждённые факты, то есть только то, что действительно есть в переданных ей данных, а каждый вывод должен сохранять связь со своим источником. Без этого правила обогащение быстро превращалось бы в те же фантазии, только теперь они уже записывались бы в базу.
Следующим возник вопрос: что считать чанком для эмбеддинга? Просто резать код на отдельные куски не хотелось, потому что фрагмент без контекста редко объясняет, когда и зачем применять конкретный метод. Поэтому чанком стала не случайная часть текста, а карточка сущности: в неё вошли сигнатура, описание, извлечённые факты и поисковые подсказки, собранные в единый текст.
Отдельной задачей стал выбор модели эмбеддинга. Я изучил сравнения и попробовал несколько вариантов, начиная с компактных англоязычных моделей. Но запросы у нас двуязычные: инженер формулирует вопрос по-русски, а названия классов, методов и сам API остаются английскими. На таких запросах устойчивее всего показала себя мультиязычная модель bge-m3 — на ней я и остановился.
Потом появилась верификация данных, затем поиск, а после этого захотелось получить уже не просто поисковую систему, а агента, который сам определяет, какой информации ему не хватает. Потом появился проверяющий слой поверх генерации, тесты промптов и трассировка. Каждый новый модуль рождался примерно из одного вопроса: «А что будет, если добавить ещё вот это?» И в какой-то момент стало понятно, что бросать проект на полпути уже не хочется — стало интересно самому довести весь контур до конца.
Но у проекта была и вполне практическая причина. Smart3D у нас — не лабораторный стенд: команда сопровождает рабочую инженерную среду крупного промышленного проекта, в которой около 5 000 типов элементов каталога, порядка 100 000 типоразмеров и 120 трубопроводных классов. Есть параметрические объекты, собственные утилиты и интеграции.
В такой среде задачи по автоматизации не заканчиваются: сегодня нужно массово изменить атрибуты, завтра — проверить данные после обновления каталога, а послезавтра — разобраться, почему штатная операция даёт неожиданный результат, и написать для этого небольшую команду.
И почти каждая такая задача упирается не в знание C#, а в знание самого API. Нужный класс, подходящую перегрузку или правильный порядок вызовов приходится искать вручную либо ставить задачу в очередь к специалисту. Поэтому бизнес-задача здесь довольно простая: сократить путь от инженерной потребности до проверяемого решения, разгрузить очередь к экспертам и не терять уже найденную техническую логику.
Для эксперимента у нас уже была подходящая инфраструктура. В компании есть департамент Цифровой трансформации бизнеса, который работает с заказчиком в закрытом контуре. Для этих задач внутри периметра уже были развёрнуты языковая модель, система распознавания документов и сервис распознавания речи. Отдельный сервер разработки был доступен для внутренних экспериментов — его я и использовал. Код и данные не покидали внутренний контур, а обращения к модели не требовали внешнего API, поэтому разные варианты можно было спокойно проверять без оплаты каждого отдельного запроса.
И ещё одна оговорка о моей роли. Я не штатный разработчик утилит для Smart3D: моя зона ответственности — инженерная среда, данные и логика процессов проектирования. Этот прототип пока нельзя назвать готовым продуктом, но отдельные сценарии уже используются на практике — от проверки технических решений до написания кода.
От памяти объекта к памяти API
В прошлой статье я писал о потере инженерной памяти объекта. Проектная организация уходит, результат остаётся, но логика, по которой он создавался, часто теряется. Если правила работы с моделью остаются только в головах отдельных специалистов, владелец постепенно теряет управляемость данными.
С автоматизацией САПР происходит нечто похожее, и дело не в том, что чужой код якобы может понять только его автор. Хороший программист разберётся в чужом коде. Сложность в другом: здесь нужен человек, который совмещает компетенции разработчика и инженерного аналитика.
Он должен не только написать команду, но и понять, какую операцию действительно стоит автоматизировать, какие сущности модели будут затронуты, как изменение повлияет на связанные данные и где потом проявится результат — в модели, атрибутах, спецификации или выходной документации. Такое сочетание компетенций встречается нечасто, поэтому знание 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 или добавлении новых сборок.
Второй контур работает уже во время запроса инженера. Это агентная часть системы: на каждый запрос агент обращается и к векторному индексу, и непосредственно к исходным данным. Общая схема двух контуров показана на третьем изображении.
В рабочем прототипе все этапы собраны в обычное консольное меню, которое запускается через файл RUN.bat. Отдельного пользовательского интерфейса пока нет. Скриншот меню нужен только для того, чтобы показать: весь пайплайн запускается как воспроизводимая локальная процедура.
Теперь разберём систему по отдельным стадиям.
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.
Структура карточки показана на пятом изображении.
Парсер извлекает сущности эвристически прямо из декомпилированных .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-цикл состоит из нескольких повторяемых шагов, которые показаны на шестом изображении.
В этом цикле я оставил несколько предохранителей от типичных ошибок 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-иерархии.
Проверка результата при этом остаётся адресной. По найденным сущностям видно, откуда появились GetNamingRules, ActiveNameRule и 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/