メインコンテンツへスキップ

Documentation Index

Fetch the complete documentation index at: https://kawax.biz/llms.txt

Use this file to discover all available pages before exploring further.

カスタムプロバイダーが必要な場面

Laravel AI SDKは OpenAI、Anthropic、Gemini、Mistral など主要なAIサービスを標準でサポートしています。しかし次のようなケースでは標準プロバイダーでは対応できません。
  • 社内に構築したオンプレミスの推論サーバー(OpenAI互換APIを持つものを含む)
  • まだ公式対応されていない新興のAIサービス
  • 社内のモデルゲートウェイや課金管理レイヤーを経由させたい
このような場合に、カスタムプロバイダーを実装してSDKの AiManager に登録することで、標準プロバイダーと同じAPIで利用できます。

アーキテクチャの概要

2層構造

Laravel AI SDKはプロバイダーとゲートウェイの2層で構成されています。
レイヤー役割
Providerアプリ側のインターフェース。モデル名の解決、設定の保持OpenAiProviderAnthropicProvider
Gateway実際のAPIリクエストを送信するPrismGatewayOpenAiGateway
すべてのプロバイダーは抽象クラス Laravel\Ai\Providers\Provider を継承し、機能ごとのコントラクト(インターフェース)を実装します。

コントラクト一覧

提供したい機能に応じて必要なコントラクトだけを実装します。
コントラクト名前空間機能
TextProviderLaravel\Ai\Contracts\Providers\TextProviderテキスト生成・エージェント
EmbeddingProviderLaravel\Ai\Contracts\Providers\EmbeddingProviderベクトル埋め込み生成
ImageProviderLaravel\Ai\Contracts\Providers\ImageProvider画像生成
AudioProviderLaravel\Ai\Contracts\Providers\AudioProvider音声合成(TTS)
TranscriptionProviderLaravel\Ai\Contracts\Providers\TranscriptionProvider音声認識(STT)
ほとんどの場合は TextProvider だけ実装すれば十分です。

TextProviderコントラクト

テキスト生成プロバイダーが実装するインターフェースです(src/Contracts/Providers/TextProvider.php)。
interface TextProvider
{
    public function prompt(AgentPrompt $prompt): AgentResponse;
    public function stream(AgentPrompt $prompt): StreamableAgentResponse;
    public function textGateway(): TextGateway;
    public function useTextGateway(TextGateway $gateway): self;
    public function defaultTextModel(): string;
    public function cheapestTextModel(): string;
    public function smartestTextModel(): string;
}
prompt()stream() の実装は既存のトレイト(GeneratesTextStreamsText)に任せられるため、実際に実装が必要なのはモデル名を返す3つのメソッドだけです。

実装例:OpenAI互換APIのカスタムプロバイダー

社内構築のOpenAI互換推論サーバーを my-inference というプロバイダーとして登録する例です。
1

プロバイダークラスを作成する

app/Ai/Providers/MyInferenceProvider.php を作成します。
<?php

declare(strict_types=1);

namespace App\Ai\Providers;

use Illuminate\Contracts\Events\Dispatcher;
use Laravel\Ai\Contracts\Providers\EmbeddingProvider;
use Laravel\Ai\Contracts\Providers\TextProvider;
use Laravel\Ai\Gateway\Prism\PrismGateway;
use Laravel\Ai\Providers\Concerns\GeneratesEmbeddings;
use Laravel\Ai\Providers\Concerns\GeneratesText;
use Laravel\Ai\Providers\Concerns\HasEmbeddingGateway;
use Laravel\Ai\Providers\Concerns\HasTextGateway;
use Laravel\Ai\Providers\Concerns\StreamsText;
use Laravel\Ai\Providers\Provider;

class MyInferenceProvider extends Provider implements EmbeddingProvider, TextProvider
{
    use GeneratesEmbeddings;
    use GeneratesText;
    use HasEmbeddingGateway;
    use HasTextGateway;
    use StreamsText;

    public function __construct(array $config, Dispatcher $events)
    {
        parent::__construct(new PrismGateway($events), $config, $events);
    }

    public function defaultTextModel(): string
    {
        return $this->config['models']['text']['default'] ?? 'llama3.3-70b';
    }

    public function cheapestTextModel(): string
    {
        return $this->config['models']['text']['cheapest'] ?? 'llama3.2-3b';
    }

    public function smartestTextModel(): string
    {
        return $this->config['models']['text']['smartest'] ?? 'llama3.3-70b';
    }

    public function defaultEmbeddingsModel(): string
    {
        return $this->config['models']['embeddings']['default'] ?? 'nomic-embed-text';
    }

    public function defaultEmbeddingsDimensions(): int
    {
        return $this->config['models']['embeddings']['dimensions'] ?? 768;
    }
}
PrismGatewayPrism を使いOpenAI互換APIに対応しています。独自の非互換APIを持つサービスには、後述するカスタムゲートウェイを使います。
2

AppServiceProviderに登録する

App\Providers\AppServiceProviderboot メソッドで 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

config/ai.phpにプロバイダーを追加する

'providers' => [
    // ...既存のプロバイダー...

    'my-inference' => [
        'driver' => 'my-inference',
        'key'    => env('MY_INFERENCE_API_KEY'),
        'url'    => env('MY_INFERENCE_URL', 'http://localhost:8080/v1'),
    ],
],
.env にも追加します。
MY_INFERENCE_API_KEY=your-api-key
MY_INFERENCE_URL=https://inference.example.internal/v1
4

エージェントから使う

登録後は prompt()provider 引数にプロバイダー名を指定するだけで標準プロバイダーと同じように使えます。
use App\Ai\Agents\SummaryAgent;

$response = SummaryAgent::make()->prompt('この記事を要約してください。', provider: 'my-inference');

echo $response->text;
デフォルトのプロバイダーとして使う場合は config/ai.phpdefault キーを変更します。
'default' => 'my-inference',

カスタムゲートウェイの実装

OpenAI互換ではない独自APIを持つサービスには、TextGateway コントラクトを実装したカスタムゲートウェイが必要です。

TextGatewayコントラクト

src/Contracts/Gateway/TextGateway.php が定義するインターフェースです。
interface TextGateway
{
    public function generateText(
        Provider $provider,
        ?string $model,
        string $systemPrompt,
        array $messages,
        array $tools,
        ?ObjectSchema $schema,
        TextGenerationOptions $options,
        ?int $timeout,
    ): GatewayResponse;

    public function stream(
        Provider $provider,
        ?string $model,
        string $systemPrompt,
        array $messages,
        array $tools,
        ?ObjectSchema $schema,
        TextGenerationOptions $options,
        ?int $timeout,
    ): Generator;

    public function onToolInvocation(
        Closure $invoking,
        Closure $invoked,
    ): void;
}

カスタムゲートウェイの実装例

独自の推論APIにHTTPリクエストを送るシンプルなゲートウェイの骨格です。
<?php

declare(strict_types=1);

namespace App\Ai\Gateway;

use Closure;
use Generator;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Support\Facades\Http;
use Laravel\Ai\Contracts\Gateway\TextGateway;
use Laravel\Ai\Gateway\TextGenerationOptions;
use Laravel\Ai\ObjectSchema;
use Laravel\Ai\Providers\Provider;
use Laravel\Ai\Responses\GatewayResponse;
use Laravel\Ai\Responses\Usage;

class MyInferenceGateway implements TextGateway
{
    protected ?Closure $onInvoking = null;
    protected ?Closure $onInvoked = null;

    public function generateText(
        Provider $provider,
        ?string $model,
        string $systemPrompt,
        array $messages,
        array $tools,
        ?ObjectSchema $schema,
        TextGenerationOptions $options,
        ?int $timeout,
    ): GatewayResponse {
        $credentials = $provider->providerCredentials();
        $config      = $provider->additionalConfiguration();

        $response = Http::withToken($credentials['key'])
            ->baseUrl($config['url'])
            ->post('/chat/completions', [
                'model'    => $model ?? $provider->defaultTextModel(),
                'messages' => $this->formatMessages($systemPrompt, $messages),
            ]);

        $data = $response->json();

        return new GatewayResponse(
            text: $data['choices'][0]['message']['content'] ?? '',
            usage: new Usage(
                inputTokens: $data['usage']['prompt_tokens'] ?? 0,
                outputTokens: $data['usage']['completion_tokens'] ?? 0,
            ),
        );
    }

    public function stream(
        Provider $provider,
        ?string $model,
        string $systemPrompt,
        array $messages,
        array $tools,
        ?ObjectSchema $schema,
        TextGenerationOptions $options,
        ?int $timeout,
    ): Generator {
        // ストリーミングの実装(省略)
        yield '';
    }

    public function onToolInvocation(Closure $invoking, Closure $invoked): void
    {
        $this->onInvoking = $invoking;
        $this->onInvoked  = $invoked;
    }

    protected function formatMessages(string $systemPrompt, array $messages): array
    {
        $formatted = [['role' => 'system', 'content' => $systemPrompt]];

        foreach ($messages as $message) {
            $formatted[] = [
                'role'    => $message->role->value,
                'content' => $message->content,
            ];
        }

        return $formatted;
    }
}
カスタムゲートウェイを使うようにプロバイダーを修正します。
public function __construct(
    protected array $config,
    protected Dispatcher $events,
) {}

/**
 * Get the provider's text gateway.
 */
public function textGateway(): TextGateway
{
    return $this->textGateway ??= new MyInferenceGateway;
}
ツール呼び出し(function calling)をサポートする場合は generateText() の中でツールの実行と再帰的なメッセージ送信も実装が必要です。PrismGateway の実装(src/Gateway/Prism/PrismGateway.php)を参照してください。

テスト方法

エージェントクラスの fake() を使う

カスタムプロバイダーを使うエージェントのテストには、エージェントクラスの fake() メソッドを使います。カスタムプロバイダーかどうかに関係なく、フェイクゲートウェイがプロバイダーにセットされます。
<?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(['これは要約です。']);

        $response = SummaryAgent::make()->prompt('長い記事のテキスト...', provider: 'my-inference');

        $this->assertEquals('これは要約です。', $response->text);

        SummaryAgent::assertPrompted('長い記事のテキスト...');
    }
}

extend() を使ったモックプロバイダー

extend() を使って、テスト用のプロバイダーをコンテナから登録することもできます。
public function test_with_mock_provider(): void
{
    $this->app->make(AiManager::class)->extend(
        'my-inference',
        function (array $config) {
            $gateway = $this->app->make(\Laravel\Ai\Gateway\Prism\PrismGateway::class);
            $events  = $this->app->make(\Illuminate\Contracts\Events\Dispatcher::class);

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

    // テストコード
}

参考リンク

OllamaProvider.php — シンプルな実装例

ローカルモデルサーバーに接続するプロバイダーの最小構成です。カスタムプロバイダー実装の参考になります。

PrismGateway.php — ゲートウェイの実装例

ツール呼び出しやストリーミングを含む本格的なゲートウェイの実装です。

TextProvider Contract

テキスト生成プロバイダーが実装するインターフェースの定義です。
Last modified on April 9, 2026