Saltar al contenido principal

Introducción

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.

Proveedores compatibles

FunciónProveedores compatibles
Generación de textoOpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter
Generación de imágenesOpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter
Síntesis de voz (TTS)OpenAI, ElevenLabs, Gemini
Reconocimiento de voz (STT)OpenAI, ElevenLabs, Mistral, Gemini
EmbeddingsOpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter
RerankingCohere, Jina, VoyageAI
ArchivosOpenAI, Anthropic, Gemini, Azure

Instalación

1

Instalar el paquete

Instala Laravel AI SDK con Composer.
composer require laravel/ai
2

Publicar el archivo de configuración y las migraciones

Publica el archivo de configuración y las migraciones con el comando Artisan vendor:publish.
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
3

Ejecutar las migraciones

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.
php artisan migrate

Configuración

Variables de entorno

Configura las claves de API de los proveedores de IA que vayas a usar en el archivo .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=
Los modelos por defecto para texto, imágenes, audio, transcripción y embeddings pueden configurarse en config/ai.php.

URL base personalizada

Si necesitas enrutar el tráfico a través de un servicio proxy, puedes definir una URL personalizada por proveedor.
'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'),
    ],
],
La URL base personalizada está disponible para OpenAI, Anthropic, Gemini, Groq, Cohere, DeepSeek, xAI y OpenRouter.

Proveedor OpenAI-Compatible

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.
'providers' => [
    'local' => [
        'driver' => 'openai-compatible',
        'url' => env('LOCAL_AI_URL'),
        'key' => env('LOCAL_AI_API_KEY'),
    ],
],
Una vez configurado, puedes referenciarlo por nombre como cualquier otro proveedor.
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');
Si defines un modelo de texto por defecto, no necesitarás especificarlo cada vez.
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'text' => [
            'default' => env('LOCAL_AI_MODEL'),
        ],
    ],
],
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.

Enum Lab

Para referenciar proveedores desde el código, utiliza el enum Lab.
use Laravel\Ai\Enums\Lab;

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

Agentes

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 estructurada
php 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.
<?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

Con el método prompt() envías un mensaje al agente.
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return (string) $response;
El método estático make() crea una instancia resolviendo las dependencias desde el contenedor.
$agent = SalesCoach::make(user: $user);
Puedes sobrescribir el proveedor, el modelo y el timeout mediante los argumentos de prompt().
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);

Contexto de conversación

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.');

Salida estructurada

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

Objetos anidados

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 de objetos

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 (selección entre varios esquemas)

Cuando un valor puede coincidir con uno de varios esquemas, utiliza el método 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(),
    ];
}

Adjuntos

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

Streaming

Con el método stream() la respuesta se devuelve por fragmentos. Es ideal para enviar respuestas largas al frontend en tiempo real.
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze this sales transcript...');
});
El callback then() te permite ejecutar código cuando finalice el streaming.
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...
        });
});
También puedes iterar el stream manualmente.
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

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

Protocolo del Vercel AI SDK

Si utilizas el Vercel AI SDK en el frontend, invoca usingVercelDataProtocol().
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze...')->usingVercelDataProtocol();
});

Broadcasting

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'),
);

Saltar eventos muy grandes

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.
<?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;

    // ...
}
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).

Colas

El método queue() permite enviar el prompt a una cola para procesarlo de forma asíncrona.
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();
});

Herramientas

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
<?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(),
        ];
    }
}
Registra la herramienta en el método tools() del agente.
public function tools(): iterable
{
    return [new RandomNumberGenerator];
}

Herramienta de búsqueda por similitud

Puedes añadir fácilmente una herramienta de búsqueda por similitud basada en embeddings vectoriales.
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}
También se pueden especificar opciones.
SimilaritySearch::usingModel(
    model: Document::class,
    column: 'embedding',
    minSimilarity: 0.7,
    limit: 10,
    query: fn ($query) => $query->where('published', true),
),
O bien definir la lógica de búsqueda a medida mediante una closure.
new SimilaritySearch(using: function (string $query) {
    return Document::query()
        ->where('user_id', $this->user->id)
        ->whereVectorSimilarTo('embedding', $query)
        ->limit(10)
        ->get();
}),
withDescription() permite personalizar la descripción de la herramienta.
SimilaritySearch::usingModel(Document::class, 'embedding')
    ->withDescription('Search the knowledge base for relevant articles.'),

Herramientas de almacenamiento de archivos

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

Herramientas MCP

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(),
    ];
}
O conectar con un servidor MCP local.
use Laravel\Mcp\Client;

public function tools(): iterable
{
    return [
        ...Client::local('php', ['artisan', 'mcp:start'])->tools(),
    ];
}
Consulta la documentación del cliente MCP para más información sobre la creación de clientes y la autenticación (Bearer tokens, OAuth, etc.).

Herramientas del proveedor

Son herramientas especiales implementadas de forma nativa por los proveedores de IA.

Búsqueda web

Añade búsqueda web al agente. Compatible con Anthropic, OpenAI, Gemini y OpenRouter.
use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
    return [new WebSearch];
}
Puedes limitar el número de resultados, restringir dominios o indicar una ubicación.
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
(new WebSearch)->location(city: 'New York', region: 'NY', country: 'US');

Recuperación web

Herramienta para descargar el contenido de una URL indicada. Compatible con Anthropic y Gemini.
use Laravel\Ai\Providers\Tools\WebFetch;

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

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

Búsqueda de archivos

Herramienta que busca documentos en un vector store. Compatible con OpenAI y Gemini.
use Laravel\Ai\Providers\Tools\FileSearch;

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

// Múltiples stores
new FileSearch(stores: ['store_1', 'store_2']);

// Filtrar por metadatos
new FileSearch(stores: ['store_id'], where: ['author' => 'Taylor Otwell', 'year' => 2026]);
También se admiten filtros más complejos con FileSearchQuery.
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'])
);

Subagentes

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

Middleware

Puedes añadir middleware al agente para interceptar los prompts y las respuestas.
php artisan make:agent-middleware LogPrompts
Implementa la interfaz HasMiddleware en el agente y registra el middleware en el método middleware().
<?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];
    }
}
Ejemplo de implementación de la clase de middleware.
<?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() también puedes ejecutar código después de recibir la respuesta.
public function handle(AgentPrompt $prompt, Closure $next)
{
    return $next($prompt)->then(function (AgentResponse $response) {
        Log::info('Agent responded', ['text' => $response->text]);
    });
}

Agentes anónimos

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

Configuración del agente (atributos PHP)

Puedes declarar la configuración por defecto del agente de forma declarativa mediante atributos PHP.
AtributoDescripción
#[Provider]Indica el proveedor a usar
#[Model]Indica el modelo a usar
#[MaxSteps]Número máximo de pasos de llamada a herramientas
#[MaxTokens]Número máximo de tokens
#[Temperature]Parámetro de temperatura (0.0 a 1.0)
#[TopP]Probabilidad de nucleus sampling (0.0 a 1.0)
#[Timeout]Timeout (segundos)
#[UseCheapestModel]Selecciona automáticamente el modelo más barato
#[UseSmartestModel]Selecciona automáticamente el modelo más 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;
}
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;
}

Opciones del proveedor

Al implementar la interfaz HasProviderOptions puedes pasar opciones específicas del proveedor.
<?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 => [],
        };
    }
}

Generación de imágenes

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

Guardar imágenes

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

Generación de imágenes en cola

use Laravel\Ai\Responses\ImageResponse;

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

Síntesis de voz (TTS)

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

Guardar el audio

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

Generar audio en cola

use Laravel\Ai\Responses\AudioResponse;

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

Transcripción (STT)

La clase Transcription convierte archivos de audio en texto. Los proveedores compatibles son OpenAI, ElevenLabs y 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;

Diarización (separación de hablantes)

Con diarize() obtienes una transcripción con separación por hablante.
$transcript = Transcription::fromStorage('audio.mp3')->diarize()->generate();

Transcripción en cola

use Laravel\Ai\Responses\TranscriptionResponse;

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

Embeddings

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

Búsqueda vectorial (pgvector)

Ejemplo de configuración de búsqueda vectorial con PostgreSQL y la extensión pgvector.
1

Crear la migración

Schema::ensureVectorExtensionExists();

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

// Añadir índice HNSW
$table->vector('embedding', dimensions: 1536)->index();
2

Configurar el modelo

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 dispones de métodos de bajo nivel.
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();

Cacheo de embeddings

Puedes cachear los embeddings para no volver a generarlos para el mismo texto. Configura los valores por defecto de cacheo en config/ai.php.
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
    ],
],
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);

Reranking

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.
$response = Reranking::of($documents)->limit(5)->rerank('search query');

Reranking de colecciones

Puedes reordenar directamente colecciones de Eloquent.
// Un solo campo
$posts = Post::all()->rerank('body', 'Laravel tutorials');

// Varios campos (se envían como JSON)
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');

// Generación de texto personalizada con una closure
$reranked = $posts->rerank(fn ($post) => $post->title.': '.$post->body, 'Laravel tutorials');

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

Gestión de archivos

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

Referenciar archivos ya subidos

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

Obtener y eliminar archivos

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

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

Indicar el proveedor

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

Opciones específicas del proveedor

Con el método withProviderOptions puedes pasar opciones de subida propias del proveedor. Por ejemplo, el purpose de un archivo de OpenAI.
use Laravel\Ai\Files\Document;

$response = Document::fromPath('/home/laravel/knowledge.txt')
    ->withProviderOptions(['purpose' => 'assistants'])
    ->put();
Si necesitas opciones distintas para cada proveedor, pasa una closure.
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

Los vector stores permiten gestionar los documentos en el propio proveedor.
use Laravel\Ai\Stores;

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

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

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

Añadir archivos a un store

$store = Stores::get('store_id');

// Distintas formas de añadir archivos
$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;
También puedes adjuntar metadatos.
$store->add(
    Document::fromPath('/path/to/document.pdf'),
    metadata: [
        'author' => 'Taylor Otwell',
        'department' => 'Engineering',
        'year' => 2026,
    ]
);

Eliminar archivos de un store

$store->remove('file_id');

// Si además quieres eliminar el archivo en sí
$store->remove('file_abc123', deleteFile: true);

Failover

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

Pruebas

Laravel AI SDK ofrece utilidades de fake para probar el código sin invocar realmente a la API.

Pruebas de agentes

use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;

// Respuesta fija
SalesCoach::fake();
SalesCoach::fake(['First response', 'Second response']);

// Respuestas dinámicas mediante closure
SalesCoach::fake(function (AgentPrompt $prompt) {
    return 'Response for: '.$prompt->prompt;
});

// Aserciones
SalesCoach::assertPrompted('Analyze this...');
SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});
SalesCoach::assertNotPrompted('Missing prompt');
SalesCoach::assertNeverPrompted();
También hay aserciones para prompts encolados.
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']);

Pruebas de generación de imágenes

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

Pruebas de síntesis de voz

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

Pruebas de transcripción

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

Pruebas de embeddings

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

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

Pruebas de archivos

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

Pruebas de vector stores

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

Eventos

Laravel AI SDK despacha los siguientes eventos. Puedes escucharlos para generar logs, monitorizar o realizar otras acciones.
  • PromptingAgent — antes de enviar el prompt
  • AgentPrompted — después de enviar el prompt
  • StreamingAgent — al iniciar el streaming
  • AgentStreamed — al finalizar el streaming
  • InvokingTool — antes de invocar una herramienta
  • ToolInvoked — después de invocar una herramienta
  • GeneratingImage — antes de generar la imagen
  • ImageGenerated — después de generar la imagen
  • GeneratingAudio — antes de generar el audio
  • AudioGenerated — después de generar el audio
  • GeneratingTranscription — antes de transcribir
  • TranscriptionGenerated — después de transcribir
  • GeneratingEmbeddings — antes de generar los embeddings
  • EmbeddingsGenerated — después de generar los embeddings
  • Reranking — antes del reranking
  • Reranked — después del reranking
  • StoringFile — antes de guardar el archivo
  • FileStored — después de guardar el archivo
  • FileDeleted — después de eliminar el archivo
  • CreatingStore — antes de crear el store
  • StoreCreated — después de crear el store
  • AddingFileToStore — antes de añadir un archivo al store
  • FileAddedToStore — después de añadir un archivo al store
  • RemovingFileFromStore — antes de eliminar un archivo del store
  • FileRemovedFromStore — después de eliminar un archivo del store
Última modificación el 13 de julio de 2026