laravel/ai の v1.3.0 の実装をもとに、AI SDKの基本操作より一歩踏み込んで実行順と停止条件を整理します。
ミドルウェアが実行される単位
エージェントがモデルの応答を受け取り、ツールを実行して再びモデルを呼ぶ場合は、次のモデル呼び出しが別のステップになります。各ステップの直前に、HasMiddleware::middleware() で返したミドルウェアを宣言順に通します。
ミドルウェアの handle(PendingStep $step, Closure $next) に渡される PendingStep には、実行済みステップの配列 steps、その累積使用量 usage、現在のゼロ始まりのステップ番号 number が含まれます。初回の usage はまだ今回のモデル呼び出しの使用量を含みません。
$next($step) の結果に付けた then() は、モデルが応答した後、ツール呼び出しが実行される前に動きます。ツール実行後にだけ呼ばれるフックではありません。また、この時点の usage は以前に完了したステップの合計であり、進行中のリクエストのトークン数を予測しません。
ステップごとの処理を登録する
ステップ番号と、それまでに使った入力トークン数だけをログに残す例です。プロンプト本文やモデルの出力をログへ無条件に保存しないようにします。HasMiddleware を実装し、middleware() からインスタンスを返します。
middleware() の並び順では、Stop を先に置きます。上限を超えて次のステップを開始しない場合、後続の LogAgentSteps はそのステップで実行されません。順序を逆にすると、ログの「開始」だけが記録され、「モデルが応答した」は記録されないことがあります。$next($step) の結果を handle() から返し、then() のコールバックではレスポンスを返す必要はありません。
Stopは次のモデル呼び出しを止める
Stop::when(...) は条件がtrueになったとき、Stop::unless(...) は条件がfalseになったときに、そのステップの前で処理を終了します。上の例は完了済みステップの累積入力トークンが50,000以上になったら次の呼び出しを行いません。1回のモデル呼び出しが閾値を超える前に強制停止するハードリミットではありません。
独自条件で停止したいミドルウェアは
Laravel\Ai\Gateway\StepResult::stop() を返すこともできます。handle() からは $next($step) の結果、StepResponse、または StepResult::stop() を返します。無関係な型を返すと例外になります。
実行順を検証する
モデル応答とツール実行を混同しないよう、少なくとも次のケースを確認します。- 1ステップだけの生成では、開始ログとモデル応答ログが1回ずつ出る。
- ツールに続く追加ステップでは、番号が0から増え、後続ステップの
usageに前のステップの使用量が含まれる。 - 最初から停止条件が成立する場合、モデル呼び出しと
LogAgentStepsのログが発生しない。 - 途中で条件に達する場合、それまでのステップは残り、次のステップのモデル呼び出しは発生しない。
then() に副作用が必要な場合も、モデル応答を得る前に失敗・停止した経路では呼ばれない前提で設計してください。