Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Події
Інше
Про нас
Новини 25 серпня 2026

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

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

Пакет із семи pull request від @pushpak1300, об'єднаних як #870 через #876, змінює цю ситуацію. Тепер кожен запуск має один ID, і кожне звернення до провайдера та виклик інструмента повідомляє про початок і кінець із точним хронометражем.

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

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

Тепер prompt() генерує ID наперед, а провайдер використовує те, що надав викликач (#871). Кожна подія нижче несе його як перший аргумент конструктора, тож ви можете групувати події запуску за одним рядком:

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

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

Події кроків

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

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

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

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

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

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

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

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

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

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). При налаштованому failover перший провайдер, що викинув FailoverableException, не є терміновим, тож подія спрацьовує після вичерпання ланцюга. Вона несе ID виклику, промпт та виняток.

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

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

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

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

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

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

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

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

Події вийшли у v0.11.0, разом із hosted пошуком інструментів та ширшим failover провайдерів. Для читання власної HTTP-відповіді провайдера під час запуску, включно з заголовками rate limit та ID запитів, див. властивість raw HTTP response, додану у v0.10.3.

Для ознайомлення з пакетом див. оголошення AI SDK та наше висвітлення затвердження інструментів human-in-the-loop. Вихідний код знаходиться на laravel/ai на GitHub.

1

Читати в документації

Коментарі

Увійдіть, щоб залишити коментар

Будьте першим, хто залишить коментар!

Читайте також

whereBinary()
Новини 28 серпня 2026

whereBinary(): регістрозалежні запити MySQL у Laravel

Laravel 13.27 додає новий метод whereBinary() для точного побайтового порівняння рядків у MySQL. Він вирішує проблему, коли стандартне collation utf8mb4_unicode_ci ігнорує регістр, акценти та пробіли при порівнянні токенів, slug-ів та інших критичних даних.

ToolSearch
Новини 27 серпня 2026

Laravel AI: завантаження інструментів на вимогу з ToolSearch

Laravel AI v0.11.0 додає механізм відкладеного завантаження інструментів агента через ToolSearch. Замість надсилання всіх тридцяти інструментів одразу, провайдер отримує лише пошуковий запис і завантажує повні визначення тільки тоді, коли модель вирішує їх використати.

2

Пакети за темою

Laravel Google Calendar

spatie/laravel-google-calendar

Керування подіями на Google Calendar.

1,402 3.8.5 13 3

Laravel Kafka

mateusjunges/laravel-kafka

Драйвер Kafka для Laravel, що забезпечує інтеграцію з Apache Kafka для обробки потокових повідомлень та подій в додатках Laravel.

727 v2.11.4 13 3