Guía completa del Laravel AI SDK, que permite trabajar con múltiples proveedores de IA (OpenAI, Anthropic, Gemini y otros) mediante una interfaz unificada. Cubre agentes, generación de imágenes, audio, embeddings y pruebas.
Laravel AI SDK ofrece una API unificada y expresiva para interactuar con proveedores de IA como OpenAI, Anthropic o Gemini. Con el AI SDK puedes construir agentes inteligentes con herramientas y salidas estructuradas, generar imágenes, sintetizar y transcribir voz, crear embeddings vectoriales y mucho más, todo mediante una interfaz consistente al estilo Laravel.
Laravel AI SDK es un paquete oficial añadido en Laravel 13. Se distribuye como laravel/ai y permite usar múltiples proveedores de IA con una API unificada.
Ejecuta las migraciones de la base de datos. Se crearán las tablas agent_conversations y agent_conversation_messages, que se emplean para almacenar el historial de conversaciones.
Si usas una API compatible con OpenAI (LM Studio, vLLM, Together, Fireworks, gateways locales, etc.), puedes configurar el proveedor con el driver openai-compatible. El campo url es obligatorio y, si se indica key, se enviará como token Bearer.
El proveedor OpenAI-Compatible admite generación de texto, streaming, herramientas, salida estructurada y adjuntos de imagen. Si el endpoint requiere campos adicionales en el cuerpo de la petición, utiliza las opciones del proveedor.
Los agentes son la unidad básica del Laravel AI SDK. Puedes generar una clase de agente con el comando make:agent.
php artisan make:agent SalesCoach# Generar un agente con salida estructuradaphp artisan make:agent SalesCoach --structured
Los agentes generados se ubican en el directorio app/Ai/Agents/. A continuación se muestra un ejemplo de agente que implementa todas las interfaces principales.
<?phpnamespace 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(), ]; }}
Si implementas la interfaz Conversational y defines el método messages(), podrás pasar el historial de conversación a la IA.Con el trait RemembersConversations el historial se guarda y recupera automáticamente en la base de datos.
use Laravel\Ai\Concerns\RemembersConversations;class SalesCoach implements Agent, Conversational{ use Promptable, RemembersConversations; public function instructions(): string { return 'You are a sales coach...'; }}
Inicia una conversación con forUser() y utiliza el conversationId devuelto para continuar la conversación con continue().
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');$conversationId = $response->conversationId;$response = (new SalesCoach)->continue($conversationId, as: $user)->prompt('Tell me more about that.');
Al implementar la interfaz HasStructuredOutput y definir un esquema JSON en el método schema(), la respuesta de la IA se recibe como datos estructurados.
public function schema(JsonSchema $schema): array{ return ['score' => $schema->integer()->required()];}$response = (new SalesCoach)->prompt('Analyze this...');return $response['score'];
Puedes pasar documentos e imágenes al agente mediante el argumento attachments.
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'), ]);
Los adjuntos de imagen funcionan de forma equivalente.
$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'),]);
Puedes emitir los eventos del stream a un canal de broadcasting, por ejemplo con Laravel Echo.
use Illuminate\Broadcasting\Channel;$stream = (new SalesCoach)->stream('Analyze this sales transcript...');foreach ($stream as $event) { $event->broadcast(new Channel('channel-name'));}
Con broadcastOnQueue() la emisión se realiza a través de una cola.
(new SalesCoach)->broadcastOnQueue( 'Analyze this sales transcript...', new Channel('channel-name'),);
Algunas plataformas de broadcasting limitan el tamaño de los mensajes WebSocket a unos 10 KB. Los eventos de stream con mucha carga (por ejemplo, resultados grandes de herramientas) pueden superar ese límite y fallar al emitirse. Con el atributo WithoutBroadcasting puedes excluir determinados tipos de evento del broadcasting.
Los eventos excluidos no se emiten por broadcasting, pero se siguen guardando en la tabla agent_conversation_messages. De este modo, el frontend puede recuperar todos los datos de la herramienta cuando el stream haya finalizado. Funciona tanto de forma síncrona (broadcast / broadcastNow) como a través de colas (broadcastOnQueue).
Las herramientas permiten que la IA invoque funciones de tu código. Genera una clase de herramienta con el comando make:tool.
php artisan make:tool RandomNumberGenerator
<?phpnamespace 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(), ]; }}
Registra la herramienta en el método tools() del agente.
public function tools(): iterable{ return [new RandomNumberGenerator];}
La factoría FileStorage proporciona al agente acceso a los discos del sistema de ficheros de Laravel. El método all devuelve un conjunto de herramientas para listar, leer, generar URLs, escribir, eliminar y copiar archivos en el disco indicado.
use Laravel\Ai\Tools\FileStorage;public function tools(): iterable{ return FileStorage::all('local');}
Si solo quieres permitir acceso de lectura, utiliza el método readOnly.
return FileStorage::readOnly('local');
Estos métodos devuelven un Illuminate\Support\Collection, por lo que puedes refinar todavía más las herramientas que ofreces.
use Laravel\Ai\Tools\Filesystem\DeleteFile;return FileStorage::all('s3') ->reject(fn ($tool) => $tool instanceof DeleteFile);
Si tu aplicación utiliza Laravel MCP, puedes ofrecer al agente las herramientas expuestas por un servidor Model Context Protocol. El cliente MCP de Laravel se conecta a servidores MCP remotos o locales y puedes pasar sus herramientas directamente al agente.
Para usar herramientas MCP debes tener instalado el paquete Laravel MCP en tu aplicación.
El método tools del cliente MCP devuelve una colección, por lo que puedes expandirla con el operador spread ... dentro del array tools del agente.
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, ];}
El AI SDK envuelve cada herramienta MCP automáticamente para que el agente pueda invocarlas como cualquier otra. También puedes usar clientes MCP con nombre.
use Laravel\Mcp\Facades\Mcp;public function tools(): iterable{ return [ ...Mcp::client('github')->tools(), ];}
Un agente puede devolverse como resultado del método tools() de otro agente. Al registrarlo como herramienta, el agente padre puede delegar tareas específicas al subagente e incorporar el resultado a su respuesta. Es útil cuando un agente generalista necesita acceder a agentes especializados con instrucciones, herramientas, modelos o proveedores concretos.Por ejemplo, un agente de atención al cliente puede delegar las preguntas sobre política de reembolsos a un agente especialista.
<?phpnamespace 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, ]; }}
Para personalizar cómo se le presenta un subagente al agente padre, implementa la interfaz CanActAsTool en el subagente y define el nombre y la descripción con los que se expondrá como herramienta.
<?phpnamespace 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, ]; }}
Cuando un subagente no implementa CanActAsTool, Laravel usa el nombre de la clase como nombre de la herramienta y genera automáticamente una descripción genérica. Cada invocación de un subagente es independiente y no hereda el historial de conversación del agente padre.
Con la función auxiliar agent() puedes utilizar agentes anónimos sin definir una clase.
use function Laravel\Ai\{agent};$response = agent( instructions: 'You are an expert at software development.', messages: [], tools: [],)->prompt('Tell me about Laravel');
También puedes crear agentes anónimos con salida estructurada.
use Illuminate\Contracts\JsonSchema\JsonSchema;$response = agent( schema: fn (JsonSchema $schema) => ['number' => $schema->integer()->required()],)->prompt('Generate a random number less than 100');
También hay atributos de conveniencia para la selección de modelo.
// Usar el modelo más barato#[UseCheapestModel]class SimpleSummarizer implements Agent{ use Promptable;}// Usar el modelo más potente#[UseSmartestModel]class ComplexReasoner implements Agent{ use Promptable;}
La clase Image permite generar imágenes. Los proveedores compatibles son OpenAI, Gemini y xAI.
use Laravel\Ai\Image;$image = Image::of('A donut sitting on the kitchen counter')->generate();$rawContent = (string) $image;
Puedes indicar la calidad, la relación de aspecto y el timeout.
$image = Image::of('A donut sitting on the kitchen counter') ->quality('high') ->landscape() ->timeout(120) ->generate();
También puedes adjuntar imágenes de referencia para editarlas.
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();
La clase Audio convierte texto en voz. Los proveedores compatibles son OpenAI y ElevenLabs.
use Laravel\Ai\Audio;$audio = Audio::of('I love coding with Laravel.')->generate();$rawContent = (string) $audio;
Puedes especificar el género, un ID o nombre concreto de voz e instrucciones sobre el estilo de habla.
$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();
use Laravel\Ai\Responses\AudioResponse;Audio::of('I love coding with Laravel.') ->queue() ->then(function (AudioResponse $audio) { $path = $audio->store(); });
Convierte texto en representaciones vectoriales para búsquedas por similitud y casos similares.
use Illuminate\Support\Str;// Usando Stringable$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
use Laravel\Ai\Embeddings;// Procesar varios textos a la vez$response = Embeddings::for(['Napa Valley has great wine.', 'Laravel is a PHP framework.'])->generate();$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]
También puedes indicar el proveedor, el modelo y las dimensiones.
$response = Embeddings::for(['Napa Valley has great wine.']) ->dimensions(1536) ->generate(Lab::OpenAI, 'text-embedding-3-small');
protected function casts(): array{ return ['embedding' => 'array'];}
3
Consulta de búsqueda por similitud
// Buscar mediante un vector de embedding$documents = Document::query() ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4) ->limit(10) ->get();// Si pasas una cadena, se generan los embeddings automáticamente$documents = Document::query() ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley') ->limit(10) ->get();
También puedes controlar la caché por cada petición.
$response = Embeddings::for(['Napa Valley has great wine.'])->cache()->generate();$response = Embeddings::for(['Napa Valley has great wine.'])->cache(seconds: 3600)->generate();// Igual con Stringable$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);
Puedes reordenar los resultados de búsqueda según su relevancia respecto a una consulta. Los proveedores compatibles son Cohere y 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; // "Laravel is a PHP web application framework."$response->first()->score; // 0.95$response->first()->index; // 1
Con limit() puedes restringir el número de resultados devueltos.
Puedes subir archivos al proveedor de IA y referenciarlos más tarde.
use Laravel\Ai\Files\Document;use Laravel\Ai\Files\Image;// Desde una ruta$response = Document::fromPath('/home/laravel/document.pdf')->put();$response = Image::fromPath('/home/laravel/photo.jpg')->put();// Desde el sistema de ficheros$response = Document::fromStorage('document.pdf', disk: 'local')->put();$response = Image::fromStorage('photo.jpg', disk: 'local')->put();// Desde una URL$response = Document::fromUrl('https://example.com/document.pdf')->put();$response = Image::fromUrl('https://example.com/photo.jpg')->put();return $response->id;
También puedes trabajar con cadenas o subidas de formulario.
Puedes adjuntar un archivo previamente subido a un agente por su ID.
use Laravel\Ai\Files;$response = (new SalesCoach)->prompt( 'Analyze the attached sales transcript...', attachments: [Files\Document::fromId('file-id')]);
Si indicas varios proveedores en un array, cuando el primero falla se recurre automáticamente al siguiente.
use App\Ai\Agents\SalesCoach;use Laravel\Ai\Image;// Failover en el agente$response = (new SalesCoach)->prompt( 'Analyze this sales transcript...', provider: [Lab::OpenAI, Lab::Anthropic],);// Failover en la generación de imágenes$image = Image::of('A donut sitting on the kitchen counter') ->generate(provider: [Lab::Gemini, Lab::xAI]);
use Laravel\Ai\QueuedAgentPrompt;SalesCoach::assertQueued('Analyze this...');SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) { return $prompt->contains('Analyze');});SalesCoach::assertNotQueued('Missing prompt');SalesCoach::assertNeverQueued();
Con preventStrayPrompts() se lanza una excepción si se invoca un prompt no definido en el fake.
SalesCoach::fake()->preventStrayPrompts();
Si el agente devuelve una salida estructurada, puedes indicar la respuesta como array. El agente devolverá una respuesta estructurada que incluye los datos proporcionados.
SalesCoach::fake([ ['score' => 87],]);
Si llamas a fake() sobre un agente con salida estructurada sin proporcionar datos, Laravel generará datos falsos que respeten el esquema definido por el agente.
Para probar agentes anónimos utiliza AnonymousAgent::fake().
use Laravel\Ai\AnonymousAgent;AnonymousAgent::fake(['Test response']);
use Laravel\Ai\Stores;Stores::fake(); // También hace fake de las operaciones de archivo$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();
También puedes aplicar aserciones a operaciones de archivos sobre un 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');// Validar el contenido con una closure$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!');