Границы в архитектуре ИИ: вероятностные модели внутри детерминированных систем
LLM меняет проектирование систем: контекст становится компонентом, вывод модели не заслуживает доверия, ответ 200 может оказаться сбоем, а стоимость — ограничением рантайма. Правила и полномочия держите в коде.
Возьмём планировщик раскладки, собранный из трёх частей с моделями внутри. LLM читает запрос пользователя, RAG подтягивает применимые стандарты, а графовая сеть с вниманием предлагает, куда поставить каждый элемент. Ни у одной из них нет последнего слова. Что попадёт в раскладку, решает детерминированный движок с правилами и проверками размещения, и он постоянно отклоняет предложения модели.
Такое разделение не было заранее выбранным принципом проектирования. Оно появилось после раскладки, которую модель вернула с полной уверенностью: четыре компонента, соединённые друг с другом и наложенные друг на друга, — геометрия, которая невозможна на реальной детской площадке. Никакой ошибки не возникло. Ответ был корректно оформлен, рассуждение читалось нормально, а результат был физически невозможен.
Начнём с привычного
Возьмём обычный бэкенд интернет-магазина:
User → API Gateway → Backend Services → Database
В продакшен-системах есть и дополнительные части — кэши, очереди, авторизация, наблюдаемость, внешние API, — но общая картина сохраняется, и её главная черта — контроль. Приложение само выбирает, какие операции выполнять, какие данные трогать, какие бизнес-правила действуют, как обрабатывать сбои и что считать успехом. Когда заказ оплачен, никто не просит сервис интерпретировать, правда ли это:
if order.status == "PAID":
ship_order()
Решает программа.
Теперь добавим модель
Допустим, в систему приходит ИИ-ассистент поддержки. В первый день архитектура такая:
User → Backend → LLM API → Response
Его выпускают. Вскоре клиент спрашивает о статусе заказа, и модели нужны данные, которых нет в промпте:
User → Backend → Fetch order data → Build context → LLM → Response
Потом другой клиент просит ассистента отменить заказ. Теперь модель должна что-то сделать, а не просто что-то сказать:
User → LLM → Decide → Call cancellation API → DB → LLM → Response
Затем приходит вопрос, для которого нужны три отдельные операции, и картинка вообще перестаёт быть линией.
Модель больше не просто зависимость. Она стала частью потока управления, и именно здесь архитектура меняется по-настоящему.
Настоящее отличие — вероятностные вычисления
Традиционный софт устроен так:
Input → Program → Output
ИИ-софт выглядит так:
Input → Context → Model → Probabilistic Output
Разницу хорошо показывает функция, которую можно протестировать напрямую:
def calculate_discount(price):
return price * 0.9
Вызов calculate_discount(100) даёт 90, и никакой судья не нужен, чтобы сказать, примерно ли верно 90.
Возьмём запрос вроде пересказа договора. Здесь нет одной правильной строки для сравнения. Проверки касаются поведения. Охвачены ли все важные пункты? Не выдумано ли что-нибудь? Не пропало ли условие расторжения? Не прочитан ли юридический термин неправильно? Помогает ли результат хоть кому-то?
Поэтому архитектуре нужно то, чего нет в большинстве систем: оценка поведения, а не только выполнение кода.
Ответ 200 всё равно может быть сбоем
На этом попадаются многие команды.
Классический мониторинг начинается с простого правила: 200 — всё хорошо, 500 — сломалось. ИИ-системы могут вернуть 200 с неправильным ответом. Или 200, построенный на устаревшем контексте. Или 200, в котором утекло что-то чувствительное. Или 200, где агент крутился девятнадцать раз и набил счёт.
Каждый отдельный вызов прошёл успешно, а приложение всё равно не справилось.
Одних статус-кодов недостаточно, чтобы судить о здоровье системы. Командам нужны задержка, доля ошибок, расход токенов, стоимость, качество поиска, качество ответов, вызовы инструментов и отзывы пользователей, связанные одной трассой.
Контекст — отдельный компонент
Традиционное приложение спрашивает, какие данные нужны программе. ИИ-приложение задаёт ещё один вопрос: что должна увидеть модель?
Для вопроса о политике возврата могут понадобиться сам вопрос, политика компании, аккаунт клиента и последние четыре реплики разговора.
Добавить векторную базу — простая часть. Сложнее решить, что должно попасть в окно, потому что приложению нужно взвесить, насколько каждый элемент релевантен и свеж, кому разрешено его видеть, сколько места он занимает, что говорит история, что делать при противоречии источников и как держать арендаторов раздельно. Контекст перестаёт быть трубопроводом и становится архитектурой.
Бизнес-правила не принадлежат модели
Допустим, пользователь просит удалить его аккаунт. Можно написать в промпте правило: удаляй, если просят. Но тогда граница проведена не там. Лучший поток:
User → Model → Intent: DELETE_ACCOUNT → Validation
→ Authorization → Business rules → Delete
Модель выясняет, чего хочет пользователь. Детерминированный код решает, разрешено ли это.
Пусть модель разбирается с неоднозначностью. Пусть софт распоряжается полномочиями.
Новая граница доверия
В классическом варианте граница одна:
Untrusted user → Validation → Trusted application
Как только в потоке появляется модель, её вывод тоже нужно считать недоверенным:
User → Application → Model → Model output → Validation → Real systems
На этот вывод может влиять многое: ввод пользователя, найденные документы, внешние данные, ответы инструментов, внедрённые инструкции или контекст, который никто не планировал.
Если проверять и авторизовать вывод модели перед выполнением, получается совсем другая система, чем если выполнять его напрямую.
Инструменты превращают один запрос в распределённый процесс
Реплика в чате — это один вызов модели. Агентный ход может быть цепочкой: модель, поиск, модель, вызов API, модель, ещё один поиск, модель, ответ.
Пользователь видит один запрос, а бэкенд выполняет десять операций.
Возникает проблема наблюдаемости, которую обычные логи запросов не решают. Нужны сквозные трассы, и не только для отладки. Они отвечают, почему запрос стоил $3, почему агент пять раз дёрнул один и тот же API, какой найденный фрагмент привёл к неверному ответу и куда на самом деле ушло время.
Стоимость становится ограничением рантайма
Бэкенд-команды и так планируют бюджет CPU, памяти, соединений и пропускной способности. ИИ добавляет новые статьи: токены, вызовы моделей, размер контекста, вызовы инструментов и расходы на инференс.
Один прогон агента может раскладываться так:
Model $0.02
Search
Model $0.04
API call
Model $0.03
Search
Model $0.05
─────────────
Total $0.14
Этот прогон стоит четырнадцать центов. Умножьте на тысячи пользователей в день, и число перестаёт быть бухгалтерией и становится ограничением дизайна. Поэтому в продакшен-системах появляются маршрутизация моделей, кэширование промптов, атрибуция затрат и бюджеты на каждый запрос.
Распределённые системы всегда так обращались с ограничениями процессора и памяти. Здесь ресурсы, которые потребляет каждый запрос, — это доллары, токены и секунды.
Повторы становятся рискованнее
Повторные попытки — стандартная практика. А теперь представьте, что запрос — отправить клиенту счёт.
Модель вызывает send_invoice(), и вызов проходит. Сеть теряет ответ, поэтому вызов повторяется и снова проходит. Клиент получает два счёта.
Понятия здесь старые: идемпотентность, таймауты, границы транзакций, частичные сбои и компенсация. ИИ их не придумал. Он вернул их в центр приложений, которые умудрялись их игнорировать.
Тестирование тоже меняется
Обычный пайплайн идёт от кода к юнит-тестам, интеграционным тестам и деплою. Для ИИ нужен ещё один слой рядом, где юнит-тесты, оценки и проверки безопасности прогоняют ИИ-процесс до релиза.
Поведение может сдвинуться при изменении чего угодно из этого: кода, промпта, модели, контекста, поиска или определения инструмента. Можно не выпустить ни строчки кода приложения, провайдер обновит модель, и к понедельнику продукт уже другой.
Как выглядит архитектура
Представьте поток, который идёт от входа через логику приложения, сборку контекста и слой модели, затем через валидацию и политики и наконец к инструментам. Под ним проходит сквозной слой: оценка, наблюдаемость, стоимость, безопасность, обратная связь и управление.
Самое важное в этой картине просто: LLM — это не архитектура. Это один компонент внутри неё.
Действительно ли архитектура ИИ другая?
Отчасти. Всё старое по-прежнему нужно: API, базы данных, авторизация, кэширование, очереди, балансировка нагрузки, повторы, транзакции, мониторинг и безопасность. Ничто из этого не исчезло.
Изменился тип одного компонента:
Traditional: deterministic → deterministic → deterministic
AI: deterministic → PROBABILISTIC → deterministic
Эта единственная замена меняет всё вокруг.
Принцип
Весь аргумент сводится к одной строке:
Помещайте вероятностный интеллект внутрь детерминированных границ.
Модель может прийти к выводу, что пользователь, вероятно, хочет отменить заказ. Приложение решает, разрешено ли этому пользователю его отменить.
Модель может заявить, что возврат составит $42. Сама сумма должна приходить из детерминированного кода, например refund = order.amount - fees.
Модель может решить, что нужно вызвать API возврата. Детерминированный софт решает, есть ли у запроса право его вызывать.
Вот это и есть граница.
Если посмотреть шире
Десятилетиями инженеры учились делать системы предсказуемыми, тестируемыми, наблюдаемыми, безопасными, восстанавливаемыми и масштабируемыми. ИИ эти усилия не отменяет. Он добавляет часть, у которой этих свойств по природе нет, и остальной системе приходится их обеспечивать.
Вопросы, которые демо никогда не задаёт
Самая лёгкая ошибка — думать, что достаточно вызвать LLM. Для демо достаточно, для продукта нет.
Как только ИИ оказывается в реальной системе, на несколько вопросов, на которые детерминированная версия отвечала бесплатно, теперь нужны ответы:
- Что должна знать модель?
- Можно ли доверять её выводу?
- Что будет, если она ошибётся?
- Кто вправе исполнять её решения?
- Как тестировать поведение, если оно меняется у вас под ногами?
- Как обнаружить сбой, если вызов вернул 200?
- Сколько может потратить один запрос?
- Что будет, если провайдер упадёт?
- Как восстановить процесс, оборвавшийся на полпути?
Ответ не в том, чтобы добавить больше логики в промпт. Ответ — в более продуманных границах вокруг модели.
Пусть ИИ имеет дело с неоднозначностью, а софт — с определённостью. Границу между ними стоит строить тщательно.
Модель поняла, чего хотел пользователь, но не поняла, может ли такой результат существовать. Её работа — только первое.