# 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.

- URL: https://laravel-vuejs.space/this-week-in-laravel-inertia-2026-10-19
- Author: Anass Ez-zouaine (https://laravel-vuejs.space/author/anass-ez-zouaine)
- Published: 2026-10-19
- Topics: Laravel, VueJs, Inertia, Releases

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

| Package | Version | Released (UTC) | Should you hurry? |
| --- | --- | --- | --- |
| `laravel/ai` | 1.2.2 | Oct 14 | Yes, if you use conversations on OpenAI, Azure or xAI |
| `laravel/ai` | 1.2.3 | Oct 15 | Yes, if you’re on Laravel 12 with AI conversations |
| `laravel/wayfinder` | 0.1.23 | Oct 16 | Only if your build has no database |
| `laravel/framework` (12.x line) | 12.70.0 | Oct 13 | Yes, 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](https://laravel-vuejs.space/laravel-13-37-numeric-strict-queue-jxl).

## 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.](https://laravel-vuejs.space/diagrams/laravel-ai-failure-events-failover-light.svg)
_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.php`
```php
<?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.php`
> ```php
> 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](https://laravel-vuejs.space/laravel-ai-sdk-1-2-skills-approval-cohere), 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.php`
```php
'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.php`
```php
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

| Fix | Version | Who it affects |
| --- | --- | --- |
| Structured-output sub-agents run unstreamed when the parent streams | 1.2.2 (#1117) | You stream an agent whose tools include a [structured-output agent](https://laravel-vuejs.space/laravel-ai-structured-output-vue). The tool result was “Agent failed: Streaming structured output is not currently supported.” |
| A failed turn’s interrupted tool result keeps its call ID | 1.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 12 | 1.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:**

`Dockerfile`
```bash
RUN php artisan wayfinder:generate
```

**0.1.23, no database:**

`Dockerfile`
```bash
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.ts`
```ts
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 docblock | Generated parameter type |
| --- | --- |
| `@property int $id` | `number` |
| none | `string | 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](https://laravel-vuejs.space/wayfinder-precognition-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.php`
```php
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

**Q: 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.

**Q: 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.

**Q: 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.

**Q: 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.

**Q: 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

- [Laravel AI SDK 1.2: Skills, tool approval and Cohere](https://laravel-vuejs.space/laravel-ai-sdk-1-2-skills-approval-cohere), where classification first landed.
- [Laravel AI SDK 1.2.1: Stop middleware for agent runs](https://laravel-vuejs.space/laravel-ai-sdk-1-2-1-stop-middleware)
- [Wayfinder + Precognition catch up with Laravel’s QUERY routes](https://laravel-vuejs.space/wayfinder-precognition-query-method)
- [Laravel AI SDK docs: events](https://laravel.com/docs/13.x/ai-sdk#events)

## Sources

- [Laravel AI v1.2.2 release](https://github.com/laravel/ai/releases/tag/v1.2.2): [PR #1090 (failure events)](https://github.com/laravel/ai/pull/1090), [PR #1128 (openai-compatible classification)](https://github.com/laravel/ai/pull/1128), [PR #1117 (structured sub-agents)](https://github.com/laravel/ai/pull/1117), [PR #1133 (call id)](https://github.com/laravel/ai/pull/1133)
- [Laravel AI v1.2.3 release](https://github.com/laravel/ai/releases/tag/v1.2.3): [PR #1137 (non-incrementing models)](https://github.com/laravel/ai/pull/1137), [PR #1134 (image classification)](https://github.com/laravel/ai/pull/1134)
- [Laravel AI SDK docs: classification](https://laravel.com/docs/13.x/ai-sdk#classification), [OpenAI-compatible providers](https://laravel.com/docs/13.x/ai-sdk#openai-compatible-providers) and [events](https://laravel.com/docs/13.x/ai-sdk#events)
- [Laravel events docs: union-type listeners and discovery](https://laravel.com/docs/13.x/events#event-discovery)
- [Wayfinder v0.1.23 release](https://github.com/laravel/wayfinder/releases/tag/v0.1.23), [PR #331](https://github.com/laravel/wayfinder/pull/331), [README](https://github.com/laravel/wayfinder/blob/v0.1.23/README.md)
- [Vite plugin Wayfinder v0.1.11](https://github.com/laravel/vite-plugin-wayfinder/releases/tag/v0.1.11), [PR #34](https://github.com/laravel/vite-plugin-wayfinder/pull/34)
- [Laravel v12.70.0 release](https://github.com/laravel/framework/releases/tag/v12.70.0): [PR #61871 (EncodedParameter)](https://github.com/laravel/framework/pull/61871), [PR #61838 (query fixes)](https://github.com/laravel/framework/pull/61838), [PR #61822 (Blade cache)](https://github.com/laravel/framework/pull/61822)
- [Laravel Blade docs: HTML entity encoding](https://laravel.com/docs/12.x/blade#html-entity-encoding) and [cursor pagination](https://laravel.com/docs/12.x/pagination#cursor-pagination)
