Codex for Sales: Как Запускать Сгенерированный ИИ Бизнес-Код в Продакшене
Сгенерированные ИИ код для ценообразования и планирования требует управления жизненным циклом как в git: стейджинг, валидация, утверждение, откат. Реальные паттерны из продакшен-системы с мульти-агентной архитектурой.
Когда владелец спа-салона говорит «мы добавили тайский массаж, 200 бат», должно произойти пять вещей. Схема услуг (offerings schema) должна содержать эту услугу. Схема заказа (order schema) должна ссылаться на неё. Формула ценообразования должна её рассчитывать. Функция планирования должна её бронировать. Функция валидации должна её проверять.
Эти файлы образуют цепочку зависимостей. Нарушьте последовательность — и система откажется не с 404, а с ценой за услугу, которой нет в каталоге.
Большинство AI-платформ передают весь этот каскад языковой модели. Модель читает промпт: «обнови все нижестоящие навыки». Иногда она справляется. Иногда пропускает шаг. Иногда переставляет файлы местами. Отката не существует. Валидация не ловит пробел, пока клиент не оспорит счёт.
В QuotyAI мы решаем эту проблему, распределяя творческую работу между LLM, а структурную — между детерминированным кодом. LLM извлекает намерение: «добавить тайский массаж, 200 бат» превращается в типизированные структурированные данные. Всё после этого выполняется детерминированной оркестрацией: граф зависимостей определяет порядок каскада, подагенты генерации кода атомарно создают каждый файл, конвейер валидации проверяет каждый результат, а оператор утверждает diff до того, как любое изменение попадёт в продакшен.
Вот шесть паттернов, которые обеспечивают эту работу в продакшене.
«Система должна делать структурные ошибки структурно невозможными.»
Важно о версиях: Эта статья описывает архитектуру QuotyAI skill builder по состоянию на июнь 2026. Слой оркестрации использует библиотеку deepagents для композиции агентов. Все Zod-схемы используют Zod 4.x. Граф зависимостей и определения навыков хранятся в инфраструктуре билдера — не дублируются в данные тенанта.
🔗 Проблема каскада
Бизнес-логика в системе продаж — это не один файл. Это направленный ациклический граф взаимозависимых ресурсов:
offerings_schema → order_schema → pricing_formula
→ scheduling_function
→ validation_function
router, callback, system_prompt (независимые)
Измени один узел — и каждый нижестоящий узел должен быть перегенерирован. Новая услуга означает новую запись в схеме, новую ветку ценообразования, новый слот планирования и новое правило валидации. Пропусти что-то из этого — и ассистент будет выдавать уверенные неверные ответы: счета, которые не сходятся, бронирования, которые не проходят валидацию, цены, которых не существует.
Проблема каскада не уникальна для продаж. Любая AI-система, управляющая структурированными взаимозависимыми данными, сталкивается с ней. Управление запасами, движки правил комплаенса, платформы конфигурации как кода — у всех одно и то же фундаментальное ограничение.
| Требование | Наивный подход с LLM | Детерминированный каскад |
|---|---|---|
| Порядок обновления | Зависит от промпта | Топологическая сортировка из графа зависимостей |
| Обработка сбоев | Частичные обновления | Атомарная отмена при любом сбое |
| Валидация | Нет (доверяй выводу) | Валидация схем + компиляция в песочнице |
| Аудит | Разрозненные логи чатов | Неизменяемый журнал событий на каждое продвижение |
| Откат | Ручное восстановление | Указатель HEAD на любой предыдущий коммит |
📐 Паттерн 1: LLM для смысла, код для структуры
Ключевая идея — строгое разделение ответственности. LLM делает ровно одну вещь: преобразует естественный язык в типизированные структурированные данные. Всё после этого — детерминировано.
"เพิ่มนวดไทย 200 บาท" → LLM со структурированным выводом
→ { name: "Thai Massage", price: 200, category: "massage" }
→ Детерминированный каскад выполняется
Вызов LLM использует model.withStructuredOutput() с Zod-схемой, а не свободную генерацию:
const IntentClassificationSchema = z.object({
mode: z.enum(['query', 'update']),
pattern: z.string().nullable(),
confidence: z.number().min(0).max(1),
});
const result = await model.withStructuredOutput(IntentClassificationSchema)
.invoke([/* история переписки */]);
Классификатор возвращает типизированные объекты: mode, pattern, confidence. Никакого парсинга строк, никаких regex, никаких хрупких форматов вывода, поддерживаемых промпт-инжинирингом.
Когда режим update и уверенность превышает порог, управление передаётся детерминированному оркестратору. LLM никогда не решает, какой файл редактировать следующим, никогда не решает, как упорядочивать обновления, никогда не решает, прошла ли валидация.
💡 Уникальное наблюдение: Схема структурированного вывода для классификации намерений прошла пять итераций в продакшене. Первая версия возвращала свободный JSON, который оркестратор парсил вручную — что приводило к 7% ошибок парсинга при повторных попытках. Переход на
withStructuredOutputс типизированной Zod-схемой полностью устранил ошибки парсинга. Урок: если LLM порождает схему, LLM может её и нарушить. Используйте схемы с принудительной проверкой во время выполнения для каждого структурированного вывода.
📊 Паттерн 2: Граф зависимостей с топологической сортировкой
Граф зависимостей хранится в единственном файле — едином источнике истины в инфраструктуре билдера, а не дублируется в данные тенанта:
# patterns/common/dependency-graph.md
offerings_schema → order_schema → pricing_formula
→ scheduling_function
→ validation_function
router, callback, system_prompt (независимые, без зависимостей)
pricing, scheduling, validation (могут выполняться параллельно)
Оркестратор встраивает этот граф в свой системный промпт и использует его для упорядочивания каскада:
// Фрагмент промпта оркестратора (перефразировано)
const DEPENDENCY_GRAPH = `
Заблокированные навыки имеют строгий порядок зависимостей:
1. offerings_schema (нет зависимостей)
2. order_schema (зависит от offerings_schema)
3. pricing_formula (зависит от order_schema)
4. scheduling_function (зависит от order_schema)
5. validation_function (зависит от order_schema)
Шаги 3-5 могут выполняться параллельно после завершения шага 2.
`;
const KNOWN_PATTERN_CASCADE = {
'add-offering': ['offerings_schema', 'order_schema', 'pricing_formula', 'scheduling_function', 'validation_function'],
'remove-offering': ['offerings_schema', 'order_schema', 'pricing_formula', 'scheduling_function', 'validation_function'],
'update-price': ['offerings_schema', 'pricing_formula'],
};
Оркестратор делегирует задачи подагентам редактирования, которые запускают инструменты генерации кода в порядке каскада. Каждый подагент генерации кода читает выходные данные своих вышестоящих зависимостей:
// Промпт подагента генерации кода (перефразировано)
const pricingFormulaGenerator = {
name: 'sales-assistant-pricing-formula-generator',
tools: [readSkillFile, writeSkillFile, editSkillFile],
prompt: `
Ты — генератор формул ценообразования.
Ты ОБЯЗАН прочитать offerings_types и order_types перед генерацией.
Твой вывод зависит от этих вышестоящих файлов.
`,
};
Ни одна LLM не решает, какой файл будет следующим. Граф детерминирован.
💡 Уникальное наблюдение: Изначально граф зависимостей существовал только в промпте оркестратора в виде текста на естественном языке. После двух инцидентов в продакшене, когда промпт случайно изменялся при итерациях (мы случайно переписали порядок каскада), мы вынесли его в отдельный файл
dependency-graph.md, который читают и промпт оркестратора, и CI-задача валидации из одного источника. Единый источник истины устранил рассинхронизацию.
🗂️ Паттерн 3: Жизненный цикл базы знаний с семантикой git
Каждый сеанс редактирования рассматривает базу знаний как git-репозиторий:
Стейдж (нить редактирования) → Предложенная генерация → Утверждено → Опубликовано
Отклонённая ветка (удалена)
Запись в журнале аудита для каждого перехода
Модель данных использует пять коллекций для отслеживания версионированной, неизменяемой истории:
// collections/business-code-base.collections.ts
interface BusinessCodeBaseStagingAreaDoc {
threadId: string; // одна на нить редактирования
baseGenerationId: ObjectId;
overlay: Record<string, string>; // ключ → versionId
state: 'open' | 'sealed' | 'abandoned';
}
interface BusinessCodeBaseGenerationDoc {
state: 'draft' | 'proposed' | 'approved' | 'rejected' | 'promoted' | 'rolled_back';
sealedAt?: Date;
approvedAt?: Date;
promotedAt?: Date;
rolledBackAt?: Date;
rejectedAt?: Date;
}
Операции жизненного цикла отображаются на REST-эндпоинты с атомарной семантикой:
POST /environments/seed → Инициализировать следующую среду
POST /environments/promote → Переместить HEAD продакшена на следующий HEAD
POST /environments/rollback → Откатить HEAD продакшена назад
GET /environments/diff → Показать изменения в стейдже vs продакшен
GET /environments/history → Неизменяемый журнал событий
Продвижение использует паттерн compare-and-swap для предотвращения конкурентных перезаписей:
// ide.service.ts
async promote(tenantId: ObjectId, businessEntityId: ObjectId, generationId: ObjectId) {
// CAS: продвигать, только если HEAD продакшена всё ещё указывает на ожидаемую генерацию
const head = await this.getLiveHead(tenantId, businessEntityId);
if (head.generationId !== expectedGenerationId) {
throw new ConcurrentModificationError();
}
await this.swapGenerationPointer(environment, 'live', generationId);
await this.recordEvent(tenantId, businessEntityId, {
action: 'promote',
generationId,
previousHead: head.generationId,
});
}
Ни одно частичное обновление никогда не попадает в продакшен. Если какой-либо шаг каскада не удался, область стейджинга удаляется, и генерация никогда не достигает состояния proposed.
💡 Уникальное наблюдение: Эндпоинт отката использует ту же функцию
swapGenerationPointer, что и promote — только идентификатор генерации указывает на более старый коммит. Это означает, что продвижение и откат имеют одинаковые гарантии конкурентности. Единственная разница — направление указателя HEAD. Когда мы добавили откат, это было изменение на 30 строк, а не новая функция.
🎯 Паттерн 4: Известные паттерны vs Общий каскад
Типовые операции выполняются через специализированные workflows — называемые навыками (skills). Каждый навык — это файл SKILL.md с предопределённой последовательностью каскада:
patterns/
add-offering/
SKILL.md ← «Прочитать состояние, отредактировать ссылку, каскад через 5 генераторов, валидация»
reference-template.md
remove-offering/
SKILL.md
update-price/
SKILL.md
Оркестратор направляет задачу соответствующему подагенту на основе классификации намерения:
const orchestrator = createDeepAgent({
name: 'sales-assistant-skill-builder',
subagents: [
knownPatternEditor, // обрабатывает add-offering, remove-offering, update-price
generalCascadeEditor, // обрабатывает всё, что не подходит под известный паттерн
...codeGenSubagents, // 9 агентов генерации кода
],
tools: [readSkillFile],
// Оркестратор НИКОГДА не пишет файлы напрямую — только делегирует
});
Редактор известных паттернов читает соответствующий SKILL.md и точно следует инструкциям. Редактор общего каскада строит последовательность обновления с нуля, используя граф зависимостей.
const knownPatternEditor = {
name: 'known-pattern-editor',
prompt: `
Ты — редактор известных паттернов. Ты выполняешь предопределённые сценарии обновления.
Прочитай SKILL.md, следуй каскаду в точности.
Ты работаешь с веткой — все записи идут в файлы ветки, а не в продакшен.
`,
};
const generalCascadeEditor = {
name: 'general-cascade-editor',
prompt: `
Ты — редактор общего каскада. Ни один известный паттерн не подходит для этого обновления.
Используй граф зависимостей для определения порядка каскада.
Ты работаешь с веткой — все записи идут в файлы ветки, а не в продакшен.
`,
};
Оркестратор никогда не пишет файл напрямую. Он читает состояние, планирует изменения и делегирует подагентам редактирования. Подагенты используют операции find-and-replace — тот же паттерн, который разработчик использует в IDE, — а не кастомные парсеры markdown или хрупкие строковые манипуляции.
💡 Уникальное наблюдение: Мы начинали с одного универсального редактора, который обрабатывал всё. Система известных паттернов появилась после того, как мы заметили, что 73% обновлений в продакшене приходится на три категории: добавить услугу, удалить услугу, изменить цену. Создание шаблона
SKILL.mdдля каждого сократило время обновления на 60% и уменьшило количество ошибок валидации на 80%, потому что последовательность каскада была жёстко задана, а не выводилась заново каждый раз.
💬 Паттерн 5: Обновления в формате диалога
Агент спрашивает перед тем, как запустить каскад. Один уточняющий вопрос предотвращает каскад из пяти файлов, построенный на неверных предположениях:
Пользователь: «Добавьте тайский массаж, 200 бат.»
Агент: «Я могу добавить тайский массаж за 200 бат. К какой категории его отнести?
(текущие категории: массаж, уход за лицом, обёртывания)»
После чтения текущего состояния агент проактивно предлагает связанные изменения:
Агент: «Я вижу, что вы предлагаете ароматерапевтические масла. Стоит ли добавить
масляный аддон к тайскому массажу? Тайский массаж обычно включает
нанесение масла как опцию.»
После успешного обновления агент показывает наблюдения:
Агент: «Обновлено. Функция планирования теперь охватывает 12 услуг,
но ваш календарь поддерживает 8 одновременных бронирований.
Рекомендую расширить вместимость в часы пик.»
Это не прерывания рабочего процесса. Это и есть продукт. Сеанс обновления — это диалог, а не транзакция «команда-выполнение-завершение».
Промпт оркестратора закрепляет такое поведение на системном уровне:
Ты — оркестратор. Ты ЧИТАЕШЬ файлы, задаёшь уточняющие вопросы,
ПЛАНИРУЕШЬ изменения и ДЕЛЕГИРУЕШЬ подагентам редактирования.
Ты НИКОГДА не вызываешь write_skill_file или edit_skill_file напрямую.
Ты думаешь шаг за шагом.
💡 Уникальное наблюдение: Паттерн «спроси перед каскадом» появился после инцидента в продакшене, когда пользователь сказал «удалите массаж для беременных» — и агент удалил его из каталога, формулы ценообразования, функции планирования и функции валидации. Пользователь имел в виду «скрыть со страницы бронирования», а не «удалить из системы». Добавление одного уточняющего вопроса («скрыть или удалить?») полностью устранило этот класс инцидентов.
🧪 Паттерн 6: Конвейер валидации перед коммитом
Каждая предложенная генерация должна пройти валидацию, прежде чем достичь состояния approved. Конвейер валидации выполняет четыре проверки:
Валидация схем. JSON-схемы проверяются на соответствие draft 2020-12. Все кросс-ссылки между схемами должны разрешаться.
Проверка покрытия. Формула ценообразования должна покрывать каждую услугу в каталоге. Если в каталоге указаны «Тайский массаж 60 мин» и «Тайский массаж 90 мин», формула ценообразования должна обрабатывать оба варианта.
Компиляция TypeScript. Сгенерированный код компилируется в песочнице vm.Context с таймаутом 5 секунд:
import * as vm from 'vm';
const SANDBOX_TIMEOUT_MS = 5000;
function validateGeneratedCode(code: string, functionName: string): boolean {
const sandbox = {
Date, Math, Array, Object, JSON,
String, Number, Boolean, RegExp,
parseInt, parseFloat, isNaN, isFinite,
};
const context = vm.createContext(sandbox, {
name: 'ValidationSandbox',
codeGeneration: { strings: false, wasm: false },
});
try {
new vm.Script(`"use strict";\n${code}\n${functionName}`, {
filename: `validation-${functionName}.js`
}).runInContext(context, { timeout: SANDBOX_TIMEOUT_MS });
return true;
} catch {
return false;
}
}
Целостность зависимостей. Каждый файл, на который есть ссылки в генерации, должен существовать. Никаких висящих импортов, никаких осиротевших схем.
Если любая проверка не удалась, вся генерация отклоняется — область стейджинга удаляется, и diff никогда не попадает в продакшен.
async function sealAndValidate(stagingArea: StagingArea): Promise<Generation | null> {
// Запечатываем область стейджинга в предложенную генерацию
const generation = await sealStagingAreaIntoGeneration(stagingArea);
// Запускаем конвейер валидации
const schemasValid = await validateSchemas(generation);
const coverageValid = await verifyCoverage(generation);
const compilationValid = await compileGeneratedCode(generation);
const integrityValid = await checkDependencies(generation);
if (!schemasValid || !coverageValid || !compilationValid || !integrityValid) {
await updateGenerationState(generation._id, 'rejected');
await abandonStagingArea(stagingArea.threadId);
return null;
}
return generation; // готово к утверждению оператором
}
💡 Уникальное наблюдение: Проверка компиляции поймала баг на первой неделе в продакшене: генератор формул ценообразования создавал JavaScript, который ссылался на
thisвнутри стрелочной функции — валидный JavaScript, но некорректный при выполнении в контексте песочницы. Компиляция в песочнице выявляет синтаксические ошибки и ошибки ссылок, отлавливая на этапе сборки то, что иначе проявилось бы как загадочные цены NaN в продакшене.
⚠️ Что не сработало
Управление каскадом только через LLM. Первая версия использовала один агент, редактирующий все файлы последовательно. Это работало в 60% случаев. В остальных 40% агент пропускал файлы, переставлял каскад или вносил несоответствия. Подход с графом зависимостей стал нашей второй попыткой и повысил успешность до ~95%. Оставшиеся 5% потребовали конвейер валидации как страховочную сеть.
Глобальный сервис-локатор для DI. Прежде чем внедрить DI по уровням зависимостей, у нас был плоский список регистрации. Когда два сервиса образовывали циклическую зависимость, ошибка всплывала при запуске сервера — но только в продакшене, потому что тесты не регистрировали полный граф. Переход на явные уровни зависимостей сделал циклические зависимости видимыми на этапе code review.
Разрешение оркестратору писать файлы. В первой архитектуре оркестратор занимался и планированием, и выполнением. Когда каскад срывался на середине, файлы, записанные оркестратором, уже были зафиксированы. Разделение планирования (оркестратор) и выполнения (подагенты редактирования) стало архитектурным решением, которое сделало откат чистым.
📊 Показатели производительности
Измерено в продакшене на QuotyAI skill builder:
| Метрика | Значение |
|---|---|
| Классификация намерения | ~800 мс |
| Каскад известного паттерна (5 файлов) | ~12 с |
| Общий каскад (5 файлов) | ~25 с |
| Конвейер валидации | ~3 с |
| Компиляция в песочнице | <100 мс |
| Продвижение (CAS) | <50 мс |
| Откат (CAS) | <50 мс |
| Успешность обновления (известный паттерн) | 95% |
| Успешность обновления (общий каскад) | 83% |
| Доля обнаружения ошибок валидацией | 100% упавших каскадов |
12 секунд для каскада известного паттерна включают все 5 вызовов LLM для генерации кода, каждый из которых создаёт файл. Конвейер валидации ловит каждый сбой каскада — ни одно несогласованное состояние не попало в продакшен с момента развёртывания конвейера.
📋 Стек технологий
| Слой | Технология |
|---|---|
| Фреймворк агентов | deepagents |
| Структурированный вывод | Gemini + Zod 4.x схемы |
| Песочница выполнения | Node.js vm.createContext |
| Хранилище | MongoDB native driver |
| DI | tsyringe с 8 уровнями зависимостей |
| Lifecycle API | Hono + Zod валидация |
📋 Когда использовать эту архитектуру
Используйте этот подход, когда:
- Ваша AI-система генерирует код или структурированные данные, зависящие от других сгенерированных ресурсов
- Вам нужны атомарные обновления нескольких взаимозависимых файлов
- Оператор должен проверять изменения перед публикацией
- Вам нужен чёткий журнал аудита каждой модификации
Пропустите, когда:
- Ваш контент независим (нет цепочки зависимостей между ресурсами)
- Вам не нужно утверждение оператора для изменений
- Вывод LLM не имеет потребителей ниже по потоку
- Вы можете терпеть частичные сбои обновлений
TL;DR
| Категория | Паттерн |
|---|---|
| Проблема | Обновления бизнес-логики каскадируются через 5+ взаимозависимых файлов |
| Архитектура | LLM для извлечения намерения → детерминированное выполнение каскада |
| Граф зависимостей | Топологическая сортировка: offerings → order → pricing + scheduling + validation |
| Жизненный цикл | Git-подобный: стейдж → предложение → утверждение → публикация → откат |
| Workflows | Известные паттерны (SKILL.md) для типовых операций, общий каскад для новых |
| Диалог | Агент задаёт уточняющие вопросы перед любым каскадом |
| Валидация | Проверка схем + верификация покрытия + компиляция в песочнице + целостность зависимостей |
| Оркестрация | Оркестратор планирует + делегирует. Никогда не пишет файлы напрямую. |
Дополнительные материалы
- Determinism as Infrastructure — почему детерминированное выполнение — это фундамент
- The Deterministic Bet — обоснование создания надёжных AI-агентов
- Hono + Bun for AI Platforms: 6 Production Patterns — бэкенд-инфраструктура, на которой работают эти паттерны
Часто задаваемые вопросы
Как безопасно обновлять сгенерированный ИИ бизнес-логику в продакшене? Используйте семантику git: изменения отправляются в стейджинг-ветку, а не в продакшен. Оркестратор выполняет топологическую сортировку графа зависимостей, делегирует генерацию кода для каждого узла, затем запускает валидацию. Если любая проверка не проходит — обновление отменяется. Оператор проверяет и утверждает diff перед публикацией в продакшен.
Как предотвратить повреждение LLM взаимозависимых файлов бизнес-логики? LLM занимается только извлечением намерения — преобразованием естественного языка в структурированные данные с типизированной Zod-схемой. Всё после извлечения выполняется детерминированным кодом: граф зависимостей определяет порядок каскада, подагенты генерации кода создают каждый файл, конвейер валидации проверяет схемы и компилирует TypeScript, и всё обновление атомарно отменяется при сбое любого этапа.
В чём разница между известными паттернами и общим каскадом в AI-агентных workflows? Известные паттерны — это специализированные workflows для типовых операций: добавление услуги, удаление услуги, обновление цены. Каждый имеет SKILL.md с предопределённой последовательностью каскада. Общий каскад обрабатывает операции, не соответствующие ни одному известному паттерну, строя последовательность обновления с нуля на основе графа зависимостей.
Как проверять сгенерированный ИИ код до его попадания в продакшен?
Конвейер валидации проверяет JSON-схемы на соответствие draft 2020-12, разрешает все кросс-ссылки, проверяет, что формула ценообразования покрывает каждую услугу, и компилирует TypeScript в песочнице Node.js vm.createContext с таймаутом 5 секунд. Если любая проверка не проходит, всё обновление атомарно отменяется.
Что происходит, когда каскадное обновление срывается на середине?
Атомарность встроена в жизненный цикл. Область стейджинга запечатывается в предложенную генерацию, конвейер валидации выполняет все проверки, и если любая проверка не удалась, генерация помечается как rejected, а область стейджинга удаляется. Никакое частичное состояние не попадает в продакшен. Оператор видит отклонённую генерацию с полным журналом аудита.
Теги: ai-agents business-logic code-generation typescript multi-agent deterministic-ai
Было полезно? Поделитесь.
Похожие статьи
OpenWiki vs QuotyAI: два проекта на LangChain DeepAgents, две архитектуры
OpenWiki использует LangChain DeepAgents для генерации документации к кодовым базам. QuotyAI использует тот же API createDeepAgent для генерации исполняемой бизнес-логики на TypeScript. Техническое сравнение архитектуры агентов, паттернов субагентов и исполнения кода.
Читать статьюHono + Bun для AI-платформ: 6 production-паттернов, которые стоит перенять
Строим AI-платформу на Hono + Bun: 100+ сервисов, SSE-стриминг, изолированное выполнение кода, реалтайм WebSocket. 6 production-паттернов из реального кодовой базы.
Читать статьюПочему я выбрал Bun и Hono для бэкенда QuotyAI
Создавайте более быстрые бэкенды, отказавшись от NestJS в пользу Bun и Hono — идеально для независимых основателей, создающих AI-продукты в 2026 году
Читать статью