Запустите флоу в фоне и дождитесь его результата

PHP

Отправьте запуск, не дожидаясь его, дождитесь выполнения, которое ответило queued или waiting, и прочитайте его статус и трассировку.

6 мин чтения

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

Когда ответ не окончательный

Три статуса значат, что выполнение ещё продолжится.

СтатусЧто он значит
queuedЗапуск отправлен в асинхронном режиме. Он принят, и ни один воркер ещё не начал флоу
runningФлоу выполняется прямо сейчас
waitingФлоу остановился на узле, который ждёт. Он продолжится сам, когда случится то, чего он ждёт

На запрос отвечают 202, когда его выполнение не закончилось: queued в асинхронном режиме, waiting на узле, который ждёт, и running, когда повторный запрос застаёт выполнение, которое ещё идёт. Ответ несёт идентификатор выполнения и retry_after_seconds — сколько Slen советует подождать, прежде чем читать статус.

Выполнение со статусом waiting говорит, чего оно ждёт, в waiting_on. node_key называет узел, resume_at — момент, когда ожидание закончится, если он есть, а type — причина.

typeЧего ждёт выполнение
queue_nodeУзел Очередь держит его до resume_at
approvalРешение принимает человек на странице Согласования. resume_at нет
rate_limit_queueНи одно подключение пула провайдеров не может сейчас принять вызов, а AI-модель или его пул настроены ждать, а не отказывать. Флоу пробует вызов снова в resume_at
provider_backgroundПровайдер выполняет вызов как фоновую задачу, и Slen спрашивает у него ответ. Когда провайдера не удаётся спросить даже после многих попыток, выполнение заканчивается со статусом uncertain. Вызов, который провайдер продолжает выполнять, ждут столько же, сколько любое ожидание, — по умолчанию 72 часа в сумме, — а затем выполнение заканчивается со статусом failed и кодом parked_expired

У выполнения со статусом queued поле waiting_on равно null.

  • Запрос — По умолчанию → Очередь
  • Очередь — По умолчанию → AI-модель
  • AI-модель — Успех → Вернуть ответ

На запуск этого флоу отвечают waiting, как только выполнение доходит до Очередь.

Отправить запуск, не дожидаясь его

operations.executeAsync() принимает те же аргументы, что и operations.execute(). Флоу выполняется в фоне, а вызов сразу возвращается со статусом queued.

PHP
use Slen\Sdk\Modules\Operations\Options\ExecuteOptions;

$queued = $slen->operations()->executeAsync(
    'summarize_report',
    ['messages' => [['role' => 'user', 'content' => 'Summarize the third-quarter report.']]],
    new ExecuteOptions(idempotencyKey: 'report-2026-q3:summary'),
);

echo $queued->status->value, PHP_EOL;

Ключ идемпотентности работает так же, как в синхронном запуске. Запуск, повторённый под тем же ключом с тем же телом, ничего не начинает. Ответом на него будет выполнение, которое начал первый запрос, — в том состоянии, в каком оно находится в этот момент: queued, пока его не взял воркер, и его результат, когда оно закончилось.

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

Дождаться результата

executions.wait() читает статус выполнения снова и снова, пока выполнение не закончится.

PHP
use Slen\Sdk\Exceptions\SlenException;
use Slen\Sdk\Modules\Executions\Options\WaitOptions;

// In a queue job or a script, not in an HTTP handler: the wait sleeps in the process.
$finished = $slen->executions()->wait($queued->executionId, new WaitOptions(
    timeoutSeconds: 60,
    onPollError: static function (SlenException $error, int $failuresInARow): void {
        echo sprintf('Status read failed (%d in a row): %s', $failuresInARow, $error->getMessage()), PHP_EOL;
    },
));
  • Пауза между двумя чтениями — та, о которой просит Slen: неокончательный статус несёт retry_after_seconds, и следующее чтение происходит через столько секунд. Статус без него получает паузу, которая начинается с одной секунды и удваивается до 30. Каждая пауза сдвигается до четверти в любую сторону, чтобы выполнения, отправленные вместе, не читались в одно мгновение.

  • Таймаут — timeoutSeconds опций, WaitOptions в обоих SDK, — сколько ждёт вызов: 300 секунд, если вы не передали другой, и не меньше 1. Пауза сокращается до того, что осталось от таймаута, и когда таймаут истёк, статус читается ещё один раз.

  • Таймаут — не ошибка. Вызов возвращает последний прочитанный статус, и этот статус всё ещё не окончательный. Вызовите executions.wait() ещё раз, чтобы ждать дальше.

Чтению отводится то, что осталось от таймаута, — не меньше пяти секунд и не больше таймаута конфигурации, — поэтому вызов возвращается не позже чем через пять секунд после своего таймаута. Guzzle и Symfony HttpClient принимают таймаут чтения. Любой другой HTTP-клиент читает со своими таймаутами, и с ним вызов может вернуться на это время позже.

Ожидание читает статус тем же API-ключом, а для чтения нужно разрешение read. У ключа с gateway:write оно есть.

Прочитать, чем всё закончилось

PHP
echo match (true) {
    $finished->isCompleted() => $finished->output['content'],
    $finished->isPending() => 'Not over yet: ' . $finished->status->value,
    default => sprintf('Ended as %s: %s', $finished->status->value, $finished->errorCode),
}, PHP_EOL;

Ответ ожидания заканчивается одним из трёх способов.

  • completed — в output лежит то, что вернул флоу.

  • queued, waiting или running — таймаут наступил раньше. Подождите ещё.

  • rejected, failed или uncertain — выполнение не завершилось. error_code говорит почему, а error_message говорит то же фразой для человека. У выполнения со статусом failed, закрытого ошибкой, для которой у API нет кода, нет ни того ни другого: оба равны null.

Ожидание возвращает ExecutionStatusView: поля ExecutionResult без error и replayed.

Когда чтение статуса не удаётся

Чтение, которое не удалось по временной причине, не заканчивает ожидание. Клиент сам не отправляет это чтение ещё раз: ожидание читает статус заново после паузы, которая начинается с одной секунды и удваивается до 30.

  • Временная причина — запрос без ответа, 429 и любой 5xx. Чтение статуса учитывается в лимите запросов ключа, поэтому 429 — одна из них.

  • Любая другая ошибка — 404, 401 — сразу заканчивает ожидание.

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

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

Прочитать статус самому

executions.get() читает статус один раз. Это запрос GET /v1/executions/{id}/status — тот, который повторяет ожидание.

executions.trace() читает всю историю выполнения: каждый узел, через который оно прошло, и каждое событие. Это запрос GET /v1/executions/{id} — его поля описывает статья Выполнения.

В консоли те же выполнения — на вкладке Выполнения своего флоу, со статусами В очереди, Выполняется и Ожидает.

В сервисе используйте вебхук

Ожидание длится столько, сколько идёт выполнение, и спит между своими чтениями. Это подходит задаче вашей очереди или скрипту, но не обработчику HTTP-запроса: тот, кто его вызвал, ждёт всё это время, а воркер PHP в это время не обслуживает другие запросы. Сервис отправляет запуск через operations.executeAsync() и сразу отвечает тому, кто его вызвал. Результат он читает, когда приходит вебхук execution.finished, — Вебхуки, — или в задаче своей очереди, которая его ждёт.

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