# Как резервный маршрут ИИ-конвейера защищает пропускную способность

> Спроектируйте резервный маршрут для ИИ-конвейера, который выдерживает ограничения частоты запросов и сбои, контролирует стоимость принятого результата и помогает выполнять обязательства перед клиентами.

ИИ-конвейер без просчитанного резервного маршрута остается единой зависимостью с более удобным интерфейсом. Пока трафик небольшой, он может выглядеть надежным. Затем ограничения частоты запросов, исчерпанные квоты или сбой у провайдера превращают каждый запрос в очереди в более дорогую версию той же ошибки.

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

## Резервному маршруту нужен договор о выдаче результата

Резервный маршрут - это полноценный способ завершить запрос при изменившихся ограничениях. В нем есть правило для входных данных, провайдер или действие без модели, требования к результату, ограничение по времени и лимит затрат. Фраза «попробовать другую модель» выражает намерение, но не задает рабочую политику.

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

Многие команды смешивают три события, для которых нужны разные действия:

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

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

Запишите маршрут как явный договор. Например, резервный маршрут для извлечения данных из счета может принимать только PDF с разборчивым текстом, использовать детерминированное OCR и правила для полей, возвращать `needs_review: true` при отсутствии налоговых значений и стоить дешевле обычного маршрута через модель. Он не делает вид, что способен разобраться с каждым неоднозначным сканом. Именно это ограничение делает его безопасным.

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

## Опубликованная цена токенов вводит в заблуждение

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

Для каждого класса задач используйте такой расчет:

```text
cost per accepted result =
(provider and tool spend + retry spend + review cost + repair cost)
/ accepted results
```

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

Представим, что маршрут A стоит 0,18 доллара за попытку и без проверки проходит 92 из 100 запросов. Маршрут B стоит 0,07 доллара за попытку, но проходит 55 из 100, а отклоненную работу нужно отправить на новую попытку стоимостью 0,18 доллара или на проверку человеком. Маршрут B может быть полезен для простых задач, но для того же договора он не становится автоматически более дешевым резервным вариантом. Здесь привлекательная страница с ценами превращается в плохое инженерное решение.

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

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

## Одна учетная запись провайдера не заменяет план мощностей

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

Распространенный сценарий начинается с приложения, отправляющего каждый запрос в одно развертывание модели. После появления ответов HTTP 429 команда добавляет экспоненциальную задержку. Воркеры продолжают повторять запросы, задачи остаются активными, очередь заполняется. Когда окно квоты наконец сбрасывается, все ожидающие воркеры просыпаются и отправляют новую волну запросов. Восстановительный трафик создает новое ограничение частоты, а клиенты видят долгие задержки без понятного статуса.

RFC 6585 определяет HTTP 429 как Too Many Requests и разрешает серверу передавать `Retry-After`. RFC 9110 описывает `Retry-After` для ответов, включая 503 Service Unavailable. Уважать этот заголовок - базовое поведение по протоколу, но оно не создает дополнительную мощность. Заголовок лишь сообщает, когда неисправный маршрут считает повторную попытку уместной.

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

Независимость требует проверки. Отдельные API-учетные данные при одной общей квоте организации не дают существенной защиты. Отдельные региональные развертывания могут помочь при региональном инциденте, но не при глобальном правиле квоты. Другой провайдер защищает от сбоя первого провайдера, но общие вышестоящие инструменты, базы данных или сетевой выход все равно могут остановить оба маршрута. Прежде чем заявлять о резервировании, нарисуйте цепочку зависимостей.

## Именно в очереди управляемая деградация становится реальной

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

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

Практическая запись маршрутизации может быть такой:

```json
{
  "job_type": "invoice_extract",
  "quality_tier": "complete_or_review",
  "deadline_seconds": 300,
  "max_cost_cents": 35,
  "attempts_by_route": {
    "primary": 1,
    "secondary": 0,
    "ocr_rules": 0
  }
}
```

Перед вызовом любого провайдера воркер читает эту запись. Маршрутизатор отклоняет маршрут, если его ожидаемая стоимость превышает оставшийся бюджет или если он не может обеспечить запрошенный уровень качества. Выбранный маршрут и причину выбора он записывает обратно в задачу. Благодаря этой истории инженер может объяснить, почему результат занял больше времени или потребовал проверки, не собирая догадки по разрозненным логам.

Учитывайте возраст очереди при принятии решения. Фоновую задачу, которая ждала пять минут, может быть дешевле обработать у вторичного провайдера, чем держать в очереди еще час. Интерактивной задаче, которая уже пропустила целевое время ответа, может понадобиться короткий ответ более низкого уровня с явным обещанием продолжить работу, а не идеальный ответ, пришедший слишком поздно.

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

## Порядок резервирования должен быть политикой, а не промптом

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

В этой политике порядок виден сразу. Названия служат примерами, их нужно связать с вашими маршрутами.

```yaml
invoice_extract:
  primary:
    route: structured_model
    max_attempts: 2
    retry_on: [timeout, 503]
  fallbacks:
    - route: secondary_structured_model
      when: [429, quota_exhausted, provider_unavailable]
      max_cost_cents: 28
      requires: [json_schema]
    - route: ocr_and_rules
      when: [primary_quality_failure, secondary_quality_failure]
      max_cost_cents: 12
      output: needs_review
  terminal:
    route: review_queue
    when: [deadline_exceeded, missing_required_field]
```

Эта политика предотвращает незаметную, но серьезную ошибку. Без условия `requires: [json_schema]` маршрутизатор может выбрать дешевую текстовую модель после сбоя провайдера структурированных результатов. В логах текст выглядит правдоподобно. Затем следующий код начинает угадывать границы полей, записывает сумму не в ту запись или гораздо позже завершается ошибкой, которая кажется не связанной с резервным маршрутом. С точки зрения провайдера маршрут сработал, а с точки зрения бизнеса - нет.

Сохраняйте конечное состояние. Каждый запрос должен завершаться как выданный, выданный с проверкой, отложенный или завершившийся ошибкой с понятной причиной. «Повторная попытка продолжается» - не результат. Конечное состояние защищает очередь от постоянных жильцов и позволяет службе поддержки честно ответить клиенту.

У политики должна быть и версия. После изменения порядка из-за резкого роста затрат добавляйте версию политики к каждому результату. Иначе данные за неделю будет трудно сравнивать, а хорошее изменение может выглядеть плохим, потому что оно унаследовало трафик от более раннего сбоя.

## Автоматические выключатели защищают окно восстановления

Автоматический выключатель останавливает маршрут, когда накопленные данные показывают, что новые вызовы, скорее всего, снова завершатся ошибкой. Он не заменяет проверки состояния или ограничение частоты запросов. Это защита от превращения проблемы провайдера в собственную проблему очереди и расходов.

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

При включении выключателя на определенный интервал прекратите отправлять на маршрут новые задачи. Подходящую работу направьте на следующий маршрут, работу, которую нельзя упростить, отложите, а для проверки восстановления оставьте небольшой лимит пробных запросов. Один пробный запрос через контролируемый интервал покажет, восстановился ли маршрут. Если после первого успешного пробного запроса выпустить всю очередь, перегрузка, из-за которой выключатель сработал, повторится.

Отслеживайте состояние провайдера отдельно от состояния задачи. Модель может вернуть HTTP 200, а доля прохождения схемы упасть после изменения промпта или версии модели. Провайдер доступен, но маршрут нездоров. Полезный выключатель может реагировать на долю принятых результатов для конкретного типа задач, пока другие типы задач продолжают использовать того же провайдера.

Вот почему единый глобальный флаг «ИИ недоступен» приносит вред. Он отправляет недорогие классификации с низким риском на ручную обработку, даже если они по-прежнему завершаются правильно. Ограничивайте выключатель той зависимостью и тем договором, которые действительно отказали.

## Малые модели должны работать в четко заданных границах

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

Хорошие кандидаты - классификация намерений с фиксированным набором меток, определение языка, простое извлечение со строгой проверкой, удаление дубликатов и маршрутизация. Давайте малому маршруту меньше вариантов. Просите его возвращать данные по схеме, выбирать из утвержденных значений или объяснять неопределенность через отметку о проверке. Не ждите, что он компенсирует нехватку контекста уверенным тоном.

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

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

Храните уровень качества в объекте ответа. Система, которой нужен финальный ответ, должна отклонять результат с меткой `draft` или `needs_review`. Одно это поле блокирует частую ошибку: временный путь деградации становится постоянным, потому что другая команда считает все успешные ответы HTTP одинаково надежными.

## Для вызовов инструментов нужен отдельный план деградации

Резервная модель не поможет, если она может завершить работу только через неисправный инструмент. Многие ИИ-процессы зависят от поиска, извлечения данных, выполнения кода, баз данных, электронной почты или внутренних API. Модель может быть доступна, а рабочий процесс фактически заблокирован.

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

Делайте сбой инструмента видимым в результате маршрута. Сохраняйте причину вроде `customer_api_unavailable`, а не расплывчатое `model_failed`. Это не позволит менять модели во время сбоя внутренней базы данных. Кроме того, специалисты по инцидентам будут точно знать, сколько клиентов получили ответ в режиме деградации.

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

## Проверьте пути отказа до того, как их проверят клиенты

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

Проведите небольшое испытание на типичных задачах. Верните искусственный 429 с `Retry-After`, заставьте основной маршрут завершиться по тайм-ауту, вызовите нарушение схемы во вторичном маршруте и сделайте обязательный инструмент недоступным. Проверьте итоговое состояние задачи, выбранный резервный маршрут, потраченный бюджет, ответ, видимый клиенту, и возраст очереди. Если инженер не может увидеть всю эту историю в одной трассировке, инцидент будет идти дольше, чем нужно.

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

Повторяйте эти испытания после каждого существенного изменения маршрутизации, версий моделей или договоров инструментов. Резервный маршрут, который был безопасен для старой JSON-схемы, может стать опасным после того, как команда следующего этапа добавит обязательное поле. Надежность снижается, когда основной путь меняется, а путь деградации остается прежним.

## Лимиты расходов должны определять обещания клиенту

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

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

Не прячьте результаты в режиме деградации в общих метриках успеха. Показывайте долю запросов, использовавших резервный маршрут, выбранный маршрут, долю принятых результатов по маршрутам, задержку очереди и стоимость принятого результата. Разбивайте данные по типу задачи. Здоровый общий показатель может скрывать сломанный процесс подготовки договоров, если в общем объеме преобладает простая классификация.

Основателю стоит задать на еженедельном инженерном обсуждении один прямой вопрос: если предпочтительный провайдер исчезнет на сутки, какие обязательства перед клиентами продолжат выполняться, а какие изменятся? Если ответ сводится к расплывчатому обещанию повторять запросы, у конвейера нет просчитанного резервного маршрута.

Для внешней проверки Team & AI Audit поможет описать эти зависимости, стоимость маршрутов и условия отказа до того, как рост нагрузки превратит их в экстренный проект.
