Comments 4
Вы предлагаете считать стоимость по задачам, а не только по токенам, и уже учитываете повторы и переключения между моделями. Но пользовательский сценарий может закончиться ручной правкой или откатом после успешного ответа шлюза. Планируете связывать запись о вызове с идентификатором бизнес-операции и её финальной приёмкой? Тогда маршруты можно сравнивать по стоимости принятого результата, а не только выполненной AI-задачи.
Да, это хороший пойнт. В статье я остановился на стоимости отдельной AI-задачи, но для реального SaaS этого действительно мало. Иначе дешёвый маршрут, который чаще требует повторной генерации или ручной правки, формально будет выглядеть лучше дорогого, который сразу даёт принимаемый результат.
Я бы связывал каждый вызов как минимум с operation_id/workflow_id, а попытки внутри одной операции — с attempt_id. После завершения бизнес-сценария уже фиксировать outcome: принят результат сразу, потребовался retry/fallback, ручная правка, повторная генерация или полный отказ.
Тогда можно считать не только cost per AI task, а что-то ближе к cost per accepted result и сравнивать маршруты уже по конечному результату бизнеса. Для недетерминированных LLM это, наверное, даже более полезная метрика, чем цена одного вызова.
Отдельно интересный вопрос — что считать «приёмкой» в разных сценариях. Для structured extraction это можно проверить автоматически, а для сгенерированного текста часть сигнала придётся брать из действий пользователя или следующего шага workflow.
С fail-closed согласен. Но у fallback есть проблема и помимо прав на данные: он меняет смысл ответа. Вместо основной модели ответила дешёвая или локальная, а вызывающий код получает тот же AIResponse и понятия не имеет. У меня модель иногда не отдаёт нормальный блок с оценкой риска, и тогда оценку считает простая эвристика. Если этого не видно в самом результате, гейт в CI уронит билд по эвристике, а все будут думать, что так решила модель. Дашборд с retry_count тут не спасёт, результат читает не человек, а следующий шаг пайплайна. Поэтому провайдер, модель и флаг "это эвристика" у меня лежат прямо в ответе, в любом формате вывода. Ответ, который не говорит, кто его сделал, врёт.
Да, согласен — это важное уточнение. Сам по себе retry_count действительно не показывает, что в результате изменился исполнитель и вместе с ним могли измениться свойства ответа.
Поэтому fallback я бы тоже не делал полностью прозрачным для системы. Результат должен нести provenance: какой provider/model реально ответил, какой route был выбран, был ли fallback и по какой причине. А если запасная модель имеет другой профиль качества, это уже должно быть частью контракта route, а не просто техническим переключением endpoint.
Например, если основная модель используется для оценки риска, я бы не разрешал автоматически подменять её дешёвой моделью только потому, что JSON Schema у обеих совпадает. Либо fallback должен быть эквивалентным по требованиям задачи, либо результат нужно явно пометить как degraded/heuristic и downstream-логика должна учитывать это.
С флагом прямо в domain-response тоже согласен как с идеей: скрывать происхождение результата точно не стоит. Я бы, вероятно, держал это отдельной метой ответа (model, route, fallback, quality_profile), чтобы бизнес-данные не смешивать с инфраструктурными, но смысл тот же — вызывающий код должен понимать, кем и в каком режиме был получен результат.
И да, это хороший аргумент против fallback вида «любая доступная модель лучше ошибки». Для части задач ошибка действительно честнее, чем незаметное ухудшение семантики.
Почему LLMProvider недостаточно: проектируем AI Gateway для SaaS на Go