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

# Creare un provider personalizzato per l'AI SDK

> Analizza il codice sorgente del Laravel AI SDK e implementa un provider personalizzato per servizi AI non supportati di default.

## Quando serve un provider personalizzato

Laravel AI SDK supporta di default i principali servizi AI (OpenAI, Anthropic, Gemini, Mistral). Ma nei casi seguenti i provider standard non bastano.

* Servizi AI emergenti non ancora ufficialmente supportati
* Vuoi passare attraverso un gateway di modelli interno o un livello di gestione fatturazione aziendale
* Server di inferenza on-premise con protocolli o metodi di autenticazione proprietari

In queste situazioni implementi un provider personalizzato e lo registri nel `AiManager` dell'SDK: potrai poi usarlo con la stessa API dei provider standard.

<Info>
  **Per API OpenAI-compatibili usa il driver integrato**

  Da SDK 0.9 è disponibile il driver `openai-compatible`. Per server di inferenza interni OpenAI-compatibili (per esempio l'endpoint OpenAI-compatibile di Ollama) basta aggiungere una voce a `config/ai.php` senza implementare un provider custom.

  ```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>

## Panoramica dell'architettura

### Struttura a due livelli

Laravel AI SDK è composto da due livelli: Provider e Gateway.

| Layer        | Ruolo                                                                                   | Esempi                                        |
| ------------ | --------------------------------------------------------------------------------------- | --------------------------------------------- |
| **Provider** | Interfaccia lato app. Risoluzione del nome modello e conservazione della configurazione | `OpenAiProvider`, `AnthropicProvider`         |
| **Gateway**  | Invia la singola richiesta API concreta                                                 | `AnthropicGateway`, `OpenAiCompatibleGateway` |

Tutti i provider estendono la classe astratta `Laravel\Ai\Providers\Provider` e implementano i contract (interfacce) delle funzionalità che offrono. Il loop multi-step con chiamata di tool è realizzato da `TextGenerationLoop` che invoca ripetutamente il gateway.

### Elenco dei contract

Implementi solo i contract corrispondenti alle funzionalità che vuoi offrire.

| Contract                | Namespace                                              | Funzionalità                    |
| ----------------------- | ------------------------------------------------------ | ------------------------------- |
| `TextProvider`          | `Laravel\Ai\Contracts\Providers\TextProvider`          | Generazione di testo e agenti   |
| `EmbeddingProvider`     | `Laravel\Ai\Contracts\Providers\EmbeddingProvider`     | Generazione di vector embedding |
| `ImageProvider`         | `Laravel\Ai\Contracts\Providers\ImageProvider`         | Generazione di immagini         |
| `AudioProvider`         | `Laravel\Ai\Contracts\Providers\AudioProvider`         | Sintesi vocale (TTS)            |
| `TranscriptionProvider` | `Laravel\Ai\Contracts\Providers\TranscriptionProvider` | Riconoscimento vocale (STT)     |

<Info>
  Nella maggior parte dei casi implementare solo `TextProvider` è sufficiente.
</Info>

## Il contract TextProvider

Interfaccia che i provider di generazione testo implementano (`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;
}
```

L'implementazione di `prompt()` e `stream()` può essere delegata ai trait esistenti (`GeneratesText`, `StreamsText`), quindi in pratica devi implementare quattro metodi: i tre che restituiscono i nomi dei modelli e `textGateway()`. Il loop multi-step per i tool è gestito da `TextGenerationLoop`: il gateway elabora solo la richiesta di un singolo step.

## Esempio di implementazione: provider personalizzato

Esempio di registrazione di un servizio di inferenza proprietario con un provider chiamato `my-inference`.

<Steps>
  <Step title="Creare la classe provider">
    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="Registrarlo in AppServiceProvider">
    Nel metodo `boot` di `App\Providers\AppServiceProvider` registra il provider con `extend()`.

    ```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="Aggiungere il provider a config/ai.php">
    ```php theme={null}
    'providers' => [
        // ...provider esistenti...

        'my-inference' => [
            'driver' => 'my-inference',
            'key'    => env('MY_INFERENCE_API_KEY'),
            'url'    => env('MY_INFERENCE_URL', 'http://localhost:8080'),
        ],
    ],
    ```

    Aggiungi anche in `.env`.

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

  <Step title="Utilizzo da un agente">
    Dopo la registrazione, basta indicare il nome del provider nell'argomento `provider` di `prompt()` per usarlo come qualsiasi altro provider standard.

    ```php theme={null}
    use App\Ai\Agents\SummaryAgent;

    $response = SummaryAgent::make()->prompt('Riassumi questo articolo.', provider: 'my-inference');

    echo $response->text;
    ```

    Per usarlo come provider di default, cambia la chiave `default` in `config/ai.php`.

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

## Implementazione di un gateway personalizzato

Per servizi con API proprietarie non OpenAI-compatibili ti serve un gateway custom che implementi il contract `StepTextGateway`.

### Il contract StepTextGateway

Interfaccia definita in `src/Contracts/Gateway/StepTextGateway.php`. Il gateway gestisce la richiesta di **un solo step** della conversazione e restituisce uno `StepResponse`. Il loop delle chiamate di tool è gestito dal `TextGenerationLoop` chiamante.

```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>
  Il vecchio contract `TextGateway` (`generateText()`, `stream()`, `onToolInvocation()`) presente fino alla 0.8 è stato rimosso nella 0.9. Se hai gateway custom devi migrare a `StepTextGateway`. `onToolInvocation()` è stato spostato in `TextGenerationLoop`.
</Warning>

### Esempio di implementazione di un gateway custom

Scheletro di un gateway semplice che invia richieste HTTP a un'API di inferenza proprietaria.

```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 {
        // implementazione dello streaming (omessa)
        yield from [];

        return null;
    }

    protected function formatMessages(array $messages): array
    {
        return array_map(fn ($message) => [
            'role'    => $message->role->value,
            'content' => $message->content,
        ], $messages);
    }
}
```

<Info>
  Se vuoi supportare la chiamata di tool (function calling), in `generateTextStep()` includi le tool call in `toolCalls` e imposta `finishReason` su `FinishReason::ToolCalls`. L'esecuzione dei tool e il passaggio allo step successivo sono gestiti automaticamente da `TextGenerationLoop`. Come riferimento consulta `AnthropicGateway.php`.
</Info>

## Come testare

### Usare fake() sulla classe Agent

Per testare un agente che usa un provider custom, usa il metodo `fake()` della classe Agent. Indipendentemente dal fatto che sia custom o meno, un gateway fake viene assegnato al provider.

```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(['Questo è un riassunto.']);

        $response = SummaryAgent::make()->prompt('Testo di un articolo lungo...', provider: 'my-inference');

        $this->assertEquals('Questo è un riassunto.', $response->text);

        SummaryAgent::assertPrompted('Testo di un articolo lungo...');
    }
}
```

<Info>
  Da 0.9, le risposte di `Agent::fake()` passano attraverso lo stesso `TextGenerationLoop` del provider reale. Se imposti una chiamata di tool fake su un agente che non registra tool, viene sollevata `NoSuchToolException`.
</Info>

### Provider mock con extend()

Puoi anche registrare un provider di test tramite il container 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);
        }
    );

    // codice del test
}
```

## Link di riferimento

<Card title="OllamaProvider.php — esempio semplice di provider" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Providers/OllamaProvider.php">
  Configurazione minima di un provider che si collega a un model server locale. Utile come riferimento per implementare provider custom.
</Card>

<Card title="AnthropicGateway.php — esempio di gateway" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Gateway/Anthropic/AnthropicGateway.php">
  Esempio di gateway che implementa `StepTextGateway`. Puoi vedere l'implementazione di `generateTextStep()` e `generateStreamStep()`.
</Card>

<Card title="Contract StepTextGateway" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Contracts/Gateway/StepTextGateway.php">
  Definizione dell'interfaccia che un text generation gateway deve implementare.
</Card>

<Card title="Contract TextProvider" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Contracts/Providers/TextProvider.php">
  Definizione dell'interfaccia che un text generation provider deve implementare.
</Card>


## Related topics

- [Creare un agente personalizzato per Boost](/it/advanced/boost-custom-agent.md)
- [Implementare un guard di autenticazione personalizzato](/it/advanced/custom-auth-guard.md)
- [Service provider](/it/service-providers.md)
- [Come creare uno starter kit per Laravel](/it/advanced/starter-kit-creation.md)
- [Laravel AI SDK](/it/ai-sdk.md)
