Vai al contenuto principale

Introduzione

Laravel AI SDK offre un’API unificata ed espressiva per interagire con provider AI come OpenAI, Anthropic, Gemini. Puoi costruire agenti intelligenti con tool e output strutturato, generare immagini, sintetizzare/trascrivere audio, creare embedding vettoriali — tutto con un’interfaccia coerente in stile Laravel.
Laravel AI SDK è il pacchetto ufficiale aggiunto in Laravel 13. Distribuito come laravel/ai, permette di usare più provider AI con la stessa API.

Supporto dei provider

FunzionalitàProvider supportati
Generazione testoOpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter
Generazione immaginiOpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter
Sintesi vocale (TTS)OpenAI, ElevenLabs, Gemini
Riconoscimento vocale (STT)OpenAI, ElevenLabs, Mistral, Gemini
EmbeddingOpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter
RerankingCohere, Jina, VoyageAI
FileOpenAI, Anthropic, Gemini, Azure

Installazione

1

Installa il pacchetto

composer require laravel/ai
2

Pubblica config e migration

php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
3

Esegui le migration

Crea le tabelle agent_conversations e agent_conversation_messages per lo storico delle conversazioni.
php artisan migrate

Configurazione

Variabili d’ambiente

Configura le API key in .env:
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=
I modelli predefiniti per testo, immagini, audio, trascrizione, embedding si impostano in config/ai.php.

URL base personalizzato

Con proxy:
'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'),
    ],
],
Disponibile per OpenAI, Anthropic, Gemini, Groq, Cohere, DeepSeek, xAI, OpenRouter.

Provider OpenAI-Compatible

Per LM Studio, vLLM, Together, Fireworks, gateway locali, usa il driver openai-compatible. url è obbligatorio; key viene inviata come Bearer token.
'providers' => [
    'local' => [
        'driver' => 'openai-compatible',
        'url' => env('LOCAL_AI_URL'),
        'key' => env('LOCAL_AI_API_KEY'),
    ],
],
Uso:
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');
Con modello di default:
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'text' => [
            'default' => env('LOCAL_AI_MODEL'),
        ],
    ],
],
Supporta testo, streaming, tool, output strutturato, allegati immagine. Per campi aggiuntivi usa Opzioni del provider.

Enum Lab

use Laravel\Ai\Enums\Lab;

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

Agenti

Blocco di base dell’SDK. Crea con:
php artisan make:agent SalesCoach

# Con output strutturato
php artisan make:agent SalesCoach --structured
In app/Ai/Agents/:
<?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(),
        ];
    }
}

Prompt

$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return (string) $response;
make() statico usa il container:
$agent = SalesCoach::make(user: $user);
Override provider, modello, timeout:
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);

Contesto conversazionale

Implementando Conversational e definendo messages(), passi la cronologia al modello. Con il trait RemembersConversations la persistenza avviene automaticamente su DB.
use Laravel\Ai\Concerns\RemembersConversations;

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

    public function instructions(): string
    {
        return 'You are a sales coach...';
    }
}
Con forUser() e continue():
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');
$conversationId = $response->conversationId;

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

Output strutturato

Con HasStructuredOutput e schema():
public function schema(JsonSchema $schema): array
{
    return ['score' => $schema->integer()->required()];
}

$response = (new SalesCoach)->prompt('Analyze this...');
return $response['score'];

Oggetti annidati

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

Array di oggetti

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

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

Allegati

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'),
    ]
);
Immagini:
$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

Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze this sales transcript...');
});
Callback finale con then():
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...
        });
});
Iterazione manuale:
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
    // ...
}

Protocollo Vercel AI SDK

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

Broadcasting

use Illuminate\Broadcasting\Channel;

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

foreach ($stream as $event) {
    $event->broadcast(new Channel('channel-name'));
}
Via coda:
(new SalesCoach)->broadcastOnQueue(
    'Analyze this sales transcript...',
    new Channel('channel-name'),
);

Escludere eventi troppo grandi

Alcune piattaforme limitano i messaggi WebSocket (~10KB). Con WithoutBroadcasting escludi eventi grandi.
<?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;
}
Il salvataggio in agent_conversation_messages continua: il frontend riceverà i dati completi al termine.

Coda

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();
});

Tool

I tool consentono all’AI di chiamare funzioni del tuo codice.
php artisan make:tool RandomNumberGenerator
<?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(),
        ];
    }
}
Registrazione:
public function tools(): iterable
{
    return [new RandomNumberGenerator];
}

Tool di ricerca per similarità

use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}
Con opzioni:
SimilaritySearch::usingModel(
    model: Document::class,
    column: 'embedding',
    minSimilarity: 0.7,
    limit: 10,
    query: fn ($query) => $query->where('published', true),
),
Con closure:
new SimilaritySearch(using: function (string $query) {
    return Document::query()
        ->where('user_id', $this->user->id)
        ->whereVectorSimilarTo('embedding', $query)
        ->limit(10)
        ->get();
}),
Descrizione custom:
SimilaritySearch::usingModel(Document::class, 'embedding')
    ->withDescription('Search the knowledge base for relevant articles.'),

Tool di file storage

Con FileStorage dai all’agente accesso ai disk.
use Laravel\Ai\Tools\FileStorage;

public function tools(): iterable
{
    return FileStorage::all('local');
}
Sola lettura:
return FileStorage::readOnly('local');
Puoi filtrare il set (restituisce Illuminate\Support\Collection):
use Laravel\Ai\Tools\Filesystem\DeleteFile;

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

Tool MCP

Se usi Laravel MCP, esponi all’agente i tool di server MCP.
Richiede il pacchetto Laravel MCP.
use App\Ai\Tools\RandomNumberGenerator;
use Laravel\Mcp\Client;

public function tools(): iterable
{
    return [
        ...Client::web('https://mcp.example.com')
            ->withToken($token)
            ->tools(),

        new RandomNumberGenerator,
    ];
}
Client con nome:
use Laravel\Mcp\Facades\Mcp;

public function tools(): iterable
{
    return [
        ...Mcp::client('github')->tools(),
    ];
}
Locale:
use Laravel\Mcp\Client;

public function tools(): iterable
{
    return [
        ...Client::local('php', ['artisan', 'mcp:start'])->tools(),
    ];
}
Vedi la documentazione MCP per creazione e autenticazione.

Tool nativi del provider

Anthropic, OpenAI, Gemini, OpenRouter.
use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
    return [new WebSearch];
}
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
(new WebSearch)->location(city: 'New York', region: 'NY', country: 'US');

Web fetch

Anthropic, Gemini.
use Laravel\Ai\Providers\Tools\WebFetch;

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

(new WebFetch)->max(3)->allow(['docs.laravel.com']),
OpenAI, Gemini.
use Laravel\Ai\Providers\Tools\FileSearch;

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

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

new FileSearch(stores: ['store_id'], where: ['author' => 'Taylor Otwell', 'year' => 2026]);
Filtri complessi:
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'])
);

Sub-agenti

Un agente può esporre altri agenti come tool.
<?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,
        ];
    }
}
Con CanActAsTool personalizzi il nome/descrizione:
<?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,
        ];
    }
}
Senza CanActAsTool, Laravel usa il nome della classe e una descrizione generica. Ogni chiamata al sub-agente è indipendente.

Middleware

php artisan make:agent-middleware LogPrompts
<?php

namespace App\Ai\Agents;

use App\Ai\Middleware\LogPrompts;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasMiddleware
{
    use Promptable;

    public function middleware(): array
    {
        return [new LogPrompts];
    }
}
<?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);
    }
}
Con then():
public function handle(AgentPrompt $prompt, Closure $next)
{
    return $next($prompt)->then(function (AgentResponse $response) {
        Log::info('Agent responded', ['text' => $response->text]);
    });
}

Agenti anonimi

use function Laravel\Ai\{agent};

$response = agent(
    instructions: 'You are an expert at software development.',
    messages: [],
    tools: [],
)->prompt('Tell me about Laravel');
Con output strutturato:
use Illuminate\Contracts\JsonSchema\JsonSchema;

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

Attributi di configurazione

AttributoDescrizione
#[Provider]Provider da usare
#[Model]Modello
#[MaxSteps]Max step di tool call
#[MaxTokens]Token massimi
#[Temperature]Temperatura (0.0–1.0)
#[TopP]Nucleus sampling (0.0–1.0)
#[Timeout]Secondi
#[UseCheapestModel]Modello più economico
#[UseSmartestModel]Modello più potente
<?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;
}
Scorciatoie:
#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
    use Promptable;
}

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

Opzioni del provider

<?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 => [],
        };
    }
}

Generazione immagini

OpenAI, Gemini, xAI.
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')->generate();
$rawContent = (string) $image;
Con opzioni:
$image = Image::of('A donut sitting on the kitchen counter')
    ->quality('high')
    ->landscape()
    ->timeout(120)
    ->generate();
Immagine di riferimento:
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();

Salvataggio

$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');

In coda

use Laravel\Ai\Responses\ImageResponse;

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

Sintesi vocale (TTS)

OpenAI, ElevenLabs.
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();
$rawContent = (string) $audio;
$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();

Salvataggio

$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');

In coda

use Laravel\Ai\Responses\AudioResponse;

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

Trascrizione (STT)

OpenAI, ElevenLabs, Mistral.
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;

Diarizzazione

$transcript = Transcription::fromStorage('audio.mp3')->diarize()->generate();

In coda

use Laravel\Ai\Responses\TranscriptionResponse;

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

Embedding

use Illuminate\Support\Str;

$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
use Laravel\Ai\Embeddings;

$response = Embeddings::for(['Napa Valley has great wine.', 'Laravel is a PHP framework.'])->generate();
$response->embeddings;
Provider e dimensioni:
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->dimensions(1536)
    ->generate(Lab::OpenAI, 'text-embedding-3-small');

Vector search (pgvector)

1

Migration

Schema::ensureVectorExtensionExists();

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

$table->vector('embedding', dimensions: 1536)->index();
2

Modello

protected function casts(): array
{
    return ['embedding' => 'array'];
}
3

Query per similarità

$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();

$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
    ->limit(10)
    ->get();
API di basso livello:
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();

Cache degli embedding

config/ai.php:
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
    ],
],
Per richiesta:
$response = Embeddings::for(['Napa Valley has great wine.'])->cache()->generate();
$response = Embeddings::for(['Napa Valley has great wine.'])->cache(seconds: 3600)->generate();

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

Reranking

Cohere, Jina.
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;
$response->first()->score;
$response->first()->index;
Limite:
$response = Reranking::of($documents)->limit(5)->rerank('search query');

Reranking di collection

$posts = Post::all()->rerank('body', 'Laravel tutorials');

$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');

$reranked = $posts->rerank(fn ($post) => $post->title.': '.$post->body, 'Laravel tutorials');

$reranked = $posts->rerank(
    by: 'content',
    query: 'Laravel tutorials',
    limit: 10,
    provider: Lab::Cohere,
);

Gestione file

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;
Da stringa o upload:
$stored = Document::fromString('Hello, World!', 'text/plain')->put();
$stored = Document::fromUpload($request->file('document'))->put();

Riferire file caricati

use Laravel\Ai\Files;

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

Recuperare/eliminare

$file = Document::fromId('file-id')->get();
$file->id;
$file->mimeType();

Document::fromId('file-id')->delete();

Provider

$response = Document::fromPath('/home/laravel/document.pdf')->put(provider: Lab::Anthropic);

Opzioni provider-specifiche

use Laravel\Ai\Files\Document;

$response = Document::fromPath('/home/laravel/knowledge.txt')
    ->withProviderOptions(['purpose' => 'assistants'])
    ->put();
Per provider:
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 store

use Laravel\Ai\Stores;

$store = Stores::create('Knowledge Base');
$store = Stores::create(
    name: 'Knowledge Base',
    description: 'Documentation.',
    expiresWhenIdleFor: days(30),
);
return $store->id;

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

Stores::delete('store_id');
$store->delete();

Aggiungere file

$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;
Con metadata:
$store->add(
    Document::fromPath('/path/to/document.pdf'),
    metadata: [
        'author' => 'Taylor Otwell',
        'department' => 'Engineering',
        'year' => 2026,
    ]
);

Rimuovere file

$store->remove('file_id');

$store->remove('file_abc123', deleteFile: true);

Failover

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]);

Test

Agent

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();
Code:
use Laravel\Ai\QueuedAgentPrompt;

SalesCoach::assertQueued('Analyze this...');
SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});
SalesCoach::assertNotQueued('Missing prompt');
SalesCoach::assertNeverQueued();
Prevenire prompt non fake:
SalesCoach::fake()->preventStrayPrompts();
Output strutturato:
SalesCoach::fake([
    ['score' => 87],
]);
Con agenti a output strutturato senza dati espliciti, Laravel genera dati fake conformi allo schema.
Agenti anonimi:
use Laravel\Ai\AnonymousAgent;

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

Immagini

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

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();

Trascrizione

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();

Embedding

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

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();

File

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 store

use Laravel\Ai\Stores;

Stores::fake();

$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();
Operazioni file dello store:
$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');

$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!');

Eventi

  • PromptingAgent — prima dell’invio del prompt
  • AgentPrompted — dopo l’invio
  • StreamingAgent — inizio streaming
  • AgentStreamed — fine streaming
  • InvokingTool — prima della chiamata al tool
  • ToolInvoked — dopo la chiamata al tool
  • GeneratingImage / ImageGenerated
  • GeneratingAudio / AudioGenerated
  • GeneratingTranscription / TranscriptionGenerated
  • GeneratingEmbeddings / EmbeddingsGenerated
  • Reranking / Reranked
  • StoringFile / FileStored / FileDeleted
  • CreatingStore / StoreCreated
  • AddingFileToStore / FileAddedToStore
  • RemovingFileFromStore / FileRemovedFromStore
Ultima modifica il 13 luglio 2026