Vai al contenuto principale

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.
Per API OpenAI-compatibili usa il driver integratoDa 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.
'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'],
    ],
],

Panoramica dell’architettura

Struttura a due livelli

Laravel AI SDK è composto da due livelli: Provider e Gateway.
LayerRuoloEsempi
ProviderInterfaccia lato app. Risoluzione del nome modello e conservazione della configurazioneOpenAiProvider, AnthropicProvider
GatewayInvia la singola richiesta API concretaAnthropicGateway, 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.
ContractNamespaceFunzionalità
TextProviderLaravel\Ai\Contracts\Providers\TextProviderGenerazione di testo e agenti
EmbeddingProviderLaravel\Ai\Contracts\Providers\EmbeddingProviderGenerazione di vector embedding
ImageProviderLaravel\Ai\Contracts\Providers\ImageProviderGenerazione di immagini
AudioProviderLaravel\Ai\Contracts\Providers\AudioProviderSintesi vocale (TTS)
TranscriptionProviderLaravel\Ai\Contracts\Providers\TranscriptionProviderRiconoscimento vocale (STT)
Nella maggior parte dei casi implementare solo TextProvider è sufficiente.

Il contract TextProvider

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

Creare la classe provider

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

Registrarlo in AppServiceProvider

Nel metodo boot di App\Providers\AppServiceProvider registra il provider con extend().
<?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

Aggiungere il provider a config/ai.php

'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.
MY_INFERENCE_API_KEY=your-api-key
MY_INFERENCE_URL=https://inference.example.internal
4

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.
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.
'default' => 'my-inference',

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

Esempio di implementazione di un gateway custom

Scheletro di un gateway semplice che invia richieste HTTP a un’API di inferenza proprietaria.
<?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);
    }
}
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.

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

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

Provider mock con extend()

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

    // codice del test
}

OllamaProvider.php — esempio semplice di provider

Configurazione minima di un provider che si collega a un model server locale. Utile come riferimento per implementare provider custom.

AnthropicGateway.php — esempio di gateway

Esempio di gateway che implementa StepTextGateway. Puoi vedere l’implementazione di generateTextStep() e generateStreamStep().

Contract StepTextGateway

Definizione dell’interfaccia che un text generation gateway deve implementare.

Contract TextProvider

Definizione dell’interfaccia che un text generation provider deve implementare.
Ultima modifica il 13 luglio 2026