> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel AI SDK

> A unified, expressive API for interacting with AI providers — build agents with tools and structured output, generate images, synthesise and transcribe audio, create vector embeddings, and more, all with a consistent Laravel-friendly interface.

## Introduction

[Laravel AI SDK](https://github.com/laravel/ai) provides a unified, expressive API for interacting with AI providers such as OpenAI, Anthropic, Gemini, and more. With the AI SDK you can build intelligent agents with tools and structured output, generate images, synthesize and transcribe audio, create vector embeddings, and much more — all using a consistent, Laravel-friendly interface.

<Info>
  Laravel AI SDK is an official package (`laravel/ai`) available in Laravel 13.
</Info>

| Capability | Supported providers                                                                                            |
| ---------- | -------------------------------------------------------------------------------------------------------------- |
| Text       | OpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter |
| Images     | OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter                                                                |
| TTS        | OpenAI, ElevenLabs, Gemini                                                                                     |
| STT        | OpenAI, ElevenLabs, Mistral, Gemini                                                                            |
| Embeddings | OpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter                            |
| Reranking  | Cohere, Jina, VoyageAI                                                                                         |
| Files      | OpenAI, Anthropic, Gemini, Azure                                                                               |

## Installation

<Steps>
  <Step title="Install the package">
    ```shell theme={null}
    composer require laravel/ai
    ```
  </Step>

  <Step title="Publish config and migrations">
    ```shell theme={null}
    php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
    ```
  </Step>

  <Step title="Run migrations">
    ```shell theme={null}
    php artisan migrate
    ```

    This creates the `agent_conversations` and `agent_conversation_messages` tables used to persist conversation history.
  </Step>
</Steps>

## Configuration

Set your API keys in `.env`. Only add keys for providers you plan to use:

```ini theme={null}
ANTHROPIC_API_KEY=
AZURE_OPENAI_API_KEY=
COHERE_API_KEY=
DEEPSEEK_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
GROQ_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_KEY=
OPENAI_API_KEY=
OPENROUTER_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=
```

The default models for each capability (text, images, audio, transcription, embeddings) may also be configured in `config/ai.php`. For example, to default to GPT-4o for text generation and `dall-e-3` for image generation:

```php theme={null}
// config/ai.php
'models' => [
    'text'          => env('AI_TEXT_MODEL', 'gpt-4o'),
    'image'         => env('AI_IMAGE_MODEL', 'dall-e-3'),
    'audio'         => env('AI_AUDIO_MODEL', 'tts-1'),
    'transcription' => env('AI_TRANSCRIPTION_MODEL', 'whisper-1'),
    'embeddings'    => env('AI_EMBEDDINGS_MODEL', 'text-embedding-3-small'),
],
```

### Custom base URLs

If you route requests through a proxy, configure a custom URL per provider in `config/ai.php`:

```php theme={null}
'providers' => [
    'openai' => [
        'driver' => 'openai',
        'key'    => env('OPENAI_API_KEY'),
        'url'    => env('OPENAI_URL'),
    ],
    'anthropic' => [
        'driver' => 'anthropic',
        'key'    => env('ANTHROPIC_API_KEY'),
        'url'    => env('ANTHROPIC_BASE_URL'),
    ],
],
```

Custom base URLs are supported for OpenAI, Anthropic, Gemini, Groq, Cohere, DeepSeek, xAI, and OpenRouter.

### OpenAI-compatible providers

For APIs compatible with OpenAI, such as LM Studio, vLLM, Together, Fireworks, or a local gateway, configure a provider with the `openai-compatible` driver. The `url` is required. If you specify a `key`, it is sent as a bearer token.

```php theme={null}
'providers' => [
    'local' => [
        'driver' => 'openai-compatible',
        'url' => env('LOCAL_AI_URL'),
        'key' => env('LOCAL_AI_API_KEY'),
    ],
],
```

Use the configured provider name like any other provider:

```php theme={null}
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');
```

Configure a default text model to avoid specifying it for every request:

```php theme={null}
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'text' => [
            'default' => env('LOCAL_AI_MODEL'),
        ],
    ],
],
```

OpenAI-compatible providers support text generation, streaming, tools, structured output, and image attachments. If the endpoint requires additional request body fields, use [provider options](#provider-options).

### Lab enum

Use the `Lab` enum to reference providers without hardcoding strings:

```php theme={null}
use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::Gemini;
```

## Agents

Agents are the fundamental building block of the Laravel AI SDK. Each agent is a PHP class that encapsulates a system prompt, conversation context, tools, and an optional structured output schema.

### Creating an agent

```shell theme={null}
php artisan make:agent SalesCoach
```

Add `--structured` for agents that return structured JSON output:

```shell theme={null}
php artisan make:agent SalesCoach --structured
```

A full agent implementing all available interfaces:

```php theme={null}
<?php

namespace App\Ai\Agents;

use App\Ai\Tools\RetrievePreviousTranscripts;
use App\Models\History;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;

class SalesCoach implements Agent, Conversational, HasTools, HasStructuredOutput
{
    use Promptable;

    public function __construct(public User $user) {}

    public function instructions(): Stringable|string
    {
        return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
    }

    public function messages(): iterable
    {
        return History::where('user_id', $this->user->id)
            ->latest()
            ->limit(50)
            ->get()
            ->reverse()
            ->map(function ($message) {
                return new Message($message->role, $message->content);
            })
            ->all();
    }

    public function tools(): iterable
    {
        return [new RetrievePreviousTranscripts];
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'feedback' => $schema->string()->required(),
            'score'    => $schema->integer()->min(1)->max(10)->required(),
        ];
    }
}
```

### Prompting

Instantiate the agent and call `prompt`:

```php theme={null}
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

return (string) $response;
```

Use `make` to resolve the agent from the service container:

```php theme={null}
$agent = SalesCoach::make(user: $user);
```

Override the provider, model, or timeout per request:

```php theme={null}
use Laravel\Ai\Enums\Lab;

$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model:    'claude-haiku-4-5-20251001',
    timeout:  120,
);
```

### Conversation context

Implement `Conversational` and define a `messages()` method to supply previous messages to the agent.

#### Manual history

```php theme={null}
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Messages\Message;

class SalesCoach implements Agent, Conversational
{
    use Promptable;

    public function messages(): iterable
    {
        return History::where('user_id', $this->user->id)
            ->latest()->limit(50)->get()->reverse()
            ->map(fn ($message) => new Message($message->role, $message->content))
            ->all();
    }
}
```

#### Automatic DB storage with `RemembersConversations`

The `RemembersConversations` trait stores and retrieves history automatically using the published migrations:

```php theme={null}
use Laravel\Ai\Concerns\RemembersConversations;

class SalesCoach implements Agent, Conversational
{
    use Promptable, RemembersConversations;

    public function instructions(): string
    {
        return 'You are a sales coach...';
    }
}

// Start a new conversation
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');
$conversationId = $response->conversationId;

// Continue an existing conversation
$response = (new SalesCoach)
    ->continue($conversationId, as: $user)
    ->prompt('Tell me more about that.');
```

### Structured output

Implement `HasStructuredOutput` and define a `schema()` method:

```php theme={null}
public function schema(JsonSchema $schema): array
{
    return [
        'feedback' => $schema->string()->required(),
        'score'    => $schema->integer()->min(1)->max(10)->required(),
    ];
}

$response = (new SalesCoach)->prompt('Analyze this...');

return $response['score'];
```

#### Nested objects

```php theme={null}
public function schema(JsonSchema $schema): array
{
    return [
        'score'    => $schema->integer()->required(),
        'metadata' => $schema->object(fn ($schema) => [
            'confidence' => $schema->string()->enum(['low', 'medium', 'high'])->required(),
            'language'   => $schema->string()->required(),
        ])->required(),
    ];
}
```

#### Arrays of objects

```php theme={null}
public function schema(JsonSchema $schema): array
{
    return [
        'feedback' => $schema->array()->items(
            $schema->object(fn ($schema) => [
                'comment' => $schema->string()->required(),
                'score'   => $schema->integer()->required(),
            ])
        )->required(),
    ];
}
```

#### anyOf

If a value may match one of several schemas, use the `anyOf` method:

```php theme={null}
public function schema(JsonSchema $schema): array
{
    return [
        'content' => $schema->anyOf([
            $schema->object(fn ($schema) => [
                'type' => $schema->string()->enum(['article'])->required(),
                'title' => $schema->string()->required(),
            ]),
            $schema->object(fn ($schema) => [
                'type' => $schema->string()->enum(['image'])->required(),
                'url' => $schema->string()->required(),
            ]),
        ])->required(),
    ];
}
```

### Attachments

Attach documents or images to a prompt:

```php theme={null}
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
    'Analyze the attached sales transcript...',
    attachments: [
        Files\Document::fromStorage('transcript.pdf'),
        Files\Document::fromPath('/home/laravel/transcript.md'),
        $request->file('transcript'),
    ]
);
```

For image attachments use `Files\Image`:

```php theme={null}
$response = (new ImageAnalyzer)->prompt('What is in this image?', attachments: [
    Files\Image::fromStorage('photo.jpg'),
    Files\Image::fromPath('/home/laravel/photo.jpg'),
    $request->file('photo'),
]);
```

### Streaming

Return a stream from a route to deliver the response as Server-Sent Events (SSE):

```php theme={null}
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze this sales transcript...');
});
```

Register a `then` callback to run after streaming completes:

```php theme={null}
use Laravel\Ai\Responses\StreamedAgentResponse;

Route::get('/coach', function () {
    return (new SalesCoach)
        ->stream('Analyze this sales transcript...')
        ->then(function (StreamedAgentResponse $response) {
            // $response->text, $response->events, $response->usage...
        });
});
```

Iterate the stream manually:

```php theme={null}
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
    // handle each event
}
```

#### Vercel AI SDK protocol

```php theme={null}
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze...')->usingVercelDataProtocol();
});
```

### Broadcasting

Broadcast each streamed event over a Laravel channel:

```php theme={null}
use Illuminate\Broadcasting\Channel;

$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
    $event->broadcast(new Channel('channel-name'));
}
```

Or use `broadcastOnQueue` to broadcast asynchronously:

```php theme={null}
(new SalesCoach)->broadcastOnQueue(
    'Analyze this sales transcript...',
    new Channel('channel-name'),
);
```

#### Skipping oversized events

Some broadcasting platforms limit WebSocket messages to approximately 10 KB. Data-heavy stream events, such as large tool results, can exceed that limit and fail to broadcast. Use the `WithoutBroadcasting` attribute to exclude specific event types.

```php theme={null}
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Attributes\WithoutBroadcasting;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use Laravel\Ai\Streaming\Events\ToolCall;
use Laravel\Ai\Streaming\Events\ToolResult;

#[WithoutBroadcasting(ToolCall::class, ToolResult::class)]
class SearchAgent implements Agent, HasTools
{
    use Promptable;

    // ...
}
```

Excluded events are not broadcast, but they are still stored in the `agent_conversation_messages` table. The frontend can retrieve the complete tool data after streaming finishes. This works for queued broadcasting with `broadcastOnQueue` and synchronous broadcasting with `broadcast` or `broadcastNow`.

### Queueing

Run the agent in the background:

```php theme={null}
use Laravel\Ai\Responses\AgentResponse;

Route::post('/coach', function (Request $request) {
    (new SalesCoach)
        ->queue($request->input('transcript'))
        ->then(function (AgentResponse $response) { /* ... */ })
        ->catch(function (Throwable $e) { /* ... */ });

    return back();
});
```

### Tools

Tools extend what an agent can do — query databases, call external APIs, perform calculations, etc.

```shell theme={null}
php artisan make:tool RandomNumberGenerator
```

```php theme={null}
<?php

namespace App\Ai\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class RandomNumberGenerator implements Tool
{
    public function description(): Stringable|string
    {
        return 'This tool may be used to generate cryptographically secure random numbers.';
    }

    public function handle(Request $request): Stringable|string
    {
        return (string) random_int($request['min'], $request['max']);
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'min' => $schema->integer()->min(0)->required(),
            'max' => $schema->integer()->required(),
        ];
    }
}
```

Register tools in the agent's `tools` method:

```php theme={null}
use Laravel\Ai\Contracts\HasTools;

class SalesCoach implements Agent, HasTools
{
    use Promptable;

    public function tools(): iterable
    {
        return [new RandomNumberGenerator];
    }
}
```

#### Similarity search tool

Use the built-in `SimilaritySearch` tool to query a vector-enabled Eloquent model:

```php theme={null}
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}
```

With additional options:

```php theme={null}
SimilaritySearch::usingModel(
    model:         Document::class,
    column:        'embedding',
    minSimilarity: 0.7,
    limit:         10,
    query:         fn ($query) => $query->where('published', true),
),
```

Using a custom closure:

```php theme={null}
new SimilaritySearch(using: function (string $query) {
    return Document::query()
        ->where('user_id', $this->user->id)
        ->whereVectorSimilarTo('embedding', $query)
        ->limit(10)
        ->get();
}),
```

Provide a custom description shown to the model:

```php theme={null}
SimilaritySearch::usingModel(Document::class, 'embedding')
    ->withDescription('Search the knowledge base for relevant articles.'),
```

### File storage tools

The `FileStorage` tool factory gives an agent access to a Laravel [filesystem disk](/en/filesystem). The `all` method returns tools for listing, reading, generating URLs for, writing, deleting, and copying files on the selected disk.

```php theme={null}
use Laravel\Ai\Tools\FileStorage;

public function tools(): iterable
{
    return FileStorage::all('local');
}
```

Use `readOnly` to provide read-only access:

```php theme={null}
return FileStorage::readOnly('local');
```

These methods return an `Illuminate\Support\Collection`, so you can further restrict the tools you expose:

```php theme={null}
use Laravel\Ai\Tools\Filesystem\DeleteFile;

return FileStorage::all('s3')
    ->reject(fn ($tool) => $tool instanceof DeleteFile);
```

### MCP Tools

If your application uses [Laravel MCP](/mcp), you may give your agents tools exposed by [Model Context Protocol](https://modelcontextprotocol.io) servers. Using the [Laravel MCP client](/mcp#mcp-client), you may connect to a remote or local MCP server and pass its tools directly to your agent.

<Info>
  MCP tools require the [Laravel MCP](/mcp) package to be installed in your application.
</Info>

Because an MCP client's `tools` method returns a collection, spread it into your agent's `tools` array using the `...` operator:

```php theme={null}
use App\Ai\Tools\RandomNumberGenerator;
use Laravel\Mcp\Client;

/**
 * Get the tools available to the agent.
 *
 * @return Tool[]
 */
public function tools(): iterable
{
    return [
        ...Client::web('https://mcp.example.com')
            ->withToken($token)
            ->tools(),

        new RandomNumberGenerator,
    ];
}
```

The AI SDK automatically wraps each MCP tool so the agent can call it like any other tool. You may also use a [named MCP client](/mcp#named-clients):

```php theme={null}
use Laravel\Mcp\Facades\Mcp;

public function tools(): iterable
{
    return [
        ...Mcp::client('github')->tools(),
    ];
}
```

Or connect to a [local MCP server](/mcp#client-connecting):

```php theme={null}
use Laravel\Mcp\Client;

public function tools(): iterable
{
    return [
        ...Client::local('php', ['artisan', 'mcp:start'])->tools(),
    ];
}
```

For more information on creating and authenticating MCP clients, including bearer tokens and OAuth, consult the [MCP client documentation](/mcp#mcp-client).

### Provider tools

Provider tools are implemented natively by AI providers.

#### Web Search

Supported: Anthropic, OpenAI, Gemini, OpenRouter

```php theme={null}
use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
    return [new WebSearch];
}
```

With options:

```php theme={null}
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
(new WebSearch)->location(city: 'New York', region: 'NY', country: 'US');
```

#### Web Fetch

Supported: Anthropic, Gemini

```php theme={null}
use Laravel\Ai\Providers\Tools\WebFetch;

public function tools(): iterable
{
    return [new WebFetch];
}

(new WebFetch)->max(3)->allow(['docs.laravel.com']),
```

#### File Search

Supported: OpenAI, Gemini

```php theme={null}
use Laravel\Ai\Providers\Tools\FileSearch;

public function tools(): iterable
{
    return [new FileSearch(stores: ['store_id'])];
}

// Multiple stores
new FileSearch(stores: ['store_1', 'store_2']);

// Filter by metadata
new FileSearch(stores: ['store_id'], where: ['author' => 'Taylor Otwell', 'year' => 2026]);

// Complex filter using a closure
use Laravel\Ai\Providers\Tools\FileSearchQuery;

new FileSearch(stores: ['store_id'], where: fn (FileSearchQuery $query) =>
    $query->where('author', 'Taylor Otwell')
          ->whereNot('status', 'draft')
          ->whereIn('category', ['news', 'updates'])
);
```

### Subagents

You can return another agent from an agent's `tools()` method. Registering an agent as a tool lets the parent delegate a specific task to the subagent and incorporate the result into its original response. This is useful when a general-purpose agent needs access to specialists with their own instructions, tools, model, and provider settings.

For example, a customer support agent can delegate refund policy questions to a refund specialist:

```php theme={null}
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

class CustomerSupportAgent implements Agent, HasTools
{
    use Promptable;

    public function instructions(): string
    {
        return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.';
    }

    public function tools(): iterable
    {
        return [new RefundsAgent];
    }
}
```

To customize how the subagent appears to its parent, implement `CanActAsTool` and define its tool name and description:

```php theme={null}
<?php

namespace App\Ai\Agents;

use App\Ai\Tools\LookupOrder;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\CanActAsTool;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

#[Provider(Lab::Anthropic)]
class RefundsAgent implements Agent, CanActAsTool, HasTools
{
    use Promptable;

    public function instructions(): string
    {
        return 'You are a refunds specialist. Use order details and the refund policy to give concise eligibility guidance.';
    }

    public function name(): string
    {
        return 'refunds_specialist';
    }

    public function description(): string
    {
        return 'Determine whether an order is eligible for a refund and explain the next step.';
    }

    public function tools(): iterable
    {
        return [new LookupOrder];
    }
}
```

If a subagent does not implement `CanActAsTool`, Laravel derives the tool name from its class and generates a generic description. Each subagent invocation is isolated and does not inherit the parent's conversation history.

### Middleware

Agent middleware lets you inspect or modify prompts and responses before and after they are sent.

```shell theme={null}
php artisan make:agent-middleware LogPrompts
```

Implement the middleware on the agent:

```php theme={null}
use App\Ai\Middleware\LogPrompts;
use Laravel\Ai\Contracts\HasMiddleware;

class SalesCoach implements Agent, HasMiddleware
{
    use Promptable;

    public function middleware(): array
    {
        return [new LogPrompts];
    }
}
```

A middleware class:

```php theme={null}
<?php

namespace App\Ai\Middleware;

use Closure;
use Laravel\Ai\Prompts\AgentPrompt;

class LogPrompts
{
    public function handle(AgentPrompt $prompt, Closure $next)
    {
        Log::info('Prompting agent', ['prompt' => $prompt->prompt]);

        return $next($prompt);
    }
}
```

Inspect the response after it is generated using `then`:

```php theme={null}
public function handle(AgentPrompt $prompt, Closure $next)
{
    return $next($prompt)->then(function (AgentResponse $response) {
        Log::info('Agent responded', ['text' => $response->text]);
    });
}
```

### Anonymous agents

Create a one-off agent inline with the `agent()` helper:

```php theme={null}
use function Laravel\Ai\{agent};

$response = agent(
    instructions: 'You are an expert at software development.',
    messages:     [],
    tools:        [],
)->prompt('Tell me about Laravel');
```

With structured output:

```php theme={null}
use Illuminate\Contracts\JsonSchema\JsonSchema;

$response = agent(
    schema: fn (JsonSchema $schema) => ['number' => $schema->integer()->required()],
)->prompt('Generate a random number less than 100');
```

### Agent configuration via PHP attributes

Use PHP attributes to configure an agent's default provider, model, and behaviour:

```php theme={null}
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\MaxTokens;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Attributes\TopP;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

#[Provider(Lab::Anthropic)]
#[Model('claude-haiku-4-5-20251001')]
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Temperature(0.7)]
#[Timeout(120)]
#[TopP(0.9)]
class SalesCoach implements Agent
{
    use Promptable;
}
```

Two convenience attributes automatically select the cheapest or most capable model available:

```php theme={null}
use Laravel\Ai\Attributes\UseCheapestModel;
use Laravel\Ai\Attributes\UseSmartestModel;

#[UseCheapestModel]
class SimpleSummarizer implements Agent { use Promptable; }

#[UseSmartestModel]
class ComplexReasoner implements Agent { use Promptable; }
```

### Provider options

Pass provider-specific options by implementing `HasProviderOptions`:

```php theme={null}
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasProviderOptions;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasProviderOptions
{
    use Promptable;

    public function providerOptions(Lab|string $provider): array
    {
        return match ($provider) {
            Lab::OpenAI => [
                'reasoning'         => ['effort' => 'low'],
                'frequency_penalty' => 0.5,
                'presence_penalty'  => 0.3,
            ],
            Lab::Anthropic => [
                'thinking' => ['budget_tokens' => 1024],
            ],
            default => [],
        };
    }
}
```

## Image generation

Generate images with the `Image` class. Supported providers: OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter.

```php theme={null}
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')->generate();

$rawContent = (string) $image;
```

Specify quality and aspect ratio:

```php theme={null}
$image = Image::of('A donut sitting on the kitchen counter')
    ->quality('high')
    ->landscape()
    ->timeout(120)
    ->generate();
```

Generate from a reference image:

```php theme={null}
use Laravel\Ai\Files;

$image = Image::of('Update this photo to be in the style of an impressionist painting.')
    ->attachments([Files\Image::fromStorage('photo.jpg')])
    ->landscape()
    ->generate();
```

### Storing generated images

```php theme={null}
$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');
```

### Queuing image generation

```php theme={null}
use Laravel\Ai\Responses\ImageResponse;

Image::of('A donut sitting on the kitchen counter')
    ->portrait()
    ->queue()
    ->then(function (ImageResponse $image) {
        $path = $image->store();
    });
```

## Audio (TTS)

Generate speech from text with the `Audio` class. Supported providers: OpenAI, ElevenLabs, Gemini.

```php theme={null}
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();

$rawContent = (string) $audio;
```

Select a voice:

```php theme={null}
$audio = Audio::of('I love coding with Laravel.')->female()->generate();
$audio = Audio::of('I love coding with Laravel.')->voice('voice-id-or-name')->generate();
$audio = Audio::of('I love coding with Laravel.')->female()->instructions('Said like a pirate')->generate();
```

### Storing generated audio

```php theme={null}
$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');
```

### Queuing audio generation

```php theme={null}
use Laravel\Ai\Responses\AudioResponse;

Audio::of('I love coding with Laravel.')
    ->queue()
    ->then(function (AudioResponse $audio) {
        $path = $audio->store();
    });
```

## Transcription (STT)

Transcribe audio files with the `Transcription` class. Supported providers: OpenAI, ElevenLabs, Mistral, Gemini.

```php theme={null}
use Laravel\Ai\Transcription;

$transcript = Transcription::fromPath('/home/laravel/audio.mp3')->generate();
$transcript = Transcription::fromStorage('audio.mp3')->generate();
$transcript = Transcription::fromUpload($request->file('audio'))->generate();

return (string) $transcript;
```

Enable speaker diarization:

```php theme={null}
$transcript = Transcription::fromStorage('audio.mp3')->diarize()->generate();
```

### Queuing transcription

```php theme={null}
use Laravel\Ai\Responses\TranscriptionResponse;

Transcription::fromStorage('audio.mp3')
    ->queue()
    ->then(function (TranscriptionResponse $transcript) {
        // ...
    });
```

## Embeddings

Generate embeddings using the `Embeddings` class or the `Str` macro.

```php theme={null}
use Illuminate\Support\Str;

$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
```

```php theme={null}
use Laravel\Ai\Embeddings;

$response = Embeddings::for([
    'Napa Valley has great wine.',
    'Laravel is a PHP framework.',
])->generate();

$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]
```

Specify dimensions and model:

```php theme={null}
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->dimensions(1536)
    ->generate(Lab::OpenAI, 'text-embedding-3-small');
```

### Querying embeddings (pgvector)

Add the vector column in your migration:

```php theme={null}
Schema::ensureVectorExtensionExists();

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->vector('embedding', dimensions: 1536);
    $table->timestamps();
});

// Optional HNSW index for performance
$table->vector('embedding', dimensions: 1536)->index();
```

Cast the column to an array on the model:

```php theme={null}
protected function casts(): array
{
    return ['embedding' => 'array'];
}
```

Query by vector similarity:

```php theme={null}
// Using a pre-computed embedding vector
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();

// Pass a string — it is automatically embedded
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
    ->limit(10)
    ->get();
```

Lower-level query methods:

```php theme={null}
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();
```

### Caching embeddings

Enable caching globally in `config/ai.php`:

```php theme={null}
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
    ],
],
```

Or enable per request:

```php theme={null}
$response = Embeddings::for(['Napa Valley has great wine.'])->cache()->generate();
$response = Embeddings::for(['Napa Valley has great wine.'])->cache(seconds: 3600)->generate();

// Via the Str macro
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);
```

## Reranking

Rerank a list of documents by relevance to a query. Supported providers: Cohere, Jina, VoyageAI.

```php theme={null}
use Laravel\Ai\Reranking;

$response = Reranking::of([
    'Django is a Python web framework.',
    'Laravel is a PHP web application framework.',
    'React is a JavaScript library for building user interfaces.',
])->rerank('PHP frameworks');

$response->first()->document; // "Laravel is a PHP web application framework."
$response->first()->score;    // 0.95
$response->first()->index;    // 1

$response = Reranking::of($documents)->limit(5)->rerank('search query');
```

### Reranking Eloquent collections

Rerank by a single field:

```php theme={null}
$posts = Post::all()->rerank('body', 'Laravel tutorials');
```

Rerank by multiple fields (concatenated as JSON):

```php theme={null}
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');
```

Using a closure:

```php theme={null}
$reranked = $posts->rerank(
    fn ($post) => $post->title.': '.$post->body,
    'Laravel tutorials'
);
```

With additional options:

```php theme={null}
$reranked = $posts->rerank(
    by:       'content',
    query:    'Laravel tutorials',
    limit:    10,
    provider: Lab::Cohere,
);
```

## Files

Upload files to AI providers for use in subsequent prompts or vector stores.

```php theme={null}
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;

$response = Document::fromPath('/home/laravel/document.pdf')->put();
$response = Image::fromPath('/home/laravel/photo.jpg')->put();
$response = Document::fromStorage('document.pdf', disk: 'local')->put();
$response = Image::fromStorage('photo.jpg', disk: 'local')->put();
$response = Document::fromUrl('https://example.com/document.pdf')->put();
$response = Image::fromUrl('https://example.com/photo.jpg')->put();

return $response->id;
```

Upload raw content or a form upload:

```php theme={null}
$stored = Document::fromString('Hello, World!', 'text/plain')->put();
$stored = Document::fromUpload($request->file('document'))->put();
```

Reference a previously stored file in a prompt:

```php theme={null}
$response = (new SalesCoach)->prompt(
    'Analyze the attached sales transcript...',
    attachments: [Files\Document::fromId('file-id')]
);
```

Retrieve file metadata:

```php theme={null}
$file = Document::fromId('file-id')->get();
$file->id;
$file->mimeType();
```

Delete a stored file:

```php theme={null}
Document::fromId('file-id')->delete();
```

Specify the provider explicitly:

```php theme={null}
$response = Document::fromPath('/home/laravel/document.pdf')->put(provider: Lab::Anthropic);
```

### Provider-specific options

Use `withProviderOptions` to pass provider-specific upload options, such as OpenAI's file `purpose`:

```php theme={null}
use Laravel\Ai\Files\Document;

$response = Document::fromPath('/home/laravel/knowledge.txt')
    ->withProviderOptions(['purpose' => 'assistants'])
    ->put();
```

Pass a closure to configure different options for each provider:

```php theme={null}
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Files\Document;

$response = Document::fromPath('/home/laravel/training.jsonl')
    ->withProviderOptions(fn (Lab|string $provider) => match ($provider) {
        Lab::OpenAI => ['purpose' => 'fine-tune'],
        default => [],
    })
    ->put();
```

## Vector stores

Manage vector stores for file-search tools.

```php theme={null}
use Laravel\Ai\Stores;

// Create a store
$store = Stores::create('Knowledge Base');
$store = Stores::create(
    name:                 'Knowledge Base',
    description:          'Documentation.',
    expiresWhenIdleFor:   days(30),
);

return $store->id;

// Retrieve a store
$store = Stores::get('store_id');
$store->id;
$store->name;
$store->fileCounts;
$store->ready;

// Delete a store
Stores::delete('store_id');
$store->delete();
```

### Adding files to a store

```php theme={null}
$store = Stores::get('store_id');

$document = $store->add('file_id');
$document = $store->add(Document::fromId('file_id'));
$document = $store->add(Document::fromPath('/path/to/document.pdf'));
$document = $store->add(Document::fromStorage('manual.pdf'));
$document = $store->add($request->file('document'));

$document->id;
$document->fileId;
```

With metadata:

```php theme={null}
$store->add(
    Document::fromPath('/path/to/document.pdf'),
    metadata: [
        'author'     => 'Taylor Otwell',
        'department' => 'Engineering',
        'year'       => 2026,
    ]
);
```

Remove a file from a store:

```php theme={null}
$store->remove('file_id');
$store->remove('file_abc123', deleteFile: true);
```

## Failover

Pass an array of providers to automatically fall back when one is unavailable:

```php theme={null}
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Image;

$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: [Lab::OpenAI, Lab::Anthropic],
);

$image = Image::of('A donut sitting on the kitchen counter')
    ->generate(provider: [Lab::Gemini, Lab::xAI]);
```

## Testing

The AI SDK provides fake implementations for every capability so you can test without real API calls.

### Agents

```php theme={null}
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;

SalesCoach::fake();
SalesCoach::fake(['First response', 'Second response']);
SalesCoach::fake(function (AgentPrompt $prompt) {
    return 'Response for: '.$prompt->prompt;
});

SalesCoach::assertPrompted('Analyze this...');
SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});
SalesCoach::assertNotPrompted('Missing prompt');
SalesCoach::assertNeverPrompted();
```

Queued prompt assertions:

```php theme={null}
use Laravel\Ai\QueuedAgentPrompt;

SalesCoach::assertQueued('Analyze this...');
SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});
SalesCoach::assertNotQueued('Missing prompt');
SalesCoach::assertNeverQueued();
```

Prevent real prompts from being sent during tests:

```php theme={null}
SalesCoach::fake()->preventStrayPrompts();
```

For agents that return structured output, pass an array as a fake response:

```php theme={null}
SalesCoach::fake([
    ['score' => 87],
]);
```

<Info>
  If you call `fake()` for a structured-output agent without explicitly providing fake data, Laravel automatically generates data that matches the agent's schema.
</Info>

For anonymous agents:

```php theme={null}
use Laravel\Ai\AnonymousAgent;

AnonymousAgent::fake(['Test response']);
```

### Images

```php theme={null}
use Laravel\Ai\Image;
use Laravel\Ai\Prompts\ImagePrompt;

Image::fake();
Image::fake([base64_encode($firstImage), base64_encode($secondImage)]);
Image::fake(function (ImagePrompt $prompt) {
    return base64_encode('...');
});

Image::assertGenerated(function (ImagePrompt $prompt) {
    return $prompt->contains('sunset') && $prompt->isLandscape();
});
Image::assertNotGenerated('Missing prompt');
Image::assertNothingGenerated();

Image::assertQueued(fn (QueuedImagePrompt $prompt) => $prompt->contains('sunset'));
Image::assertNotQueued('Missing prompt');
Image::assertNothingQueued();

Image::fake()->preventStrayImages();
```

### Audio

```php theme={null}
use Laravel\Ai\Audio;
use Laravel\Ai\Prompts\AudioPrompt;

Audio::fake();
Audio::fake([base64_encode($firstAudio), base64_encode($secondAudio)]);
Audio::fake(function (AudioPrompt $prompt) {
    return base64_encode('...');
});

Audio::assertGenerated(function (AudioPrompt $prompt) {
    return $prompt->contains('Hello') && $prompt->isFemale();
});
Audio::assertNotGenerated('Missing prompt');
Audio::assertNothingGenerated();

Audio::assertQueued(fn (QueuedAudioPrompt $prompt) => $prompt->contains('Hello'));
Audio::assertNotQueued('Missing prompt');
Audio::assertNothingQueued();

Audio::fake()->preventStrayAudio();
```

### Transcriptions

```php theme={null}
use Laravel\Ai\Transcription;
use Laravel\Ai\Prompts\TranscriptionPrompt;

Transcription::fake();
Transcription::fake(['First transcription text.', 'Second transcription text.']);
Transcription::fake(function (TranscriptionPrompt $prompt) {
    return 'Transcribed text...';
});

Transcription::assertGenerated(function (TranscriptionPrompt $prompt) {
    return $prompt->language === 'en' && $prompt->isDiarized();
});
Transcription::assertNotGenerated(fn (TranscriptionPrompt $prompt) => $prompt->language === 'fr');
Transcription::assertNothingGenerated();

Transcription::assertQueued(fn (QueuedTranscriptionPrompt $prompt) => $prompt->isDiarized());
Transcription::assertNotQueued(fn (QueuedTranscriptionPrompt $prompt) => $prompt->language === 'fr');
Transcription::assertNothingQueued();

Transcription::fake()->preventStrayTranscriptions();
```

### Embeddings

```php theme={null}
use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\EmbeddingsPrompt;

Embeddings::fake();
Embeddings::fake([[$firstEmbeddingVector], [$secondEmbeddingVector]]);
Embeddings::fake(function (EmbeddingsPrompt $prompt) {
    return array_map(
        fn () => Embeddings::fakeEmbedding($prompt->dimensions),
        $prompt->inputs
    );
});

Embeddings::assertGenerated(function (EmbeddingsPrompt $prompt) {
    return $prompt->contains('Laravel') && $prompt->dimensions === 1536;
});
Embeddings::assertNotGenerated(fn (EmbeddingsPrompt $prompt) => $prompt->contains('Other'));
Embeddings::assertNothingGenerated();

Embeddings::assertQueued(fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Laravel'));
Embeddings::assertNotQueued(fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Other'));
Embeddings::assertNothingQueued();

Embeddings::fake()->preventStrayEmbeddings();
```

### Reranking

```php theme={null}
use Laravel\Ai\Reranking;
use Laravel\Ai\Prompts\RerankingPrompt;
use Laravel\Ai\Responses\Data\RankedDocument;

Reranking::fake();
Reranking::fake([[
    new RankedDocument(index: 0, document: 'First', score: 0.95),
    new RankedDocument(index: 1, document: 'Second', score: 0.80),
]]);

Reranking::assertReranked(function (RerankingPrompt $prompt) {
    return $prompt->contains('Laravel') && $prompt->limit === 5;
});
Reranking::assertNotReranked(fn (RerankingPrompt $prompt) => $prompt->contains('Django'));
Reranking::assertNothingReranked();
```

### Files

```php theme={null}
use Laravel\Ai\Files;
use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;

Files::fake();

Document::fromString('Hello, Laravel!', mimeType: 'text/plain')->as('hello.txt')->put();

Files::assertStored(fn (StorableFile $file) =>
    (string) $file === 'Hello, Laravel!' && $file->mimeType() === 'text/plain'
);
Files::assertNotStored(fn (StorableFile $file) => (string) $file === 'Hello, World!');
Files::assertNothingStored();

Files::assertDeleted('file-id');
Files::assertNotDeleted('file-id');
Files::assertNothingDeleted();
```

### Vector stores

```php theme={null}
use Laravel\Ai\Stores;

Stores::fake(); // Also fakes file operations

$store = Stores::create('Knowledge Base');

Stores::assertCreated('Knowledge Base');
Stores::assertCreated(fn (string $name, ?string $description) => $name === 'Knowledge Base');
Stores::assertNotCreated('Other Store');
Stores::assertNothingCreated();

Stores::assertDeleted('store_id');
Stores::assertNotDeleted('other_store_id');
Stores::assertNothingDeleted();
```

File operations on a store:

```php theme={null}
$store = Stores::get('store_id');

$store->add('added_id');
$store->remove('removed_id');

$store->assertAdded('added_id');
$store->assertRemoved('removed_id');
$store->assertNotAdded('other_file_id');
$store->assertNotRemoved('other_file_id');

// Assert by file content
$store->add(Document::fromString('Hello, World!', 'text/plain')->as('hello.txt'));
$store->assertAdded(fn (StorableFile $file) => $file->name() === 'hello.txt');
$store->assertAdded(fn (StorableFile $file) => $file->content() === 'Hello, World!');
```

<Warning>
  Always call `fake()` in tests to prevent real API calls, unexpected charges, and rate-limit errors.
</Warning>

## Events

The Laravel AI SDK dispatches the following events. You can listen for them using standard Laravel event listeners.

<AccordionGroup>
  <Accordion title="Agent events">
    | Event            | Fired when                                 |
    | ---------------- | ------------------------------------------ |
    | `PromptingAgent` | Before an agent prompt is sent             |
    | `AgentPrompted`  | After an agent prompt completes            |
    | `StreamingAgent` | Before a streaming agent response starts   |
    | `AgentStreamed`  | After a streaming agent response completes |
    | `InvokingTool`   | Before an agent tool is invoked            |
    | `ToolInvoked`    | After an agent tool is invoked             |
  </Accordion>

  <Accordion title="Image, audio, and transcription events">
    | Event                     | Fired when                     |
    | ------------------------- | ------------------------------ |
    | `GeneratingImage`         | Before image generation starts |
    | `ImageGenerated`          | After an image is generated    |
    | `GeneratingAudio`         | Before audio generation starts |
    | `AudioGenerated`          | After audio is generated       |
    | `GeneratingTranscription` | Before transcription starts    |
    | `TranscriptionGenerated`  | After transcription completes  |
  </Accordion>

  <Accordion title="Embeddings and reranking events">
    | Event                  | Fired when                          |
    | ---------------------- | ----------------------------------- |
    | `GeneratingEmbeddings` | Before embeddings generation starts |
    | `EmbeddingsGenerated`  | After embeddings are generated      |
    | `Reranking`            | Before reranking starts             |
    | `Reranked`             | After reranking completes           |
  </Accordion>

  <Accordion title="Files and vector store events">
    | Event                   | Fired when                                   |
    | ----------------------- | -------------------------------------------- |
    | `StoringFile`           | Before a file is uploaded to a provider      |
    | `FileStored`            | After a file is uploaded to a provider       |
    | `FileDeleted`           | After a stored file is deleted               |
    | `CreatingStore`         | Before a vector store is created             |
    | `StoreCreated`          | After a vector store is created              |
    | `AddingFileToStore`     | Before a file is added to a vector store     |
    | `FileAddedToStore`      | After a file is added to a vector store      |
    | `RemovingFileFromStore` | Before a file is removed from a vector store |
    | `FileRemovedFromStore`  | After a file is removed from a vector store  |
  </Accordion>
</AccordionGroup>


## Related topics

- [What's New in Laravel 13](/en/blog/laravel-13-new-features.md)
- [March 2026 Laravel Updates](/en/blog/changelog/202603.md)
- [Laravel AI SDK integration](/en/packages/laravel-copilot-sdk/ai-sdk.md)
- [Laravel AI SDK Integration - VOICEVOX for Laravel](/en/packages/laravel-voicevox/ai-sdk.md)
- [Creating a Custom Provider for AI SDK](/en/advanced/ai-sdk-custom-provider.md)
