---
title: "Laravel AI: Нові події життєвого циклу для трейсингу запусків агентів"
url: https://laravelukraine.com/blog/laravel-ai-novi-podiyi-zittjevogo-ciklu-dlia-treisingu-zapuskiv-agentiv
date: 2026-08-25
source: https://laravel-news.com/laravel-ai-agent-run-events?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+laravelnews
---

# Laravel AI: Нові події життєвого циклу для трейсингу запусків агентів

Запуск AI-агента рідко обмежується одним викликом API. Модель повертає виклики інструментів, SDK виконує їх, надсилає результати назад і повторює процес, доки модель не завершить роботу. До виходу Laravel AI v0.11.0 цикл генерації не мав подій для окремих кроків - повідомлялися лише `PromptingAgent` на початку та `AgentPrompted` в кінці всього запуску. Запуск, який виконав п'ять звернень до провайдера, виглядав ідентично до запуску з одним зверненням, а запуск, що викинув виняток на середині шляху, взагалі нічого не повідомляв, оскільки `AgentPrompted` ніколи не досягався.

Пакет із семи pull request від [@pushpak1300](https://github.com/pushpak1300), об'єднаних як [#870](https://github.com/laravel/ai/pull/870) через [#876](https://github.com/laravel/ai/pull/876), змінює цю ситуацію. Тепер кожен запуск має один ID, і кожне звернення до провайдера та виклик інструмента повідомляє про початок і кінець із точним хронометражем.

## Один ID для всього запуску

`streamPrompt()` вже генерував ID виклику на рівні запуску, але `prompt()` цього не робив. Синхронні middleware бачили `$prompt->invocationId === null`, тоді як streaming middleware бачили справжнє значення. Оскільки провайдер генерував власний ID для кожної спроби, запуск, що переключався між трьома провайдерами, створював три непов'язані ID для того, що з точки зору викликача було одним запуском.

Тепер `prompt()` генерує ID наперед, а провайдер використовує те, що надав викликач ([#871](https://github.com/laravel/ai/pull/871)). Кожна подія нижче несе його як перший аргумент конструктора, тож ви можете групувати події запуску за одним рядком:

```php
public function __construct(
    public string $invocationId,
    public int $stepNumber,
    // ...
) {}
```

`AgentFailedOver` також отримала ID: вона тепер приймає обов'язковий `string $invocationId` як перший аргумент конструктора. Слухачі не постраждали, але будь-який код, що конструює подію вручну, потребує оновлення.

## Події кроків

`StartingStep`, `StepCompleted` та `StepFailed` спрацьовують навколо кожного звернення до провайдера як на синхронних, так і на streaming шляхах ([#873](https://github.com/laravel/ai/pull/873)).

`StartingStep` несе повідомлення, надіслані для кроку, включно з результатами інструментів попередніх кроків, та опції, вирішені для цього кроку, які можуть відрізнятися від власних опцій агента після задоволення примусового вибору інструмента. Також несе `stepNumber` та `isFinalStep`:

```php
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`, тож код, що читає хронометраж запитів, може обробляти це однаково.

```php
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`, але лише як масовий payload на завершальній події, без прикріпленого хронометражу. Тепер вартість і тривалість кожного звернення повідомляються по ходу виконання запуску.

`StepFailed` охоплює випадки, коли крок завершується без створення відповіді, несучи `Throwable` та час, витрачений до викидання винятку.

## Події інструментів

`InvokingTool` та `ToolInvoked` вже існували. Проте вони повідомлялися через пару callback-функцій, які кожен провайдер реєстрував у циклі генерації, а поточний ID виклику інструмента зберігався в одній змінній властивості провайдера, що призводило до збоїв при вкладенні. Оскільки менеджер повертає один екземпляр провайдера на ім'я, агент, викликаний як інструмент, перезаписував ID до того, як спрацьовувала зовнішня `ToolInvoked`, через що зовнішня подія повідомляла ID внутрішнього виклику.

Тепер `RunContext` несе ідентичність запуску та відправляє ці події безпосередньо, а ID виклику інструмента генерується всередині `executeTool()`, тож кожен виклик отримує свій унікальний ID ([#872](https://github.com/laravel/ai/pull/872)). Інструменти можуть самі читати його через request:

```php
public function handle(Request $request): string
{
    $request->toolInvocationId(); // string|null
}
```

Нова подія - це `ToolFailed` ([#874](https://github.com/laravel/ai/pull/874)). Раніше `executeTool()` не мала `catch`, тож якщо обробник інструмента викидав виняток, він поширювався прямо з циклу генерації: виклик ніколи не повідомляв про завершення, а запуск переривався без запису, який інструмент його спричинив. `ToolFailed` повідомляє про цей збій, несучи той самий `toolInvocationId`, що й `InvokingTool`, яка відкрила виклик. Виняток все ще перекидається, тож поведінка в іншому не змінилася. Охороняється лише виклик обробника, тож слухач, що викидає виняток, не буде помилково повідомлений як збій інструмента.

`ToolInvoked` також отримала обов'язковий `float $time` - час, витрачений в обробнику. Якщо ви конструюєте ці події вручну, оновіть виклик конструктора, щоб включити тривалість.

Обидві події несуть екземпляр `Tool`, а не його ім'я, тож з'єднуйте їх за `toolInvocationId` та отримуйте мітку з об'єкта:

```php
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(),
    ]);
});
```

## Події збою запуску

Кожна подія закриття проміжку в пакеті раніше існувала лише на успішному шляху. Якщо gateway викидав виняток, `AgentPrompted` ніколи не відправлялася, тож слухач, що відстежує запуск, не мав способу дізнатися про його збій.

`AgentFailed` повідомляє про це один раз на запуск, і лише після завершення запуску ([#876](https://github.com/laravel/ai/pull/876)). При налаштованому failover перший провайдер, що викинув `FailoverableException`, не є терміновим, тож подія спрацьовує після вичерпання ланцюга. Вона несе ID виклику, промпт та виняток.

`AgentFailedOver` також більше не спрацьовує для останнього провайдера в ланцюгу. Ця спроба не має на що відступати, тож вона повідомляється як збій запуску натомість. Раніше подія failover відправлялася безумовно в кожному catch, включно з останнім.

## Зв'язування під-агента з його батьком

Агент, викликаний як інструмент, виглядав як окремий запуск, без нічого, що корелює його з батьківським. Виклик інструмента тепер відстежує свої ID запуску та виклику інструмента протягом свого виконання. Будь-який агент, запрошений під час виконання цього інструмента, отримує їх як `parentInvocationId` та `parentToolInvocationId` у своєму промпті ([#875](https://github.com/laravel/ai/pull/875)).

Це охоплює написаний вручну інструмент, що запрошує агента, не тільки `AgentTool`. ID зберігаються в статичній властивості, а не в контексті, що тримає делегуючий виклик поза payloads черг.

Зв'язок не перетинає межу черги. Промпт, відправлений з `promptOnQueue()` зсередини інструмента, починає свій власний запуск без батька.

## Слухачі черг та історія повідомлень

`StartingStep` несе всю історію повідомлень запуску. Слухач, що реалізує `ShouldQueue`, серіалізує все це, разом з будь-якими вкладеннями. Це передбачуваний компроміс, оскільки слухачу, що відкриває проміжок, потрібен запит, з яким був надісланий крок. Якщо вам потрібні лише хронометраж та кількість токенів, слухайте натомість `StepCompleted`, яка несе власну відповідь кроку, а не повну історію.

## Подальше читання

Події вийшли у [v0.11.0](https://laravel-news.com/laravel-ai-sdk-0-11), разом із hosted пошуком інструментів та ширшим failover провайдерів. Для читання власної HTTP-відповіді провайдера під час запуску, включно з заголовками rate limit та ID запитів, див. [властивість raw HTTP response](https://laravel-news.com/laravel-ai-raw-http-response), додану у v0.10.3.

Для ознайомлення з пакетом див. [оголошення AI SDK](https://laravel-news.com/laravel-announces-official-ai-sdk-for-building-ai-powered-apps) та наше висвітлення [затвердження інструментів human-in-the-loop](https://laravel-news.com/laravel-ai-sdk-adds-human-in-the-loop-tool-approval). Вихідний код знаходиться на [laravel/ai](https://github.com/laravel/ai) на GitHub.
