Production LLM Stack: роутинг, evals и надежность
Что такое production LLM stack?
Production LLM stack — это набор прикладных, политических, инфраструктурных и операционных контролей вокруг одной или нескольких языковых моделей: контракт задачи, роутинг, контекст, инструменты, валидация, наблюдаемость, оценка и релизы. Он заставляет вероятностную модель выполнять измеримый продуктовый контракт с учетом задержки, стоимости, безопасности и сбоев.
TL;DR
- -Production LLM stack — это контур управления вокруг вероятностного компонента, а не список модных моделей и векторных баз
- -До выбора модели нужны типизированный контракт задачи и решение по политике данных; модель, промпт, инструменты и источники — части релиза
- -Маршрутизировать надо по возможностям, политике данных, бюджету задержки и измеренному качеству задачи; одной цены недостаточно
- -Повторять стоит только временные ошибки внутри общего дедлайна, а fallback допустим лишь при сохранении контракта и политики данных
- -Один пользовательский результат должен иметь один trace, релизы — проверяться на фиксированных кейсах, а production-сбои — возвращаться в датасет
Production LLM stack — это контур управления вокруг компонента, который может вернуть
убедительный неправильный ответ вместе с 200 OK.
Вызов модели — только один блок:
клиент
→ входной слой и политика
→ оркестратор задачи
→ контекст, retrieval, memory и инструменты
→ model gateway и провайдер
→ валидация ответа
→ продуктовое действие
каждый шаг → trace → evaluation → решение о релизе
Сложность не в подключении еще одного API. Нужно сохранить один продуктовый контракт, когда меняются промпты, модели, инструменты, база знаний, нагрузка и поведение провайдеров.
Ниже — рабочий каркас этой системы. Ссылки ведут в отдельные практические руководства, чтобы страница не превратилась в документацию одного вендора.
Начните с контракта задачи
Не начинайте с вопроса «какую модель взять». Сначала опишите, что должна сделать одна задача, видимая пользователю.
Для ответа службы поддержки контракт может выглядеть так:
task: support.answer
input_schema: support_answer_request.v3
output_schema: support_answer_response.v2
required_capabilities:
- structured_output
- tool_calling
allowed_data_regions: [eu]
max_total_latency_ms: 8000
max_provider_attempts: 2
requires_citations: true
requires_human_review_when:
- refund_amount > 500
- source_conflict == true
Это не промпт, а обещание приложения. По нему роутер отсекает неподходящие модели, оркестратор решает, когда остановиться, валидатор отклоняет ответ, а eval suite измеряет результат.
Разделяйте три уровня успеха:
| Уровень | Вопрос | Пример сигнала |
|---|---|---|
| Контракт | Ответ соблюдает интерфейс? | Схема валидна, citation ID существуют |
| Качество задачи | Результат достаточно хорош? | Метка верна, ответ опирается на источник |
| Продуктовый исход | Пользователю помогло? | Тикет закрыт, правка принята, бронь завершена |
Валидный JSON может быть неправильным. Красивый ответ может не дать полезного исхода. Нужны все три уровня.
Разделите data plane и control plane
Data plane обслуживает запрос: собирает контекст, вызывает инструменты и модели, валидирует результат и возвращает его. Этот путь должен быть коротким и ограниченным.
Control plane решает, что именно исполнит data plane:
- версии промптов и политик;
- допустимые модели и провайдеры;
- правила роутинга и fallback;
- evaluation datasets и пороги релиза;
- бюджеты, квоты и kill switch;
- release labels и точки отката.
Не позволяйте правке в dashboard менять все production-запросы без истории. Каждое изменение control plane должно создавать неизменяемый release ID. Записывайте его в trace.
Практичный release manifest:
{
"release": "support-answer-2026-08-21.3",
"prompt": "support-answer@41",
"policy": "support-policy@12",
"router": "support-router@7",
"retrieval": "help-center-index@2026-08-20",
"toolset": "support-tools@5",
"outputSchema": "support_answer_response.v2"
}
После этого фраза «AI стал хуже» превращается в diff, который можно проверить.
Применяйте политику до роутинга
Аутентифицируйте вызывающую сторону и классифицируйте запрос до выбора провайдера. Роутер должен получать проверенные атрибуты приложения, а не доверять полям из текста пользователя.
Полезные входы политики:
- задача и tenant;
- класс данных и разрешенные регионы обработки;
- обязательные возможности: vision, structured output, tool calling;
- разрешения на инструменты;
- класс задержки и оставшийся дедлайн;
- бюджет пользователя, tenant и задачи;
- назначенный релиз или эксперимент.
Результатом должен быть явный execution envelope:
{
"task": "support.answer",
"tenantId": "tenant_internal_42",
"dataClass": "confidential",
"allowedProviders": ["provider-eu-a"],
"allowedTools": ["search_help_center", "read_subscription"],
"deadlineMs": 8000,
"release": "support-answer-2026-08-21.3"
}
Не отправляйте credentials провайдера или чувствительные детали классификации в браузер. Ими владеет серверный execution layer.
Введите внутренний контракт модели
Типы provider SDK быстро протекают в бизнес-логику: finish reasons, формы tool call, usage и коды ошибок оказываются по всему проекту. Поставьте узкий адаптер между приложением и каждым провайдером или gateway.
Как минимум нормализуйте:
- сообщения запроса и типы контента;
- structured output и определения инструментов;
- timeout и cancellation;
- контент ответа и tool calls;
- usage и данные для расчета стоимости;
- request ID провайдера;
- класс ошибки и возможность повтора;
- фактически использованные модель и провайдера.
Не скрывайте важные возможности за ложной универсальностью. Интерфейс с escape hatch для каждого вендора только маскирует lock-in. Оставьте общее ядро и явные capabilities, а несовместимый маршрут отклоняйте до вызова.
LLM gateway может централизовать этот контракт, credentials, квоты и телеметрию. Но с него не обязательно начинать. Сначала закройте одного провайдера своим адаптером. Gateway становится полезен, когда одинаковые операционные контролы нужны нескольким сервисам. Детали — в руководстве по multi-provider LLM architecture.
Роутите задачи, а не абстрактный «интеллект»
Самый безопасный роутер сначала работает детерминированно. Он фильтрует кандидатов, а затем выбирает из допустимого множества:
обязательная модальность и инструменты
→ политика данных и регион
→ совместимость контракта ответа
→ оставшийся бюджет задержки
→ порог качества на этой задаче
→ лимиты скорости и расходов
→ предпочтительный кандидат
Цена стоит почти в конце. Дешевая модель, которая дважды проваливает задачу, обходится недешево.
Начните с таблицы, а не с еще одного LLM-вызова:
| Класс задачи | Обязательное поведение | Основной путь | Допустимый fallback |
|---|---|---|---|
| Intent classification | Фиксированный enum, малая задержка | Проверенная малая модель | Правила или модель того же класса |
| Ответ по базе знаний | Citations и retrieval | Модель, проверенная на support set | Эквивалентная grounded-модель |
| Извлечение из документов | Строгая схема | Structured-output модель | Асинхронная очередь review |
| Рискованное действие | Tool call и approval | Достаточная модель | Без скрытого fallback |
AI-роутер добавляет промпт, задержку, стоимость и еще один вид сбоя. Подключайте его, только если intent нельзя надежно определить по метаданным и детерминированным правилам. Сам роутер оценивайте отдельно от модели-исполнителя.
Задайте бюджет контекста
Контекст — ограниченный вход, а не мешок, который надо заполнить. Выделите отдельные бюджеты и provenance для инструкций, текущего состояния, retrieval, memory, tool results и истории разговора.
system и policy фиксированы, высший приоритет
вход задачи обязателен
текущее состояние точное и scoped
retrieved evidence ранжировано, с источниками и freshness
memory отфильтрована по субъекту и lifecycle
tool results типизированы и ограничены по размеру
история разговора суммаризируется только при необходимости
Сборка и приоритеты разобраны в руководстве по context engineering, а долговременное состояние — в статье про память AI-агента. Держите оба слоя вне model adapter, чтобы тестировать их независимо.
Для retrieval записывайте document IDs, версию индекса, фильтры, scores и финальные chunks, отправленные модели. Тогда неправильный ответ можно разложить на «источника не было», «retrieval его пропустил» или «модель его проигнорировала».
Считайте инструменты привилегированным кодом
Tool call — предложение действия, а не авторизация. Проверяйте его в коде приложения с учетом подтвержденного principal и текущего состояния.
Каждому инструменту нужны:
- узкая схема и ограниченный ответ;
- серверная авторизация;
- timeout короче дедлайна задачи;
- idempotency key для повторяемых записей;
- audit event без секретов и лишних персональных данных;
- approval boundary для значимых действий;
- типизированная ошибка, понятная оркестратору.
Не давайте пользовательскому агенту универсальные run_sql, http_request или shell.
Разделяйте возможности по бизнес-действиям. Для удаленных MCP-инструментов действуют те
же правила из руководства по безопасности MCP.
Лимит цикла контролирует оркестратор. Модель не должна бесконечно тратить tool calls, потому что продолжает «исследовать» задачу.
Валидируйте до побочного эффекта
Structured output переносит сбой из разбора прозы в проверку контракта, но не делает значения истинными.
Проверяйте слоями:
- Синтаксис: JSON разбирается, content type ожидаемый.
- Структура: schema, enum, required fields, длина и cardinality.
- Ссылочная целостность: ID существуют и принадлежат текущему tenant.
- Grounding: citations указывают на evidence, которое модель действительно видела.
- Политика: действие разрешено этому principal в текущем состоянии.
- Бизнес-правила: суммы сходятся, даты допустимы, переход состояния разрешен.
Валидатор возвращает типизированное решение: принять, исправить в оставшемся бюджете, отправить человеку или завершить ошибкой. Никогда не передавайте свободный текст модели напрямую в оплату, удаление, изменение доступа или утверждение для клиента.
Стройте надежность вокруг одного дедлайна
У всей задачи должен быть сквозной дедлайн. Retrieval, tools, model calls, retry и валидация тратят его вместе. Пять независимых таймаутов по 30 секунд превращают один запрос в минуты ожидания.
Сначала классифицируйте сбой:
| Сбой | Повторить тот же route? | Fallback? | Обычное действие |
|---|---|---|---|
| Timeout до ответа | Один раз, если есть бюджет | Да, на совместимый route | Backoff с jitter |
| Provider 429/5xx | Ограниченно | Да | Учесть retry hint, открыть circuit при серии сбоев |
| Невалидный запрос или schema | Нет | Обычно нет | Исправить caller или контракт |
| Auth/permission | Нет | Нет | Остановить и поднять alert |
| Ответ нарушил контракт | Иногда один repair | Только на проверенный эквивалент | Повторить валидацию |
| Оборванный stream | Не replay прозрачно | Только явный restart | Не дублировать side effect |
Retry умножает нагрузку во время инцидента. Задайте малый attempt budget, exponential backoff с jitter и circuit breaker, который остановит трафик к падающей зависимости.
Cloudflare AI Gateway — один из вариантов реализации. В актуальной документации request handling есть per-request timeouts и ограниченные retry; сам gateway поддерживает роутинг, fallback, rate limiting, caching и budget controls. Но эти переключатели не заменяют общий дедлайн и политику совместимости на уровне приложения.
Fallback — не «попробовать следующую модель». До релиза докажите, что каждый кандидат:
- поддерживает обязательные инструменты и тот же output contract;
- имеет право обрабатывать этот класс данных в нужном регионе;
- укладывается в оставшийся latency и cost budget;
- проходит task dataset с согласованным порогом;
- не повторит уже совершенный side effect.
Если подходящего кандидата нет, деградируйте честно: верните частичный read-only ответ, поставьте задачу в очередь, запросите review или завершите стабильной ошибкой.
Один trace на пользовательскую задачу
Создавайте один root trace на один продуктовый результат. Retrieval, tool calls, model generations, parsing, validation, retry и fallback должны быть дочерними шагами.
Используйте стабильные имена операций:
support.answer
├── load-policy
├── retrieve-help-center
├── generate-answer
├── validate-citations
└── record-outcome
Записывайте release IDs и ограниченный набор диагностических измерений:
- task, environment, app release и experiment;
- версии prompt, policy, router, retrieval и toolset;
- выбранные model/provider и причина fallback;
- token usage, оценочная стоимость и задержка шагов;
- результат контракта, quality scores и product outcome;
- внутренние request IDs для корреляции и удаления.
По умолчанию не пишите в telemetry текст пользователя, секреты, access tokens и сырые чувствительные tool results. Успешный трафик можно сэмплировать, но ошибок и canary должно хватать для расследования регрессий.
Актуальные рекомендации Langfuse также советуют стабильные имена trace и observations, осмысленные root input/output, model и cost metadata на generations и связь prompt versions с traces. Руководство по LLM observability превращает это в telemetry contract, а пошаговая настройка Langfuse показывает реализацию.
Постройте evaluation loop до смены моделей
Eval — это release test, а не демонстрационный leaderboard. Он строится из задач, которые обязан выполнять продукт.
Комбинируйте разные способы проверки:
- детерминированные проверки schemas, citations, permissions, totals и известных labels;
- сравнение с reference, когда существует стабильный ожидаемый ответ;
- human review для рискованных или неоднозначных решений;
- откалиброванный LLM judge для узких rubric-based сигналов в масштабе;
- реальные продуктовые outcomes, присоединенные после их появления.
Руководство по LLM-as-a-judge показывает, где judge помогает, а где врет. В статье про human-in-the-loop разобраны очереди review и escalation.
Храните фиксированный regression set с ID, входом задачи, policy context, ожидаемыми инвариантами и labels для срезов. Добавляйте граничные и adversarial cases, а не только happy path. Сравнивайте текущий и новый релизы на одном наборе и читайте ошибки, а не один средний score.
Затем замкните цикл:
production failure или проверенная жалоба
→ удалить чувствительные данные и воспроизвести
→ добавить или обновить dataset case
→ написать самый дешевый надежный evaluator
→ прогнать текущий и новый релизы
→ canary
→ наблюдать тот же сигнал online
Langfuse описывает тот же offline-to-online цикл в evaluation concepts: изменение проверяется на фиксированном dataset, live traces наблюдаются, а новые edge cases возвращаются в набор. Инструмент вторичен; актив — сам feedback loop.
Считайте стоимость успешной задачи
Токены — входная метрика. Полезная единица выглядит так:
model + retrieval + tool + evaluation cost
-------------------------------------------
число задач, прошедших продуктовый критерий
Сегментируйте ее по task и release. Дешевая модель с дополнительными retries, repairs и human escalation может увеличить реальную стоимость.
Нужны четыре guardrail:
- предел токенов, tool steps и attempts на запрос;
- квоты пользователя, tenant, task и environment;
- spend alerts плюс hard stop или согласованный degraded route;
- асинхронный режим для работы без требования интерактивной задержки.
Кэшируйте только там, где повторное использование семантически безопасно. Включайте в ключ prompt, model, policy, retrieval version, locale и нужный user scope. Никогда не делите персонализированные или permission-dependent ответы между principals. Руководство по оптимизации LLM-затрат подробно разбирает prompt caching, semantic caching, batching и model downsizing.
Выпускайте prompt, model и retrieval как релиз
Считайте их программными релизами, даже если они живут в dashboard.
Безопасная последовательность:
- создать неизменяемые версии prompt, policy, router и retrieval;
- запустить deterministic tests и фиксированный evaluation set;
- сравнить quality, contract failures, latency и cost по важным срезам;
- направить canary только на трафик, подходящий кандидату;
- наблюдать заранее заданные guardrails и product outcomes;
- передвинуть label или вернуть его на предыдущую версию;
- сохранить release manifest на каждом trace.
Langfuse Prompt Management использует неизменяемые версии и перемещаемые labels вроде
production; актуальная
документация data model
описывает promotion и rollback через labels. Кэшируйте удаленную конфигурацию prompt и
держите локальный known-good fallback, чтобы сбой control plane не остановил data plane.
Не меняйте одновременно prompt, model, retriever и tool schema, если релиз не обязан быть атомарным. Иначе вы потеряете причинность и усложните rollback.
Alerts должны вести к действию
Алерт должен сообщать о сбое сервиса или продукта, а не о каждом шумном измерении. Минимальный набор:
- contract failures выше baseline релиза;
- end-to-end latency или timeout rate выше task SLO;
- доля fallback и открытых circuits;
- стоимость успешной задачи или скорость расходов;
- retrieval-empty и unresolved-citation rate;
- падение quality score на стабильной выборке;
- возраст human-review backlog для рискованных задач.
Каждому alert нужны owner, окно, runbook и release dimension. Руководство по автоматическим алертам объясняет thresholds и burn rate. Названия событий лучше один раз закрепить через event taxonomy для AI-продукта, чтобы dashboard не стал набором несовместимых счетчиков.
Защитите данные и credentials заранее
До production-трафика ответьте:
- Какие классы данных разрешено отправлять каждому провайдеру и региону?
- Какие поля удаляются или токенизируются перед inference и telemetry?
- Где хранятся, как ограничиваются, ротируются и аудируются provider keys?
- Может ли tenant экспортировать или удалить prompts, traces, memory и cached outputs?
- Какие tools могут читать и менять конкретные ресурсы?
- Каков retention для raw inputs, derived scores и backups?
Используйте environment variables или secret manager, а не исходный код и client bundle. Разделяйте staging и production credentials и quotas. В актуальном production guide OpenAI также рекомендованы server-side хранение секретов, отдельные environments и явные spend controls.
Redaction — не regex, который добавляют к логам после запуска. Определите разрешенные поля на границе задачи, несите классификацию через trace и тестируйте, что error paths не пишут raw payload.
Последовательность внедрения без лишней сложности
Этап 1: одна задача, один провайдер
- типизированные input и output contracts;
- один server-side provider adapter;
- end-to-end deadline и validation;
- root trace с release IDs;
- фиксированный regression dataset;
- явные spend и tool-step limits.
Этап 2: операционные контролы
- versioning prompt и policy;
- dashboards и полезные alerts;
- bounded retry и circuit breaker;
- второй провайдер, проверенный как fallback;
- canary и процедура rollback.
Этап 3: роутинг и стоимость
- deterministic task routing;
- пороги quality и cost для каждой задачи;
- безопасный cache и async/batch paths;
- online evaluation на выборке;
- перенос production failures в regression cases.
Этап 4: агенты и сложная оркестрация
- несколько tools и bounded loops;
- durable memory с lifecycle controls;
- human approval для значимых действий;
- evaluation траекторий и tool behavior;
- trace propagation между сервисами.
Не начинайте с четвертого этапа только потому, что agent framework быстро собрал demo. Каждый этап добавляет состояния, которые придется тестировать, наблюдать и восстанавливать.
Checklist готовности к production
Контракт и политика
- У каждой задачи есть typed input/output, критерий успеха и общий дедлайн.
- Authentication, data policy и tool permissions определяются до роутинга.
- Модель не авторизует собственные side effects.
Исполнение и надежность
- Специфика провайдера закрыта capability-aware адаптером.
- Retry ограничены, идемпотентны где нужно и делят дедлайн задачи.
- Каждый fallback сохраняет capabilities, policy, schema и проверенное quality.
- Для partial stream и committed side effects задано recovery behavior.
Качество и релизы
- Текущий и новый релизы запускаются на одном репрезентативном dataset.
- Contract checks, human review и calibrated judges закрывают разные сбои.
- Prompt, model, policy, tools и retrieval versions присутствуют в traces.
- Canary, guardrails, rollback и kill switch проверены на практике.
Операции и стоимость
- Один root trace соответствует одной пользовательской задаче.
- Dashboard показывает task success, latency, fallback и cost per successful task.
- Бюджеты заданы на запрос и нужный уровень user, tenant или task.
- Сбой telemetry не ломает продуктовый путь.
Безопасность и данные
- Keys остаются на сервере и разделены по environment.
- Чувствительные inputs и tool results минимизируются до inference и telemetry.
- Retention, access, correction, export и deletion описаны и протестированы.
Stack готов, когда релиз может сломаться известным способом, оставить достаточно сигналов для расследования и откатиться без гадания. Больше вендоров и агентов этого не дают. Это дают контракты, ограниченное исполнение, traces, evals и дисциплина релизов.