何时需要自定义 Provider
Laravel AI SDK 内置支持 OpenAI、Anthropic、Gemini、Mistral 等主流 AI 服务。然而在以下场景,标准 Provider 无法满足需求:
尚未获得官方支持的新兴 AI 服务
想要经过公司内部的模型网关或计费管理层
拥有独立协议或认证方式的本地部署推理服务器
在这些情况下,你可以实现自定义 Provider 并注册到 SDK 的 AiManager,就能像标准 Provider 一样通过同一 API 使用它。
OpenAI 兼容 API 请使用内置驱动 从 SDK 0.9 开始,openai-compatible 驱动已内置提供。对于公司内部的 OpenAI 兼容推理服务器(例如 Ollama 的 OpenAI 兼容端点),只需在 config/ai.php 中添加配置即可使用,无需实现自定义 Provider。 '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' ],
],
],
架构概览
两层结构
Laravel AI SDK 由 Provider 与 Gateway 两层构成。
层 职责 示例 Provider 面向应用的接口。负责模型名解析与配置的持有 OpenAiProvider、AnthropicProviderGateway 发送一个步骤的实际 API 请求 AnthropicGateway、OpenAiCompatibleGateway
所有 Provider 都继承抽象类 Laravel\Ai\Providers\Provider,并按功能实现对应的契约(接口)。包含工具调用的多步骤循环由 TextGenerationLoop 反复调用 Gateway 来实现。
契约一览
根据要支持的功能,实现所需的契约即可。
契约 命名空间 功能 TextProviderLaravel\Ai\Contracts\Providers\TextProvider文本生成、Agent EmbeddingProviderLaravel\Ai\Contracts\Providers\EmbeddingProvider向量嵌入生成 ImageProviderLaravel\Ai\Contracts\Providers\ImageProvider图像生成 AudioProviderLaravel\Ai\Contracts\Providers\AudioProvider语音合成(TTS) TranscriptionProviderLaravel\Ai\Contracts\Providers\TranscriptionProvider语音识别(STT)
多数情况下只需实现 TextProvider 即可。
TextProvider 契约
这是文本生成 Provider 所要实现的接口(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 ;
}
prompt() 与 stream() 的实现可交由现有 trait(GeneratesText、StreamsText)处理,因此实际需要实现的只有返回模型名的 3 个方法以及 textGateway(),合计 4 个方法。多步骤的工具循环由 TextGenerationLoop 管理,Gateway 只需处理一步的请求。
实现示例:自定义 Provider
以下示例将一个拥有独立 API 的推理服务作为 my-inference Provider 进行注册。
创建 Provider 类
创建 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' ;
}
}
在 AppServiceProvider 中注册
在 App\Providers\AppServiceProvider 的 boot 方法中使用 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 )
)
);
}
}
在 config/ai.php 中添加 Provider
'providers' => [
// ...已有的 Provider...
'my-inference' => [
'driver' => 'my-inference' ,
'key' => env ( 'MY_INFERENCE_API_KEY' ),
'url' => env ( 'MY_INFERENCE_URL' , 'http://localhost:8080' ),
],
],
同时在 .env 中添加: MY_INFERENCE_API_KEY =your-api-key
MY_INFERENCE_URL =https://inference.example.internal
在 Agent 中使用
注册后,只需将 Provider 名称传给 prompt() 的 provider 参数,就能像使用标准 Provider 一样使用它。 use App\Ai\Agents\ SummaryAgent ;
$response = SummaryAgent :: make () -> prompt ( '请总结这篇文章。' , provider : 'my-inference' );
echo $response -> text ;
若要将其作为默认 Provider,请修改 config/ai.php 的 default 键。 'default' => 'my-inference' ,
自定义 Gateway 的实现
对于并非 OpenAI 兼容、拥有独立 API 的服务,需要实现 StepTextGateway 契约的自定义 Gateway。
StepTextGateway 契约
以下是 src/Contracts/Gateway/StepTextGateway.php 定义的接口。Gateway 负责处理会话的单步 请求并返回 StepResponse,工具调用循环由调用方的 TextGenerationLoop 管理。
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 ;
}
0.8 之前的 TextGateway 契约(generateText()、stream()、onToolInvocation())已在 0.9 中移除。如果你拥有自定义 Gateway,需要迁移到 StepTextGateway。工具调用的 onToolInvocation() 已被移动到 TextGenerationLoop。
自定义 Gateway 实现示例
以下是向自定义推理 API 发起 HTTP 请求的简单 Gateway 骨架。
<? 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 {
// 流式实现(略)
yield from [];
return null ;
}
protected function formatMessages ( array $messages ) : array
{
return array_map ( fn ( $message ) => [
'role' => $message -> role -> value ,
'content' => $message -> content ,
], $messages );
}
}
如需支持工具调用(function calling),请在 generateTextStep() 中将工具的调用结果加入 toolCalls,并将 finishReason 设为 FinishReason::ToolCalls。工具的执行以及进入下一步的过渡由 TextGenerationLoop 自动处理。实现参考可查看 AnthropicGateway.php。
测试方法
使用 Agent 类的 fake()
要测试使用自定义 Provider 的 Agent,可使用 Agent 类的 fake() 方法。不论是否是自定义 Provider,都会将 fake Gateway 注入到 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 ([ '这是摘要。' ]);
$response = SummaryAgent :: make () -> prompt ( '长篇文章的文本...' , provider : 'my-inference' );
$this -> assertEquals ( '这是摘要。' , $response -> text );
SummaryAgent :: assertPrompted ( '长篇文章的文本...' );
}
}
从 0.9 开始,Agent::fake() 的响应会经过与真实 Provider 相同的 TextGenerationLoop。如果在没有注册工具的 Agent 上设置了 fake 工具调用,会抛出 NoSuchToolException。
使用 extend() 的 Mock Provider
也可以通过 extend() 从容器注册测试用 Provider。
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 );
}
);
// 测试代码
}
参考链接
OllamaProvider.php — 简单的 Provider 实现示例 连接本地模型服务器的 Provider 最小实现。可作为自定义 Provider 实现的参考。
AnthropicGateway.php — Gateway 实现示例 实现了 StepTextGateway 的 Gateway 示例。可查看 generateTextStep() 与 generateStreamStep() 的实现。
StepTextGateway Contract 文本生成 Gateway 所要实现的接口定义。
TextProvider Contract 文本生成 Provider 所要实现的接口定义。