Инженерия

LLM решает — код пишет: почему модель не должна писать в базу

← Все статьи

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

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

Поэтому у нас есть инвариант, который нельзя нарушать: LLM решает — код пишет.

Вердикт как данные

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

Планировщик, например, отвечает примерно так:

{
  "verdict": "continue",
  "steps": [{ "agent": "backend", "session": "resume", "prompt": "..." }],
  "summary_update": "Разобраны требования, начата миграция схемы.",
  "reason": "Требования непротиворечивы, можно переходить к схеме данных."
}

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

Разница принципиальная. Модель, у которой есть инструмент записи, уже изменила мир к моменту, когда вы увидели её ответ. Модель, которая возвращает вердикт, ничего не изменила — у вас есть точка, где можно вмешаться.

Всё важное — в required

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

Мы навязываем схему ответа принудительно и держим в required всё, без чего вердикт бессмысленен: сам вердикт, обоснование, обновление проекций. Опциональным остаётся только то, отсутствие чего — валидное состояние.

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

Одна транзакция

Вердикт применяется целиком или не применяется вовсе. Шаги, обновление сводки, запись в журнал, отметка о провенансе — это один коммит.

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

Каждое решение несёт reason

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

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

Гарды вокруг цикла

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

  • таймбокс на шаг и на задачу;
  • бюджет итераций — сколько раз планировщик может сказать «продолжаем» подряд;
  • несоответствие агента и сессии — попытка продолжить чужую линию рассуждений;
  • пустой промпт — вердикт формально валиден, но работы в нём нет.

Любая из этих ситуаций останавливает цикл и поднимает вопрос человеку. Это скучные проверки, и именно поэтому они работают.

Модель может ошибиться в суждении — это нормально и ожидаемо. Она не должна иметь возможности ошибиться в записи.

Что это даёт

Три вещи, которые окупают дополнительный слой:

Воспроизводимость. Вердикт — это данные. Его можно сохранить, показать, переприменить на тестовом стенде и сравнить результат.

Ревьюируемость. Логика применения — обычный код, он проходит обычное ревью. Никто не проверяет промпт «на глаз», надеясь, что модель поймёт правильно.

Аудит. На вопрос «на каком основании система это сделала» есть ответ в виде цепочки: входящее → решение с обоснованием → транзакция → запись в журнале. Без пробелов.

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

Контакты

Начните с пилота на одной команде

Расскажите про стек и текущий процесс — вернёмся с оценкой эффекта и планом внедрения.

Электронная почта
Режим работы
Пн–Пт, 10:00–19:00 МСК

Укажите телефон или e-mail. Форма подготовит письмо — отправьте его в своём почтовом клиенте.