v1.3.0 implementation of laravel/ai, this page goes a step beyond the AI SDK basics and explains execution order and stop conditions.
The unit that middleware runs on
When an agent receives a model response, runs tools, and calls the model again, the next model call is a separate step. Right before each step, the middleware returned fromHasMiddleware::middleware() runs in the order it was declared.
The PendingStep passed to the middleware’s handle(PendingStep $step, Closure $next) contains steps, an array of completed steps, usage, their cumulative usage, and number, the current zero-based step number. On the first step, usage does not yet include the usage of the current model call.
The then() attached to the result of $next($step) runs after the model responds and before any tool calls run. It is not a hook that is called only after tools run. Also, usage at this point is the total of previously completed steps, and it does not predict the token count of the in-progress request.
Registering per-step processing
This example logs only the step number and the number of input tokens used so far. Avoid unconditionally saving prompt text or model output to logs.HasMiddleware on the agent and return instances from middleware().
Stop first in the middleware() list. When the limit is exceeded and the next step does not start, the subsequent LogAgentSteps does not run for that step. If you reverse the order, only the “starting” log may be recorded, without the “model responded” log. Return the result of $next($step) from handle(). The then() callback does not need to return the response.
Stop prevents the next model call
Stop::when(...) ends processing before the step when the condition is true, and Stop::unless(...) does so when the condition is false. The example above skips the next call once the cumulative input tokens of completed steps reach 50,000 or more. It is not a hard limit that forcibly stops a single model call before it crosses the threshold.
Middleware that wants to stop on a custom condition can also return
Laravel\Ai\Gateway\StepResult::stop(). From handle(), return the result of $next($step), a StepResponse, or StepResult::stop(). Returning an unrelated type throws an exception.
Verifying execution order
To avoid confusing model responses with tool execution, check at least the following cases.- For a single-step generation, the starting log and the model response log each appear once.
- For additional steps following tools, the number increases from 0, and the
usageof later steps includes the usage of earlier steps. - When the stop condition is met from the start, no model call and no
LogAgentStepslogs occur. - When the condition is reached partway through, the earlier steps remain and the model call for the next step does not occur.
then() needs side effects, design them on the assumption that it is not called on paths that fail or stop before a model response is received.