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

# Créer un fournisseur personnalisé pour l'AI SDK

> Découvrez le code source de Laravel AI SDK et apprenez à implémenter un fournisseur personnalisé pour prendre en charge des services d'IA non fournis en standard.

## Quand un fournisseur personnalisé est nécessaire

Laravel AI SDK prend en charge en standard les principaux services d'IA tels qu'OpenAI, Anthropic, Gemini et Mistral. Cependant, les fournisseurs standards ne suffisent pas dans les cas suivants :

* Un service d'IA émergent qui n'est pas encore officiellement pris en charge
* Vous voulez faire transiter les requêtes par une passerelle de modèles interne ou une couche de gestion de la facturation
* Un serveur d'inférence on-premise possédant son propre protocole ou mode d'authentification

Dans ces cas, implémentez un fournisseur personnalisé et enregistrez-le auprès de l'`AiManager` du SDK afin de pouvoir l'utiliser avec la même API que les fournisseurs standards.

<Info>
  **Pour les API compatibles OpenAI, utilisez le pilote intégré**

  Depuis la version 0.9 du SDK, un pilote `openai-compatible` est fourni en standard. Pour un serveur d'inférence interne compatible OpenAI (par exemple l'endpoint compatible OpenAI d'Ollama), il suffit d'ajouter la configuration dans `config/ai.php` pour l'utiliser, sans avoir besoin d'implémenter un fournisseur personnalisé.

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

## Vue d'ensemble de l'architecture

### Structure en deux couches

Laravel AI SDK est composé de deux couches : les fournisseurs (providers) et les passerelles (gateways).

| Couche       | Rôle                                                                                      | Exemples                                      |
| ------------ | ----------------------------------------------------------------------------------------- | --------------------------------------------- |
| **Provider** | Interface côté application. Résolution du nom de modèle, conservation de la configuration | `OpenAiProvider`, `AnthropicProvider`         |
| **Gateway**  | Envoie une seule étape de requête à l'API                                                 | `AnthropicGateway`, `OpenAiCompatibleGateway` |

Tous les fournisseurs héritent de la classe abstraite `Laravel\Ai\Providers\Provider` et implémentent les contrats (interfaces) correspondant à chaque fonctionnalité. Les boucles multi-étapes incluant des appels d'outils sont réalisées par `TextGenerationLoop`, qui appelle la passerelle de manière itérative.

### Liste des contrats

Implémentez uniquement les contrats correspondant aux fonctionnalités que vous souhaitez fournir.

| Contrat                 | Espace de noms                                         | Fonctionnalité                     |
| ----------------------- | ------------------------------------------------------ | ---------------------------------- |
| `TextProvider`          | `Laravel\Ai\Contracts\Providers\TextProvider`          | Génération de texte, agents        |
| `EmbeddingProvider`     | `Laravel\Ai\Contracts\Providers\EmbeddingProvider`     | Génération d'embeddings vectoriels |
| `ImageProvider`         | `Laravel\Ai\Contracts\Providers\ImageProvider`         | Génération d'images                |
| `AudioProvider`         | `Laravel\Ai\Contracts\Providers\AudioProvider`         | Synthèse vocale (TTS)              |
| `TranscriptionProvider` | `Laravel\Ai\Contracts\Providers\TranscriptionProvider` | Reconnaissance vocale (STT)        |

<Info>
  Dans la plupart des cas, implémenter uniquement `TextProvider` est suffisant.
</Info>

## Le contrat TextProvider

Voici l'interface qu'un fournisseur de génération de texte doit implémenter (`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'implémentation de `prompt()` et `stream()` peut être déléguée aux traits existants (`GeneratesText`, `StreamsText`), donc au total seules quatre méthodes doivent réellement être implémentées : les trois qui retournent les noms de modèles et `textGateway()`. La boucle multi-étapes d'outils est gérée par `TextGenerationLoop`, la passerelle ne traite donc qu'une seule étape de requête à la fois.

## Exemple d'implémentation : fournisseur personnalisé

Voici un exemple d'enregistrement d'un service d'inférence disposant d'une API propre en tant que fournisseur nommé `my-inference`.

<Steps>
  <Step title="Créer la classe du fournisseur">
    Créez `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="Enregistrer dans l'AppServiceProvider">
    Enregistrez-le en utilisant `extend()` dans la méthode `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="Ajouter le fournisseur dans config/ai.php">
    ```php theme={null}
    'providers' => [
        // ...fournisseurs existants...

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

    Ajoutez également les variables dans `.env`.

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

  <Step title="Utiliser depuis un agent">
    Une fois enregistré, il suffit de passer le nom du fournisseur à l'argument `provider` de `prompt()` pour l'utiliser comme un fournisseur standard.

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

    $response = SummaryAgent::make()->prompt('Veuillez résumer cet article.', provider: 'my-inference');

    echo $response->text;
    ```

    Pour l'utiliser comme fournisseur par défaut, modifiez la clé `default` de `config/ai.php`.

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

## Implémentation d'une passerelle personnalisée

Pour les services qui possèdent une API propre non compatible OpenAI, une passerelle personnalisée implémentant le contrat `StepTextGateway` est nécessaire.

### Le contrat StepTextGateway

Voici l'interface définie par `src/Contracts/Gateway/StepTextGateway.php`. La passerelle traite **une seule étape** de la conversation et retourne une `StepResponse`. La boucle d'appels d'outils est gérée par le `TextGenerationLoop` appelant.

```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>
  L'ancien contrat `TextGateway` (avec `generateText()`, `stream()`, `onToolInvocation()`) présent avant la 0.8 a été supprimé en 0.9. Si vous avez une passerelle personnalisée, vous devez migrer vers `StepTextGateway`. La méthode `onToolInvocation()` liée aux appels d'outils a été déplacée dans `TextGenerationLoop`.
</Warning>

### Exemple d'implémentation d'une passerelle personnalisée

Voici le squelette d'une passerelle simple qui envoie des requêtes HTTP à une API d'inférence propriétaire.

```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 {
        // Implémentation du streaming (omise)
        yield from [];

        return null;
    }

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

<Info>
  Pour prendre en charge les appels d'outils (function calling), incluez les résultats des appels d'outils dans `toolCalls` au sein de `generateTextStep()` et définissez `finishReason` à `FinishReason::ToolCalls`. L'exécution des outils et le passage à l'étape suivante sont gérés automatiquement par `TextGenerationLoop`. Reportez-vous à `AnthropicGateway.php` pour un exemple concret d'implémentation.
</Info>

## Comment tester

### Utiliser la méthode fake() de la classe d'agent

Pour tester un agent qui utilise un fournisseur personnalisé, utilisez la méthode `fake()` de la classe d'agent. Une passerelle factice est assignée au fournisseur, qu'il soit personnalisé ou non.

```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(['Ceci est un résumé.']);

        $response = SummaryAgent::make()->prompt('Texte du long article...', provider: 'my-inference');

        $this->assertEquals('Ceci est un résumé.', $response->text);

        SummaryAgent::assertPrompted('Texte du long article...');
    }
}
```

<Info>
  Depuis la 0.9, les réponses de `Agent::fake()` passent par le même `TextGenerationLoop` que le fournisseur réel. Si vous configurez un appel d'outil factice sur un agent qui n'a pas d'outils enregistrés, une exception `NoSuchToolException` sera levée.
</Info>

### Fournisseur mock avec extend()

Vous pouvez également enregistrer un fournisseur de test depuis le conteneur en utilisant `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);
        }
    );

    // Code de test
}
```

## Liens de référence

<Card title="OllamaProvider.php — Exemple d'implémentation simple d'un fournisseur" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Providers/OllamaProvider.php">
  Configuration minimale d'un fournisseur qui se connecte à un serveur de modèle local. Utile comme référence pour implémenter un fournisseur personnalisé.
</Card>

<Card title="AnthropicGateway.php — Exemple d'implémentation d'une passerelle" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Gateway/Anthropic/AnthropicGateway.php">
  Exemple d'implémentation d'une passerelle qui implémente `StepTextGateway`. Vous pouvez y examiner l'implémentation de `generateTextStep()` et `generateStreamStep()`.
</Card>

<Card title="Contrat StepTextGateway" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Contracts/Gateway/StepTextGateway.php">
  Définition de l'interface qu'une passerelle de génération de texte doit implémenter.
</Card>

<Card title="Contrat TextProvider" icon="github" href="https://github.com/laravel/ai/blob/0.x/src/Contracts/Providers/TextProvider.php">
  Définition de l'interface qu'un fournisseur de génération de texte doit implémenter.
</Card>


## Related topics

- [Créer un agent personnalisé pour Boost](/fr/advanced/boost-custom-agent.md)
- [Fournisseurs personnalisés](/fr/packages/laravel-copilot-sdk/custom-providers.md)
- [Contrats](/fr/contracts.md)
- [Pilote Amazon Bedrock pour Laravel AI SDK](/fr/packages/laravel-amazon-bedrock.md)
- [Démarrer - GitHub Copilot SDK pour Laravel](/fr/packages/laravel-copilot-sdk/getting-started.md)
