Обработайте отклонённую трату

PHP

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

3 мин чтения

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

Семейство отклонённой траты

На такой отказ отвечают статусом 402. Оба SDK выбрасывают такие отказы как одно семейство ошибок: SpendRefusedException в PHP и SpendRefusedError в Node.js, и у каждого кода есть свой тип, который расширяет тип семейства. 402 с кодом, для которого у SDK нет типа, выбрасывается как сам тип семейства.

КодHTTP-статусТип ошибки в SDKЧто это значитЧто делать
budget_exceeded402BudgetExceededException PHPBudgetExceededError Node.jsВызов вывел бы бюджет за его лимит. То, что бюджет уже держит и потратил в текущем окне, вместе с холдом, который просит этот вызов, больше лимита.Дождитесь следующего окна бюджета — его resets_at говорит когда; бюджет типа total заново не начинается. Или поднимите лимит бюджета.
operation_limit_exceeded402OperationLimitExceededException PHPOperationLimitExceededError Node.jsБюджет на операцию ограничивает, сколько может зарезервировать один вызов операции, а холд, который просит этот вызов, выше этого ограничения.Просите меньше — меньше выходных токенов, модель дешевле — или поднимите ограничение. Тот же запрос будет отклонён снова, пока одно из них не изменится.
concurrency_limit_exceeded402ConcurrencyLimitException PHPConcurrencyLimitError Node.jsБюджет типа concurrency — в консоли «Одновременные резервы» — разрешает держать открытыми заданное число холдов одновременно, и все они заняты вызовами, которые выполняются.Отправьте запрос снова, когда закончится один из выполняющихся вызовов, или поднимите лимит бюджета. Запрос к флоу отправляйте снова с новым ключом, потому что его отклонённое выполнение сохраняет ключ, который у него был. Это не лимит запросов ключа.
insufficient_credits402InsufficientCreditsException PHPInsufficientCreditsError Node.jsСчёт клиента не может покрыть цену вызова в кредитах, или у клиента нет счёта этого типа кредитов. Списание или корректировка отклоняются так же, когда оставили бы баланс ниже того, что держит счёт.Пополните кредиты клиента. Счёт, в настройках которого разрешён отрицательный баланс, не отклоняет.
overrun_stopped402OverrunStoppedException PHPOverrunStoppedError Node.jsОценка вызова модели во флоу, сделанная заново после того, как взят холд, и до вызова провайдера, превысила холд больше, чем позволяет допуск превышения узла, а политика перерасхода узла модели — остановить. Холд возвращён, провайдер не вызывался. Тот же код завершает выполнение, когда человек отклонил согласование, которое запросил перерасход, или никто не ответил вовремя. У нового узла модели политики перерасхода нет, а политика, которая у узла есть, практически не срабатывает, потому что обе оценки считаются по одному и тому же запросу с разницей в мгновение.Отправьте запрос снова с новым ключом — вызов будет оценён заново и возьмёт собственный холд. Чтобы таких отказов не было, выключите политику перерасхода узла модели и опубликуйте новую версию.
overrun_approval_required402OverrunApprovalRequiredException PHPOverrunApprovalRequiredError Node.jsОценка вызова модели во флоу, сделанная заново после того, как взят холд, и до вызова провайдера, превысила холд больше, чем позволяет допуск превышения узла, а политика перерасхода узла модели просит человека одобрить разницу. Выполнение ждёт этого решения, поэтому на запрос отвечает ждущее выполнение, а не этот код. У нового узла модели политики перерасхода нет, а политика, которая у узла есть, практически не срабатывает, потому что обе оценки считаются по одному и тому же запросу с разницей в мгновение.Примите решение по согласованию в консоли. Согласование, которое отклонили или по которому никто не решил вовремя, завершает выполнение с кодом overrun_stopped.

Политика, которая отказывает, в семейство не входит. На policy_denied и model_not_allowed отвечают 403 и выбрасывают их как PolicyDeniedException или PolicyDeniedError — как проверяется политика, говорит статья Политики. Запрос для заблокированного клиента отклоняется так же, с customer_blocked, как CustomerBlockedException или CustomerBlockedError, которые расширяют тип отказа политики.

Два кода семейства, overrun_stopped и overrun_approval_required, приходят от политики перерасхода узла модели во флоу, а такая политика практически не срабатывает — почему, говорят их строки в таблице выше.

Поймать его

PHP
use Slen\Sdk\Exceptions\OperationUncertainException;
use Slen\Sdk\Exceptions\SlenException;
use Slen\Sdk\Exceptions\SpendRefusedException;
use Slen\Sdk\Modules\Operations\Options\ExecuteOptions;

$input = ['messages' => [['role' => 'user', 'content' => 'Write a short follow-up email.']]];

try {
    $execution = $slen->operations()->execute('generate_email', $input, new ExecuteOptions(idempotencyKey: 'order-42:email'));

    echo $execution->status->value, PHP_EOL;
} catch (SpendRefusedException $error) {
    // The spend was refused, and the code says what refused it.
    echo 'refused: ', $error->errorCode, PHP_EOL;
} catch (OperationUncertainException $error) {
    // A provider was called and its answer was lost: the call may have been paid for.
    echo 'uncertain, request ', $error->requestId, PHP_EOL;
} catch (SlenException $error) {
    echo $error->statusCode, ' ', $error->errorCode, ': ', $error->getMessage(), PHP_EOL;
}

Код говорит, какой лимит отклонил вызов, а таблица выше — что делать с каждым.

  • Отклонённый вызов ничего не стоил: его провайдер не вызывался, и за него не остаётся зарезервированных денег. Во флоу вызовы моделей в узлах до него оплачены.

  • Тот же запрос отклоняется снова, пока что-то не изменится: бюджет начнёт следующее окно, лимит поднимут, клиента пополнят. Исключение — concurrency_limit_exceeded: он проходит, как только идущий вызов закончится.

  • Не отправляйте отклонённый запрос снова в цикле.

Вторая форма: выполнение, которое закончилось отказом

На запуск отвечают ошибкой один раз. Выполнение, которое он открыл, закрывается со статусом rejected и хранит код. Его cost.estimated — оценка отклонённого вызова, то есть трата, которую отказ предотвратил, а не потраченные деньги, — или null, когда отказ не назвал оценки, как для клиента без кредитов. Тот, кто читает его позже, получает выполнение, а не ошибку: повтор запроса под его ключом, ожидание, чтение статуса, запуск, отправленный в асинхронном режиме. Поэтому запуск, который вы отправляете снова после отказа, идёт под новым ключом.

PHP
$again = $slen->operations()->execute('generate_email', $input, new ExecuteOptions(idempotencyKey: 'order-42:email'));

if ($again->isRejected()) {
    echo 'rejected: ', $again->errorCode, PHP_EOL;
}

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

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

Где лимиты

Что читать дальше

Собрано из openapi b070e1f, sdk contract 6a2e0e0, php 4089db3, node 0fa87be