Месяц, 500M токенов и симулятор завода: как мы с ИИ-агентом построили имитационную модель производства чипов

Завод мечты

Завод мечты

Зачем всё это читать, в чём польза?

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

Первой же пробой пера стал вовсе не Todo-app, а самый что ни на есть хардкорный энтерпрайз. Задача — создать полноценный аналог интеграционной шины CellWorks MBX. Это ключевой элемент автоматизации завода по производству полупроводников: она связывает в единое целое MES-систему и сотню единиц технологического оборудования. Мы как раз начинали перенос легаси-стека с PA-RISC-серверов на x64, и вопрос портирования старого транспорта стоял остро.

Вторым проектом стала имитационная модель этого производства. Нет-нет, это не аналог Factorio, в ней ни намёка на 3D или игровые механики. Это симулятор реального цеха: с моделями оборудования, технологическими маршрутами, реальным незавершённым производством, производственным планом на годы вперёд (при желании). Эта модель отвечает на вопрос: какую продукцию и в какой срок я потенциально смогу выпустить, если запущу в работу вот столько-то сырья, и оборудование не встанет надолго из-за поломки или отсутствия материалов. Результат превзошёл ожидания. Софт, который раньше создавался большими коллективами, благодаря ИИ и open-source, теперь можно создавать практически в одиночку, и активно внедрять в реальное производство!

Диаграмма загрузки оборудования. Симулятор спланировал работу на квартал вперёд.

Диаграмма загрузки оборудования. Симулятор спланировал работу на квартал вперёд.

Да, ещё: принципиально решил использовать опенсорсные китайские модели. Cursor и Claude — прекрасны, вопросов нет, но давайте пробовать в деле open-source! 😉

Методология работы с агентом рождалась на ходу, как-то интуитивно пришёл к тому, что сейчас называют Spec-driven development, Markdown-driven development. На Хабре есть хорошие статьи на эту тему. Много программируя на C#, подустав от EF6 и Code First, в новом проекте положил в основу такой подход: ТЗ в markdown, представление модели данных и история симуляции — в JSON.

Вот такой себе «JSON-driven development». Отказался от 40 таблиц, заменив их JSONB-колонками, и затащил проект на основе фреймворка ASP.NET Boilerplate, библиотеки SimSharp (Discrete Event Simulation) — аналога питоновской SimPy, и PostgreSQL (ну, куда же без него).

Разминка. Как я научился доверять работу ИИ-агенту

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

Мне нужен был мост между двумя мирами. С одной стороны — legacy-мир промышленной автоматизации с концепцией mailbox: bash-скрипты, equipment-менеджеры на Perl, дожившие со времён CellWorks MBX и TIBCO Rendezvous. С другой — современный брокер RabbitMQ. Идея: написать тонкий gateway — .NET 8 демон, слушающий Unix-сокет и транслирующий простейшие mbx_put/mbx_get в полноценные AMQP-операции. Для shell-мира ничего не меняется, а под капотом — взрослый брокер. Прямая замена!

Выбор архитектуры решения должен быть за человеком. ИИ не знает всего контекста: ваш стек (в моём случае — .NET), legacy-системы вокруг, какие ограничения наложены заводом, и главного — каков технический уровень тех, кому предстоит поддерживать новое решение. Архитектура рождается из этого знания, а у ИИ его нет. Дайте ему зрелую, продуманную идею, объясните «зачем» и «как», и реализует он её на высшем уровне. Чище, чем многие senior’ы. В моём случае получилось вот что.

    legacy-мир                             современный мир
    (bash, Perl, PAM-протоколы)            (AMQP, веб-мониторинг)
          │                                          │
     mbx_put ──┐                               ┌── BasicPublish
     mbx_get ──┤                               ├── BasicGet (autoAck)
     mbx_wait──┤      ┌──────────────────┐     ├── QueueDeclare
     mbx_flush─┤  ───>│   Unix-сокет     │     ├── QueuePurge
     mbx_open──┤      │ /run/.../mbx_    │     │
     mbx_close─┤      │      rabbit.sock │     │
     mbx_is_up─┘      └────────┬─────────┘     │
                               │               │
                      ┌────────▼────────┐      │
                      │   MbxRabbit     │──────┘
                      │   .NET 8 daemon │
                      │    ~550 строк   │
                      └─────────────────┘
                               │
                      ┌────────▼────────┐
                      │    RabbitMQ     │
                      │ durable broker  │
                      └─────────────────┘

Эту идею ИИ реализовал так чисто, что я местами подсматривал приёмы. Всё ядро уместилось примерно в 550 строк C# при production-качестве. Семь shell-утилит, systemd-юнит с RuntimeDirectory, симлинки в /usr/local/bin.

Дальше нужно было помочь ИИ спроектировать unit-тесты, от и до продумать как система обязана себя вести, записать это в виде проверок которые вас убедят. Только вы знаете, что обязательно: round-trip PUT->GET, очередь пуста после чтения, reply-to дописывается в тело, FIFO-упорядоченность, конкурентная запись из 50 фоновых процессов т.п. ИИ реализовал всё это по моему сценарию, тесты выявили проблемы в коде, далее цикл исправлений, и — ✅ 21 passed, 0 failed как доказательство что контракт выполняется.

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

Задача: что такое fab и зачем его симулировать

AMHS в действии: роботы-транспортировщики FOUP передвигают партии пластин между установками. Симуляция отвечает на вопрос - сколько таких роботов нужно?

AMHS в действии: роботы-транспортировщики FOUP передвигают партии пластин между установками. Симуляция отвечает на вопрос — сколько таких роботов нужно?

Полупроводниковая фабрика (wafer fab, производство полупроводников) — это завод, где из кремниевых пластин (wafer) делают интегральные схемы. Один «маршрут» производства — это 300-500 операций, пластина проводит в цеху 2-4 месяца. В современных фабах — 30-50 слоёв фотолитографии, т.е. каждый раз пластина возвращается на одну и ту же группу установок. Это называется re-entrant flow, и именно он делает планирование кошмаром.

Почему симулировать обязательно. Строительство фаба — это $5-15 млрд инвестиций и 3-5 лет. Никто не строит вслепую. С моделирования начинается всё: какой парк оборудования, в каком количестве, какую мощность это даст на выходе. За рубежом этой теме посвящены журналы, ежегодные конференции, сотни диссертаций. Симуляция позволяет тестировать стратегии диспетчирования и наращивания мощностей до того, как вы потратите миллиарды на станки, которые потом простаивают. Планирование производства на TSMC/Intel/Samsung в основном работает на эвристиках и LP-солверах.

Российский контекст. Для России это — недосягаемый уровень. Вместо строительства русской промышленности — миллиарды законсервированы в ФНБ и бетоне пустующих многоэтажек. Кое-что есть, конечно… несколько небольших производств, и то не передового уровня. Но! И это поворот — малые производства нуждаются в планировании не меньше, а часто больше крупных! Парадокс в том, что большие мощности дают усреднение: огромное НЗП, запас по мощности, предсказуемый поток. А маленький fab хрупок: одна остановка ключевой установки выбивает из графика всю партию, весь заказ.

За рубежом моделирование fab’ов — отдельная индустрия. У нас желающих из «учёной среды» не нашлось… Отвечать за планирование продукции на миллиарды рублей — ответственность не академическая. Но, скажу вам, ведь и отечественная MES микроэлектроники — это история стартапа, почему бы не заняться симулятором ?! 🙂.

Ландшафт: как это делают обычно и почему не подошло

Коммерческие DES-платформы: FlexSim, AnyLogic, Siemens Tecnomatix. Мощные, с 3D и дашбордами, стоят от $5–50 тысяч за лицензию. AnyLogic и FlexSim реально используются для полупроводников — есть статьи. Американский AutoSched (AutoSched AP) — стандарт имитационного моделирования для полупроводниковой промышленности. У китайских товарищей есть неплохие MES/Automation/SPC решения, но в симуляторах — пробел, я не нашёл.

Готового fab-симулятора в опенсорсе нет. Есть библиотеки модельного времени: SimPy (Python), SimSharp (.NET), JaamSim. Это не «симулятор завода», это движок, из которого завод собираешь сам.

Буду честен с аргументами «за покупку». Коммерческая платформа даёт DES-движок (модельное время, события, ресурсы), проверенный годами. Даёт визуализацию из коробки — Гант, 3D-анимацию, дашборды. Если бы задача стояла «провести одноразовое исследование и показать руководству красивый отчёт» — AnyLogic, возможно, выиграл бы по времени.

Но у нас не тот случай. У нас куча специфики: re-entrant flow, суточные лимиты фотолитографии, межоперационное время хранения, пакетная сборка в диффузии, камеры в DOWN — этого нет ни в одной коробке. Всю эту логику пришлось бы моделировать вручную, только внутри проприетарного GUI.

Ещё аргумент. На горизонте — reinforcement learning. Чтобы тренировать RL-агента, нужен gym-интерфейс: reset() -> observation, step(action) -> награда. Агент гоняет тысячи эпизодов быстро и без UI. Коммерческие тулы под это не спроектированы вовсе.

Итог прост. DES-движок — лёгкая часть, SimSharp отдаёт его бесплатно. Тяжёлая часть — фаб-логика, её пишешь сам при любом выборе инструмента. Раз усилия одинаковые, а на выходе либо закрытый файл, либо твой код под git с 146 тестами и дорогой в RL — выбор, в общем, не стоял.

Архитектурный выбор: JSON driven development

Звучит как переизобретение велосипеда — и, наверное, так и есть. Но как ещё кратко назвать вот это: ТЗ в markdown, настройки симуляции и схему данных описать и хранить в JSON, а само это описание сделать живым контрактом-требованием к ИИ ?!

Ещё раз: у нас JSON не формат обмена, а первичная модель данных. Не «сначала схема БД, потом маппим на объекты», а наоборот: сначала описываем сущности как JSON-документы, и в таком виде их и храним. В PostgreSQL для этого есть JSONB — бинарный JSON с индексацией и запросами. От классической реляционной схемы мы отказались почти полностью.

В проекте всего две таблицы, и этого хватило. Вот, собственно, вся модель данных:

┌─────────────────────────────────┐   ┌──────────────────────────────────────┐
│  GlobalSettings (одна строка)   │   │  Experiments  (по строке на экспер.) │
├─────────────────────────────────┤   ├──────────────────────────────────────┤
│  ModelsJson  ─┐                 │   │  Title, IsPublic, LastRunTime  ←──── │ метаданные
│  ToolsJson    ├─ статика фабрики│   │  (реляционно, для списка/сортировки) │
│  ProductsJson ├─                │   │                                      │
│  RoutesJson  ─┘                 │   │  SettingsJson ─┐  входные данные     │
│                                 │   │  ToolsJson    ─┤                     │
└─────────────────────────────────┘   │  WipJson      ─┤                     │
                                      │  WipStartJson ─┘                     │
                                      │  ToolHistoryJson       ─┐            │
                                      │  WipReleaseJson         │            │
                                      │  WipProductionPlanJson  ├ результаты │
                                      │  FabMonitorJson         │            │
                                      │  ProblemsJson           │            │
                                      │  LotHistoryJson        ─┘            │
                                      └──────────────────────────────────────┘

Логика простая. GlobalSettings — это «физика» фабрики: какие есть модели установок, оборудование, продукты, технологические маршруты. Меняется редко, одна строка на всё приложение, редактирует админ. Experiments — это конкретный сценарий «что если»: состояние оборудования на сегодня, начальное НЗП, план запуска, и после прогона — результаты симуляции (график загрузки, выпуск, проблемы). Каждое «что если» — отдельная строка. Метаданные (название, автор, публичность, время последнего прогона) живут обычными колонками — потому что по ним надо фильтровать и сортировать список экспериментов. А всё тяжёлое и полуструктурированное — в JSONB.

Главное в методе — данные ходят туда-обратно между JSON, кодом и базой без маппинга. Каждая JSONB-колонка показывается как таблица и как textarea с сырым JSON. Правишь -> Сохранить -> десериализация -> валидация -> пишется в JSONB. И обратно: Загрузить -> JSON из БД в textarea, а из MES приезжает готовым. Нет ORM-маппинга, нет миграций, нет EF-боли. Поле добавилось в JSON — поле появилось в UI. Всё.

Что мы выиграли. Первое — скорость итераций с ИИ. Агент читает JSON лучше любой реляционной схемы: вот документ, вот его структура, допиши поле. Никаких ALTER TABLE, никаких миграций. Второе — совместимость с MES. Реальная фабрика отдаёт данные в JSON — мы принимаем их почти как есть, без трансформации в 15 таблиц. Третье — гибкость схемы: полупроводниковый домен тяжёлый и меняется (новые модели установок, новые типы рецептов, sequence-формат) — реляционка заставила бы писать миграции на каждый чих.

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

Движок под капотом | «как JSON превращается в симуляцию?»

Архитектура выбрана, JSON лежит в базе. Но JSON — это фотография фабрики, статичная. Кто по ней «проигрывает» время, двигает партии, считает узкие места? Движок. И вот тут — важный методологический момент, без которого «трактор не поедет».

Сначала — руками. Без ИИ. Прежде чем вести с агентом хоть какой-то разговор про движок, я взял компилятор и написал простую симуляцию вручную: две установки, партия проходит через них, время двигается. Цель была — не сделать продукт, а почувствовать SimSharp. А он нетривиален.

SimSharp — это C#-порт питоновского SimPy, библиотека дискретно-событийного моделирования. Её ключевая абстракция — Environment: единое модельное время и очередь событий. Процессы в ней — это не потоки, а генераторы (IEnumerable), которые «спят» между тактами через yield return Env.Timeout(…):

IEnumerable<Event> OperatorLoop(SimState state, SimResults results)
{
    while (state.Now < cfg.PeriodTo)
    {
        DoWork(state, results);           // один такт: снять готовых, запустить новых
        yield return _env.Timeout(cfg.WakeupPeriod);  // «уснуть» на 10 минут
    }
}

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

Классы проектирую сам. Дальше — самое главное: как именно устроена виртуальная фабрика, решает человек. Не ИИ. Потому что ни один ИИ не знает вашу фабрику лучше вас. Какое бывает оборудование, как работает оператор участка, в каком порядке брать партии из очереди, что такое пакетная сборка в диффузии, как считать длительность sequence-рецепта — это доменное знание, и оно моё.

Логику работы операторов и оборудования подробно пишу в ТЗ. Иерархия: SimOperator (база, цикл «проснулся-поработал-уснул») -> SimDumbOperator (наивный: снял готовых, запустил свободную установку) -> SimDiffOperator (диффузия: ждёт полного пакета) и SimPhotoOperator (фотолитография: суточные лимиты, мин. циклы по приоритетам). Клонирование оборудования. В маршруте стоит FOX01, но qty=3. Значит — три взаимозаменяемых клона: FOX01, FOX01#2, FOX01#3. Оператор берёт первый свободный. Скалярный рецепт интерполируется по количеству пластин. А sequence-рецепт — это конвейер: T = sum(шагов) + (qty−1) × max(шагов) — это физика реального оборудования. Учитываю МВХ (межоперационное время хранения): партия после операции «ложится на полку» и имеет ограниченный срок жизни до следующей операции.

Вот, кстати, как выглядит реальный фрагмент маршрута в нашем JSON. Это не выдумка, это вырезка из тестовой фабрики. Обратите внимание на блок iot — это и есть то самое МВХ, «застывшее в JSON»:

{
  "route": "A2F",                   // название маршрута
  "fab": "150",                     // название цеха
  "steps": [                        // перечень шагов
    {
      "number": "1200",             // номер операции
      "tools": ["SCR01", "SCR02"],  // установки, на которых обрабатывается партия
      "recipe": "42",               // название рецепта обработки
      "workarea": "WET",            // участок, который выполняет данную операцию
      "iot": {
        "toOperation": "1400",      // до какой операции действует МВХ
        "toTool": "FOX01",          // до процесса на какой установке
        "hours": 14,                // сколько часов допустимо хранить партию
        "isConservation": false,    // допустима ли консервация
        "criticalCycle": "OX1",     // название критического цикла (если МВХ критическое)
        "isHoldPoint": true         // можно ли остановить партию без риска для качества
      }
    }
  ]
}

Сразу видно устройство шага: операция 1200, выполняется на SCR01 или SCR02 (взаимозаменяемые клоны), участок WET. А дальше — iot: после этой операции партия имеет 14 часов жизни до операции 1400 на установке FOX01, цикл критический (OX1), консервация недопустима, точка остановки разрешена. И обратите внимание на комментарии — это не для движка (он их не видит), это для человека и ИИ. Каждый символ описан. Человек читает это как спецификацию, движок — как данные, ИИ — как ТЗ. Один документ — три потребителя. И это та самая красота, ради которой затевался JSON-driven.

Метрики — закладываем сразу

Тут ключевой инсайт, который я выстрадал: механизмы метрик нельзя «прикрутить» потом. Если движок уже написан, а потом понадобится знать «сколько партий прямо сейчас стоит в очереди перед печью FOX01, с учётом тех, что на 2-3 операции позади» — пересчитывать все очереди на каждом такте безумно дорого. Поэтому именованные множества (MetricSet) заложены в архитектуру с первого дня: каждый шаг маршрута заранее размечен, какие множества он открывает/закрывает, а партия помнит обратные ссылки и входит/выходит из множеств за O(1). Это нужно для диспетчирования — но, как выяснится позже, этой же конструкции найдётся применение ещё в двух местах.

И вот ключевая методологическая деталь, ради которой и стоило писать эту статью. Все контракты классов — сигнатуры, методы, инварианты — я описываю в markdown-ТЗ до того, как написана хоть одна строка реализации. Markdown-специфиацию видит и человек, и ИИ. Вот, буквально, вырезка из ТЗ, которая задаёт поведение очереди партий:

// Очередь партий одного участка. Выбор кандидата — по Priority (больше = выше:
// 3 — высший, 0 — низший); при равном Priority — по ProductPriority (приоритет продукта,
// тоже больше = выше); при равенстве обоих — FIFO.
class SimQueue
{
  string Workarea { get; }           // участок очереди
  int Count { get; }                 // кол-во партий

  SimQueue(string workarea) {}

  // поместить партию (ставит метку времени PutInQueueDate)
  void PutLot(SimLot lot, DateTime now) {}
  // поместить пакет партий
  void PutBatch(List<SimLot> lots, DateTime now) {}
  // взять конкретную партию по имени, удаляет из очереди
  SimLot GetLot(string lotName) {}
  // лучший кандидат: макс. Priority, при равенстве — макс. ProductPriority, далее FIFO.
  // удаляет из очереди.
  SimLot GetBest() {}
  // снимок всех партий, упорядоченный по Priority desc → ProductPriority desc → FIFO; не удаляет
  List<SimLot> GetAll() {}
}

Это не код — это контракт. Комментарии на русском, тело методов пустое. Когда агент начинает реализацию, у него нет свободы «как сделать красиво» — у него есть точное обязательство: очередь сортируется по трём ключам, GetBest удаляет, GetAll нет. Реализация — это уже следствие контракта, а не наоборот. И именно поэтому 146 тестов проходят: они проверяют контракт, который был зафиксирован раньше кода.

Диаграмма ключевых классов

┌─────────────────────────────────────────────────────────────────────────┐
│  SimConfig (неизменяемое на прогоне)        SimState (observation/RL)   │
│  ─ Env, WakeupPeriod, TimeBetweenMvou       ─ Queues, Tools, ToolGroups  │
│  ─ MaxPhotoWafersPerDay, MinCycle*          ─ ReleaseQueue, Now          │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  SimOperator (abstract) - цикл «проснулся → DoWork → уснул              |
│  ├─ SimDumbOperator      - снять готовых, запустить свободную установку │
│  │   ├─ SimDiffOperator  - ждать пакет или подполнение (metric sets!)   │
│  │   └─ SimPhotoOperator - лимиты CUV/сутки, мин. циклы по приоритетам  │
│  └─ SimWipStartOperator  - в 8:00 даты плана кладёт новые партии T*     │
└─────────────────────────────────────────────────────────────────────────┘
                                    │ работают с
                                    ▼
┌────────────────────────────────────┐   ┌────────────────────────────────┐
│  SimQueue (очередь участка)        │   │  SimToolBase (abstract)        │
│  ─ Workarea                        │   │  ─ ToolName, BatchSize, Ports[]│
│  ─ PutLot / PutBatch               │   │  ─ PutLot / PutBatch           │
│  ─ GetBest (Priority->FIFO)        │   │  ─ Run / RunBatch              │
│  ─ GetLot(name) / GetAll           │   │  ─ GetDuration(recipe, qty) ◀── sequence-формула
└────────────────────────────────────┘   │  ├─ Simple1LotTool..Simple6Lot │
                                         │  └─ Prs2chTool (камеры UP/DOWN)│
                                         └────────────────────────────────┘
                                                    │
                          ┌─────────────────────────┴──────────────────────┐
                          ▼                                                ▼
┌────────────────────────────────────┐   ┌────────────────────────────────┐
│  SimLot (партия 1–25 пластин)      │   │  SimObserver + MetricSet       │
│  ─ Name, Product, Qty, Priority    │   │  ─ OnLotArrived / OnLotLeft    │
│  ─ NextStep (LinkedListNode<>)     │   │  ─ OnLotStartProcess (МВХ end) │
│  ─ Iot / IotStart (активный цикл)  │   │  ─ GetSize(name) → O(1)        │
│                                    │   │  наблюдение для диспетчера + RL│
└────────────────────────────────────┘   └────────────────────────────────┘
                          │                                                
                          └─────► SimResults (BatchEvents, WipProductionPlan,
                                              Snapshots) → JSONB колонки

Как читать эту карту. Сверху — неизменяемая конфигурация и наблюдаемое состояние (для RL). В середине — операторы, «мозг» фабрики; они работают с очередями и установками. Снизу — партия, которая несёт своё состояние (включая активный МВХ-цикл), и наблюдатель, который ведёт именованные множества. Все события стекаются в SimResults, а оттуда — в JSONB-колонки эксперимента.

Итог раздела в одном предложении: движок — это не магия ИИ, это доменное знание, застывшее в коде. Но если ещё 5 лет назад на эту работу уходил труд коллектива, то с ИИ это доступно и стартапу.

RL-заготовка: множества наблюдения

Сразу честно: это — будущее, не настоящее. Хватит ли у меня ума довести RL-агента до ума — не знаю. Но задумка есть.

Реальные фабы сегодня работают на правилах, не на ИИ. Диспетчирование — это эвристики («возьми партию с высшим приоритетом», «дождись полного пакета») и LP-солверы для планирования. Так живут TSMC, Intel, Samsung. Никакой нейросети, которая решает «какую партию поставить на печь следующей», в продуктиве нет, хотя академический вал работ по RL для fab уже идёт (IEEE/Nature 2025, arXiv 2023).

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

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

SimConfig отделён от SimState. Неизменяемая конфигурация фабрики — отдельно, наблюдаемое состояние — отдельно. Это и есть классический gym-интерфейс: reset() → observation, step(action) -> reward. Именованные множества (MetricSet). Для каждого ключевого узкого места в реальном времени известно, сколько партий прямо сейчас стоят в очереди — и на установке, и «на подходе» за 2–3 операции. Я делал это для эвристик диспетчера. Но это же готовый вектор наблюдения для RL-агента: O(1)-чтение вместо пересчёта очередей. Самое красивое. Пара хуков SimObserver (OnLotArrived/OnLotLeft) решает три задачи разом: учёт МВХ-нарушений, диспетчирование диффузии и observation для RL. Один механизм — три потребителя. Лучше сразу строить с умом, потом меньше переписывать.

Как мы строили с ИИ | «как не потерять нить разработки»

Техзадание — это диалог, застывший в JSON. Утро с него начиналось, вечер им заканчивался.

Знаете, что самое странное в этой истории? Я ничего не хотел изобретать. Просто интуитивно топал — и притопал к чему-то, чем захотелось поделиться.

В какой-то момент я принял простое решение: техзадание становится не документом «написал и забыл», а плодом совместной работы с ИИ — тем, с чего начинается и чем заканчивается каждый день. Утром я открываю WaferDuck specs.md, дописываю новые требования, что ещё улучшить. Вечером — прошу ИИ дописать ТЗ, отразить то новое что получилось в коде, проверить непротиворечивость, подправить комменты.

Как-то умная машина предложила: хочешь, говорит, заверну наши результаты в handoff-документ? Идея простая: в конце каждого дня пишется короткий файл — что сделали, какие баги поймали, что отложено, где что лежит, и первое сообщение для завтрашней сессии. Завтра на свежей машине (контекст-то не переносится) открываешь handoff — и входишь в работу за минуту, а не за полчаса. Мы делали это каждый день, и это спасло проект от распада. Вот типичный handoff:

## 1. Что сделано за сегодня (по порядку)
## 2. Состояние тестов (всё зелёное на конец дня)
## 3. Ключевые файлы, тронутые сегодня
## 4. Что НЕ сделано / отложено
## 5. Где что лежит (шпаргалка на завтра)
## 6. Первое сообщение завтра

Вот и вся методология. С утра — читаем код, написанный ИИ вчера, уточняем ТЗ. За его рамки можно выходить только в течение 1 дня разработки. В конце дня заставляем ИИ исправить ТЗ, зафиксировать правду о коде. Плюс handoff.md. И так по кругу:

                  📋 ТЗ
            контракт ДО кода
              ↗            ↘
   🌅 утро                    ❓ вопросы
   читаем handoff,            🔴 🟡 🟢
   дописываем ТЗ             🔴 не закрыт —
        ↑                     код не пишется
        ↑                         ↓
   📦 handoff                 💻 код
   вечер, самокритика,       строго по контракту,
   «первое сообщение завтра»  🧑‍💻 держит архитектуру
        ↑                         ↓
        └───────── ✅ тесты ──────┘
                 гоняем до зелёных

Владеть кодом

Да, чуть не забыл главное — кодом нужно владеть. Я это так называю. Нужно понимать что и как написано машиной, ключевые классы, сервисы, тесты и прочее. Нужно понимать каждую строку ТЗ. Пока эти условия выполняются — вы управляете разработкой. Потерять нить разработки очень просто — достаточно дать ИИ слишком большое задание на день, не «принять» его работу, не «зарефакторить» его код.

Вывод: ТЗ фиксирует решения до кода. Handoff фиксирует решения после кода. Владение кодом — это и есть способность эти решения принимать. Пока этот баланс держится — система работает.

Что вышло: результаты

Сделали тестовую фабрику

Подведём скромный итог. месяц работы, 500M токенов, куча handoff’ов, 146 зелёных тестов. За основу взяли открыто опубликованное описание типового техпроцесса 100 нм CMOS (Samarth Parikh, «Manufacturing Design and Fabrication of 100 nm (Leff) CMOS»). В нём дано добротное описание и самого процесса, и технологического маршрута производства. На этой виртуальной фабрике мы и проверяем всё. И если когда-нибудь делиться решением в open-source — выкладывать можно именно её, а не реальные заводские данные.

Реализовали диспетчирование на простых, но рабочих механиках

Очереди по приоритетам, пакетная сборка в диффузии, лимиты фотолитографии, возможность задавать кол-во оборудования. Сделали импорт данных из цеховой MES. Не идеально, но этого достаточно, чтобы симулятор не был игрушкой. Развернули сервис in-house. В самом первом приближении модель уже позволяет: видеть узкие места, циклы движения продукции при разных уровнях НЗП, и многое другое. Это не замена специалиста по планированию, это инструмент, которого у него раньше не было.

Честная критика: где агент споткнулся

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

Выдуманные данные. Написал тест для диффузионной печи FOX с длительностями 4/20 — а в реальном tools.json стоит 275/275. Подозрение на «batch-баг» физически невозможно при равных 1w=25w. Поймал человек. Тест без реального прогона. Дёрнул GetDuration напрямую вместо того, чтобы прогнать процесс PutLot → Run → env.Run. Поймал человек. Неправильное место для имени цикла. Хотел положить имя МВХ-цикла в аннотацию шага — а оно должно браться из step.Iot.CriticalCycle в момент старта. Понял ошибку до реализации, но сам — не дошёл. Урок, который мы вынесли и который записали прямо в handoff:

«Жёсткие вопросы пользователя (‘зачем менять множества вне переходов?’, ‘SCR — это BatchSize=1, какой batch?!’) делали результат сильно лучше. Партнёрство человек↔ИИ работает именно так.»

ИИ отлично реализует зрелую идею. Но он не знает вашу фабрику, он не отличит 4/20 от 275/275, он не поймёт, что печь греется 12 часов. ИИ может совсем пойти не туда — начинает читать сигнатуры методов из DLL! Тут его нужно аккуратно остановить и попросить найти примеры кода в интернете. Обычно это работает. Ещё, помню, вроде простая задача — сделать надписи на job-ах Ганта. Ну, чего проще. Агент не справился ни с первой, ни со второй попытки, сделал (!) тестовый html с Гантом, убедился, что лейблы проставляются, но исправить баг в проектной cshtml-ке не смог. По-видимому, помешал большой контекст. С багой справилась другая модель, причём сразу.

Вместо UI у ИИ получается иногда — постная каша. Облагородить, оживить, что ли, допилить интерфейс до приятного глазу состояния — пока приходится человеку. Слава Богу в команде есть кому.

Благодарности & что дальше?

Ловлю себя на мысли, что в ИТ из года в год становится всё интереснее и интереснее — сегодня у одного разработчика или небольшой команды возможностей куда больше, чем вчера. Парная разработка с агентом уровня GLM-5.2 — великолепна. Меньше времени на рутину — больше на создание продуктового кода. Идеальная разработка это диалог, рождающий код: заказчик — тимлид/продакт, тимлид — команда. Если вы генерируете стоящие идеи, вам нужно научиться ставить задачи ИИ 🙂 А где не справится — там вы, с компилятором и знанием домена. То, на что раньше нужен был отдел, теперь реально потянуть в одиночку или вдвоём.

Как и обещал с самого начала — только китайский open-source. Основную работу над проектом проделала GLM-5.2 от Z.AI (именно она, в лице ZCode): открытая модель, доступная по цене и при этом рабочий агент. Не Cursor, не Claude, не заоблачные подписки. Я не знаю как она насчитала 500М токенов, но суточных/недельных лимитов хватало за глаза.

И вот, знаете, о чём я думаю в финале. Хочется, чтобы у нас появился Государственный центр открытых моделей для промышленности — с разумными лимитами на GLM-5.2, KIMI K3, DeepSeek V4 Pro. Поставить молодёжным стартапам достойные задачи — и многое закроется быстрее, чем очередной раунд «стратегий цифровизации». Талантов полно, модели — огонь, жаль — менеджмент «не тянет».

Что дальше? Допилить логику оператора фотолитографии, многокамерное оборудование ETCH, CVD/PVD с её камерами и sequence-рецептами, добавить MTTR/MTBF — чтобы моделировать отказы оборудования и видеть, как они бьют по выпуску. Подключить sequence-рецепты и учитывать состояние конкретных камер установок.. И, может быть, если хватит дерзости — попробовать RL-агента. Каркас построен, двери открыты — но до взрослого симулятора ещё пилить и пилить.

Да, отдельное спасибо @runaway_llm который держит нас в курсе актуальных новостей. Именно с его новости о выпуске ZCode я решил попробовать этот замечательный инструмент и модель в задаче симулятора фабрики.

Спасибо всем, кто дочитал. Слава Z.AI! 🚀🕊️

Автор: oliva80

Источник

Оставить комментарий