Запуск агента рідко обмежується одним викликом API. Зазвичай це цикл: модель повертає виклик інструменту, SDK його виконує, надсилає результат назад і так до завершення. До версії Laravel AI v0.11.0 цей цикл не мав проміжних подій: система фіксувала лише PromptingAgent на початку та AgentPrompted у самому кінці. Через це запуск із п’ятьма ітераціями виглядав так само, як і з одною, а помилка всередині циклу взагалі не залишала слідів, бо подія AgentPrompted не встигала спрацювати.
Завдяки серії з семи pull requests від @pushpak1300 (від #870 до #876) ситуація змінилася. Тепер кожен запуск має єдиний ID, а кожен запит до провайдера чи виклик інструменту супроводжується подіями початку та завершення з точним таймінгом.
# Єдиний ID для всього циклу
Раніше streamPrompt() генерував ID виклику на рівні запуску, а prompt() — ні. Це призводило до розбіжностей: синхронний middleware бачив $prompt->invocationId === null, тоді як стрімінговий отримував реальне значення. Крім того, при перемиканні між провайдерами (failover) кожен новий запит отримував власний ID, що заважало відстежувати єдиний процес.
Тепер prompt() генерує ID заздалегідь, а провайдер використовує надане значення (#871). Кожна подія нижче отримує цей ID як перший аргумент конструктора, що дозволяє групувати логи в один рядок:
public function __construct(
public string $invocationId,
public int $stepNumber,
// ...
) {}
Подія AgentFailedOver також отримала ID: тепер обов'язковий аргумент string $invocationId є першим у конструкторі. Це не вплине на стандартні слухачі (listeners), але код, де ця подія створюється вручну, потребує оновлення.
# Події кроків (Step Events)
Події StartingStep, StepCompleted та StepFailed тепер спрацьовують при кожній ітерації (round-trip) як у синхронному, так і в стрімінговому режимах (#873).
StartingStep містить повідомлення, надіслані на поточному кроці (включаючи результати попередніх викликів інструментів), та параметри, що можуть відрізнятися від налаштувань агента (наприклад, при примусовому виборі інструменту). Також доступні stepNumber та isFinalStep:
use Laravel\Ai\Events\StartingStep;
Event::listen(StartingStep::class, function (StartingStep $event) {
// $event->stepNumber, $event->model, $event->isFinalStep
// $event->messages, $event->options
});
StepCompleted передає повний об'єкт StepResponse та час виконання float $time у мілісекундах. Формат часу збігається з QueryExecuted::$time, тож таймінги ШІ можна обробляти аналогічно до запитів у БД.
use Laravel\Ai\Events\StepCompleted;
Event::listen(StepCompleted::class, function (StepCompleted $event) {
Log::info('AI step completed', [
'invocation' => $event->invocationId,
'step' => $event->stepNumber,
'ms' => $event->time,
'prompt_tokens' => $event->response->usage->promptTokens,
'finish' => $event->response->finishReason->value,
]);
});
Раніше дані про використання кроків були доступні лише у фінальній події в загальному масиві $response->steps без прив'язки до часу. Тепер вартість та тривалість кожної ітерації фіксуються в реальному часі.
StepFailed спрацьовує, коли крок завершується без відповіді, передаючи Throwable та час, витрачений до виникнення помилки.
# Події інструментів (Tool Events)
Події InvokingTool та ToolInvoked існували й раніше, але мали ваду: ID інструменту зберігався у спільній властивості провайдера, яка перезаписувалася при вкладених викликах. Це призводило до того, що внутрішній агент-інструмент "затирав" ID зовнішнього виклику.
Тепер RunContext керує ідентифікацією запуску та надсилає події безпосередньо, а ID виклику інструменту генерується всередині executeTool() (#872). Інструменти можуть отримати цей ID через об'єкт запиту:
public function handle(Request $request): string
{
$request->toolInvocationId(); // string|null
}
Нова подія ToolFailed (#874) виправляє стару проблему: раніше помилка в обробнику інструменту переривала весь цикл без фіксації завершення виклику. Тепер ToolFailed реєструє збій, використовуючи той самий toolInvocationId, що й при старті. Виключення прокидається далі, тож загальна поведінка залишається незмінною.
ToolInvoked тепер також вимагає float $time — час роботи обробника інструменту. Обидві події тепер передають екземпляр Tool замість простої назви, що дозволяє отримувати метадані безпосередньо з об'єкта:
use Laravel\Ai\Events\ToolFailed;
Event::listen(ToolFailed::class, function (ToolFailed $event) {
Log::error('AI tool failed', [
'invocation' => $event->invocationId,
'tool_invocation' => $event->toolInvocationId,
'tool' => class_basename($event->tool),
'arguments' => $event->arguments,
'ms' => $event->time,
'exception' => $event->exception->getMessage(),
]);
});
# Події помилок запуску
Раніше всі події завершення в пакеті спрацьовували лише в разі успіху. Якщо шлюз видавав помилку, подія AgentPrompted не надсилалася, і відстежити крах запуску було неможливо.
AgentFailed тепер сповіщає про фінальний збій один раз за весь цикл (#876). Якщо налаштовано failover, перша помилка FailoverableException не вважається фатальною — подія спрацює лише тоді, коли весь ланцюжок провайдерів буде вичерпано. Вона передає invocationId, промпт та саме виключення.
Крім того, AgentFailedOver більше не спрацьовує для останнього провайдера в черзі. Його невдача тепер реєструється як загальна помилка запуску.
# Зв’язок дочірнього агента з батьківським
Агент, викликаний як інструмент, раніше виглядав як окремий процес. Тепер виклик інструменту відстежує свої ID запуску та інстансу протягом усієї роботи. Будь-який агент, активований під час виконання цього інструменту, отримує parentInvocationId та parentToolInvocationId (#875).
Це стосується не лише AgentTool, а й будь-яких власноруч написаних інструментів, що викликають агентів. Ідентифікатори зберігаються у статичній властивості, що дозволяє уникати зайвих даних у корисних навантаженнях черг.
Важливо: цей зв'язок не перетинає межі черги. Промпт, надісланий через promptOnQueue() зсередини інструменту, почне власний незалежний цикл без батьківського ID.
# Черги та історія повідомлень
StartingStep передає повну історію повідомлень запуску. Слухач, що реалізує ShouldQueue, буде серіалізувати всі ці дані разом із вкладеннями. Це свідоме рішення, оскільки слухачу для аналізу кроку потрібен контекст запиту. Якщо вам потрібні лише таймінги та кількість токенів, використовуйте StepCompleted — вона містить лише відповідь конкретного кроку, а не всю історію.
# Що ще почитати
Ці події з'явилися у версії v0.11.0 разом із хостингом пошуку інструментів та розширеним failover. Про те, як отримати сиру HTTP-відповідь провайдера (заголовки лімітів, ID запитів), читайте в описі raw HTTP response property з версії v0.10.3.
Більше про пакет дізнавайтеся в офіційному анонсі AI SDK та матеріалі про підтвердження дій інструментів людиною. Сирцевий код доступний на GitHub у репозиторії laravel/ai.