Демо длилось четыре минуты и прошло безупречно: помощник разобрал три обращения, ни разу не соврал, продакт сказал «запускаем в понедельник». Прототип к этому моменту жил в одном файле, ходил в провайдера без таймаута, хранил промпт в строковой константе и знал ровно те три обращения, на которых его показывали.
Между этим файлом и сервисом, который выдержит понедельник, лежит не «доработка», а другой класс задачи. Прототип отвечает на вопрос «может ли модель это сделать». Production отвечает на вопрос «что произойдёт, когда она это сделает десять тысяч раз, и половина входов будет не такой, как на демо».
Прототип показывает, что модель иногда справляется. Production должен выдерживать безопасные изменения под реальной нагрузкой. Переход между ними обеспечивает progressive delivery.
Почему «сначала на 10%» недостаточно
Команда отправляет часть трафика на новый prompt. Общая latency и HTTP errors не меняются, rollout доходит до всех. Через день выясняется: новый вариант чаще отвечает уверенно, но неверно; повторные обращения выросли, стоимость успешного решения тоже.
Canary без контроля, метрик результата и автоматического отката — это big bang, растянутый во времени: тот же взрыв, только в замедленной съёмке и с графиками.
Сначала отделите release от exposure
Соберите immutable behavioral manifest из главы 42 и разверните кандидат без пользовательского трафика. Управляйте доступом отдельно:
build -> offline gates -> deploy candidate
-> shadow -> internal users -> canary cohorts
-> staged exposure -> full rollout
\-> abort + rollbackFeature flag должен выбирать целый manifest, а не независимо prompt, model и index. Assignment детерминирован по устойчивому ключу (tenant_id/user_id), иначе один пользователь скачет между вариантами, а измерения загрязняются. Для stateful agent закрепляйте вариант на весь workflow.
Shadow не равен canary
В shadow кандидат получает копию входа, но его ответ не видит пользователь и его tool calls не имеют side effects.
async def handle(req):
control = await run(CONTROL, req, tools=real_tools)
if shadow_sample(req):
enqueue(run, CANDIDATE, redact(req), tools=read_only_stubs)
return controlShadow ловит schema errors, latency, cost и retrieval drift. Он не измеряет реакцию пользователя и может быть опасен, если копирует PII в другое окружение или вызывает реальные инструменты. Проверяйте consent/retention, redaction, tenant ACL и идемпотентность.
Canary и control должны быть сопоставимы
Одновременный control нужен, потому что меняются нагрузка, пользователи и upstream. Рандомизируйте единицей, соответствующей причинному эффекту: обычно user/tenant, не request. Крупного tenant иногда выделяют в отдельную страту, чтобы он не перевесил эксперимент.
До запуска зафиксируйте:
- primary outcome: business success, а не judge score;
- guardrails: безопасность, false accepts, contract/tool failures, latency, availability;
cost_per_success = total_inference_and_review_cost / successful_business_outcomes;- срезы: tenant, язык, task type, длина контекста, model route;
- stop/rollback policy, minimum observation window и владельца решения.
Маркированный пример, не норматив: этапы exposure могут быть internal → малая когорта → несколько последовательно расширяемых когорт. Конкретные доли, длительность и пороги выводят из трафика, сезонности и blast radius, а не копируют из книги.
Не оптимизируйте по cost/request: дешёвый ответ, после которого пользователь возвращается, дороже. Success должен быть наблюдаемым: решённый тикет без повторного обращения, корректно завершённая операция, принятый документ. Если outcome запаздывает, используйте ранние proxy только как временные guardrails.
Анализ rollout
while rollout.active:
c = metrics(CONTROL, same_window=True)
n = metrics(CANARY, same_window=True)
if security_incident(n) or contract_break(n):
rollback(reason="hard invariant")
elif confidence_bound(n.error_rate - c.error_rate) > ERROR_BUDGET:
rollback(reason="error regression")
elif confidence_bound(n.cost_per_success - c.cost_per_success) > COST_BUDGET:
rollback(reason="economics regression")
elif enough_data() and all_gates_green():
promote_one_step()Это схема, не готовый статистический тест. Для долей и попарных сравнений применяйте главу 38; учитывайте задержку outcome и кластеризацию по tenant. Нельзя проверять каждые пять минут обычный p-value и остановиться при первом удобном результате: repeated peeking повышает ложные срабатывания. Используйте заранее заданные checkpoints/последовательный метод либо относитесь к раннему откату как к консервативному safety rule, не как к доказательству эффекта.
Проверяйте и абсолютный SLO, и delta к control. Оба варианта могут деградировать из-за провайдера; delta будет зелёной, а пользователям от этого не легче.
Автоматический rollback
Rollback должен быть машинной операцией:
- остановить дальнейшее увеличение exposure;
- направить новые workflows на control manifest;
- безопасно завершить или отменить in-flight операции;
- вернуть совместимые index alias/configuration;
- сохранить candidate logs, traces и eval artifacts;
- проверить control synthetic-запросами;
- открыть incident с причиной и временной шкалой.
analysis:
hard_abort:
- security_policy_violation > 0
- tool_contract_failure > allowed_budget
comparative:
- metric: business_success
compare_to: control
- metric: cost_per_success
compare_to: control
rollback:
target_manifest: support-assistant/previous-known-good
verify: [synthetic_safe_refusal, retrieval_acl, tool_dry_run]Здесь значения: policy команды; пример намеренно не задаёт универсальных чисел.
Rollback к коду не поможет после несовместимой записи в memory/DB. Нужны expand/contract migrations, dual-read/dual-write при необходимости и blue-green index. Для необратимого tool action rollback означает остановить новые действия и запустить compensating workflow; «развернуть старую версию» деньги клиенту не вернёт.
Что автоматизировать, что оставить человеку
Автоматически откатывайте нарушение hard invariant: security policy, cross-tenant leak, невалидный tool contract, потерю данных. Для шумных или запаздывающих business metrics автомат может заморозить rollout и вызвать владельца. Иначе ложный alarm создаст deployment flapping.
Продвижение и откат должны быть идемпотентны, сериализованы и аудируемы. После rollback действует cooldown; причина не «лечится» простым перезапуском того же кандидата.
Failure modes
- canary получает только лёгких или только внутренних пользователей;
- routing по request смешивает варианты внутри диалога;
- candidate и control используют разные периоды или upstream;
- judge одновременно обновлён с candidate;
- среднее скрывает утечку у одного tenant или языка;
- shadow исполняет side effects;
- rollout продолжается при недостатке данных, потому что «красного нет»;
- alert есть, но rollback требует ручного редактирования пяти систем;
- откат ломает новую memory schema или читает новый индекс старым retriever;
- метрика стоимости игнорирует retry, human review и повторные обращения;
- логи candidate удаляются при rollback, расследовать нечего.
Rehearsal перед первым canary
Проведите game day в staging или на безопасной prod-когорте:
- искусственно ухудшите schema validity: hard gate должен остановить rollout;
- внесите задержку: latency gate сравнит candidate и control;
- симулируйте запрещённый tool call: side effect не должен исполниться;
- переключите green index и верните blue;
- откатите manifest при активных workflows;
- убедитесь, что dashboard показывает assignment, release ID, outcome, cost и security events.
Rollback, который не репетировали, остаётся инструкцией на бумаге. Огнетушитель в коридоре тоже висит уверенно, пока его не пробовали снять со стены.
Правило главы
Production-релиз LLM-системы: управляемое увеличение blast radius с одновременным control. Продвигают не по отсутствию HTTP 500, а по business success, cost-per-success и security guardrails. Hard invariant откатывает автоматически; состояние и side effects проектируют под откат заранее.
Чеклист главы
Практикум
Задача 53.1 — манифест поведения вместо россыпи настроек. ⭑⭑
Возьмите свою фичу и соберите immutable behavioral manifest: prompt-версия, модель, параметры генерации, версия индекса, набор инструментов, версия judge. Сделайте так, чтобы флаг выбирал манифест целиком.
Критерии приёмки:
Подсказки:
- Начните с YAML-файла в репозитории и хеша содержимого как идентификатора: этого достаточно, чтобы прекратить споры «а что было включено вчера в 14:00».
- Sticky assignment проще делать хешем от
tenant_id, а не случайным числом на запрос.
Задача 53.2 — cost per success. ⭑⭑
Определите наблюдаемый бизнес-успех для своей фичи и посчитайте стоимость одного успеха, а не одного запроса.
Критерии приёмки:
Подсказки:
- Если outcome приходит с задержкой в сутки, посчитайте его офлайн-джобой; ранние proxy оставьте только как guardrail.
- Самый частый сюрприз — стоимость ручной проверки. Её не считают, потому что она приходит не счётом от провайдера, а рабочим временем.
Задача 53.3 — game day с откатом. ⭑⭑⭑
Проведите репетицию отката на staging или на безопасной когорте прода.
Критерии приёмки:
Подсказки:
- Начните с самого неудобного сценария — отката при активных многошаговых операциях. Именно он показывает, что схема данных к откату не готова.
- Для необратимых действий репетируйте не «откат», а компенсирующую операцию: возврат денег обратно не деплоится.
Первичные источники
- Kubernetes Deployments: rollout status, pause/resume и rollback
- Argo Rollouts: Canary strategy
- Argo Rollouts: Analysis and automated abort