Run a flow and read the answer

PHP

Run a flow with its input, the customer it is for and the context of the call, and read every field of the execution that comes back.

6 min read

A run names an operation code, gives the flow its input and, when it needs them, the customer it is for and the context of the call. Slen walks the flow bound to the code and answers with the execution. This page shows what goes into the call and what comes back; Quickstart is the shortest way to a first answer.

Send the request

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

$execution = $slen->operations()->execute(
    'generate_email',
    ['messages' => [['role' => 'user', 'content' => 'Write a short follow-up email to Ada.']]],
    new ExecuteOptions(customer: 'user_8421', context: ['plan' => 'pro'], idempotencyKey: 'order-57:email'),
);
  • The first argument is the operation code. It is bound to a flow in the settings of the flow — AI flows says how.

  • The second argument is the input of the flow, which its nodes read under input. It is an object, and an empty one is allowed. A flow with an AI Model node needs a non-empty input.messages when it reaches that node. Without them the execution ends with the code invalid_flow_input.

  • The customer names your user the run is for — below.

  • The idempotency key names the piece of work. Idempotency keys says what a repeat under it is answered with.

The customer, the credit type, the context and the key are named arguments of ExecuteOptions, the last argument of the call, beside timeout and retries — Retries and timeouts.

The customer and the context

  • The customer is your user the run is for: the external user id you created the customer with. The SDK sends it as context.user_id. The model calls of the flow are then made for that customer: a budget set for the customer and a policy with a condition on the customer apply, and the customer's credits pay when the project has a credit price for the operation — Customers and credits.

  • The credit type names the account of the customer that pays. The SDK sends it as context.credit_type; without it the account is default.

  • The context carries the rest of what the flow should know and does not get as input: the plan of your customer, the region of the request. It is a flat object of up to 64 values. A value is a string, a number, a boolean or null, and the whole context is at most 16 384 bytes as JSON.

  • The nodes of the flow read a value of the context by its path, context.<name>. A Condition node can branch on context.plan.

  • Slen trims the white space at the ends of a string value of the context, and a string that is left empty becomes null. The input is not changed: it reaches the flow as you sent it.

A context whose user_id names another customer than the one you pass, an empty customer and an empty credit type are refused before the request is sent, with the code invalid_request. A customer who is blocked is refused with customer_blocked, and nothing runs.

Read the answer

PHP
echo match (true) {
    $execution->isCompleted() => $execution->output['content'],
    $execution->isRejected() => "Refused: {$execution->errorCode} — {$execution->errorMessage}",
    $execution->isPending() => "Not over yet: {$execution->status->value}. Read it again in {$execution->retryAfterSeconds} s.",
    default => "Did not complete: {$execution->status->value}, {$execution->errorCode}",
}, PHP_EOL;

The call returns the execution. Its status says how the request ended or that it has not ended yet — What the answer says lists every status.

FieldWhat it carries
execution_idThe id of the execution. A status read and the trace take it
statusHow the request ended, or that it has not ended yet
operationThe operation code the request named
outputWhat the flow returned, when the status is completed. null otherwise
error_codeWhy an execution that did not complete ended
error_messageThe same reason in a sentence for a person. Show it or log it; branch on error_code
errorerror_code and error_message as one object, when the execution has a message
costestimated — what the model calls were expected to cost, added up. actual — what they cost
cachehit is true when output came from the cache, and cached_at says when that answer was stored
replayedtrue when the answer belongs to an execution an earlier request under the same key started
waiting_onWhat a waiting execution waits for
retry_after_secondsHow long to wait before the status is read again. Only while the execution is not over
trace_urlThe path of the trace of the execution in the API

ExecutionDTO has these fields as properties in camel case: executionId, status — an ExecutionStatus — operationCode, output, errorCode, errorMessage, error, cost, cache, replayed, waitingOn, retryAfterSeconds and traceUrl. isCompleted(), isRejected(), isPending() and isFinished() read the status — Result objects.

  • cost.actual comes after the answer. The model calls of the execution are settled in the background once it ends, so a synchronous answer carries null, and a later read — a status read, a repeat of the key — carries the amount.

  • A cached answer was paid for by the call that produced it. cost.actual stays null unless the flow called a model besides.

  • After a refusal cost.estimated is not spend. A rejected execution carries the estimate of the call that was refused — the spend the refusal prevented — or null when the refusal named no estimate.

A refusal and a failure

  • A flow that refuses answers like any other: a Reject Request node ends the execution rejected, with the code and the message its author gave it. The call returns the execution and raises nothing. isRejected() tells such an answer from a completed one.

  • A refusal of the platform on the way — a budget, a policy, the credits of the customer, a blocked customer — is raised as an error when the flow does not route it. The execution is closed with the same code. A model node with a connection on its Deny port routes the refusal into the flow instead, and the flow decides what the caller hears.

  • An error that is not a refusal — the input lacks messages, the provider failed and the flow does not route it — is raised as well. The execution is closed failed, or uncertain when the answer of the provider was lost.

Read later — by a status read, by a wait, by a repeat of the key — such an execution comes back as an answer, with its status and error_code, and not as an error.

When spend is refused shows how to catch a refusal of the platform in both its forms, and Handling errors — any other error of the SDK.

When the answer is not final

  • A flow that stops at a node that waits is answered with 202 and the status waiting. The call returns without an error, and the result is read later — Async runs and waiting.

  • A flow that runs longer than the timeout of the client is not run twice. The SDK sends the request again under the same key, and the answer is the execution as it is: running while the walk goes on, its outcome once it is over.

Where to go next