Saltar al contenido principal

Cuándo necesitas un proveedor personalizado

Laravel AI SDK soporta de forma predeterminada los principales servicios de IA como OpenAI, Anthropic, Gemini y Mistral. Sin embargo, en los siguientes casos los proveedores estándar no bastan:
  • Servicios de IA emergentes que aún no cuentan con soporte oficial.
  • Cuando quieres pasar por una gateway de modelos interna o una capa de gestión de facturación.
  • Servidores de inferencia on-premise con protocolos o esquemas de autenticación propios.
En estos casos, si implementas un proveedor personalizado y lo registras en el AiManager del SDK, podrás utilizarlo con la misma API que los proveedores estándar.
Para APIs compatibles con OpenAI, usa el driver integradoDesde la versión 0.9 del SDK se proporciona el driver openai-compatible. Para servidores de inferencia internos compatibles con OpenAI (como el endpoint compatible con OpenAI de Ollama), basta con añadir la configuración a config/ai.php; no es necesario implementar un proveedor personalizado.
'my-server' => [
    'driver' => 'openai-compatible',
    'key'    => env('MY_SERVER_API_KEY'),
    'url'    => env('MY_SERVER_URL', 'http://localhost:8080/v1'),
    'models' => [
        'text' => ['default' => 'llama3.3-70b'],
    ],
],

Descripción general de la arquitectura

Estructura de dos capas

Laravel AI SDK está compuesto por dos capas: proveedores (providers) y gateways.
CapaRolEjemplo
ProviderInterfaz del lado de la aplicación. Resuelve el nombre del modelo y conserva la configuración.OpenAiProvider, AnthropicProvider
GatewayEnvía la petición real a la API correspondiente a un paso.AnthropicGateway, OpenAiCompatibleGateway
Todos los proveedores extienden la clase abstracta Laravel\Ai\Providers\Provider e implementan los contratos (interfaces) correspondientes a cada capacidad. Los bucles de varios pasos que incluyen llamadas a herramientas se ejecutan haciendo que TextGenerationLoop invoque el gateway de forma repetida.

Lista de contratos

Implementa solo los contratos necesarios según las funcionalidades que quieras ofrecer.
ContratoNamespaceFuncionalidad
TextProviderLaravel\Ai\Contracts\Providers\TextProviderGeneración de texto y agentes
EmbeddingProviderLaravel\Ai\Contracts\Providers\EmbeddingProviderGeneración de embeddings vectoriales
ImageProviderLaravel\Ai\Contracts\Providers\ImageProviderGeneración de imágenes
AudioProviderLaravel\Ai\Contracts\Providers\AudioProviderSíntesis de voz (TTS)
TranscriptionProviderLaravel\Ai\Contracts\Providers\TranscriptionProviderReconocimiento de voz (STT)
En la mayoría de los casos, basta con implementar TextProvider.

Contrato TextProvider

Es la interfaz que implementan los proveedores de generación de texto (src/Contracts/Providers/TextProvider.php).
interface TextProvider extends Provider
{
    public function prompt(AgentPrompt $prompt): AgentResponse;
    public function stream(AgentPrompt $prompt): StreamableAgentResponse;
    public function useTextGateway(StepTextGateway $gateway): self;
    public function textGenerationLoop(): TextGenerationLoop;
    public function defaultTextModel(): string;
    public function cheapestTextModel(): string;
    public function smartestTextModel(): string;
}
Las implementaciones de prompt() y stream() pueden delegarse en los traits existentes (GeneratesText, StreamsText), por lo que en la práctica solo debes implementar los tres métodos que devuelven el nombre del modelo y textGateway(), un total de cuatro. Como el bucle de herramientas multi-paso lo gestiona TextGenerationLoop, el gateway solo procesa la petición de un único paso.

Ejemplo de implementación: un proveedor personalizado

Ejemplo para registrar un servicio de inferencia con una API propia como proveedor llamado my-inference.
1

Crea la clase del proveedor

Crea app/Ai/Providers/MyInferenceProvider.php.
<?php

declare(strict_types=1);

namespace App\Ai\Providers;

use Illuminate\Contracts\Events\Dispatcher;
use Laravel\Ai\Contracts\Gateway\StepTextGateway;
use Laravel\Ai\Contracts\Providers\TextProvider;
use Laravel\Ai\Providers\Concerns\GeneratesText;
use Laravel\Ai\Providers\Concerns\HasTextGateway;
use Laravel\Ai\Providers\Concerns\StreamsText;
use Laravel\Ai\Providers\Provider;

class MyInferenceProvider extends Provider implements TextProvider
{
    use GeneratesText;
    use HasTextGateway;
    use StreamsText;

    public function __construct(protected array $config, protected Dispatcher $events)
    {
        //
    }

    /**
     * Get the credentials for the provider.
     */
    public function providerCredentials(): array
    {
        return ['key' => $this->config['key'] ?? null];
    }

    /**
     * Get the provider's text gateway.
     */
    public function textGateway(): StepTextGateway
    {
        return $this->textGateway ??= new \App\Ai\Gateway\MyInferenceGateway($this->events);
    }

    public function defaultTextModel(): string
    {
        return $this->config['models']['text']['default'] ?? 'my-model-v1';
    }

    public function cheapestTextModel(): string
    {
        return $this->config['models']['text']['cheapest'] ?? 'my-model-v1';
    }

    public function smartestTextModel(): string
    {
        return $this->config['models']['text']['smartest'] ?? 'my-model-v1';
    }
}
2

Regístralo en el AppServiceProvider

Regístralo con extend() en el método boot de App\Providers\AppServiceProvider.
<?php

namespace App\Providers;

use App\Ai\Providers\MyInferenceProvider;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\ServiceProvider;
use Laravel\Ai\AiManager;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->app->make(AiManager::class)->extend(
            'my-inference',
            fn (array $config) => new MyInferenceProvider(
                $config,
                $this->app->make(Dispatcher::class)
            )
        );
    }
}
3

Añade el proveedor a config/ai.php

'providers' => [
    // ...proveedores existentes...

    'my-inference' => [
        'driver' => 'my-inference',
        'key'    => env('MY_INFERENCE_API_KEY'),
        'url'    => env('MY_INFERENCE_URL', 'http://localhost:8080'),
    ],
],
Añádelo también en tu .env.
MY_INFERENCE_API_KEY=your-api-key
MY_INFERENCE_URL=https://inference.example.internal
4

Úsalo desde un agente

Una vez registrado, basta con especificar el nombre del proveedor en el argumento provider de prompt() para usarlo igual que un proveedor estándar.
use App\Ai\Agents\SummaryAgent;

$response = SummaryAgent::make()->prompt('Resume este artículo, por favor.', provider: 'my-inference');

echo $response->text;
Si quieres usarlo como proveedor predeterminado, cambia la clave default de config/ai.php.
'default' => 'my-inference',

Implementar un gateway personalizado

Para servicios con una API propia que no sea compatible con OpenAI, necesitarás un gateway personalizado que implemente el contrato StepTextGateway.

Contrato StepTextGateway

Es la interfaz definida en src/Contracts/Gateway/StepTextGateway.php. El gateway procesa la petición correspondiente a un solo paso de la conversación y devuelve un StepResponse. El bucle de llamadas a herramientas lo gestiona TextGenerationLoop desde la capa superior.
interface StepTextGateway
{
    public function generateTextStep(
        TextProvider $provider,
        string $model,
        ?string $instructions,
        array $messages,
        array $tools,
        ?array $schema,
        ?TextGenerationOptions $options,
        ?int $timeout,
        StepContext $stepContext,
    ): StepResponse;

    public function generateStreamStep(
        string $invocationId,
        TextProvider $provider,
        string $model,
        ?string $instructions,
        array $messages,
        array $tools,
        ?array $schema,
        ?TextGenerationOptions $options,
        ?int $timeout,
        StepContext $stepContext,
    ): Generator;
}
El contrato TextGateway anterior a 0.8 (generateText(), stream(), onToolInvocation()) fue eliminado en 0.9. Si tienes un gateway personalizado, debes migrarlo a StepTextGateway. El onToolInvocation() para llamadas a herramientas se ha movido a TextGenerationLoop.

Ejemplo de implementación de un gateway personalizado

Este es el esqueleto de un gateway sencillo que envía peticiones HTTP a una API de inferencia propia.
<?php

declare(strict_types=1);

namespace App\Ai\Gateway;

use Generator;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\Http;
use Laravel\Ai\Contracts\Gateway\StepTextGateway;
use Laravel\Ai\Contracts\Providers\TextProvider;
use Laravel\Ai\Gateway\StepContext;
use Laravel\Ai\Gateway\StepResponse;
use Laravel\Ai\Gateway\TextGenerationOptions;
use Laravel\Ai\Responses\Data\FinishReason;
use Laravel\Ai\Responses\Data\Meta;
use Laravel\Ai\Responses\Data\Usage;

class MyInferenceGateway implements StepTextGateway
{
    public function __construct(protected Dispatcher $events) {}

    public function generateTextStep(
        TextProvider $provider,
        string $model,
        ?string $instructions,
        array $messages,
        array $tools,
        ?array $schema,
        ?TextGenerationOptions $options,
        ?int $timeout,
        StepContext $stepContext,
    ): StepResponse {
        $credentials = $provider->providerCredentials();
        $config      = $provider->additionalConfiguration();

        $response = Http::withToken($credentials['key'])
            ->baseUrl($config['url'])
            ->timeout($timeout ?? 30)
            ->post('/generate', [
                'model'        => $model,
                'instructions' => $instructions,
                'messages'     => $this->formatMessages($messages),
            ]);

        $data = $response->json();

        return new StepResponse(
            text: $data['output']['text'] ?? '',
            toolCalls: [],
            finishReason: FinishReason::Stop,
            usage: new Usage(
                promptTokens: $data['usage']['input_tokens'] ?? 0,
                completionTokens: $data['usage']['output_tokens'] ?? 0,
            ),
            meta: new Meta(
                provider: $provider->name(),
                model: $model,
            ),
        );
    }

    public function generateStreamStep(
        string $invocationId,
        TextProvider $provider,
        string $model,
        ?string $instructions,
        array $messages,
        array $tools,
        ?array $schema,
        ?TextGenerationOptions $options,
        ?int $timeout,
        StepContext $stepContext,
    ): Generator {
        // Implementación del streaming (omitida)
        yield from [];

        return null;
    }

    protected function formatMessages(array $messages): array
    {
        return array_map(fn ($message) => [
            'role'    => $message->role->value,
            'content' => $message->content,
        ], $messages);
    }
}
Si tu gateway soporta llamadas a herramientas (function calling), incluye los resultados de la llamada en toolCalls desde generateTextStep() y establece finishReason como FinishReason::ToolCalls. La ejecución de las herramientas y el paso al siguiente step los gestiona TextGenerationLoop automáticamente. Como referencia de implementación, consulta AnthropicGateway.php.

Cómo hacer pruebas

Usa fake() en la clase del agente

Para probar un agente que utiliza un proveedor personalizado, utiliza el método fake() de la clase del agente. Se instalará un gateway falso en el proveedor, independientemente de si se trata de un proveedor personalizado o no.
<?php

namespace Tests\Feature;

use App\Ai\Agents\SummaryAgent;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class SummaryAgentTest extends TestCase
{
    use RefreshDatabase;

    public function test_summary_agent_returns_text(): void
    {
        SummaryAgent::fake(['Este es el resumen.']);

        $response = SummaryAgent::make()->prompt('Texto de un artículo largo...', provider: 'my-inference');

        $this->assertEquals('Este es el resumen.', $response->text);

        SummaryAgent::assertPrompted('Texto de un artículo largo...');
    }
}
Desde 0.9, las respuestas de Agent::fake() pasan por el mismo TextGenerationLoop que un proveedor real. Si configuras una llamada falsa a una herramienta en un agente sin herramientas registradas, se lanzará NoSuchToolException.

Proveedor mock usando extend()

También puedes registrar un proveedor de prueba en el contenedor usando extend().
public function test_with_mock_provider(): void
{
    $this->app->make(AiManager::class)->extend(
        'my-inference',
        function (array $config) {
            $events = $this->app->make(\Illuminate\Contracts\Events\Dispatcher::class);

            return new MyInferenceProvider($config, $events);
        }
    );

    // Código de la prueba
}

Enlaces de referencia

OllamaProvider.php — ejemplo simple de implementación de un provider

Configuración mínima de un proveedor que se conecta a un servidor de modelos local. Es una buena referencia para implementar tu propio proveedor.

AnthropicGateway.php — ejemplo de implementación de gateway

Ejemplo de implementación de un gateway que implementa StepTextGateway. Puedes ver la implementación de generateTextStep() y generateStreamStep().

StepTextGateway Contract

Definición de la interfaz que implementan los gateways de generación de texto.

TextProvider Contract

Definición de la interfaz que implementan los proveedores de generación de texto.
Última modificación el 13 de julio de 2026