Senior-практики: архитектура, решения, лидерство
Сеньора отличают решения, которые переживают смену модели, и команда, которая их исполняет без него. Как записывать и защищать архитектурные решения (ADR, design review), выбирать модель и вендора по TCO, держать надёжность недетерминированной системы через SLO и error budget, разбирать AI-инциденты без поиска виноватых, говорить с бизнесом на языке денег и риска и задавать стандарты команды, включая работу с coding agents.
System design (модуль 12) отвечает на вопрос «как устроить систему». Здесь — что начинается после доски: как решение записать, провести через ревью, пересмотреть, эксплуатировать и объяснить людям, которые не читают трейсы. Код — калькулятор TCO, error budget и линтер ADR на GoJava — лежит в examples/m13-seniorexamples/java/m13-senior, шаблоны документов — в examples/m13-senior/templates.
Основные спецификации из модуля SDD фиксируют, что должна делать система; ADR объясняет, как и почему выбран способ реализации. Перед design review проверьте связь требований, дельт и сценариев приёмки.
Архитектурные решения: ADR для AI-систем
ADR (Architecture Decision Record, запись об архитектурном решении) — короткий документ об одном решении. Формат предложил Майкл Найгард в 2011 году: заголовок, контекст (какие силы конфликтуют), решение («мы будем…»), статус (предложено, принято, заменено) и последствия — хорошие, плохие и нейтральные. ADR лежат в репозитории рядом с кодом (обычно docs/adr/0007-….md), нумеруются и не переписываются: если решение поменялось, пишут новый ADR со ссылкой «заменяет ADR-0007», а старый получает статус «заменено». Через год история решений читается как changelog архитектуры.
Для AI-системы пяти разделов мало: решения устаревают быстрее и опираются на доказательства, которые сами устаревают. Модели выводят из эксплуатации — Anthropic, например, предупреждает о выводе публично выпущенной модели минимум за 60 дней (по состоянию на октябрь 2026). Цены сдвигаются, и «self-host, потому что API дорого» однажды становится неверным. «Haiku достаточно» верно только для конкретной версии eval-набора. А модель не падает с 500, она уверенно ошибается. Поэтому в ADR добавляются разделы:
| Раздел | Что писать в AI-системе |
|---|---|
| Альтернативы | Минимум две реальные, включая «ничего не менять» и «без LLM», с цифрами по тем же метрикам |
| Eval-доказательства | Набор (имя, версия, n), метрики с доверительными интервалами, критичные кейсы, прогон CI |
| Стоимость | $ за запрос и в месяц сейчас и при ×10, допущения по токенам и кэшу, цены с датой |
| Данные и комплаенс | Что уходит провайдеру, PII, хранение, регион; что проверяет юрист |
| Режимы отказа | Провайдер недоступен, уверенная ошибка, рост латентности, атака: что видит пользователь, чем ловим |
| План отката | Флаг или реестр промптов, сколько минут, кто может, когда проверяли. «Откатим» без механизма — это деплой посреди инцидента |
| Пересмотр | Дата и триггеры: вывод модели, изменение цены, рост объёма, метрика ниже порога |
Когда писать ADR: решение дорого развернуть назад (вендор, где хранятся данные, граница между моделью и кодом), затрагивает другие команды или через полгода вызовет вопрос «почему так?». Правке промпта хватает PR с evals (модуль 3), смене модели в продакшене — нет.
# ADR-NNNN: <решение одной строкой: глагол + объект>
## Статус
Предложено | Принято | Заменено ADR-NNNN. Дата: ГГГГ-ММ-ДД. Владелец, кто утверждает.
## Контекст
Задача и метрика успеха; ограничения (латентность, бюджет, PII, регуляторика); почему решать сейчас.
## Решение
«Мы будем …». Модель, промпт (имя@версия), параметры, граница между моделью и кодом.
## Альтернативы
| Вариант | Качество на eval | $ за запрос | p95 латентности | Почему не выбран |
|---|---|---|---|---|
| Ничего не менять | | | | |
| Без LLM (правила, классический ML) | | | | |
| <реальная альтернатива> | | | | |
## Последствия
Что лучше, что хуже, что сложнее. Lock-in: что переделывать при смене модели или провайдера.
## Eval-доказательства
Набор (имя, версия, n), метрики с интервалами, критичные кейсы, прогон в CI. Где хуже baseline — прямо.
## Стоимость
$ за запрос и в месяц сейчас и при ×10; токены, доля кэша, цены с датой; TCO.
## Данные и комплаенс
Что уходит провайдеру, маскирование PII, хранение логов, регион; вопросы юристу.
## Режимы отказа
Провайдер недоступен, уверенная ошибка, рост латентности, атака: что видит пользователь, чем ловим.
## План отката
Механизм (флаг, реестр), время, кто может, по какому сигналу, когда проверяли.
## Пересмотр
Дата: ГГГГ-ММ-ДД. Досрочно: вывод модели из эксплуатации, цена ±N%, объём ×N, метрика ниже порога.Пример учебный: компания PayFlow, цифры eval и латентности вымышлены. Цены — Claude по курсу, сентябрь 2026.
ADR-0012: Триаж обращений на Haiku 4.5 с эскалацией на Sonnet 5
Статус
Принято 2026-09-18. Владелец: команда support-ai. Утвердили: техлид поддержки и владелец продукта.
Контекст
Триаж 600 тыс. обращений в месяц на Sonnet 5. Категория должна появляться до открытия карточки оператором: p95 ≤ 1 с, сейчас 1.9 с. Стоимость (≈ $2.6K в месяц) не главный драйвер.
Решение
Мы будем классифицировать обращения на Haiku 4.5 (support_triage@v7) и отправлять на Sonnet 5, если категория рисковая (fraud, legal, dispute) или other, JSON не прошёл схему или предфильтр нашёл маркеры мошенничества. Маршрут выбирает код, а не самооценка модели.
Альтернативы
| Вариант | Точность (n = 1 200) | p95 | $/мес | Почему нет |
|---|---|---|---|---|
| Оставить Sonnet 5 | 94.0% | 1.9 с | $2 556 | Не выполняет требование по латентности |
| Только Haiku 4.5 | 90.5% | 0.8 с | $1 278 | Провал на fraud и legal (критичные кейсы 141/150) |
| Fine-tune малой модели | не мерили | — | — | ≈ 5K размеченных примеров и переобучение; при 600K в месяц не окупается |
| Без LLM: правила по словам | 71% | 10 мс | ≈ 0 | Остаётся как деградированный режим |
Последствия
Два промпта в реестре и два набора регрессий. Эскалированные 15% обращений стали медленнее (два вызова). Lock-in прежний: оба вызова идут через шлюз.
Eval-доказательства
triage-golden@v5, n = 1 200, из них 150 критичных. Роутер 93.6% против 94.0%: Δ −0.4 п.п., 95% bootstrap-интервал [−1.3; +0.5]; критичные 150/150. Shadow на 10% трафика две недели: совпадение с Sonnet 96.8%, эскалаций 15.2%. Здесь же ссылка на прогон CI.
Стоимость
Вход 3 000 токенов (60% из кэша), выход 150: Sonnet 5 — $0.00426 за вызов, Haiku 4.5 — $0.00213. Роутер: 0.85 × 0.00213 + 0.15 × (0.00213 + 0.00426) ≈ $0.0028. В месяц ≈ $1 660 против $2 556.
Данные и комплаенс
Без изменений относительно ADR-0004: номера карт маскируются до вызова модели, провайдер и регион те же.
Режимы отказа
Haiku недоступен — шлюз шлёт всё на Sonnet, p95 возвращается к 1.9 с. Haiku ошибся в fraud-обращении — страхуют предфильтр и рисковые категории; метрика — доля переклассификаций операторами.
План отката
Флаг triage.router=off: весь трафик на Sonnet за < 5 минут без деплоя, может дежурный. Проверено на staging 2026-09-16.
Пересмотр
2027-03-01. Досрочно: объявлен вывод claude-haiku-4-5, эскалаций выше 25% неделю подряд или точность на онлайн-выборке ниже 92%.
В примере решение принято ради латентности, а экономия ≈ $900 в месяц названа побочным эффектом: это снимает на ревью спор «ради $900 усложнили систему». И роутер честно хуже Sonnet по точности: минус с доверительным интервалом убеждает лучше таблицы, где выбранный вариант выигрывает во всех столбцах.
Срок пересмотра работает, только если его проверяют. Дешевле всего — линтер обязательных разделов в CI и ночная джоба, открывающая задачу на каждый ADR с прошедшей датой: функции MissingSections и ReviewBy в examples/m13-senior/adr.gomissingSections и reviewBy в examples/java/m13-senior/…/Adr.java, тест прогоняет их по шаблону выше.
Design review: какие вопросы задавать AI-системе
Design doc описывает систему целиком и ссылается на ADR по ключевым решениям. Структура — как в эссе Мальте Убля «Design Docs at Google»: контекст и масштаб, цели и не-цели, решение, альтернативы, сквозные вопросы (безопасность, приватность, наблюдаемость). Для AI-системы добавляются метрика успеха и критерии запуска, evals и стоимость. Шаблон из examples/m13-senior/templates/design-doc.md:
# Design doc: <название>
Автор · ревьюеры (данные, безопасность, SRE, продукт) · статус · дата · ADR: <ссылки>
## 1. Контекст и масштаб
Кто пользователи, какая боль, что уже есть.
## 2. Цели и не-цели
Что система делает и что сознательно НЕ делает.
## 3. Метрика успеха и критерии запуска
Метрика и способ измерения, baseline без AI, порог запуска, guardrail-метрики, kill-критерий.
## 4. Требования и цифры
Нагрузка и пик, латентность (TTFT, полное время), бюджет, PII, регуляторика.
## 5. Решение
Схема и поток данных, модель на каждом шаге. Права, деньги и необратимые действия — в коде.
## 6. Альтернативы
Минимум две, включая «без LLM»: качество, стоимость, латентность, риски.
## 7. Evals
Набор и критичные кейсы, судья и калибровка, онлайн-метрики, гейты в CI.
## 8. Безопасность и данные
Модель угроз, права инструментов, PII, хранение логов и доступ к ним.
## 9. Надёжность и эксплуатация
SLO и политика error budget, fallback, деградация, runbook, эскалация к людям.
## 10. Стоимость
$ за запрос и в месяц, TCO, рычаги снижения.
## 11. Запуск и откат
Этапы, canary, критерии перехода, откат.
## 12. Открытые вопросы
Что не решено, кто и к какому сроку решает.
Чек-лист ревьюера
Ревью ищет риски, а не проверяет эрудицию автора. Третий столбец — ответы, после которых стоит копать глубже.
| Область | Вопросы | Красный флаг в ответе |
|---|---|---|
| Продукт и метрика | Какая метрика говорит, что фича удалась? Baseline без AI? Когда закрываем? Кто владелец и дежурный? | «Пользователям понравится»; метрика появится после запуска |
| Данные | Откуда знания, как часто меняются? Права доступа к документам? Удаление по запросу? | «Положим всё в векторную БД»; ACL не упомянуты |
| Evals | Какой набор, откуда кейсы, какие критичные? Судья откалиброван? Что блокирует релиз? | Набор из 20 придуманных автором вопросов; судья без калибровки; гейта нет |
| Безопасность | Что сделает скомпрометированная модель с этими инструментами? Где проверяются права? | «В системном промпте запретим»; инструменты с правами сервиса, а не пользователя |
| Стоимость | $ за запрос и в месяц, при ×10? Лимиты на тенанта и на цикл агента? | Стоимость не посчитана; агент без лимита шагов |
| Отказ и деградация | Что видит пользователь, когда провайдер лёг или модель уверенно ошибается? Как откатываем? | «Провайдер надёжный»; откат через деплой |
| Наблюдаемость | По трейсу можно объяснить плохой ответ? Версии в каждом вызове? SLO и алерты? | Логируем только ошибки; качество в проде не измеряется |
| Комплаенс | PII и регулируемые данные: куда уходят, сколько хранятся? Раскрываем ли, что это ИИ? Кто из юристов смотрел? | «Это вопрос юристов, потом»; логи диалогов в общем стеке |
Как проводить ревью
- Сначала асинхронно: документ за 2–3 дня до встречи, встреча — только для неснятых разногласий.
- Ревьюеры: безопасность, данные (DPO при PII), будущий дежурный, продукт; один явно принимает решение.
- Уровни замечаний: «блокер», «до запуска», «пожелание» — иначе автор тонет в 40 комментариях одного веса.
- Вопросы вместо предписаний: «что будет, если retrieval вернёт ноль документов?» лучше, чем «добавьте fallback».
- Итог записан: принято, с условиями (с владельцами) или на доработку; ключевые решения — в ADR.
Как защищать свой дизайн
- Не-цели вслух. Половина споров — о том, чего система и не собиралась делать.
- Цифры против мнений. На «а давайте агента» — «workflow даёт 91% на eval за $0.004, агент — 92% за $0.03 и втрое дольше; 1 п.п. в пределах шума».
- «Не знаю» с планом: «не мерил; к четвергу прогоню 200 кейсов». Выдуманные цифры ревьюеры запоминают.
- Обратимое и необратимое. Идея «дверей в одну и в две стороны» из писем акционерам Amazon: вендор, схема хранения PII, публичный API обсуждаются долго; формулировку промпта или порог роутера меняют конфигом — тут предлагайте эксперимент вместо спора.
- Disagree and commit. Решение не в вашу пользу — запишите несогласие и условия пересмотра в ADR и делайте как решили.
StructuredTaskScope в Java 21 всё ещё preview. Ваши решения проще, но цикл тот же: предложение, обсуждение, записанное решение.golang/proposal, обсуждается публично, и группа ревью выносит решение: принято, отклонено, отложено. Generics прошли через несколько черновиков дизайна, прежде чем попали в Go 1.18. Ваши решения проще, но цикл тот же: предложение, обсуждение, записанное решение.Build-vs-buy и выбор модели или вендора
На senior-уровне вопрос не «какую модель взять», а какой вариант реализации даёт нужное качество при приемлемых стоимости, латентности и рисках на горизонте года, с учётом людей на эксплуатацию. В книге Chip Huyen «AI Engineering» это раздел «Model Build Versus Buy» (глава 4); ниже — рабочая версия для команды.
| Критерий | Как измерять | Ловушка |
|---|---|---|
| Качество | Ваш eval-набор и критичные кейсы, а не публичные бенчмарки | Выбор по лидерборду: на вашей задаче порядок моделей другой |
| Стоимость | TCO в месяц при текущем объёме и ×10, стоимость на успешную задачу | Сравнение цены токена с ценой GPU-часа без людей и утилизации |
| Латентность | TTFT и p95 полного ответа под вашей нагрузкой, из вашего региона | Цифры из демо на пустом эндпоинте |
| Lock-in | Стоимость выхода: сколько недель займёт переход на другой вариант при готовом eval-гейте | Видеть lock-in в самих vendor-фичах: prompt caching и batch экономят деньги, их стоит брать |
| Комплаенс | Регион, сертификаты, договор, zero data retention, отраслевые требования | Узнать о требованиях за неделю до запуска |
| Данные | Что уходит наружу, обучаются ли на этом, сколько хранится, можно ли маскировать PII | Путать «нельзя отправлять» (ограничение) и «лучше бы не отправлять» (вес) |
Жёсткие ограничения отсекают, веса ранжируют. «Данные не покидают периметр банка» — не вес 5 из 5, а фильтр: нарушающие его варианты не участвуют в сравнении. Если после фильтров не осталось ничего, это результат: требования пересматривают с бизнесом.
Четыре типичных варианта: API frontier (максимум качества, ноль инфраструктуры, самая дорогая цена токена), API дешёвая модель (в разы дешевле и быстрее, хуже на сложном), self-host open-weight (данные в периметре, но GPU, минимум две реплики и люди — модуль 10) и fine-tuned малая модель (на узкой задаче догоняет большие, цена — датасет и переобучение при смене базы).
TCO: что входит кроме токенов
Последние две строки обычно забывают: разметку для fine-tuned модели перепроверяют при каждой смене базы, а миграции у API неизбежны. Стоимость на успешную задачу честнее стоимости на запрос: модель, решающая 70% задач, может обойтись дороже модели, решающей 90%, если неуспешные уходят оператору.
Матрица выбора модели и вендора
Данные вариантов условные: порядок величин, а не замеры. Оценки 0–10, 10 — лучше (для lock-in — «легко уйти»); стоимость считается из TCO при выбранном объёме. Под схемой — какие изменения весов и объёма сменят лидера.
На пресете «Банк» включите ограничение по периметру: API-варианты исчезают. Добавьте «качество ≥ 8» — не останется ничего, и это честный ответ: требования противоречат друг другу. Если лидер меняется при сдвиге одного веса на единицу, решение хрупкое, и в ADR стоит записать, на какие веса оно опирается.
Тот же расчёт на Go (цена вызова API — APICost в том же файле,Java (цена вызова API — Tco.apiCost в том же классе, формула из модуля 12):
// Фрагмент Tco.java
/**
* Вариант реализации фичи. Все цифры — ваши параметры, а не справочник.
*
* @param perRequest $ переменной части: токены API (0 для self-host)
* @param unitMonthly $ в месяц за реплику self-host: GPU, хостинг (0 для API)
* @param unitCap запросов в месяц на реплику при целевой утилизации
* @param minUnits минимум реплик ради отказоустойчивости
* @param opsFte доля инженера: эксплуатация, on-call, обновления, evals, разметка
* @param successRate доля успешно решённых задач на вашем eval-наборе
*/
public record Option(String name, double perRequest, double unitMonthly, double unitCap, int minUnits,
double opsFte, double successRate) {
public static Option api(String name, double perRequest, double opsFte, double successRate) {
return new Option(name, perRequest, 0, 0, 0, opsFte, successRate);
}
public static Option selfHost(String name, double unitMonthly, double unitCap, int minUnits,
double opsFte, double successRate) {
return new Option(name, 0, unitMonthly, unitCap, minUnits, opsFte, successRate);
}
}
/** Полная стоимость владения в месяц и её структура. */
public record Result(double variable, double infra, double people, double total, int units,
double perRequest, double perSuccess) {}
/**
* TCO при объёме requests в месяц; fteMonthly — полная стоимость инженера в месяц.
* Упрощение: evals и разметка сидят в opsFte. Выделите их отдельной строкой, если они заметны в бюджете.
*/
public static Result monthly(Option o, double requests, double fteMonthly) {
double variable = o.perRequest() * requests, people = o.opsFte() * fteMonthly, infra = 0;
int units = 0;
if (o.unitCap() > 0) {
units = Math.max(o.minUnits(), (int) Math.ceil(requests / o.unitCap()));
infra = units * o.unitMonthly();
}
double total = variable + infra + people, perRequest = 0, perSuccess = 0;
if (requests > 0) {
perRequest = total / requests;
if (o.successRate() > 0) perSuccess = total / (requests * o.successRate());
}
return new Result(variable, infra, people, total, units, perRequest, perSuccess);
}
}
// Option — вариант реализации фичи. Все цифры — ваши параметры, а не справочник.
type Option struct {
Name string
PerRequest float64 // $ переменной части: токены API (0 для self-host)
UnitMonthly float64 // $ в месяц за реплику self-host: GPU, хостинг (0 для API)
UnitCap float64 // запросов в месяц на реплику при целевой утилизации
MinUnits int // минимум реплик ради отказоустойчивости
OpsFTE float64 // доля инженера: эксплуатация, on-call, обновления, evals, разметка
SuccessRate float64 // доля успешно решённых задач на вашем eval-наборе
}
// TCO — полная стоимость владения в месяц и её структура.
type TCO struct {
Variable, Infra, People, Total float64
Units int
PerRequest, PerSuccess float64
}
// MonthlyTCO считает TCO при объёме requests в месяц; fteMonthly — полная стоимость инженера в месяц.
// Упрощение: evals и разметка сидят в OpsFTE. Выделите их отдельной строкой, если они заметны в бюджете.
func MonthlyTCO(o Option, requests, fteMonthly float64) TCO {
t := TCO{Variable: o.PerRequest * requests, People: o.OpsFTE * fteMonthly}
if o.UnitCap > 0 {
t.Units = max(o.MinUnits, int(math.Ceil(requests/o.UnitCap)))
t.Infra = float64(t.Units) * o.UnitMonthly
}
t.Total = t.Variable + t.Infra + t.People
if requests > 0 {
t.PerRequest = t.Total / requests
if o.SuccessRate > 0 {
t.PerSuccess = t.Total / (requests * o.SuccessRate)
}
}
return t
}
Как не проиграть на смене модели
Выбранная сегодня модель через год будет либо выведена из эксплуатации, либо проиграет новой. Выигрывает тот, кто меняет её за дни, а не за квартал.
- Тонкая абстракция на вашей стороне: бизнес-код зовёт
Classify(ctx, ticket)classifier.classify(ticket), а не SDK; роутинг и fallback — в шлюзе (модуль 9). - Промпт привязан к модели: версия — пара «промпт + модель + параметры» (модуль 3), миграция — новая версия промпта.
- Eval-гейт, независимый от вендора: новая модель проходит тот же golden set в CI (модуль 8), затем shadow-трафик и canary.
- Календарь вывода моделей и дата пересмотра в ADR: 60 дней уведомления хватает, только если гейт и абстракция уже есть.
- Записанный план выхода. Для финансовых организаций в ЕС это требование регламента DORA (Regulation (EU) 2022/2554, применяется с 17 января 2025 года): статья 28(8) — стратегии выхода для ИКТ-услуг, поддерживающих критичные или важные функции. Применимость определяет юрист; инженерный вывод — переход к другому поставщику должен быть проверенным сценарием.
interface Classifier { Category classify(Ticket t); }) и сделайте по реализации на вариант — отдельный Spring-бин, выбираемый конфигом. Смена модели — новая реализация, прошедшая тот же eval-тест.type Classifier interface { Classify(ctx context.Context, t Ticket) (Category, error) }) и сделайте по реализации на вариант. Смена модели — новая реализация, прошедшая тот же eval-тест.SLO, error budget и on-call для недетерминированных систем
Термины из книги Google SRE. SLI — доля хороших событий: ответов без ошибки, быстрее порога, признанных судьёй верными. SLO — цель для SLI за скользящее окно, например 98% за 28 дней. Error budget — допустимая доля плохих событий, 1 − SLO. Бюджет превращает спор «релизить или чинить» в арифметику: бюджет есть — команда меняет систему, кончился — чинит надёжность. Техническая сторона (SLI по трейсам, burn-rate алерты в Prometheus) — в модуле 9 (SLO в продакшене), здесь — что из этого следует для команды.
Что считать плохим событием
У LLM-системы ответ может прийти за секунду с кодом 200 и быть неверным, поэтому SLI несколько (латентность — как обычно, по трейсам):
| SLI | Хорошее событие | Источник | Нюанс |
|---|---|---|---|
| Доступность | Ответ без ошибки и таймаута | Шлюз, трейсы | Решите заранее, считается ли хорошим ответ fallback-модели и режим «только ссылки»; долю деградации — отдельной метрикой |
| Качество | Онлайн-судья не пометил ответ как неверный или небезопасный | Выборка 1–5% ответов (модуль 8, онлайн-оценка) | Только выборка и с задержкой; у судьи своя ошибка (калибровка) |
| Прокси качества | Нет эскалации и повторного обращения за 24 ч | Аналитика | Запаздывает; может улучшаться при опасной регрессии (постмортем ниже) |
| Безопасность | — | Guardrails, аудит | Не бюджетируется: утечка PII, успешная инъекция, необратимое действие без подтверждения — всегда инцидент с постмортемом, даже при выполненном SLO |
Пример: 50 000 ответов в сутки, SLO качества 98% за 28 дней — 1.4 млн ответов, бюджет 28 000 плохих. Burn rate — во сколько раз быстрее нормы сжигается бюджет: при 1% плохих он 0.5, при 2% — 1 (бюджет кончится в последний день), при 30% — 15 (меньше двух суток).
Алерты строят по скорости сжигания на двух окнах сразу. SRE Workbook рекомендует для 30-дневного окна: 2% бюджета за 1 час (burn rate 14.4, будить), 5% за 6 часов (6, будить), 10% за 3 дня (1, тикет); короткое окно (5 мин, 30 мин, 6 ч) подтверждает, что сжигание идёт сейчас, и гасит алерт после инцидента. Порог = доля бюджета × окно SLO ÷ длинное окно; для 28 дней — 13.44, 5.6 и 0.93.
Error budget за 28 дней: фон, инцидент и политика релизов
Плохой ответ — ошибка или ответ, который онлайн-судья счёл неверным. Сверху — остаток бюджета, снизу — burn rate по часам. Алерты по часовым данным (короткие окна не моделируются); для инцидента качества алерт требует ≥ 30 оценок судьи в окне.
Политика error budget
Бюджет без политики — просто график. Политику заранее подписывают продукт, разработка и дежурные. В примере из SRE Workbook: бюджет за четыре недели превышен — релизы, кроме исправлений P0 и безопасности, останавливаются до возвращения в SLO; инцидент съел больше 20% бюджета — постмортем хотя бы с одним действием P0; споры о расчёте решает CTO. Для AI-фичи релиз — любое изменение поведения: промпт, модель, параметры, индекс.
| Остаток бюджета | Что разрешено | Кто решает |
|---|---|---|
| > 50% | Обычные релизы через eval-гейт, эксперименты (A/B дешёвой модели) | Команда |
| 0–50% | Релизы только с полным прогоном evals, canary медленнее, без экспериментов | Техлид |
| < 0 | Заморозка: только откаты, фиксы надёжности и безопасности, кейсы в golden set | По политике |
| Инцидент > 20% бюджета | Постмортем с P0-действием, независимо от общего остатка | По политике |
Политика как код — фрагмент examples/m13-senior/budget.go (там же FromSample:examples/java/m13-senior/…/Budget.java (там же Window.fromSample: перенос доли плохих по выборке судьи на весь трафик):
// Фрагмент Budget.java
/**
* Правило multiwindow multi-burn-rate из Google SRE Workbook («Alerting on SLOs»):
* за окно long сожжена доля budget бюджета, и сжигание всё ещё идёт на коротком окне shortW.
*
* @param budget доля бюджета всего окна SLO, например 0.02
* @param page true — будить дежурного, false — тикет в рабочее время
*/
public record Alert(String name, Duration longW, Duration shortW, double budget, boolean page) {}
/** Рекомендованные пороги SRE Workbook (таблица 5-8), от срочного к медленному. */
public static final List<Alert> DEFAULT_ALERTS = List.of(
new Alert("page-fast", Duration.ofHours(1), Duration.ofMinutes(5), 0.02, true),
new Alert("page-slow", Duration.ofHours(6), Duration.ofMinutes(30), 0.05, true),
new Alert("ticket", Duration.ofHours(72), Duration.ofHours(6), 0.10, false));
/** События за последние d — обёртка над вашим Prometheus или ClickHouse. */
public interface Metrics extends Function<Duration, Window> {}
/**
* Что говорит политика error budget прямо сейчас.
*
* @param budgetLeft доля оставшегося бюджета окна, бывает < 0
* @param fired первый сработавший алерт, null — тихо
* @param freeze бюджет исчерпан: релизы промптов и моделей заморожены, кроме фиксов
*/
public record Status(double budgetLeft, Alert fired, boolean freeze) {}
/** Цель по доле хороших событий за скользящее окно, например 0.99 за 28 суток. */
public record Slo(double target, Duration period) {
/** Во сколько раз быстрее нормы сжигается бюджет; 1 — ровно к концу окна. */
public double burnRate(Window w) {
return w.total() == 0 ? 0 : w.bad() / w.total() / (1 - target);
}
/** Доля бюджета → порог burn rate: 2% за 1 ч при окне 30 суток → 14.4. */
public double threshold(Alert a) {
return a.budget() * period.toNanos() / a.longW().toNanos();
}
/** Применяет алерты и политику заморозки к текущим метрикам. */
public Status check(Metrics m, List<Alert> alerts) {
double left = 1;
Window w = m.apply(period);
if (w.total() > 0) left = 1 - w.bad() / ((1 - target) * w.total());
for (Alert a : alerts) {
double th = threshold(a);
if (burnRate(m.apply(a.longW())) >= th && burnRate(m.apply(a.shortW())) >= th) {
return new Status(left, a, left <= 0);
}
}
return new Status(left, null, left <= 0);
}
}
// Alert — правило multiwindow multi-burn-rate из Google SRE Workbook («Alerting on SLOs»):
// за окно Long сожжена доля Budget бюджета, и сжигание всё ещё идёт на коротком окне Short.
type Alert struct {
Name string
Long, Short time.Duration
Budget float64 // доля бюджета всего окна SLO, например 0.02
Page bool // true — будить дежурного, false — тикет в рабочее время
}
// Threshold переводит долю бюджета в порог burn rate: 2% за 1 ч при окне 30 суток → 14.4.
func (s SLO) Threshold(a Alert) float64 { return a.Budget * float64(s.Period) / float64(a.Long) }
// DefaultAlerts — рекомендованные пороги SRE Workbook (таблица 5-8), от срочного к медленному.
var DefaultAlerts = []Alert{
{"page-fast", time.Hour, 5 * time.Minute, 0.02, true},
{"page-slow", 6 * time.Hour, 30 * time.Minute, 0.05, true},
{"ticket", 72 * time.Hour, 6 * time.Hour, 0.10, false},
}
// Metrics отдаёт события за последние d — обёртка над вашим Prometheus или ClickHouse.
type Metrics func(d time.Duration) Window
// Status — что говорит политика error budget прямо сейчас.
type Status struct {
BudgetLeft float64 // доля оставшегося бюджета окна, бывает < 0
Fired *Alert // первый сработавший алерт, nil — тихо
Freeze bool // бюджет исчерпан: релизы промптов и моделей заморожены, кроме фиксов
}
// Check применяет алерты и политику заморозки к текущим метрикам.
func (s SLO) Check(m Metrics, alerts []Alert) Status {
st := Status{BudgetLeft: 1}
if w := m(s.Period); w.Total > 0 {
st.BudgetLeft = 1 - w.Bad/((1-s.Target)*w.Total)
}
st.Freeze = st.BudgetLeft <= 0
for i, a := range alerts {
th := s.Threshold(a)
if s.BurnRate(m(a.Long)) >= th && s.BurnRate(m(a.Short)) >= th {
st.Fired = &alerts[i]
break
}
}
return st
}
On-call и runbook
У дежурства по AI-фиче три особенности: сигнал о качестве приходит медленно и часто от людей; главный рычаг — откат версии промпта или модели в реестре; живой fallback — операторы с ограниченной ёмкостью. Runbook — до первого инцидента:
| Сигнал | Первые 15 минут | Дальше | Эскалация |
|---|---|---|---|
| Ошибки провайдера, breaker открыт | Проверить fallback и статус провайдера | Дольше 30 минут — деградированный режим, предупредить поддержку | Владелец шлюза |
| Burn rate качества выше порога | Что менялось за 48 часов? Откат последнего изменения флагом | Релизов не было — дрейф входа или данных (модуль 9), разбор трейсов | Владелец фичи и eval-набора |
| Утечка PII, успешная инъекция, опасное действие | SEV-1: выключить фичу или инструмент kill switch'ем, сохранить трейсы | Оценка масштаба, уведомления по срокам закона (модуль 11) | Безопасность, DPO, юристы — сразу |
- Kill switch без согласований: дежурный выключает AI-фичу или инструмент сам, ночью, без звонка руководителю. Право записано в политике, иначе его не используют вовремя.
- Ёмкость людей — часть дизайна: «переводим на операторов» работает, только если поддержка заранее согласилась принять, скажем, +30% обращений на несколько часов.
Постмортем AI-инцидента
Постмортем — письменный разбор инцидента: влияние, сдерживание, причины и меры против повторения. Культура blameless из книги Google SRE: разбор ищет условия, в которых ошибка стала возможной, исходит из добросовестности участников и не обвиняет людей или команды. Это не «ничего не делаем»: результат — изменения в системе и процессе с владельцами и сроками. Если причина звучит как «инженер не проверил», настоящий вопрос — почему система позволила выкатить непроверенное.
Когда писать: видимый пользователям простой или деградация сверх порога, потеря данных, ручное вмешательство дежурного, слишком долгое устранение, инцидент, найденный людьми, а не мониторингом. Для AI-систем добавьте любой инцидент безопасности или PII, любое обязательство, данное системой от имени компании, и регрессию качества, которую первыми заметили пользователи.
Главное отличие AI-постмортема — обязательный вопрос «почему evals и гейты не поймали»: корневая причина чаще в пробеле набора или метрики, чем в строке кода.
# Постмортем: <короткое название инцидента>
Статус · SEV-N · начало и конец · авторы · blameless: ищем условия, а не виноватых.
## Кратко
3–5 предложений: что случилось, влияние, причина, что меняем.
## Влияние
Пользователи, диалоги, деньги, регуляторика. Как посчитано и с какой погрешностью.
## Таймлайн (UTC)
| Время | Событие | Источник (трейс, алерт, релиз) |
|---|---|---|
## Обнаружение
Как узнали, сколько прошло с начала, почему не раньше.
## Корневая причина и способствующие факторы
## Почему evals и гейты не поймали
Каких кейсов не было, какой проверки не было в CI, какая онлайн-метрика молчала.
## Что сработало и где повезло
## Действия
| Действие | Тип: предотвратить / обнаружить / смягчить | Владелец | Срок |
|---|---|---|---|
Обязательно: кейсы в golden set, гейт или метрика canary, guardrail, правка runbook.
Разбор: бот поддержки обещает компенсации
Кратко. Релиз support_reply@v12 вместе с переходом с Opus 5 на более новую и дешёвую Opus 5.5 привёл к тому, что бот обещал клиентам вернуть комиссии и зачислить компенсации, не имея на это полномочий. Заметили операторы; до дежурного сигнал дошёл через 31 час после выкатки на 100%, откатили через 22 минуты.
| Время (UTC) | Событие | Источник |
|---|---|---|
| Вт 14:00 | Смержен support_reply@v12: инструкция «прояви эмпатию и предложи конкретное решение», модель Opus 5.5. CI: 92.1% → 93.4%, критичные 40/40. Canary 5% | CI, реестр |
| Ср 10:00 | Метрики canary: 👎 −0.4 п.п., эскалации −2.1 п.п., стоимость −17%. Выкатка на 100% | Дашборд canary |
| Чт 11:20 | Оператор в чате поддержки: третий клиент за утро ссылается на «обещание бота». До дежурного не доходит | Чат |
| Чт 16:40 | Руководитель смены поддержки эскалирует дежурному support-ai | Звонок |
| Чт 16:55–17:02 | В трейсах «вернём вам комиссию», «зачислим 500 ₽». SEV-2; флаг в реестре: v11 на Opus 5 | Трейсы, реестр |
| Чт 18:30 | Классификатор обязательств по 51 000 диалогам на v12: 1 380 с обещаниями (2.7%) | Офлайн-джоба |
Влияние. ≈ 1 380 клиентов получили обещание денег, ≈ 940 обратились за выполнением; обещания до установленной суммы решили выполнить. Около 120 часов работы операторов.
Корневая причина. Система позволяла модели давать денежные обязательства текстом. Запрет существовал только как инструкция в промпте, и ни одна проверка не измеряла это свойство. Способствующие факторы: новая инструкция конфликтовала со старым запретом, и новая модель разрешала конфликт в пользу «помочь»; модель и промпт поменяли в одном релизе; у сигнала операторов не было пути к дежурному.
Почему evals не поймали.
- Golden set был одношаговым, а 78% обещаний случились после двух негативных реплик клиента.
- Проверка
must_not_contain: ["компенсац"]искала подстроку, а модель писала «вернём комиссию» и «зачислим на счёт». - Рубрика онлайн-судьи оценивала полезность, корректность и тон, но не «обязательства вне полномочий».
- Метрики canary улучшились: клиентам нравится, когда им обещают деньги. Прокси совпала по направлению с режимом отказа.
- Владелец политики поддержки не был ревьюером PR.
Сработало: откат флагом без деплоя; версия промпта в трейсах дала точный подсчёт влияния. Повезло: суммы были небольшими.
| Действие | Тип | Владелец | Срок |
|---|---|---|---|
| 60 многоходовых кейсов «давление на компенсацию» как критичные; v12 их проваливает | предотвратить | владелец eval-набора | 1 неделя |
| Критерий судьи «обязательства вне полномочий», калибровка на 100 размеченных диалогах | обнаружить | ML-инженер | 2 недели |
| Guardrail: классификатор обязательств на выходе, при срабатывании — оператор | смягчить | support-ai | 1 неделя |
Компенсации только через инструмент offer_compensation с лимитами; слова о деньгах — только после tool_result (ADR-0019) | предотвратить | техлид | 1 месяц |
| Метрика canary «доля ответов с обязательствами» (порог +0.1 п.п.); правило: модель и промпт не меняются в одном релизе | обнаружить | SRE, техлид | 1 неделя |
Кнопка «бот ответил не то» у оператора с алертом дежурному; владелец политики поддержки — в CODEOWNERS prompts/support_* | обнаружить, предотвратить | продукт поддержки | 2 недели |
В действиях есть все три типа: предотвращение (кейсы, инструмент вместо текста), обнаружение (судья, метрика, кнопка) и смягчение (guardrail). Постмортем только с «улучшить промпт» не дошёл до системных причин: следующая версия найдёт новый способ пообещать.
Встреча: ведущий — не участник инцидента; таймлайн по трейсам, потом причины, потом действия; язык «система позволила», а не «Петя выкатил»; через две недели — проверка действий.
Коммуникация: метрики evals на языке бизнеса
Руководителю нужны не κ и p95 TTFT, а ответы на четыре вопроса: сколько задач система решает, сколько это стоит, чем рискуем и что даст следующее вложение.
| Инженерная метрика | Вопрос бизнеса | Как сказать |
|---|---|---|
| Точность 92% на golden set, n = 400 | «Насколько ему можно доверять?» | «На 400 реальных обращениях: 92 из 100 правильно, 5 передаёт оператору, в 3 ошибается — вот примеры» |
| $0.012 за запрос | «Сколько стоит?» | «Решённое ботом обращение — $0.6 с учётом команды, у оператора — $4» |
| Δ −0.4 п.п., интервал [−1.3; +0.5] | «Стало хуже?» | «Разница в пределах погрешности; чтобы различить 1 п.п., нужно ещё N кейсов» |
Правила: всегда знаменатель, примеры ошибок, деньги рядом с риском, диапазон там, где есть неопределённость. И точность на golden set — не доля решённых обращений: между ними вопросы, на которые бот не отвечает вовсе, и клиенты, не поверившие правильному ответу.
Договориться о метрике до разработки
Самый дорогой провал — запуск, после которого никто не может сказать, удался ли он. Метрика успеха согласуется до первой строки кода, как контракт. Пример для бота PayFlow (вымышленный):
| Пункт | Значение |
|---|---|
| Целевая метрика | Доля обращений, закрытых без оператора и без повторного обращения за 7 дней |
| Как измеряем | Продуктовая аналитика; ручная проверка 200 диалогов в неделю |
| Baseline | Всё обрабатывают операторы, поиск по FAQ закрывает около 10% |
| Порог запуска на 100% | ≥ 40% на canary в течение двух недель |
| Guardrail-метрики | CSAT не ниже базового более чем на 2 п.п.; уверенных ошибок ≤ 1%; обязательств вне политики — 0; стоимость ≤ $0.05 за диалог |
| Kill-критерий | Через 8 недель доля решённых ниже 25% — сворачиваем до поиска по FAQ |
Контракт подписывают продукт, техлид и руководитель операторов. Kill-критерий делает проект безопасным для заказчика: известна максимальная цена неудачи. Guardrail-метрики защищают от оптимизации любой ценой: бот, обещающий компенсации, отлично закрывает обращения.
От метрик evals к деньгам и риску
Доля решённых — онлайн-метрика, уверенные ошибки — из разметки и судьи. LLM: 4 вызова на диалог по 3 000 токенов входа и 300 выхода без кэша, цены сентября 2026. Прочее условно — подставьте свои цифры.
Схема показывает маржинальную цену улучшений: если ошибка дорогая, −0.1 п.п. ошибок ценнее +1 п.п. автоматизации, и роадмап должен это отражать. На планировании это сильнее, чем «качество стало выше».
Как сказать «здесь LLM не нужна»
Иногда лучший вклад сеньора — убедить не делать AI-часть. Когда стоит предложить альтернативу:
| Сигнал | Почему LLM проигрывает | Что вместо |
|---|---|---|
| Есть детерминированное правило или справочник | Правило бесплатно, мгновенно и всегда одинаково | Регулярки, SQL, таблица решений |
| Нужна точность расчёта: деньги, проценты, юридически значимые числа | Модель не калькулятор, аудитор спросит, как получено число | Код считает, LLM максимум объясняет |
| Латентность в десятки миллисекунд, огромный объём простых решений | Даже малая модель — сотни миллисекунд | Классический классификатор на эмбеддингах или признаках |
| Нечем мерить качество: нет данных и эксперта для разметки | Неизвестно, работает ли система | Сначала данные и разметка |
Как сказать: не «нет», а «вот что даст результат быстрее и дешевле», с цифрами и гибридом. «Правила по 30 шаблонам закрывают 80% таких обращений за неделю работы и бесплатно в эксплуатации. Для оставшихся 20% LLM имеет смысл — вернёмся к ней, когда соберём 300 размеченных примеров для evals». Тот же вопрос в формате собеседования — в модуле 14 (вопросы с собеседований).
Команда: менторство, ревью промптов и evals, стандарты
Сеньор масштабируется через других: стандарты, которые не нужно помнить, ревью, которое учит, и людей, которые через год пишут ADR без него. В AI-команде к обычным стандартам добавляются промпты, eval-наборы и данные — артефакты, которые по привычке живут вне процессов.
| Стандарт | Минимум | Чем обеспечивается |
|---|---|---|
| Промпты в репозитории | prompts/*.tmpl через embedprompts/*.txt в ресурсах classpath (src/main/resources), владелец и changelog (модуль 3) | CODEOWNERS на prompts/ |
| Версии в трейсах | Имя, версия и хэш промпта, модель и параметры в каждом трейсе и рядом с результатом | Алерт на вызовы без prompt.version |
| Evals в CI | Smoke на каждый PR в промптах, eval-наборах, конфиге моделей; полный набор ночью | Обязательная проверка (модуль 8) |
| Политика данных | Какие данные каким провайдерам, маскирование PII, сроки логов | Проверка в шлюзе (модуль 11) |
| ADR и владение | ADR для смены модели, вендора, хранения данных; у каждой фичи владелец, SLO, runbook | Линтер ADR в CI; без runbook фича не выходит из беты |
spotless:check в сборке, CI красный. Стандарты AI-команды стоит строить так же: правило «промпт только с evals», которое проверяют глазами, нарушат в пятницу вечером, а обязательную проверку в CI на изменения в prompts/ — нет.gofmt победил потому, что не обсуждается: инструмент один, настроек нет, CI красный. Стандарты AI-команды стоит строить так же: правило «промпт только с evals», которое проверяют глазами, нарушат в пятницу вечером, а обязательную проверку в CI на изменения в prompts/ — нет.Ревью PR с изменением промпта
- Зачем: ссылка на кейсы или инцидент, которые чинит изменение. «Улучшил промпт» без примеров не ревьюится.
- Дифф evals против baseline по сегментам, критичные кейсы 100%, атаки без новых успешных (модуль 7). Рост в одной категории часто сопровождается падением в другой.
- Новые кейсы на чинимую проблему, которые падают на старой версии промпта.
- Стоимость и латентность: токены промпта, $ за запрос, кэшируемый префикс.
- Скрытые части промпта: описания инструментов, схема вывода, параметры модели; few-shot без реальных PII.
- Одна переменная за раз: не меняются вместе промпт и модель или промпт и eval-набор. Если из набора тихо удалены «неудобные» кейсы, гейт проходит, а система не стала лучше; изменения набора — отдельный PR. Изменился судья — перекалибровка до мержа (модуль 8).
Менторство и онбординг
- Трейсы вместе. Error analysis (модуль 8) учат вживую: час над 50 реальными ответами с вопросом «почему плохо и какой тест это поймает» даёт больше статьи.
- Задачи с метрикой: «подними recall@5 с 0.71 до 0.80 на наборе v3» вместо «улучши поиск». Первое ревью задачи мидла — как он будет мерить результат.
- Владение целиком: фича, eval-набор, SLO и дежурство у одного человека или пары. Раз в квартал — ADR или постмортем под ревью сеньора: так растут до сеньоров.
- Учите вопросам: «как мы узнаем, что это сломалось?», «что будет при ×10?», «что сделает скомпрометированная модель с этим инструментом?».
- Онбординг за месяц: в первые дни — окружение и evals одной командой (не запускается — первый PR в документацию), 100 трейсов с наставником, ADR и постмортемы; потом маленькое изменение промпта через весь путь до прода, тень дежурного и 10 новых кейсов в golden set.
Coding agents как командная дисциплина
Инструментальная часть — AGENTS.md и CLAUDE.md, режимы разрешений, sandbox, хуки, рабочий цикл «план → тесты → реализация → ревью диффа» — в модуле 4 (coding agents). Здесь — правила для команды, где агенты пишут заметную часть кода. Без них получается либо запрет, который тихо нарушают, либо «каждый как хочет» и код, который через полгода никто не понимает.
| Зона | Правило команды | Чем обеспечивается |
|---|---|---|
| Ответственность | За код отвечает автор PR, кто бы его ни набрал. «Это сгенерировал агент» не аргумент ни на ревью, ни в постмортеме | Культура ревью; пометка в PR об агенте — для фокуса ревьюера, не для наказания |
| Тесты прежде кода | Сначала падающий тест; агент не ослабляет и не удаляет тесты; эталон evals меняет человек отдельным PR | Файл инструкций, Stop-хук и отдельное ревью диффа тестов (модуль 4) |
| Ревью | Планка та же, внимание другое: правдоподобный, но неверный код, выдуманные API, лишние зависимости, тихо изменённые проверки. Большие диффы дробятся | Чек-лист ревью, лимит размера PR |
| Права и секреты | Продовых секретов в окружении агента нет вообще; деплой и доступ к проду — никогда; автономные режимы — только в изоляции | Настройки разрешений в репозитории, управляемые настройки организации, sandbox или контейнер |
| Инструкции репозитория | Файл инструкций — стандарт команды и ревьюится как код; это контекст для модели, а не принуждение | CODEOWNERS на файл; всё обязательное дублируется в CI |
| Где не использовать без эксперта | Авторизация, криптография, миграции данных, обработка PII; домен, в котором вы не можете проверить результат | CODEOWNERS на чувствительные пакеты, метка «нужен ревью эксперта» |
Узкие права нужны ещё и потому, что агент читает недоверенный контент — issues, README зависимостей, чужой код. Это непрямая инъекция (модуль 7), и lethal trifecta собирается легко: секреты в окружении, недоверенный текст, сеть. Разрывать её проще всего на секретах и сети.
Что мерить. Не «долю кода от агента» — она поощряет объём. Отчёт DORA 2024 связал рост использования AI с ростом индивидуальной продуктивности, но со снижением стабильности и пропускной способности поставки. Смотрите на метрики поставки: время до прода, долю изменений с инцидентами, время восстановления, размер PR. Выросли PR и откаты — правила ужесточают, даже если все довольны.
Практика
Задание 1: design doc с ADR для «AI-поддержки банка» и его защита
Спроектируйте чат-ассистента поддержки условного розничного банка и защитите дизайн. Этот design doc — заготовка для защиты капстоуна (модуль 15): там те же документы пишутся для вашего агента из сквозных проектов, а здесь ограничения чужие, и «просто взять самую сильную модель» не выйдет.
| Параметр | Ограничение |
|---|---|
| Объём | 400 тыс. диалогов в месяц, в среднем 5 сообщений клиента; пик ×6 в дни зарплат |
| Сценарии | Тарифы (≈ 3 000 статей); статус карт и операций через API банка от имени клиента; блокировка карты с подтверждением; споры — оператору |
| Бюджет | ≤ $0.03 на диалог на модели и инфраструктуру; команда — 3 инженера |
| Латентность | TTFT p95 ≤ 2 с, полный ответ ≤ 8 с |
| PII | ФИО, карты, счета, паспорт, операции; номер карты целиком — ни провайдеру, ни в логи |
| Регуляторика | Банковская тайна (в РФ — ст. 26 закона «О банках и банковской деятельности»), 152-ФЗ или GDPR, раскрытие ИИ (EU AI Act, ст. 50 — модуль 11), в ЕС — DORA. Применимость определяет юрист; от вас — инженерные последствия и вопросы к нему |
| Вне скоупа | Решения о кредитах (в EU AI Act оценка кредитоспособности — высокорисковая система, Annex III), обещания ставок и компенсаций |
Что сдать: design doc по шаблону (до 8 страниц); три ADR, проходящих линтер из examples/m13-seniorexamples/java/m13-senior (модель или вендор с матрицей; где живут и как маскируются PII; эскалация и подтверждение действий); стоимость и TCO; SLO и политику error budget; модель угроз (модуль 7). Защита: 15 минут рассказа и 30 минут вопросов. Ревьюер — коллега или LLM, которой дали эту рубрику и попросили спрашивать, а не помогать.
| Критерий | 0 — не принято | 1 — принято | 2 — сильно |
|---|---|---|---|
| 1. Требования и метрика | Нет цифр или метрики | Цифры, метрика, baseline | Плюс guardrail-метрики и kill-критерий |
| 2. Граница модели и кода | Модель решает о правах и деньгах | Права и необратимые действия — в коде | Плюс деградированный режим, поток PII на схеме |
| 3. Альтернативы и ADR | Альтернативы-чучела | Три ADR, линтер зелёный | Плюс честные минусы, триггеры пересмотра |
| 4. Evals и гейты | Нет набора или гейта | Golden set ≥ 200, критичные кейсы, гейт в CI | Плюс многоходовые и атакующие кейсы, калибровка судьи, canary |
| 5. Безопасность и PII | PII уходит провайдеру без анализа | Маскирование, инструменты от имени клиента, модель угроз | Плюс набор атак, kill switch |
| 6. Регуляторика | Не упомянута | Требования и инженерные последствия | Плюс сроки хранения логов, вопросы юристу |
| 7. Стоимость и TCO | Не посчитана | $ за диалог в бюджете | Плюс TCO с людьми, ×10, стоимость на решённое обращение |
| 8. Надёжность | Не описана | SLO, fallback, откат | Плюс политика error budget, runbook, ёмкость операторов |
| 9. Защита | Спорит без данных | Отвечает по существу, «не знаю» — с планом | Плюс меняет дизайн по сильным замечаниям и фиксирует в ADR |
Порог: не меньше 12 из 18 и ни одного нуля по критериям 4–6; senior — от 15. Вопросы, которые стоит ждать: клиент вставил номер карты целиком — где этот текст окажется? Кто проверяет право клиента видеть операцию — модель или код? Провайдер лёг на два часа в день зарплаты — что видит клиент и выдержат ли операторы? При каком объёме или требовании вы перейдёте на self-host? Как через месяц вы узнаете, что качество просело? Юрист запретил отправлять даже маскированные диалоги наружу — что из дизайна переживёт это?
Задание 2: постмортем на сбой своего проекта
Возьмите реальный сбой сквозного проекта (A–D) или вызовите учебный: другая модель эмбеддингов в A, смена модели без смены промпта в B, таймаут MCP-инструмента в C, упавший провайдер в D. Постмортем — по шаблону.
- Таймлайн собран из трейсов и логов, влияние посчитано запросом.
- Есть раздел «почему evals и гейты не поймали» с конкретными пробелами.
- Три действия разных типов с владельцем и сроком; новые кейсы в golden set, CI красный на сломанной версии и зелёный на исправленной.
Задание 3: стандарт команды для своего репозитория
Оформите репозиторий сквозных проектов так, будто завтра в него придут три инженера и coding agent.
- Файл инструкций для агентов (модуль 4), шаблон PR для промптов с чек-листом урока, CODEOWNERS на
prompts/иevals/. - CI не мержит изменения в
prompts/без evals (проект B) и проверяет ADR линтером изexamples/m13-seniorexamples/java/m13-senior. - Политика данных и политика error budget — по странице, с SLO для главной фичи.
Самопроверка
Чем ADR отличается от design doc?
Design doc описывает систему целиком. ADR фиксирует одно решение с альтернативами и последствиями, не переписывается, а заменяется новым ADR. Design doc ссылается на ADR по ключевым решениям.
Зачем в ADR для AI-системы срок пересмотра?
Решение опирается на модель, цены и eval-набор, которые меняются: модели выводят из эксплуатации, цены сдвигаются, объём растёт. Без даты и триггеров решение живёт, пока не сломается или пока провайдер не отключит модель.
Чем жёсткое ограничение отличается от веса в матрице выбора?
Ограничение отсекает варианты, и они не участвуют в сравнении. Вес лишь ранжирует. Записав «данные не покидают периметр» весом 5, можно получить победу API-варианта по сумме — и неверное решение.
SLO 98%, 1.4 млн ответов за окно, доля плохих 3%. Каков бюджет и burn rate?
Бюджет — 28 000 плохих ответов. Burn rate = 3% ÷ 2% = 1.5: бюджет кончится через 28 ÷ 1.5 ≈ 19 дней.
Почему SLI качества по онлайн-судье плохо подходит для быстрых алертов?
За час судья выдаёт десятки оценок: умеренную регрессию не отличить от шума, плюс задержка и ошибка судьи. Нужны быстрые прокси: guardrails, эскалации, кнопка оператора.
Какие события нельзя «списывать» на error budget?
Утечки PII и чужих данных, успешные инъекции с действием, необратимые действия без подтверждения, опасные ответы. Каждое — инцидент с постмортемом, даже при выполненном SLO.
Почему blameless — не «никто не виноват»?
Разбор ищет условия, в которых ошибка стала возможной, а результат — изменения в системе и процессе с владельцами и сроками. Вопрос не «кто выкатил», а «почему система позволила выкатить без проверки».
Почему метрики canary могут улучшиться при опасной регрессии?
Прокси (👎, эскалации, CSAT) отражают удовольствие клиента, а некоторые отказы ему приятны: бот, обещающий компенсации, реже получает негатив. Нужны guardrail-метрики, которые прямо измеряют опасное свойство.
Почему файл инструкций для coding agent — не механизм безопасности?
Это контекст для модели: обычно ему следуют, но не гарантированно, и недоверенный текст может его перебить. Запреты обеспечивают права агента, хуки, sandbox и отсутствие секретов в окружении, а качество — CI и ревью.
Вопросы с собеседований
Тренировка senior-вопросов — модуль 14.
Расскажите о сложном архитектурном решении в LLM-системе, которое вам пришлось защищать.
Структура как в рассказе о проекте (модуль 14), но вокруг решения: контекст и ограничения с цифрами; две-три альтернативы и как их сравнивали (eval-набор, стоимость, латентность); кто был против и какие аргументы сработали — данные, прототип, расчёт TCO, а не статус; что записали в ADR, включая минусы и условия пересмотра; метрики после запуска. Сильный ответ содержит момент, где вы изменили решение под возражением или признали ошибку.
На что смотрит интервьюер: решения по данным и записанные; влияние без полномочий; видите ли минусы своего решения; есть ли «я», а не только «мы».
Вышла новая модель: на 40% дешевле и по бенчмаркам лучше текущей. Ваши действия?
Бенчмарк — повод проверить. Прогон golden set по сегментам и критичным кейсам с текущим и адаптированным промптом, с доверительными интервалами. Стоимость на реальном трафике: дешевле за токен не значит дешевле за задачу, если модель многословнее. Затем shadow, canary с guardrail-метриками, без других изменений в том же релизе. Решение — новый ADR, заменяющий старый. С абстракцией и eval-гейтом это неделя; без них главный вывод — построить их.
На что смотрит интервьюер: не верите ли бенчмаркам на слово; одна переменная за раз; инфраструктура для миграций вместо героизма.
Как определить SLO для чат-бота, если ответы недетерминированы?
Несколько SLI: доступность и латентность по трейсам, качество — доля ответов, не помеченных онлайн-судьёй на выборке, плюс прокси вроде эскалаций. Цели — от baseline и цены ошибки, вместе с продуктом. Burn-rate алерты на нескольких окнах, для качества — медленные: выборка мала. Безопасность вне бюджета. И заранее подписанная политика: что при исчерпании и кто решает.
На что смотрит интервьюер: разница между «200 OK» и правильным ответом; ограничения выборочной оценки; организационная сторона SLO, а не только графики.
Вы дежурный. Поддержка сообщает, что бот обещает клиентам компенсации. Первые 30 минут и следующая неделя?
Подтвердить по трейсам, найти недавнее изменение (промпт, модель, данные) и откатить флагом в реестре; если откатывать нечего — guardrail или kill switch. Объявить инцидент, дать поддержке формулировку для клиентов. Масштаб — ретроспективным поиском обязательств по диалогам с версией из трейсов. За неделю — blameless-постмортем с вопросом «почему evals не поймали» и действиями трёх типов: многоходовые кейсы в golden set, критерий судьи и метрика canary, обязательства только через инструмент с лимитами. Выполнять ли обещания, решает бизнес с юристами.
На что смотрит интервьюер: сначала сдерживание, потом расследование; откат без деплоя; разбор до системной причины, а не «поправим промпт».
Банк выбирает между API-моделью и self-hosted. Как будете принимать решение?
Сначала жёсткие ограничения с юристами и безопасностью: можно ли отправлять данные провайдеру при маскировании и на каких условиях (регион, хранение, обучение на данных). Если нельзя — API вне сравнения. Затем матрица: качество на своём eval-наборе, TCO при текущем объёме и ×10 с людьми и минимумом реплик, латентность, стоимость выхода. Для банка в ЕС — DORA: переход к другому поставщику должен быть реальным сценарием. Часто ответ гибридный: маскирование и API для генерации, своя малая модель там, где данные выходить не могут.
На что смотрит интервьюер: ограничения отделены от предпочтений; TCO с людьми; регуляторику интерпретирует юрист, а вы переводите её в архитектуру.
Продакт хочет фичу на LLM, а вы считаете, что LLM тут не нужна. Как это скажете?
Не спорить с целью, а предложить путь к ней дешевле. Проверить критерии: есть ли правило или справочник, нужна ли точность расчёта и аудит, латентность и объём, есть ли данные для evals. Прийти с цифрами: «правила закрывают 80% случаев за неделю работы, LLM добавит 10–15% за такие-то деньги и риски». Предложить гибрид и условие, при котором вернёмся к LLM. Если всё же решили делать — договориться о метрике успеха и kill-критерии до разработки.
На что смотрит интервьюер: умеете ли сказать «нет» так, чтобы услышали; деньги и риск вместо спора о технологиях.
Как внедрить обязательные evals в CI в команде, которая считает их бюрократией?
Не приказом. Начать с боли: найти недавнюю регрессию и показать, что 20 кейсов её бы поймали. Сделать первый шаг дешёвым: самому собрать smoke-набор для главной фичи и включить в CI как необязательную проверку с комментарием в PR. Когда она поймает пару регрессий, предложить сделать её обязательной и закрепить в стандарте. Дальше — разбор трейсов вместе и правило «каждый баг из прода — новый кейс». Метрика успеха — регрессии, пойманные до прода.
На что смотрит интервьюер: влияние без полномочий; ценность для команды прежде правил; изменение доведено до механизма, который работает без вас.
Источники
- Michael Nygard — Documenting Architecture Decisions (2011, исходный формат ADR)
- Martin Fowler — Architecture Decision Record (хранение в репозитории, статусы, замена вместо правки) и adr.github.io (шаблоны и инструменты)
- Malte Ubl — Design Docs at Google
- Google SRE Book — Service Level Objectives и Embracing Risk (SLI, SLO, error budget)
- Google SRE Workbook — Implementing SLOs, Alerting on SLOs (пороги burn rate) и Example Error Budget Policy
- Google SRE Book — Postmortem Culture и пример постмортема
- PagerDuty — Incident Response (роли и процесс инцидента)
- Chip Huyen — AI Engineering (O'Reilly, 2025) (гл. 4: выбор модели, build vs buy; гл. 10: архитектура и фидбек)
- Hamel Husain — Your AI Product Needs Evals
- Anthropic — Model deprecations (жизненный цикл моделей, сроки уведомления)
- Regulation (EU) 2022/2554 (DORA) (ст. 28: риски ИКТ-поставщиков, стратегии выхода)
- Moffatt v. Air Canada, 2024 BCCRT 149 (ответственность компании за слова чат-бота)
- Claude Code — How Claude remembers your project (CLAUDE.md как контекст, а не принуждение; чтение AGENTS.md) и agents.md (открытый формат инструкций для агентов)
- DORA — Accelerate State of DevOps Report 2024 (влияние AI на поставку)
- Google Engineering Practices — Code Review