Run a flow and read the answer
PHPRun 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
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-emptyinput.messageswhen it reaches that node. Without them the execution ends with the codeinvalid_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 isdefault.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 oncontext.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
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.
| Field | What it carries |
|---|---|
execution_id | The id of the execution. A status read and the trace take it |
status | How the request ended, or that it has not ended yet |
operation | The operation code the request named |
output | What the flow returned, when the status is completed. null otherwise |
error_code | Why an execution that did not complete ended |
error_message | The same reason in a sentence for a person. Show it or log it; branch on error_code |
error | error_code and error_message as one object, when the execution has a message |
cost | estimated — what the model calls were expected to cost, added up. actual — what they cost |
cache | hit is true when output came from the cache, and cached_at says when that answer was stored |
replayed | true when the answer belongs to an execution an earlier request under the same key started |
waiting_on | What a waiting execution waits for |
retry_after_seconds | How long to wait before the status is read again. Only while the execution is not over |
trace_url | The 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.actualcomes after the answer. The model calls of the execution are settled in the background once it ends, so a synchronous answer carriesnull, 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.actualstaysnullunless the flow called a model besides.After a refusal
cost.estimatedis not spend. Arejectedexecution carries the estimate of the call that was refused — the spend the refusal prevented — ornullwhen 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 closedfailed, oruncertainwhen 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
202and the statuswaiting. 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:
runningwhile the walk goes on, its outcome once it is over.
Where to go next
Idempotency keys — what a repeat of the request is answered with.
Async runs and waiting — run the flow in the background and wait for its result.
Customers and credits — create the customer a run names, and top up their credits.
Operations — the same request over HTTP.