This week in Laravel & Inertia: AI failure events, Oct 19, 2026

AI SDK 1.2.2/1.2.3 add failure events and wider classification, Wayfinder 0.1.23 builds without a database, and Laravel 12.70 backports fixes.

For your AI agent

Copies a prompt. Paste it into the AI agent you already have open.

Read https://laravel-vuejs.space/this-week-in-laravel-inertia-2026-10-19.md
AI SDK 1.2.2/1.2.3 add failure events and wider classification, Wayfinder 0.1.23 builds without a database, and Laravel 12.70 backports fixes.
Show the diff. Do not commit.
On this page · 11 sections
  1. The week at a glance
  2. AI SDK 1.2.2: events for failed AI calls
  3. Log every failed AI call with one listener
  4. Classification on more providers (1.2.2 and 1.2.3)
  5. Smaller AI SDK fixes you may have hit
  6. Wayfinder 0.1.23: generate types without a database
  7. Laravel 12.70.0: backports for the 12.x line
  8. Upgrade notes
  9. Recap
  10. FAQ
  11. Next steps
  12. Sources

Your embeddings job failed at 3 a.m., failover quietly saved it, and your dashboard shows nothing. Your CI build has been “generating Wayfinder types” for 30 minutes because it’s trying to reach a database that doesn’t exist there.

This week’s small releases fix both. Here’s everything that shipped in the Laravel and Inertia world since last Monday, in one short read.

TL;DR

  • Laravel AI SDK 1.2.2 fires an event when an embeddings, image, audio, transcription, reranking or classification call fails: EmbeddingsFailed, ClassificationFailed and friends. They fire even when failover rescues the call.
  • AI SDK 1.2.2 and 1.2.3 also bring classification to the openai-compatible driver and image classification to TypeSafe and OpenRouter, and fix a streaming bug with structured sub-agents.
  • Wayfinder 0.1.23 adds wayfinder:generate --skip-database, so builds never open a database connection. The Vite plugin option is skipDatabase: true.
  • Laravel 12.70.0 backports EncodedParameter, two query-builder hardening fixes and a Blade cache fix. Update if you’re still on 12.

What you'll learn

  • How to listen for failed AI calls, including ones failover recovered from
  • How to build Wayfinder types without a database
  • Which Laravel 12.70 backports you want

Who it's for: Laravel + Vue developers using the AI SDK, Wayfinder, or still on Laravel 12

Tested with: laravel/ai 1.2.3 on Laravel 13.37 · PHP 8.4 · Pest 4 (5 tests / 14 assertions: failure events, failover, openai-compatible classification, the listener and the test below) · Wayfinder 0.1.23 --skip-database against an unreachable database · EncodedParameter and whereRowValues on 13.37

Applies to: Laravel 12.70.0 (backports checked against the PRs; the same code was run on its 13.x originals)

The week at a glance#

PackageVersionReleased (UTC)Should you hurry?
laravel/ai1.2.2Oct 14Yes, if you use conversations on OpenAI, Azure or xAI
laravel/ai1.2.3Oct 15Yes, if you’re on Laravel 12 with AI conversations
laravel/wayfinder0.1.23Oct 16Only if your build has no database
laravel/framework (12.x line)12.70.0Oct 13Yes, on Laravel 12 (query hardening)

Heads up: On Laravel 13 already?

Everything in Laravel 12.70.0 was already on 13.x: EncodedParameter since 13.33, the query fixes since 13.35 and the Blade cache fix since 13.36. The newest 13.x release got its own post: Laravel 13.37: strict Rule::numeric(), queue filters and JPEG XL.

AI SDK 1.2.2: events for failed AI calls#

Until now, embeddings, images, audio, transcription, reranking and classification dispatched a “starting” event and a “done” event, but nothing when the call blew up. So a listener that opened a span or a log line could never close it on failure.

Version 1.2.2 adds six failure events: EmbeddingsFailed, ImageFailed, AudioFailed, TranscriptionFailed, RerankingFailed and ClassificationFailed. Each one carries the invocationId (the same one the start event had), the provider, the model, the prompt and the exception.

The file and vector store operations got the same treatment: StoringFileFailed, CreatingStoreFailed, AddingFileToStoreFailed and RemovingFileFromStoreFailed. They carry the store and file IDs instead of a model and prompt.

An embeddings call starts with GeneratingEmbeddings on provider A. If provider A answers, EmbeddingsGenerated fires. If it throws, the new EmbeddingsFailed event fires with the provider, model and exception. If the error allows failover and another provider is configured, provider B tries and fires EmbeddingsGenerated on success. Otherwise the original exception is rethrown unchanged.
One EmbeddingsFailed per failed attempt, even when failover saves the call.

Log every failed AI call with one listener#

With event discovery, a listener in app/Listeners is registered from the type-hint on its handle method. A union type covers several events at once:

app/Listeners/ReportFailedAiCall.phpphp
<?php

namespace App\Listeners;

use Illuminate\Support\Facades\Log;
use Laravel\Ai\Events\ClassificationFailed;
use Laravel\Ai\Events\EmbeddingsFailed;
use Laravel\Ai\Events\ImageFailed;

class ReportFailedAiCall
{
    public function handle(EmbeddingsFailed|ClassificationFailed|ImageFailed $event): void
    {
        Log::warning('AI call failed', [
            'invocation' => $event->invocationId,
            'provider' => $event->provider->name(),
            'model' => $event->model,
            'error' => $event->exception->getMessage(),
        ]);
    }
}

Nothing else changes: the exception is rethrown exactly as before, so your existing try/catch blocks and failover setup keep working.

Gotcha: A failed event doesn't mean the user saw an error

The events fire once per failed attempt, including attempts that failover recovers from. In our test, provider A returned a 503, the backup answered, and we got one EmbeddingsFailed (provider A) plus one EmbeddingsGenerated (the backup). Alert on the rate of failures per provider, not on every single event.

Prove it in your own test suite

tests/Feature/AiFailureEventsTest.phpphp
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Http;
use Laravel\Ai\Embeddings;
use Laravel\Ai\Events\EmbeddingsFailed;

test('a failed embeddings call is reported', function () {
    config()->set('ai.providers.local', [
        'driver' => 'openai-compatible',
        'url' => 'https://ai.test/v1',
        'key' => 'test',
        'models' => ['embeddings' => ['default' => 'local-embed', 'dimensions' => 8]],
    ]);

    Event::fake([EmbeddingsFailed::class]);
    Http::fake(['ai.test/*' => Http::response(['error' => ['message' => 'boom']], 500)]);

    expect(fn () => Embeddings::for(['hello'])->generate(provider: 'local'))
        ->toThrow(Exception::class);

    Event::assertDispatched(EmbeddingsFailed::class, fn ($e) => $e->model === 'local-embed');
});

Classification on more providers (1.2.2 and 1.2.3)#

Classification arrived in AI SDK 1.2, on OpenAI’s decisions endpoint. Two patches widen where it runs.

1.2.2: the openai-compatible driver. Some OpenAI-compatible servers implement the decisions endpoint too. The driver now sends the same /decisions request to your configured url. There is no default classification model, so set one:

config/ai.phpphp
'providers' => [
    'local' => [
        'driver' => 'openai-compatible',
        'url' => env('LOCAL_AI_URL'),
        'key' => env('LOCAL_AI_API_KEY'),
        'models' => [
            'classification' => ['default' => env('LOCAL_AI_CLASSIFIER')],
        ],
    ],
],
app/Http/Controllers/TicketController.phpphp
use Laravel\Ai\Classification;
use Laravel\Ai\Classification\Boolean;

$result = Classification::of($ticket->body)
    ->question('urgent', new Boolean('Does this ticket need an immediate response?'))
    ->classify(provider: 'local');

$result['urgent']->isTrue(threshold: 0.8);

1.2.3: images on TypeSafe and OpenRouter. Image attachments used to throw on both. Now the same Classification::of($text, $images) call works on OpenAI, TypeSafe and OpenRouter. On OpenAI, images attached under a string key are sent with their name, so a question can say “the front photo”.

Tip: Classification is still experimental

The official docs flag the classification API as experimental, so it may change in a minor release. At the time of writing, the docs’ provider list and event list don’t mention these 1.2.2/1.2.3 additions yet. The pull requests in Sources are the reference.

Smaller AI SDK fixes you may have hit#

FixVersionWho it affects
Structured-output sub-agents run unstreamed when the parent streams1.2.2 (#1117)You stream an agent whose tools include a structured-output agent. The tool result was “Agent failed: Streaming structured output is not currently supported.”
A failed turn’s interrupted tool result keeps its call ID1.2.2 (#1133)Conversations on OpenAI, Azure OpenAI or xAI that got stuck returning HTTP 400 after a failed turn. They continue on the next prompt, no data fix needed.
Conversation and ConversationMessage are non-incrementing again on Laravel 121.2.3 (#1137)Laravel 12 apps that create conversations through Eloquent. The string ID was overwritten with the database row ID.

The sub-agent fix is a fallback, not streamed structured output: the sub-agent answers in one piece, while InvokingTool and ToolInvoked still fire.

bash
composer update laravel/ai
composer show laravel/ai | grep versions   # * v1.2.3

Wayfinder 0.1.23: generate types without a database#

Wayfinder types route model bindings by reading your table schema. In a build container with no database, every bound model waits for the connection to time out, twice, because Laravel retries it. The PR author reports generations that should take a minute hanging for more than 30.

The new flag skips the schema and reads the model’s @property docblocks instead:

Build needs a database, then 0.1.23, no database:

Dockerfilebash
RUN php artisan wayfinder:generate
Dockerfilebash
RUN php artisan wayfinder:generate --skip-database

If you generate through Vite, @laravel/vite-plugin-wayfinder 0.1.11 (same day) passes the flag for you:

vite.config.tstypescript
import { wayfinder } from '@laravel/vite-plugin-wayfinder'
import vue from '@vitejs/plugin-vue'
import laravel from 'laravel-vite-plugin'
import { defineConfig } from 'vite'

export default defineConfig({
    plugins: [
        laravel({ input: ['resources/js/app.ts'], refresh: true }),
        wayfinder({ skipDatabase: true }),
        vue(),
    ],
})

The types are only as good as your docblocks. Here’s what we got for a posts/{post} route:

Post model docblockGenerated parameter type
@property int $idnumber
nonestring | number

Gotcha: The flag in the PR text is not the one that shipped

The pull request description (and the Vite plugin PR) say --model-docblocks / modelDocblocks. The code that shipped in 0.1.23 uses --skip-database, and the Vite plugin option is skipDatabase. Use those, as the READMEs do.

Without the flag, nothing changes: Wayfinder still queries the schema and only falls back to docblocks if that query throws. More on typed routes in Wayfinder + Precognition and the QUERY method.

Laravel 12.70.0: backports for the 12.x line#

Three fixes from 13.x, for apps that haven’t upgraded yet.

EncodedParameter for pre-encoded route values. Since 12.69, %, ? and # in route parameters are always encoded. That’s a security fix, but it double-encoded values you had already encoded. Wrap those in EncodedParameter and they’re used as-is:

app/Http/Controllers/FileController.phpphp
use Illuminate\Routing\EncodedParameter;

route('files.show', ['path' => 'report%202026.pdf']);
// .../files/report%25202026.pdf  (encoded again)

route('files.show', ['path' => new EncodedParameter('report%202026.pdf')]);
// .../files/report%202026.pdf

Query-builder hardening (#61838). whereRowValues() now rejects an invalid operator with an InvalidArgumentException, and cursor pagination now quotes an order column that contains parentheses instead of passing it through as raw SQL (aliased select expressions still work). Both matter if any part of a column name or operator ever comes from a request.

A Blade cache file per echo format (#61822). Compiled views now get a different file name when the echo format isn’t the default, for example after Blade::withoutDoubleEncoding() or while Markdown mail renders. Views compiled with one format are no longer reused with another.

bash
composer update laravel/framework
php artisan view:clear
php artisan --version   # Laravel Framework 12.70.0

Upgrade notes#

  • No breaking changes this week. Run composer update (and npm update for the Vite plugin), then your tests.
  • AI SDK: the new events are opt-in, nothing fires that you don’t listen to. If you alert on them, remember they include attempts that failover recovered.
  • Wayfinder: add @property docblocks to bound models before turning on --skip-database, or those parameters fall back to string | number.
  • Laravel 12: php artisan view:clear after the update is optional but tidy, since compiled view file names can change.

Recap#

  • AI SDK 1.2.2 gives you *Failed events for every model operation, failovers included.
  • AI SDK 1.2.2 and 1.2.3 run classification on openai-compatible, TypeSafe and OpenRouter, and fix three bugs in streaming and conversations.
  • Wayfinder 0.1.23: --skip-database (or skipDatabase: true in Vite) for database-free builds.
  • Laravel 12.70.0: EncodedParameter plus query and Blade fixes. On 13.x you already have them.

FAQ#

Why did I get an EmbeddingsFailed event when the request succeeded?

The events fire once per failed attempt. If failover moved the call to another provider, you get a failed event for the first provider and a generated event for the second.

Do the new AI failure events replace try/catch?

No. The exception is still rethrown unchanged. The events are for logging, metrics and tracing; your error handling stays where it is.

Is it --model-docblocks or --skip-database?

It is --skip-database in Wayfinder 0.1.23 and skipDatabase in the Vite plugin 0.1.11. The PR descriptions mention the earlier name.

I am on Laravel 13. Do I need anything from 12.70.0?

No. The same changes shipped in Laravel 13.33, 13.35 and 13.36.

Is classification safe to use in production?

It works, but the docs mark it experimental, so pin your laravel/ai version and read the release notes before you update.

Next steps#

Sources#

Found this useful?

Ask questions, share what you built.

Join thousands of Laravel and Vue developers in our Facebook group, or get new posts in your Telegram feed. Sharing this article helps too.

Keep reading

Newsletter

Get the best of Laravel & Vue, once a month.

One short email with new guides, release notes worth reading and hand-picked community links. No spam, unsubscribe any time.