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 testo OpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter Generazione immagini OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter Sintesi vocale (TTS) OpenAI, ElevenLabs, Gemini Riconoscimento vocale (STT) OpenAI, ElevenLabs, Mistral, Gemini Embedding OpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter Reranking Cohere, Jina, VoyageAI File OpenAI, Anthropic, Gemini, Azure
Installazione
Installa il pacchetto
composer require laravel/ai
Pubblica config e migration
php artisan vendor:publish --provider= "Laravel\Ai\AiServiceProvider"
Esegui le migration
Crea le tabelle agent_conversations e agent_conversation_messages per lo storico delle conversazioni.
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 ();
});
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 ];
}
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.' ),
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 );
Se usi Laravel MCP , esponi all’agente i tool di server 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.
Web search
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' ]),
File search
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
Attributo Descrizione #[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)
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 ();
Modello
protected function casts () : array
{
return [ 'embedding' => 'array' ];
}
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
Immagini/audio/trascrizione
GeneratingImage / ImageGenerated
GeneratingAudio / AudioGenerated
GeneratingTranscription / TranscriptionGenerated
GeneratingEmbeddings / EmbeddingsGenerated
Reranking / Reranked
StoringFile / FileStored / FileDeleted
CreatingStore / StoreCreated
AddingFileToStore / FileAddedToStore
RemovingFileFromStore / FileRemovedFromStore