> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Crear un proveedor personalizado del AI SDK

> Lee el código fuente de Laravel AI SDK y aprende a implementar un proveedor personalizado para servicios de IA que no vienen incluidos por defecto.

## 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.

<Info>
  **Para APIs compatibles con OpenAI, usa el driver integrado**

  Desde 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.

  ```php theme={null}
  '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'],
      ],
  ],
  ```
</Info>

## Descripción general de la arquitectura

### Estructura de dos capas

Laravel AI SDK está compuesto por dos capas: proveedores (providers) y gateways.

| Capa         | Rol                                                                                            | Ejemplo                                       |
| ------------ | ---------------------------------------------------------------------------------------------- | --------------------------------------------- |
| **Provider** | Interfaz del lado de la aplicación. Resuelve el nombre del modelo y conserva la configuración. | `OpenAiProvider`, `AnthropicProvider`         |
| **Gateway**  | Enví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.

| Contrato                | Namespace                                              | Funcionalidad                        |
| ----------------------- | ------------------------------------------------------ | ------------------------------------ |
| `TextProvider`          | `Laravel\Ai\Contracts\Providers\TextProvider`          | Generación de texto y agentes        |
| `EmbeddingProvider`     | `Laravel\Ai\Contracts\Providers\EmbeddingProvider`     | Generación de embeddings vectoriales |
| `ImageProvider`         | `Laravel\Ai\Contracts\Providers\ImageProvider`         | Generación de imágenes               |
| `AudioProvider`         | `Laravel\Ai\Contracts\Providers\AudioProvider`         | Síntesis de voz (TTS)                |
| `TranscriptionProvider` | `Laravel\Ai\Contracts\Providers\TranscriptionProvider` | Reconocimiento de voz (STT)          |

<Info>
  En la mayoría de los casos, basta con implementar `TextProvider`.
</Info>

## Contrato TextProvider

Es la interfaz que implementan los proveedores de generación de texto (`src/Contracts/Providers/TextProvider.php`).

```php theme={null}
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`.

<Steps>
  <Step title="Crea la clase del proveedor">
    Crea `app/Ai/Providers/MyInferenceProvider.php`.

    ```php theme={null}
    <?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';
        }
    }
    ```
  </Step>

  <Step title="Regístralo en el AppServiceProvider">
    Regístralo con `extend()` en el método `boot` de `App\Providers\AppServiceProvider`.

    ```php theme={null}
    <?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)
                )
            );
        }
    }
    ```
  </Step>

  <Step title="Añade el proveedor a config/ai.php">
    ```php theme={null}
    '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`.

    ```ini theme={null}
    MY_INFERENCE_API_KEY=your-api-key
    MY_INFERENCE_URL=https://inference.example.internal
    ```
  </Step>

  <Step title="Ú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.

    ```php theme={null}
    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`.

    ```php theme={null}
    'default' => 'my-inference',
    ```
  </Step>
</Steps>

## 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.

```php theme={null}
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;
}
```

<Warning>
  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`.
</Warning>

### 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 theme={null}
<?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);
    }
}
```

<Info>
  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`.
</Info>

## 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 theme={null}
<?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...');
    }
}
```

<Info>
  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`.
</Info>

### Proveedor mock usando extend()

También puedes registrar un proveedor de prueba en el contenedor usando `extend()`.

```php theme={null}
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

<Card title="OllamaProvider.php — ejemplo simple de implementación de un provider" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Providers/OllamaProvider.php">
  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.
</Card>

<Card title="AnthropicGateway.php — ejemplo de implementación de gateway" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Gateway/Anthropic/AnthropicGateway.php">
  Ejemplo de implementación de un gateway que implementa `StepTextGateway`. Puedes ver la implementación de `generateTextStep()` y `generateStreamStep()`.
</Card>

<Card title="StepTextGateway Contract" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Contracts/Gateway/StepTextGateway.php">
  Definición de la interfaz que implementan los gateways de generación de texto.
</Card>

<Card title="TextProvider Contract" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Contracts/Providers/TextProvider.php">
  Definición de la interfaz que implementan los proveedores de generación de texto.
</Card>


## Related topics

- [Laravel Socialite (autenticación social)](/es/socialite.md)
- [Proveedores personalizados](/es/packages/laravel-copilot-sdk/custom-providers.md)
- [Crear un agente personalizado de Boost](/es/advanced/boost-custom-agent.md)
- [Laravel AI SDK](/es/ai-sdk.md)
- [Implementación de un guard de autenticación personalizado](/es/advanced/custom-auth-guard.md)
