Laravel AI SDK
简介
Laravel AI SDK 提供了一个统一的、富有表现力的 API,用于与 OpenAI、Anthropic、Gemini 等 AI 提供商进行交互。借助 AI SDK,你可以使用工具和结构化输出构建智能 agent、生成图像、合成和转录音频、创建向量嵌入等等 - 所有这些都使用一致的、Laravel 友好的界面。
安装
你可以通过 Composer 安装 Laravel AI SDK:
composer require laravel/ai接下来,你应该使用 vendor:publish Artisan 命令发布 AI SDK 配置和迁移文件:
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"最后,你应该运行应用程序的数据库迁移。这将创建一个 agent_conversations 和 agent_conversation_messages 表,AI SDK 使用它们来支持其对话存储:
php artisan migrate配置
你可以在应用程序的 config/ai.php 配置文件中定义 AI 提供商凭据,或者在应用程序的 .env 文件中定义为环境变量:
ANTHROPIC_API_KEY=
AZURE_OPENAI_API_KEY=
COHERE_API_KEY=
DEEPSEEK_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
GROQ_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_KEY=
OPENAI_API_KEY=
OPENAI_COMPATIBLE_API_KEY=
OPENAI_COMPATIBLE_URL=
OPENROUTER_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=用于文本、图像、音频、转录和嵌入的默认模型也可以在应用程序的 config/ai.php 配置文件中进行配置。
自定义 Base URL
默认情况下,Laravel AI SDK 直接连接到每个提供商的公共 API 端点。但是,你可能需要通过不同的端点路由请求 - 例如,当使用代理服务集中 API 密钥管理、实施速率限制或通过公司网关路由流量时。
你可以通过在提供商配置中添加 url 参数来配置自定义基本 URL:
'providers' => [
'openai' => [
'driver' => 'openai',
'key' => env('OPENAI_API_KEY'),
'url' => env('OPENAI_URL'),
],
'anthropic' => [
'driver' => 'anthropic',
'key' => env('ANTHROPIC_API_KEY'),
'url' => env('ANTHROPIC_BASE_URL'),
],
],当通过代理服务(例如 LiteLLM 或 Azure OpenAI 网关)路由请求或使用替代终结点时,这非常有用。
以下提供商支持自定义基本 URL:OpenAI、Anthropic、Gemini、Groq、Cohere、DeepSeek、xAI 和 OpenRouter。
OpenAI 兼容提供商
如果你使用 OpenAI 兼容的 API,例如 LM Studio、vLLM、Together、Fireworks 或本地网关,你可以配置 openai-compatible 提供程序。 url 选项是必需的,而 key 选项是可选的,如果存在,将作为不记名令牌发送:
'providers' => [
'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
],
],配置完成后,你可以像任何其他提供程序一样使用指定的提供程序:
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');你还可以为提供程序配置默认文本模型,这样你就不需要显式传递模型:
'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'models' => [
'text' => [
'default' => env('LOCAL_AI_MODEL'),
],
],
],你可以通过在其配置中定义 headers 数组,将自定义 HTTP 标头添加到提供程序的每个传出请求中。当端点需要除承载令牌之外的附加标识或身份验证标头时,这非常有用:
'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'headers' => [
'X-Tenant-Id' => env('LOCAL_AI_TENANT_ID'),
],
],兼容 OpenAI 的提供程序支持文本生成、流式传输、工具、结构化输出、图像附件、嵌入和转录。如果你的端点需要额外的请求正文字段,请使用 provider options 提供。
OpenAI 兼容嵌入
由于任意端点没有已知模型,因此你必须配置默认嵌入模型才能将 embeddings() 与 OpenAI 兼容的提供程序一起使用。你还可以配置固定尺寸值;如果省略,则发送请求时不带 dimensions 参数,并使用模型的原生尺寸。
'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'models' => [
'embeddings' => [
'default' => 'text-embedding-qwen3-embedding-0.6b',
'dimensions' => 1024, // optional
],
],
],OpenAI 兼容转录
同样,你必须配置默认转录模型才能将 Transcription 与 OpenAI 兼容的提供程序一起使用。音频将作为标准多部分请求上传到端点的 /audio/transcriptions 路由:
'local' => [
'driver' => 'openai-compatible',
'url' => env('LOCAL_AI_URL'),
'key' => env('LOCAL_AI_API_KEY'),
'models' => [
'transcription' => [
'default' => 'whisper-1',
],
],
],INFO
OpenAI 兼容和 Groq 提供商不支持二值化。使用这些提供程序时调用 diarize 方法将引发异常。
提供商支持
AI SDK 支持各种提供商的功能。下表总结了每个功能可用的提供程序:
| Feature | Providers |
|---|---|
| Text | OpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter |
| Images | OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter |
| TTS | OpenAI, ElevenLabs, Gemini, Mistral |
| STT | OpenAI, OpenAI Compatible, ElevenLabs, Groq, Mistral, Gemini |
| Embeddings | OpenAI, OpenAI Compatible, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter |
| Reranking | Cohere, Jina, VoyageAI, Bedrock |
| Files | OpenAI, Anthropic, Gemini, Azure |
Laravel\Ai\Enums\Lab 枚举可用于在整个代码中引用提供程序,而不是使用纯字符串:
use Laravel\Ai\Enums\Lab;
Lab::Anthropic;
Lab::OpenAI;
Lab::OpenAiCompatible;
Lab::Gemini;
// ...Agent
代理是与 Laravel AI SDK 中的 AI 提供者交互的基本构建块。每个代理都是一个专用的 PHP 类,封装了与大型语言模型交互所需的指令、对话上下文、工具和输出模式。将代理视为专业助理 - 销售教练、文档分析器、支持机器人 - 你只需配置一次即可在整个应用程序中根据需要进行提示。
你可以通过 make:agent Artisan 命令创建代理:
php artisan make:agent SalesCoach
php artisan make:agent SalesCoach --structured在生成的代理类中,你可以定义系统提示/说明、消息上下文、可用工具和输出架构(如果适用):
<?php
namespace App\Ai\Agents;
use App\Ai\Tools\RetrievePreviousTranscripts;
use App\Models\History;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;
class SalesCoach implements Agent, Conversational, HasTools, HasStructuredOutput
{
use Promptable;
public function __construct(public User $user) {}
/**
* Get the instructions that the agent should follow.
*/
public function instructions(): Stringable|string
{
return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
}
/**
* Get the list of messages comprising the conversation so far.
*/
public function messages(): iterable
{
return History::where('user_id', $this->user->id)
->latest()
->limit(50)
->get()
->reverse()
->map(function ($message) {
return new Message($message->role, $message->content);
})->all();
}
/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new RetrievePreviousTranscripts,
];
}
/**
* Get the agent's structured output schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'feedback' => $schema->string()->required(),
'score' => $schema->integer()->min(1)->max(10)->required(),
];
}
}提示
要提示代理,首先使用 make 方法或标准实例化创建一个实例,然后调用 prompt:
$response = (new SalesCoach)
->prompt('Analyze this sales transcript...');
return (string) $response;make 方法从容器解析你的代理,从而允许自动依赖项注入。你还可以将参数传递给代理的构造函数:
$agent = SalesCoach::make(user: $user);通过将其他参数传递给 prompt 方法,你可以在提示时覆盖默认提供程序、模型或 HTTP 超时:
$response = (new SalesCoach)->prompt(
'Analyze this sales transcript...',
provider: Lab::Anthropic,
model: 'claude-sonnet-5',
timeout: 120,
);原始 HTTP 响应
从文本生成代理返回的每个响应都会通过 raw 属性公开来自底层提供程序 API 调用的原始 HTTP 响应。这使你可以访问不属于 AI SDK 通用响应一部分的特定于提供商的信息 - 速率限制标头、请求 ID 或其他确切的有效负载字段:
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
$response->raw; // Illuminate\Http\Client\Response|null
$response->raw->header('X-RateLimit-Remaining-Requests');
$response->raw->json('id');在工具调用循环中,每个步骤都会保留其自身请求的原始响应:
foreach ($response->steps as $step) {
$step->raw?->header('X-RateLimit-Remaining-Requests');
}注意: 当流式传输响应、使用 Bedrock 提供程序(通过 AWS 开发工具包而不是 HTTP 客户端执行其 API 调用)以及伪造响应时,
raw属性为null,除非通过withRawResponse显式提供。
对话情境
如果你的代理实现了 Conversational 接口,你可以使用 messages 方法返回之前的对话上下文(如果适用):
use App\Models\History;
use Laravel\Ai\Messages\Message;
/**
* Get the list of messages comprising the conversation so far.
*/
public function messages(): iterable
{
return History::where('user_id', $this->user->id)
->latest()
->limit(50)
->get()
->reverse()
->map(function ($message) {
return new Message($message->role, $message->content);
})->all();
}记住对话
警告: 在使用
RemembersConversations特征之前,你应该使用vendor:publishArtisan 命令发布并运行 AI SDK 迁移。这些迁移将创建必要的数据库表来存储对话。
如果你希望 Laravel 自动存储和检索代理的对话历史记录,你可以使用 RemembersConversations 特征。此特征提供了一种将对话消息保存到数据库的简单方法,而无需手动实现 Conversational 接口:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, Conversational
{
use Promptable, RemembersConversations;
/**
* Get the instructions that the agent should follow.
*/
public function instructions(): string
{
return 'You are a sales coach...';
}
}使用 RemembersConversations 特征时,请勿在代理类中手动定义 messages 方法。如果存在 messages 方法,它将优先于特征的实现,并且不会从数据库加载对话历史记录。
要为用户启动新对话,请在提示之前调用 forUser 方法:
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');
$conversationId = $response->conversationId;对话 ID 在响应中返回,并且可以存储以供将来参考。如果你想使用 Eloquent 检索用户的所有对话,你可以将 HasConversations 特征添加到你的用户模型中:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Ai\Concerns\HasConversations;
class User extends Authenticatable
{
use HasConversations;
}将特征添加到模型后,你可以通过 conversations 关系检索和查询用户的对话:
$conversations = $user->conversations()
->latest('updated_at')
->paginate(20);要继续现有对话,请使用 continue 方法:
$response = (new SalesCoach)
->continue($conversationId, as: $user)
->prompt('Tell me more about that.');使用 RemembersConversations 特征时,系统会在提示时自动加载以前的消息并将其包含在对话上下文中。每次交互后都会自动存储新消息(用户和助理)。
对话参与者
尽管用户是最常见的对话参与者,但对话可能属于任何 Eloquent 模型。使用 forParticipant 方法启动另一种类型模型的对话:
$response = (new SalesCoach)
->forParticipant($team)
->prompt('Review our latest sales results.');参与者的变形类和主键与对话一起存储。因此,具有相同主键的不同类型的模型(例如 User ID 1 和 Team ID 1)具有不同的对话历史记录。 forUser 方法是 forParticipant 的别名。
你可以使用 continueLastConversation 方法继续参与者最近的对话:
$response = (new SalesCoach)
->continueLastConversation($team)
->prompt('Tell me more about that.');继续特定对话时,将参与者传递给 continue 方法:
$response = (new SalesCoach)
->continue($conversationId, as: $team)
->prompt('Tell me more about that.');HasConversations 特征可以添加到参与对话的任何 Eloquent 模型中。生成的 conversations 关系是一种多态关系,范围仅限于该模型的类型和主键。你还可以通过逆关系访问拥有对话的参与者:
$conversations = $team->conversations;
$participant = $conversation->participant;如果你的应用程序使用多个参与者模型类型,你应该考虑定义 Eloquent morph map ,以便存储的参与者类型不会与你的模型类名称耦合。
WARNING
continue 方法不会验证给定参与者是否拥有该对话。你的应用程序应在继续对话之前授予对对话的访问权限。
结构化输出
如果你希望代理返回结构化输出,请实现 HasStructuredOutput 接口,这要求你的代理定义 schema 方法:
<?php
namespace App\Ai\Agents;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasStructuredOutput
{
use Promptable;
// ...
/**
* Get the agent's structured output schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'score' => $schema->integer()->required(),
];
}
}当提示代理返回结构化输出时,你可以像数组一样访问返回的 StructuredAgentResponse:
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return $response['score'];嵌套对象
要定义嵌套结构化输出,请使用带有闭包的 object 方法:
<?php
namespace App\Ai\Agents;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasStructuredOutput
{
use Promptable;
// ...
/**
* Get the agent's structured output schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'score' => $schema->integer()->required(),
'metadata' => $schema->object(fn ($schema) => [
'confidence' => $schema->string()->enum(['low', 'medium', 'high'])->required(),
'language' => $schema->string()->required(),
])->required(),
];
}
}对象数组
如果你的代理应返回结构化项目列表,请结合使用 array 和 object 方法:
public function schema(JsonSchema $schema): array
{
return [
'feedback' => $schema->array()
->items(
$schema->object(fn ($schema) => [
'comment' => $schema->string()->required(),
'score' => $schema->integer()->required(),
])
)
->required(),
];
}如果某个值可能与多个模式之一匹配,请使用 anyOf 方法:
public function schema(JsonSchema $schema): array
{
return [
'content' => $schema->anyOf([
$schema->object(fn ($schema) => [
'type' => $schema->string()->enum(['article'])->required(),
'title' => $schema->string()->required(),
]),
$schema->object(fn ($schema) => [
'type' => $schema->string()->enum(['image'])->required(),
'url' => $schema->string()->required(),
]),
])->required(),
];
}附件
提示时,你还可以传递带有提示的附件,以允许模型检查图像和文档:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;
$response = (new SalesCoach)->prompt(
'Analyze the attached sales transcript...',
attachments: [
Files\Document::fromStorage('transcript.pdf'), // Attach a document from a filesystem disk...
Files\Document::fromPath('/home/laravel/transcript.md'), // Attach a document from a local path...
$request->file('transcript'), // Attach an uploaded file...
]
);同样, Laravel\Ai\Files\Image 类可用于将图像附加到提示:
use App\Ai\Agents\ImageAnalyzer;
use Laravel\Ai\Files;
$response = (new ImageAnalyzer)->prompt(
'What is in this image?',
attachments: [
Files\Image::fromStorage('photo.jpg'), // Attach an image from a filesystem disk...
Files\Image::fromPath('/home/laravel/photo.jpg'), // Attach an image from a local path...
$request->file('photo'), // Attach an uploaded file...
]
);流式输出
你可以通过调用 stream 方法来流式传输代理的响应。返回的 StreamableAgentResponse 可能会从自动向客户端发送流式响应 (SSE) 的路由返回:
use App\Ai\Agents\SalesCoach;
Route::get('/coach', function () {
return (new SalesCoach)->stream('Analyze this sales transcript...');
});then 方法可用于提供一个闭包,当整个响应流式传输到客户端时将调用该闭包:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Responses\StreamedAgentResponse;
Route::get('/coach', function () {
return (new SalesCoach)
->stream('Analyze this sales transcript...')
->then(function (StreamedAgentResponse $response) {
// $response->text, $response->events, $response->usage...
});
});或者,你可以手动迭代流式事件:
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');
foreach ($stream as $event) {
// ...
}使用 Vercel AI SDK 协议进行流式传输
你可以通过在可流式响应上调用 usingVercelDataProtocol 方法,使用 Vercel AI SDK stream protocol 流式传输事件:
use App\Ai\Agents\SalesCoach;
Route::get('/coach', function () {
return (new SalesCoach)
->stream('Analyze this sales transcript...')
->usingVercelDataProtocol();
});广播
你可以通过几种不同的方式广播流式事件。首先,你可以简单地对流式事件调用 broadcast 或 broadcastNow 方法:
use App\Ai\Agents\SalesCoach;
use Illuminate\Broadcasting\Channel;
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');
foreach ($stream as $event) {
$event->broadcast(new Channel('channel-name'));
}或者,你可以调用代理的 broadcastOnQueue 方法对代理操作进行排队并广播可用的流式事件:
(new SalesCoach)->broadcastOnQueue(
'Analyze this sales transcript...'
new Channel('channel-name'),
);跳过大型活动
一些广播平台将 WebSocket 消息限制为 10KB 左右。数据密集型流事件(例如大型工具结果)可能会超出此限制并导致广播失败。你可以使用 WithoutBroadcasting 属性从广播中排除特定事件类型:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Attributes\WithoutBroadcasting;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use Laravel\Ai\Streaming\Events\ToolCall;
use Laravel\Ai\Streaming\Events\ToolResult;
#[WithoutBroadcasting(ToolCall::class, ToolResult::class)]
class SearchAgent implements Agent, HasTools
{
use Promptable;
// ...
}排除的事件永远不会广播,但它们仍然保留到 agent_conversation_messages 表中,因此你的前端可以在流完成后加载完整的工具数据。这适用于排队 (broadcastOnQueue) 和同步 (broadcast / broadcastNow) 广播。
排队
使用代理的 queue 方法,你可以提示代理,但允许它在后台处理响应,从而使你的应用程序感觉快速且响应迅速。 then 和 catch 方法可用于注册当响应可用或发生异常时将调用的闭包:
use Illuminate\Http\Request;
use Laravel\Ai\Responses\AgentResponse;
use Throwable;
Route::post('/coach', function (Request $request) {
(new SalesCoach)
->queue($request->input('transcript'))
->then(function (AgentResponse $response) {
// ...
})
->catch(function (Throwable $e) {
// ...
});
return back();
});工具
工具可用于为代理提供额外的功能,让他们在响应提示时可以使用这些功能。可以使用 make:tool Artisan 命令创建工具:
php artisan make:tool RandomNumberGenerator生成的工具将放置在你应用程序的 app/Ai/Tools 目录中。每个工具都包含一个 handle 方法,代理在需要使用该工具时将调用该方法:
<?php
namespace App\Ai\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;
class RandomNumberGenerator implements Tool
{
/**
* Get the description of the tool's purpose.
*/
public function description(): Stringable|string
{
return 'This tool may be used to generate cryptographically secure random numbers.';
}
/**
* Execute the tool.
*/
public function handle(Request $request): Stringable|string
{
return (string) random_int($request['min'], $request['max']);
}
/**
* Get the tool's schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'min' => $schema->integer()->min(0)->required(),
'max' => $schema->integer()->required(),
];
}
}定义工具后,你可以从任何代理的 tools 方法返回它:
use App\Ai\Tools\RandomNumberGenerator;
/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new RandomNumberGenerator,
];
}验证工具参数
尽管你的工具的架构限制了模型可能提供的参数,但你可以使用请求的 validate 方法验证传入的参数:
public function handle(Request $request): Stringable|string
{
$validated = $request->validate([
'city' => 'required|string',
'days' => 'required|integer|max:7',
]);
return $this->forecast($validated['city'], $validated['days']);
}当验证失败时,验证消息将作为工具的结果返回到模型,从而允许模型更正参数并再次调用该工具。
修复工具调用
使用 RepairToolCalls 属性可以让代理在模型调用未知本地工具时恢复。 Laravel 返回对模型的失败调用以及可用本地工具的名称,从而允许它更正调用:
use Laravel\Ai\Attributes\RepairToolCalls;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
#[RepairToolCalls]
class SupportAgent implements Agent, HasTools
{
use Promptable;
// ...
}当 Laravel 自动导出最大步数时,此属性会为修复的调用添加一个步数。明确的 MaxSteps 限制保持不变。
相似性搜索
SimilaritySearch 工具允许代理使用存储在数据库中的向量嵌入来搜索与给定查询类似的文档。当你想要授予代理访问应用程序数据的权限时,这对于检索增强生成 (RAG) 非常有用。
创建相似性搜索工具的最简单方法是使用 usingModel 方法和具有向量嵌入的 Eloquent 模型:
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;
public function tools(): iterable
{
return [
SimilaritySearch::usingModel(Document::class, 'embedding'),
];
}第一个参数是 Eloquent 模型类,第二个参数是包含向量嵌入的列。
你还可以提供 0.0 和 1.0 之间的最小相似度阈值以及用于自定义查询的闭包:
SimilaritySearch::usingModel(
model: Document::class,
column: 'embedding',
minSimilarity: 0.7,
limit: 10,
query: fn ($query) => $query->where('published', true),
),为了获得更多控制,你可以创建一个具有返回搜索结果的自定义闭包的相似性搜索工具:
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;
public function tools(): iterable
{
return [
new SimilaritySearch(using: function (string $query) {
return Document::query()
->where('user_id', $this->user->id)
->whereVectorSimilarTo('embedding', $query)
->limit(10)
->get();
}),
];
}你可以使用 withDescription 方法自定义工具的描述:
SimilaritySearch::usingModel(Document::class, 'embedding')
->withDescription('Search the knowledge base for relevant articles.'),延迟工具加载
默认情况下,代理公开的每个工具都会随每个请求发送给提供者。当代理提供大量工具时,这会消耗代币,并可能降低模型工具选择的准确性。将 ToolSearch 提供程序工具与 OpenAI 或 Anthropic 结合使用,你可以推迟工具定义,以便提供程序仅在需要时加载它们:
use App\Ai\Tools\RefundOrder;
use App\Ai\Tools\SearchInvoices;
use App\Ai\Tools\Weather;
use Laravel\Ai\Providers\Tools\ToolSearch;
public function tools(): iterable
{
return [
new Weather,
new ToolSearch(tools: [
new SearchInvoices,
new RefundOrder,
]),
];
}打包的工具不需要任何修改。当它们与提示相关时,提供程序将搜索并加载它们,之后代理可以像任何其他工具一样调用它们。
使用 Anthropic 时,strategy 参数可用于确定提供者应如何搜索延迟工具。支持的策略是 regex (默认)和 bm25:
new ToolSearch(tools: [new SearchInvoices], strategy: 'bm25'),使用 Anthropic 时,可以使用 withProviderOptions 方法将其他特定于提供者的选项传递给搜索工具:
(new ToolSearch(tools: [new SearchInvoices]))
->withProviderOptions(['cache_control' => ['type' => 'ephemeral']]),WARNING
不支持工具搜索的提供者将抛出异常,而不是默默地丢弃延迟的工具。此外,Anthropic 要求在 ToolSearch 包装器之外至少提供一种工具。
文件存储工具
FileStorage 工具工厂允许你向代理授予 Laravel filesystem disk 的访问权限。 all 方法返回允许代理列出、读取、检查、生成 URL、写入、删除和复制给定磁盘上的文件的工具:
use Laravel\Ai\Tools\FileStorage;
public function tools(): iterable
{
return FileStorage::all('local');
}如果你的代理只能检查文件,请使用 readOnly 方法:
return FileStorage::readOnly('local');这些方法返回 Illuminate\Support\Collection,允许你进一步过滤提供给代理的工具:
use Laravel\Ai\Tools\Filesystem\DeleteFile;
return FileStorage::all('s3')
->reject(fn ($tool) => $tool instanceof DeleteFile);MCP 工具
如果你的应用程序使用 Laravel MCP,你可以为代理提供由 Model Context Protocol 服务器公开的工具。使用 Laravel MCP client,你可以连接到远程或本地 MCP 服务器并将其工具直接传递给你的代理。
INFO
MCP 工具需要在你的应用程序中安装 Laravel MCP 软件包。
由于 MCP 客户端的 tools 方法返回一个集合,因此请使用 ... 运算符将其传播到代理的 tools 数组中:
use App\Ai\Tools\RandomNumberGenerator;
use Laravel\Mcp\Client;
/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
...Client::web('https://mcp.example.com')
->withToken($token)
->tools(),
new RandomNumberGenerator,
];
}AI SDK 自动包装每个 MCP 工具,以便代理可以像调用任何其他工具一样调用它。你还可以使用 named MCP client:
use Laravel\Mcp\Facades\Mcp;
public function tools(): iterable
{
return [
...Mcp::client('github')->tools(),
];
}或者连接到 local MCP server:
use Laravel\Mcp\Client;
public function tools(): iterable
{
return [
...Client::local('php', ['artisan', 'mcp:start'])->tools(),
];
}有关创建和验证 MCP 客户端(包括不记名令牌和 OAuth)的更多信息,请参阅 MCP client documentation。
提供商工具
提供商工具是AI提供商本地实现的特殊工具,提供网页搜索、URL 获取和文件搜索等功能。与常规工具不同,提供程序工具由提供程序本身而不是你的应用程序执行。
提供程序工具可以通过代理的 tools 方法返回。
网页搜索
WebSearch 提供商工具允许代理在网络上搜索实时信息。这对于回答有关当前事件、最近数据或自模型训练截止以来可能发生变化的主题的问题非常有用。
支持的提供商: Anthropic、OpenAI、Azure、Gemini、xAI、OpenRouter
use Laravel\Ai\Providers\Tools\WebSearch;
public function tools(): iterable
{
return [
new WebSearch,
];
}你可以配置网络搜索工具来限制搜索数量或将结果限制为特定域:
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),要根据用户位置优化搜索结果,请使用 location 方法:
(new WebSearch)->location(
city: 'New York',
region: 'NY',
country: 'US'
);网页抓取
WebFetch 提供程序工具允许代理获取和读取网页内容。当你需要代理分析特定 URL 或从已知网页检索详细信息时,这非常有用。
支持的提供商: Anthropic、Gemini、OpenRouter
use Laravel\Ai\Providers\Tools\WebFetch;
public function tools(): iterable
{
return [
new WebFetch,
];
}你可以配置网络获取工具来限制获取数量或限制到特定域:
(new WebFetch)->max(3)->allow(['docs.laravel.com']),文件搜索
FileSearch 提供程序工具允许代理搜索存储在 vector stores 中的 files。这允许代理搜索你上传的文档以获取相关信息,从而实现检索增强生成 (RAG)。
支持的提供商: OpenAI、Gemini、xAI
use Laravel\Ai\Providers\Tools\FileSearch;
public function tools(): iterable
{
return [
new FileSearch(stores: ['store_id']),
];
}你可以提供多个向量商店 ID 来跨多个商店进行搜索:
new FileSearch(stores: ['store_1', 'store_2']);如果你的文件具有 metadata,你可以通过提供 where 参数来过滤搜索结果。对于简单的相等过滤器,传递一个数组:
new FileSearch(stores: ['store_id'], where: [
'author' => 'Taylor Otwell',
'year' => 2026,
]);对于更复杂的过滤器,你可以传递一个接收 FileSearchQuery 实例的闭包:
use Laravel\Ai\Providers\Tools\FileSearchQuery;
new FileSearch(stores: ['store_id'], where: fn (FileSearchQuery $query) =>
$query->where('author', 'Taylor Otwell')
->whereNot('status', 'draft')
->whereIn('category', ['news', 'updates'])
);子代理
代理也可以从另一个代理的 tools 方法返回。当代理作为工具返回时,父代理可以将特定任务委托给子代理,并在回答原始提示时使用子代理的响应。当通用代理需要访问具有自己的指令、工具、模型配置或提供者首选项的专用代理时,这非常有用。
例如,客户支持代理可以将退款资格问题委托给专门的退款代理:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
class CustomerSupportAgent implements Agent, HasTools
{
use Promptable;
/**
* Get the instructions that the agent should follow.
*/
public function instructions(): string
{
return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.';
}
/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new RefundsAgent,
];
}
}要自定义子代理如何向父代理公开,请在子代理上实现 CanActAsTool 接口并定义面向工具的名称和描述:
<?php
namespace App\Ai\Agents;
use App\Ai\Tools\LookupOrder;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\CanActAsTool;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
#[Provider(Lab::Anthropic)]
class RefundsAgent implements Agent, CanActAsTool, HasTools
{
use Promptable;
/**
* Get the instructions that the agent should follow.
*/
public function instructions(): string
{
return 'You are a refunds specialist. Use order details and the refund policy to give concise eligibility guidance.';
}
/**
* Get the agent's tool name.
*/
public function name(): string
{
return 'refunds_specialist';
}
/**
* Get the agent's tool description.
*/
public function description(): string
{
return 'Determine whether an order is eligible for a refund and explain the next step.';
}
/**
* Get the tools available to the agent.
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new LookupOrder,
];
}
}如果子代理没有实现 CanActAsTool,Laravel 将使用代理的类基名作为工具名称和通用描述,要求父代理传递清晰的、独立的任务描述。每个子代理调用都是独立运行的,并且不会接收父代理的对话历史记录。
中间件
代理支持中间件,允许你在将提示发送给提供商之前拦截和修改提示。可以使用 make:agent-middleware Artisan 命令创建中间件:
php artisan make:agent-middleware LogPrompts生成的中间件将放置在应用程序的 app/Ai/Middleware 目录中。要将中间件添加到代理,请实现 HasMiddleware 接口并定义返回中间件类数组的 middleware 方法:
<?php
namespace App\Ai\Agents;
use App\Ai\Middleware\LogPrompts;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasMiddleware
{
use Promptable;
// ...
/**
* Get the agent's middleware.
*/
public function middleware(): array
{
return [
new LogPrompts,
];
}
}每个中间件类应该定义一个 handle 方法来接收 AgentPrompt 和一个 Closure 将提示传递给下一个中间件:
<?php
namespace App\Ai\Middleware;
use Closure;
use Laravel\Ai\Prompts\AgentPrompt;
class LogPrompts
{
/**
* Handle the incoming prompt.
*/
public function handle(AgentPrompt $prompt, Closure $next)
{
Log::info('Prompting agent', ['prompt' => $prompt->prompt]);
return $next($prompt);
}
}你可以在响应上使用 then 方法在代理完成处理后执行代码。这适用于同步响应和流响应:
public function handle(AgentPrompt $prompt, Closure $next)
{
return $next($prompt)->then(function (AgentResponse $response) {
Log::info('Agent responded', ['text' => $response->text]);
});
}匿名代理
有时你可能希望快速与模型交互,而不创建专用的代理类。你可以使用 agent 函数创建临时匿名代理:
use function Laravel\Ai\{agent};
$response = agent(
instructions: 'You are an expert at software development.',
messages: [],
tools: [],
)->prompt('Tell me about Laravel')匿名代理也可能产生结构化输出:
use Illuminate\Contracts\JsonSchema\JsonSchema;
use function Laravel\Ai\{agent};
$response = agent(
schema: fn (JsonSchema $schema) => [
'number' => $schema->integer()->required(),
],
)->prompt('Generate a random number less than 100')代理配置
你可以使用 PHP 属性为代理配置文本生成选项。以下属性可用:
- `MaxSteps`:代理在使用工具时可以采取的最大步骤数。
- `MaxTokens`:模型可以生成的最大令牌数量。
- `Model`:代理应使用的模型。
- `Provider`:用于代理的 AI 提供程序(或故障转移提供程序)。
- `Temperature`:用于生成的采样温度(0.0 到 1.0)。
- `Timeout`:代理请求的 HTTP 超时(以秒为单位)(默认值:60)。
- `TopP`:用于生成的核采样概率(0.0 到 1.0)。
- `UseCheapestModel`:使用提供商最便宜的文本模型来优化成本。
- `UseSmartestModel`:使用提供商最强大的文本模型来完成复杂的任务。
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\MaxTokens;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Attributes\TopP;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
#[Provider(Lab::Anthropic)]
#[Model('claude-sonnet-5')]
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Temperature(0.7)]
#[Timeout(120)]
#[TopP(0.9)]
class SalesCoach implements Agent
{
use Promptable;
// ...
}UseCheapestModel 和 UseSmartestModel 属性允许你自动为给定提供商选择最具成本效益或最有能力的模型,而无需指定模型名称。当你想要优化不同提供商的成本或功能时,这非常有用:
use Laravel\Ai\Attributes\UseCheapestModel;
use Laravel\Ai\Attributes\UseSmartestModel;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
use Promptable;
// Will use the cheapest model (e.g., Haiku)...
}
#[UseSmartestModel]
class ComplexReasoner implements Agent
{
use Promptable;
// Will use the most capable model (e.g., Opus)...
}INFO
随着提供商发布新模型,UseCheapestModel 和 UseSmartestModel 选择的底层模型可能会在 Laravel AI SDK 版本之间发生变化。切换模型可能会带来行为变化、不推荐使用的参数和显著的成本差异。如果你需要稳定、可预测的模型和定价,请使用 Model 属性显式指定模型。
提供商选项
如果你的代理需要传递特定于提供商的选项(例如 OpenAI 推理工作或惩罚设置),请实施 HasProviderOptions 合约并定义 providerOptions 方法:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasProviderOptions;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasProviderOptions
{
use Promptable;
// ...
/**
* Get provider-specific generation options.
*/
public function providerOptions(Lab|string $provider): array
{
return match ($provider) {
Lab::OpenAI => [
'reasoning' => ['effort' => 'low'],
'frequency_penalty' => 0.5,
'presence_penalty' => 0.3,
],
Lab::Anthropic => [
'thinking' => ['budget_tokens' => 1024],
'cache_control' => ['type' => 'ephemeral'],
],
default => [],
};
}
}providerOptions 方法接收当前正在使用的提供程序(Lab 枚举或字符串),允许你为每个提供程序返回不同的选项。这在使用 failover 时特别有用,因为每个后备提供程序都可以接收自己的配置。
上面的 Anthropic 示例还通过 cache_control 启用 prompt caching。
提示缓存
大多数提供商会自动缓存重复的提示前缀,并以折扣价对缓存部分进行计费。 OpenAI、Gemini、Groq、DeepSeek 和 xAI 不需要配置,你可以通过响应的使用情况检查节省的情况:
$response->usage->cacheReadInputTokens;
$response->usage->cacheWriteInputTokens;anthropic 和 bedrock 提供程序仅在请求时进行缓存。 CacheInstructions 和 CacheToolDefinitions 属性在代理指令和工具定义的末尾放置一个缓存断点,因此每个对话都会从缓存中读取该前缀,而不是再次写入:
use Laravel\Ai\Attributes\CacheInstructions;
use Laravel\Ai\Attributes\CacheToolDefinitions;
#[CacheInstructions]
#[CacheToolDefinitions]
class SalesCoach implements Agent
{
use Promptable;
// ...
}如果你的说明在每次请求时都会发生变化,例如当它们嵌入当前日期时,请单独使用 CacheToolDefinitions。缓存每次请求时更改的前缀每次都会创建一个新的缓存条目,因此你需要付费将其写入缓存而无需重复使用它。
不支持这些属性的提供者会忽略它们,因此代理可以在使用 failover 时安全地声明它们。
缓存的前缀默认保留五分钟。如果你将 TTL 传递给属性,Anthropic 可能会将它们保留一个小时:
#[CacheInstructions('1h')]
#[CacheToolDefinitions('1h')]或者,可以通过顶级 cache_control provider option 启用 Anthropic 的自动缓存。这会在请求的最后一个块之后放置一个断点,因此断点会随着会话的增长而前进,并且每个回合都会从缓存中读取前一个回合。两种机制可以结合起来。
WARNING
由于提供程序按照工具、指令和消息的顺序构建提示,因此缓存指令一小时也需要缓存工具定义一小时。将两者混合会抛出 InvalidArgumentException。
人力工具批准
WARNING
工具批准需要一个 Conversational 代理,其对话历史记录将被保留,以便可以恢复暂停的呼叫。 RemembersConversations 特征提供了必要的持久性。
执行敏感或不可逆操作的工具在执行之前可能需要人工批准。要使工具获得批准,请实施 Approvable 合约并使用 InteractsWithApprovals 特征。默认情况下,可批准的工具需要批准:
<?php
namespace App\Ai\Tools;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Storage;
use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\Approvable;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;
class DeleteFile implements Approvable, Tool
{
use InteractsWithApprovals;
/**
* Get the description of the tool's purpose.
*/
public function description(): Stringable|string
{
return 'Delete a file from storage.';
}
/**
* Execute the tool.
*/
public function handle(Request $request): Stringable|string
{
Storage::delete($request['path']);
return "Deleted [{$request['path']}].";
}
/**
* Get the tool's schema definition.
*/
public function schema(JsonSchema $schema): array
{
return [
'path' => $schema->string()->required(),
];
}
}要根据工具调用的参数确定是否需要批准,请在工具上定义 needsApproval 方法。此方法可能会返回一个布尔值或一个包含批准请求原因的 Approval 实例:
use Laravel\Ai\Approvals\Approval;
/**
* Determine whether the tool needs approval for the given request.
*/
protected function needsApproval(Request $request): Approval|bool
{
return str_starts_with($request['path'], 'temporary/')
? false
: Approval::required('This will permanently delete a file.');
}从代理的 tools 方法返回工具时,你可以覆盖工具的批准要求:
public function tools(): iterable
{
return [
(new SendNotification)->withoutApproval(),
(new DeleteFile)->requireApproval('Deletion review required.'),
];
}当调用可批准的工具时,代理会在执行之前暂停。你可以检查响应的待批准,其中包含每个工具调用的 ID、工具名称、参数和批准原因:
$response = (new FileAssistant)
->forUser($user)
->prompt('Delete the old invoice.');
if ($response->hasPendingApprovals()) {
foreach ($response->pendingApprovals as $approval) {
// $approval->id
// $approval->tool
// $approval->arguments
// $approval->reason
}
}要恢复代理,请继续对话并提供一个 Decisions 实例,其中包含每个待处理工具调用的决策。决策可能会批准调用、拒绝调用或在执行前编辑其参数:
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;
$response = (new FileAssistant)
->continue($conversationId, as: $user)
->prompt(Decisions::from([
'call_abc' => Decision::approve(),
'call_ghi' => Decision::reject('The invoice must be retained.'),
]));布尔值 true 和 false 可以用作批准和拒绝的简写。每个待处理的工具调用都必须收到一个决定。未知、缺失或之前解析的工具调用 ID 将导致抛出 ApprovalMismatchException。你可以使用 approveRemaining 或 rejectRemaining 方法为调用提供默认值,而无需明确决定:
$decisions = Decisions::from([
'call_abc' => true,
])->rejectRemaining('Not approved.');
$response = (new FileAssistant)
->continue($conversationId, as: $user)
->prompt($decisions);拒绝结果(例如 Decision::reject('Not approved.'))将返回给模型,以便模型可以继续响应。没有结果的拒绝在记录拒绝后停止生成循环。
prompt、stream、queue、broadcast、broadcastNow 和 broadcastOnQueue 方法支持工具审批。
在流式传输和广播期间,暂停由 tool_approval_request 事件表示。使用 Vercel AI SDK stream protocol 时,使用协议的本机工具批准部分发出批准请求和结果。
对于排队的代理,结果响应将传递给 then 回调,Laravel 还会调度 ToolApprovalRequested 事件。
Laravel 在要求模型继续之前存储已批准工具的结果。如果生成失败,则审批已解决。使用普通文本提示继续对话,而不是再次提交相同的批准决定。
完整的审批流程
以下路线演示了完整的审批流程。 GET 路由返回聊天屏幕,而 POST 路由接受新文本提示或来自聊天屏幕的批准决定。此示例假设应用程序的 User 模型使用 HasConversations 特征:
use App\Ai\Agents\FileAssistant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\Rule;
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;
use Laravel\Ai\Models\Conversation;
Route::get('/chat/{conversation}', function (Request $request, Conversation $conversation) {
Gate::authorize('view', $conversation);
return view('chat', [
'conversation' => $conversation,
]);
})->middleware('auth');
Route::post('/chat/{conversation}', function (Request $request, Conversation $conversation) {
Gate::authorize('view', $conversation);
$validated = $request->validate([
'message' => ['nullable', 'string', 'required_without:decisions', 'prohibits:decisions'],
'decisions' => ['nullable', 'array', 'required_without:message', 'prohibits:message'],
'decisions.*.action' => ['required_with:decisions', Rule::in(['approve', 'reject'])],
'decisions.*.result' => ['nullable', 'string'],
]);
$prompt = isset($validated['decisions'])
? Decisions::from(collect($validated['decisions'])->map(
fn (array $decision) => match ($decision['action']) {
'approve' => Decision::approve(),
'reject' => Decision::reject($decision['result'] ?? null),
}
)->all())
: $validated['message'];
$response = (new FileAssistant)
->continue($conversation->id, as: $request->user())
->prompt($prompt);
return [
'conversation_id' => $response->conversationId,
'status' => $response->hasPendingApprovals() ? 'awaiting_approval' : 'complete',
'message' => $response->text,
'approvals' => $response->pendingApprovals,
];
})->middleware('auth');当响应状态为 awaiting_approval 时,聊天屏幕应呈现待批准,并使用工具调用 ID 作为每个决策的键将用户的选择提交到同一端点:
{
"decisions": {
"call_abc": {
"action": "approve"
},
"call_def": {
"action": "reject",
"result": "The invoice must be retained."
}
}
}对于正常的聊天消息,屏幕可能会提交 message 值:
{
"message": "Delete the old invoice."
}图像
Laravel\Ai\Image 类可用于使用 openai、gemini 或 xai 提供程序生成图像:
use Laravel\Ai\Image;
$image = Image::of('A donut sitting on the kitchen counter')->generate();
$rawContent = (string) $image;square、portrait 和 landscape 方法可用于控制图像的长宽比,而 quality 方法可用于指导模型最终图像质量(high、medium、low)。 timeout 方法可用于指定 HTTP 超时(以秒为单位):
use Laravel\Ai\Image;
$image = Image::of('A donut sitting on the kitchen counter')
->quality('high')
->landscape()
->timeout(120)
->generate();你可以使用 attachments 方法附加参考图像:
use Laravel\Ai\Files;
use Laravel\Ai\Image;
$image = Image::of('Update this photo of me to be in the style of an impressionist painting.')
->attachments([
Files\Image::fromStorage('photo.jpg'),
// Files\Image::fromPath('/home/laravel/photo.jpg'),
// Files\Image::fromUrl('https://example.com/photo.jpg'),
// $request->file('photo'),
])
->landscape()
->generate();生成的图像可以轻松存储在应用程序的 config/filesystems.php 配置文件中配置的默认磁盘上:
$image = Image::of('A donut sitting on the kitchen counter');
$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');图像生成也可以排队:
use Laravel\Ai\Image;
use Laravel\Ai\Responses\ImageResponse;
Image::of('A donut sitting on the kitchen counter')
->portrait()
->queue()
->then(function (ImageResponse $image) {
$path = $image->store();
// ...
});音频
Laravel\Ai\Audio 类可用于从给定文本生成音频:
use Laravel\Ai\Audio;
$audio = Audio::of('I love coding with Laravel.')->generate();
$rawContent = (string) $audio;你还可以使用 Laravel 的 Stringable 类提供的 toAudio 方法从字符串生成音频:
use Illuminate\Support\Str;
$audio = Str::of('I love coding with Laravel.')->toAudio();male、female 和 voice 方法可用于确定生成的音频的语音:
$audio = Audio::of('I love coding with Laravel.')
->female()
->generate();
$audio = Audio::of('I love coding with Laravel.')
->voice('voice-id-or-name')
->generate();类似地,instructions 方法可用于动态指导模型生成的音频听起来如何:
$audio = Audio::of('I love coding with Laravel.')
->female()
->instructions('Said like a pirate')
->generate();生成的音频可以轻松存储在应用程序的 config/filesystems.php 配置文件中配置的默认磁盘上:
$audio = Audio::of('I love coding with Laravel.')->generate();
$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');音频生成也可以排队:
use Laravel\Ai\Audio;
use Laravel\Ai\Responses\AudioResponse;
Audio::of('I love coding with Laravel.')
->queue()
->then(function (AudioResponse $audio) {
$path = $audio->store();
// ...
});转录
Laravel\Ai\Transcription 类可用于生成给定音频的转录:
use Laravel\Ai\Transcription;
$transcript = Transcription::fromPath('/home/laravel/audio.mp3')->generate();
$transcript = Transcription::fromStorage('audio.mp3')->generate();
$transcript = Transcription::fromUpload($request->file('audio'))->generate();
return (string) $transcript;diarize 方法可用于指示你希望响应除了原始文本转录本之外还包含分类转录本,以便你可以按说话者访问分段转录本:
$transcript = Transcription::fromStorage('audio.mp3')
->diarize()
->generate();转录生成也可以排队:
use Laravel\Ai\Transcription;
use Laravel\Ai\Responses\TranscriptionResponse;
Transcription::fromStorage('audio.mp3')
->queue()
->then(function (TranscriptionResponse $transcript) {
// ...
});文本摘要
你可以使用 Laravel 的 Stringable 类提供的 summarize 方法来总结文本。默认情况下,摘要将包含不超过三个句子,并将使用配置的提供商最便宜的文本模型生成:
use Illuminate\Support\Str;
$summary = Str::of($article)->summarize();你可以指定用于生成摘要的最大句子数、提供程序、模型和超时。 Str 类还提供了该方法的静态版本:
use Laravel\Ai\Enums\Lab;
$summary = Str::of($article)->summarize(
sentences: 4,
provider: Lab::Anthropic,
model: 'claude-sonnet-5',
timeout: 30,
);
$summary = Str::summarize($article, sentences: 4);向量嵌入
你可以使用 Laravel 的 Stringable 类提供的新 toEmbeddings 方法轻松为任何给定字符串生成向量嵌入:
use Illuminate\Support\Str;
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();或者,你可以使用 Embeddings 类一次为多个输入生成嵌入:
use Laravel\Ai\Embeddings;
$response = Embeddings::for([
'Napa Valley has great wine.',
'Laravel is a PHP framework.',
])->generate();
$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]你可以指定嵌入的维度和提供者:
$response = Embeddings::for(['Napa Valley has great wine.'])
->dimensions(1536)
->generate(Lab::OpenAI, 'text-embedding-3-small');多模态嵌入
除了字符串之外,Embeddings::for 方法还接受图像、音频、文档和视频输入,允许你生成非文本内容的嵌入。 Gemini 支持图像、音频、文档和视频嵌入,而 VoyageAI 支持图像和视频嵌入:
use Laravel\Ai\Embeddings;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Files\Image;
use Laravel\Ai\Files\Video;
$response = Embeddings::for([
'A vineyard at sunset.',
Image::fromStorage('vineyard.jpg'),
Video::fromPath('/home/laravel/tour.mp4'),
])->generate(Lab::Gemini);多模式输入使用相同的 file classes used for attachments。这些文件可以从本地路径、文件系统磁盘、远程 URL 或 Base64 编码内容创建。图像、文档和视频也可以从上传的文件创建,而文档可以从原始字符串内容创建:
use Laravel\Ai\Files\Audio;
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;
use Laravel\Ai\Files\Video;
Image::fromPath('/home/laravel/photo.jpg');
Image::fromStorage('photo.jpg');
Image::fromUpload($request->file('photo'));
Audio::fromPath('/home/laravel/clip.mp3');
Audio::fromStorage('clip.mp3');
Audio::fromUpload($request->file('clip.mp3'));
Video::fromPath('/home/laravel/video.mp4');
Video::fromStorage('video.mp4');
Video::fromUpload($request->file('video'));
Document::fromUrl('https://example.com/report.pdf');
Document::fromString('Laravel is a PHP framework.', 'text/plain');
Document::fromUpload($request->file('report'));INFO
VoyageAI 不允许在单个请求中混合远程 URL 媒体和 Base64 编码媒体。本地、存储和上传的文件作为 Base64 编码内容发送,文本输入可以与任一媒体源组合。请查阅提供商的文档以确定可用的多模式模型和输入。
查询嵌入
生成嵌入后,通常会将它们存储在数据库的 vector 列中,以供以后查询。 Laravel 通过 pgvector 扩展和 MariaDB 为 PostgreSQL 上的向量列提供本机支持。首先,在迁移中定义一个 vector 列,指定维度数:
Schema::ensureVectorExtensionExists();
Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->vector('embedding', dimensions: 1536);
$table->timestamps();
});你还可以添加向量索引来加速相似性搜索。当在向量列上调用 index 时,Laravel 将自动创建一个具有余弦距离的 HNSW 索引:
$table->vector('embedding', dimensions: 1536)->index();在 Eloquent 模型上,你应该使用 AsVector 转换来转换向量列:
use Illuminate\Database\Eloquent\Casts\AsVector;
protected function casts(): array
{
return [
'embedding' => AsVector::class,
];
}要查询类似记录,请使用 whereVectorSimilarTo 方法。此方法按最小余弦相似度(在 0.0 和 1.0 之间,其中 1.0 相同)过滤结果,并按相似度对结果进行排序:
use App\Models\Document;
$documents = Document::query()
->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
->limit(10)
->get();$queryEmbedding 可以是浮点数组或纯字符串。当给定一个字符串时,Laravel 将自动为其生成嵌入:
$documents = Document::query()
->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
->limit(10)
->get();如果你需要更多控制,可以单独使用较低级别的 whereVectorDistanceLessThan、selectVectorDistance 和 orderByVectorDistance 方法:
$documents = Document::query()
->select('*')
->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
->orderByVectorDistance('embedding', $queryEmbedding)
->limit(10)
->get();如果你想让代理能够作为工具执行相似性搜索,请查看 Similarity Search 工具文档。
INFO
目前,使用 pgvector 扩展和 MariaDB 11.7 或更高版本的 PostgreSQL 连接支持向量查询。
缓存嵌入
嵌入生成可以被缓存,以避免对相同输入进行冗余 API 调用。要启用缓存,请将 ai.caching.embeddings.cache 配置选项设置为 true:
'caching' => [
'embeddings' => [
'cache' => true,
'store' => env('CACHE_STORE', 'database'),
'individually' => true,
// ...
],
],启用缓存后,嵌入会缓存 30 天。缓存键基于提供者、模型、维度和输入内容,确保相同的请求返回缓存的结果,而不同的配置生成新的嵌入。
默认情况下,每个输入的嵌入都缓存在其自己的键下,因此即使输入集或其顺序已更改,后续请求也可能会命中缓存以获取之前见过的输入。要在单个键下缓存整个输入集,请将 ai.caching.embeddings.individually 配置选项设置为 false。
即使全局缓存已禁用,你也可以使用 cache 方法为特定请求启用缓存:
$response = Embeddings::for(['Napa Valley has great wine.'])
->cache()
->generate();你可以指定自定义缓存持续时间(以秒为单位):
$response = Embeddings::for(['Napa Valley has great wine.'])
->cache(seconds: 3600) // Cache for 1 hour
->generate();toEmbeddings Stringable 方法还接受 cache 参数:
// Cache with default duration...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);
// Cache for a specific duration...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);重排序
重新排名允许你根据文档与给定查询的相关性对文档列表进行重新排序。这对于通过使用语义理解来改进搜索结果非常有用:
Laravel\Ai\Reranking 类可用于对文档重新排序:
use Laravel\Ai\Reranking;
$response = Reranking::of([
'Django is a Python web framework.',
'Laravel is a PHP web application framework.',
'React is a JavaScript library for building user interfaces.',
])->rerank('PHP frameworks');
// Access the top result...
$response->first()->document; // "Laravel is a PHP web application framework."
$response->first()->score; // 0.95
$response->first()->index; // 1 (original position)limit 方法可用于限制返回结果的数量:
$response = Reranking::of($documents)
->limit(5)
->rerank('search query');重新排列集合
为了方便起见,Laravel 集合可以使用 rerank 宏重新排序。第一个参数指定用于重新排名的字段,第二个参数是查询:
// Rerank by a single field...
$posts = Post::all()
->rerank('body', 'Laravel tutorials');
// Rerank by multiple fields (sent as JSON)...
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');
// Rerank using a closure to build the document...
$reranked = $posts->rerank(
fn ($post) => $post->title.': '.$post->body,
'Laravel tutorials'
);你还可以限制结果数量并指定提供商:
$reranked = $posts->rerank(
by: 'content',
query: 'Laravel tutorials',
limit: 10,
provider: Lab::Cohere
);文件
Laravel\Ai\Files 类或单个文件类可用于将文件存储到你的 AI 提供程序中,以便以后在对话中使用。这对于你想要多次引用而不重新上传的大型文档或文件非常有用:
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;
// Store a file from a local path...
$response = Document::fromPath('/home/laravel/document.pdf')->put();
$response = Image::fromPath('/home/laravel/photo.jpg')->put();
// Store a file that is stored on a filesystem disk...
$response = Document::fromStorage('document.pdf', disk: 'local')->put();
$response = Image::fromStorage('photo.jpg', disk: 'local')->put();
// Store a file that is stored on a remote URL...
$response = Document::fromUrl('https://example.com/document.pdf')->put();
$response = Image::fromUrl('https://example.com/photo.jpg')->put();
return $response->id;你还可以存储原始内容或上传的文件:
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;
// Store raw content...
$stored = Document::fromString('Hello, World!', 'text/plain')->put();
// Store an uploaded file...
$stored = Document::fromUpload($request->file('document'))->put();存储文件后,你可以在通过代理生成文本时引用该文件,而不用重新上传文件:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;
$response = (new SalesCoach)->prompt(
'Analyze the attached sales transcript...'
attachments: [
Files\Document::fromId('file-id') // Attach a stored document...
]
);要检索以前存储的文件,请在文件实例上使用 get 方法:
use Laravel\Ai\Files\Document;
$file = Document::fromId('file-id')->get();
$file->id;
$file->mimeType();要从提供程序删除文件,请使用 delete 方法:
Document::fromId('file-id')->delete();默认情况下,Files 类使用应用程序的 config/ai.php 配置文件中配置的默认 AI 提供程序。对于大多数操作,你可以使用 provider 参数指定不同的提供程序:
$response = Document::fromPath(
'/home/laravel/document.pdf'
)->put(provider: Lab::Anthropic);你可以使用 withProviderOptions 方法传递特定于提供商的上传选项。例如,你可以设置OpenAI的文件purpose:
use Laravel\Ai\Files\Document;
$response = Document::fromPath('/home/laravel/knowledge.txt')
->withProviderOptions(['purpose' => 'assistants'])
->put();要确定每个提供者的选项范围,请传递一个接收当前提供者的闭包:
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Files\Document;
$response = Document::fromPath('/home/laravel/training.jsonl')
->withProviderOptions(fn (Lab|string $provider) => match ($provider) {
Lab::OpenAI => ['purpose' => 'fine-tune'],
default => [],
})
->put();在对话中使用存储的文件
文件存储到提供商后,你可以使用 Document 或 Image 类上的 fromId 方法在代理对话中引用该文件:
use App\Ai\Agents\DocumentAnalyzer;
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;
$stored = Document::fromPath('/path/to/report.pdf')->put();
$response = (new DocumentAnalyzer)->prompt(
'Summarize this document.',
attachments: [
Document::fromId($stored->id),
],
);同样,可以使用 Image 类引用存储的图像:
use Laravel\Ai\Files;
use Laravel\Ai\Files\Image;
$stored = Image::fromPath('/path/to/photo.jpg')->put();
$response = (new ImageAnalyzer)->prompt(
'What is in this image?',
attachments: [
Image::fromId($stored->id),
],
);向量商店
向量存储允许你创建可搜索的文件集合,可用于检索增强生成 (RAG)。 Laravel\Ai\Stores 类提供了创建、检索和删除向量存储的方法:
use Laravel\Ai\Stores;
// Create a new vector store...
$store = Stores::create('Knowledge Base');
// Create a store with additional options...
$store = Stores::create(
name: 'Knowledge Base',
description: 'Documentation and reference materials.',
expiresWhenIdleFor: days(30),
);
return $store->id;要按 ID 检索现有向量存储,请使用 get 方法:
use Laravel\Ai\Stores;
$store = Stores::get('store_id');
$store->id;
$store->name;
$store->fileCounts;
$store->ready;要删除向量存储,请在 Stores 类或存储实例上使用 delete 方法:
use Laravel\Ai\Stores;
// Delete by ID...
Stores::delete('store_id');
// Or delete via a store instance...
$store = Stores::get('store_id');
$store->delete();Adding Files to Stores
一旦你拥有向量存储,你就可以使用 add 方法将 files 添加到其中。添加到存储的文件会使用 file search provider tool 自动索引以进行语义搜索:
use Laravel\Ai\Files\Document;
use Laravel\Ai\Stores;
$store = Stores::get('store_id');
// Add a file that has already been stored with the provider...
$document = $store->add('file_id');
$document = $store->add(Document::fromId('file_id'));
// Or, store and add a file in one step...
$document = $store->add(Document::fromPath('/path/to/document.pdf'));
$document = $store->add(Document::fromStorage('manual.pdf'));
$document = $store->add($request->file('document'));
$document->id;
$document->fileId;注意: 通常,当将以前存储的文件添加到向量存储时,返回的文档 ID 将与该文件以前分配的 ID 相匹配;然而,一些向量存储提供商可能会返回一个新的、不同的「文档ID」。因此,建议你始终将这两个 ID 存储在数据库中以供将来参考。
将文件添加到商店时,你可以将元数据附加到文件。此元数据稍后可用于在使用 file search provider tool 时过滤搜索结果:
$store->add(Document::fromPath('/path/to/document.pdf'), metadata: [
'author' => 'Taylor Otwell',
'department' => 'Engineering',
'year' => 2026,
]);要从存储中删除文件,请使用 remove 方法:
$store->remove('file_id');从向量存储中删除文件不会将其从提供商的 file storage 中删除。要从向量存储中删除文件并将其从文件存储中永久删除,请使用 deleteFile 参数:
$store->remove('file_abc123', deleteFile: true);故障转移
当提示或生成其他媒体时,你可以提供一系列提供程序/模型,以便在主提供程序遇到服务中断或速率限制时自动故障转移到备份提供程序/模型:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Image;
$response = (new SalesCoach)->prompt(
'Analyze this sales transcript...',
provider: [Lab::OpenAI, Lab::Anthropic],
);
$image = Image::of('A donut sitting on the kitchen counter')
->generate(provider: [Lab::Gemini, Lab::xAI]);仅当引发 FailoverableException 时才会发生故障转移 - 例如速率限制 (RateLimitedException)、提供商超载或不可用 (ProviderOverloadedException) 或积分不足 (InsufficientCreditsException)。普通错误(例如验证或错误请求错误)不会触发故障转移。
当你传递简单的提供程序列表(例如 [Lab::OpenAI, Lab::Anthropic])时,每个提供程序都使用其默认模型。要为故障转移链中的每个提供程序指定特定模型,请传递由提供程序键入的关联数组,并使用 Lab 枚举的 value 作为键(枚举情况不能直接用作 PHP 数组键):
use Laravel\Ai\Enums\Lab;
$response = (new SalesCoach)->prompt(
'Analyze this sales transcript...',
provider: [
Lab::Gemini->value => 'gemini-3-flash-preview',
Lab::DeepSeek->value => 'deepseek-v4-pro',
],
);测试
当伪造排队图像、音频、转录或嵌入生成时,在排队生成上注册的任何 then 回调都将使用伪造的响应进行调用,从而允许你测试回调中包含的逻辑。如果你希望不调用这些回调,你也可以使用 Queue::fake() 伪造队列。
Agent
要在测试期间伪造代理的响应,请调用代理类上的 fake 方法。你可以选择提供一系列响应或结束语:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;
// Automatically generate a fixed response for every prompt...
SalesCoach::fake();
// Provide a list of prompt responses...
SalesCoach::fake([
'First response',
'Second response',
]);
// Dynamically handle prompt responses based on the incoming prompt...
SalesCoach::fake(function (AgentPrompt $prompt) {
return 'Response for: '.$prompt->prompt;
});当伪造返回结构化输出的代理时,你可以提供数组作为响应。代理将返回包含给定数据的结构化响应:
SalesCoach::fake([
['score' => 87],
]);你还可以伪造正在等待工具批准的响应:
use Laravel\Ai\Approvals\PendingApproval;
use Laravel\Ai\Responses\AgentResponse;
FileAssistant::fake([
AgentResponse::fakeWithPendingApprovals([
new PendingApproval(
id: 'call_abc',
tool: 'DeleteFile',
arguments: ['path' => 'invoice.pdf'],
reason: 'This will permanently delete a file.',
),
]),
]);
$response = (new FileAssistant)->prompt('Delete the invoice.');
$response->hasPendingApprovals(); // true注意: 当在返回结构化输出的代理上调用
Agent::fake()且未显式提供假输出时,Laravel 将自动生成与代理定义的输出模式匹配的假数据。
提示代理后,你可以对收到的提示做出断言:
use Laravel\Ai\Prompts\AgentPrompt;
SalesCoach::assertPrompted('Analyze this...');
SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
return $prompt->contains('Analyze');
});
SalesCoach::assertPromptedTimes(3);
SalesCoach::assertNotPrompted('Missing prompt');
SalesCoach::assertNeverPrompted();当断言批准继续时,你可以检查提示的批准决定:
use Laravel\Ai\Approvals\Decisions;
use Laravel\Ai\Prompts\AgentPrompt;
FileAssistant::fake();
(new FileAssistant)->prompt(Decisions::from([
'call_abc' => true,
]));
FileAssistant::assertPrompted(function (AgentPrompt $prompt) {
return $prompt->hasApprovalDecisions()
&& $prompt->approvalDecisions->get('call_abc')->isApproved();
});对于排队代理调用,请使用排队断言方法:
use Laravel\Ai\QueuedAgentPrompt;
SalesCoach::assertQueued('Analyze this...');
SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
return $prompt->contains('Analyze');
});
SalesCoach::assertNotQueued('Missing prompt');
SalesCoach::assertNeverQueued();为了确保所有代理调用都有相应的虚假响应,你可以使用 preventStrayPrompts。如果在没有定义假响应的情况下调用代理,则会抛出异常:
SalesCoach::fake()->preventStrayPrompts();图像
可以通过调用 Image 类上的 fake 方法来伪造图像生成。一旦图像被伪造,就可以根据记录的图像生成提示执行各种断言:
use Laravel\Ai\Image;
use Laravel\Ai\Prompts\ImagePrompt;
use Laravel\Ai\Prompts\QueuedImagePrompt;
// Automatically generate a fixed response for every prompt...
Image::fake();
// Provide a list of prompt responses...
Image::fake([
base64_encode($firstImage),
base64_encode($secondImage),
]);
// Dynamically handle prompt responses based on the incoming prompt...
Image::fake(function (ImagePrompt $prompt) {
return base64_encode('...');
});生成图像后,你可以对收到的提示做出断言:
Image::assertGenerated(function (ImagePrompt $prompt) {
return $prompt->contains('sunset') && $prompt->isLandscape();
});
Image::assertNotGenerated('Missing prompt');
Image::assertNothingGenerated();对于排队图像生成,请使用排队断言方法:
Image::assertQueued(
fn (QueuedImagePrompt $prompt) => $prompt->contains('sunset')
);
Image::assertNotQueued('Missing prompt');
Image::assertNothingQueued();为了确保所有图像生成都有相应的虚假响应,你可以使用 preventStrayImages。如果在没有定义假响应的情况下生成图像,则会抛出异常:
Image::fake()->preventStrayImages();音频
可以通过调用 Audio 类上的 fake 方法来伪造音频生成。一旦音频被伪造,就可以针对录制的音频生成提示执行各种断言:
use Laravel\Ai\Audio;
use Laravel\Ai\Prompts\AudioPrompt;
use Laravel\Ai\Prompts\QueuedAudioPrompt;
// Automatically generate a fixed response for every prompt...
Audio::fake();
// Provide a list of prompt responses...
Audio::fake([
base64_encode($firstAudio),
base64_encode($secondAudio),
]);
// Dynamically handle prompt responses based on the incoming prompt...
Audio::fake(function (AudioPrompt $prompt) {
return base64_encode('...');
});生成音频后,你可以对收到的提示做出断言:
Audio::assertGenerated(function (AudioPrompt $prompt) {
return $prompt->contains('Hello') && $prompt->isFemale();
});
Audio::assertNotGenerated('Missing prompt');
Audio::assertNothingGenerated();对于排队音频生成,请使用排队断言方法:
Audio::assertQueued(
fn (QueuedAudioPrompt $prompt) => $prompt->contains('Hello')
);
Audio::assertNotQueued('Missing prompt');
Audio::assertNothingQueued();为了确保所有音频生成都有相应的虚假响应,你可以使用 preventStrayAudio。如果生成音频时没有定义假响应,则会抛出异常:
Audio::fake()->preventStrayAudio();转录
可以通过调用 Transcription 类上的 fake 方法来伪造转录生成。一旦转录被伪造,就可以针对记录的转录生成提示执行各种断言:
use Laravel\Ai\Transcription;
use Laravel\Ai\Prompts\TranscriptionPrompt;
use Laravel\Ai\Prompts\QueuedTranscriptionPrompt;
// Automatically generate a fixed response for every prompt...
Transcription::fake();
// Provide a list of prompt responses...
Transcription::fake([
'First transcription text.',
'Second transcription text.',
]);
// Dynamically handle prompt responses based on the incoming prompt...
Transcription::fake(function (TranscriptionPrompt $prompt) {
return 'Transcribed text...';
});生成转录后,你可以对收到的提示做出断言:
Transcription::assertGenerated(function (TranscriptionPrompt $prompt) {
return $prompt->language === 'en' && $prompt->isDiarized();
});
Transcription::assertNotGenerated(
fn (TranscriptionPrompt $prompt) => $prompt->language === 'fr'
);
Transcription::assertNothingGenerated();对于排队转录生成,请使用排队断言方法:
Transcription::assertQueued(
fn (QueuedTranscriptionPrompt $prompt) => $prompt->isDiarized()
);
Transcription::assertNotQueued(
fn (QueuedTranscriptionPrompt $prompt) => $prompt->language === 'fr'
);
Transcription::assertNothingQueued();为了确保所有转录代都有相应的假响应,你可以使用 preventStrayTranscriptions。如果在没有定义虚假响应的情况下生成转录,则会引发异常:
Transcription::fake()->preventStrayTranscriptions();向量嵌入
可以通过调用 Embeddings 类上的 fake 方法来伪造嵌入生成。一旦嵌入被伪造,就可以针对记录的嵌入生成提示执行各种断言:
use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\EmbeddingsPrompt;
use Laravel\Ai\Prompts\QueuedEmbeddingsPrompt;
// Automatically generate fake embeddings of the proper dimensions for every prompt...
Embeddings::fake();
// Provide a list of prompt responses...
Embeddings::fake([
[$firstEmbeddingVector],
[$secondEmbeddingVector],
]);
// Dynamically handle prompt responses based on the incoming prompt...
Embeddings::fake(function (EmbeddingsPrompt $prompt) {
return array_map(
fn () => Embeddings::fakeEmbedding($prompt->dimensions),
$prompt->inputs
);
});生成嵌入后,你可以对收到的提示做出断言:
Embeddings::assertGenerated(function (EmbeddingsPrompt $prompt) {
return $prompt->contains('Laravel') && $prompt->dimensions === 1536;
});
Embeddings::assertNotGenerated(
fn (EmbeddingsPrompt $prompt) => $prompt->contains('Other')
);
Embeddings::assertNothingGenerated();对于排队嵌入生成,请使用排队断言方法:
Embeddings::assertQueued(
fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Laravel')
);
Embeddings::assertNotQueued(
fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Other')
);
Embeddings::assertNothingQueued();为了确保所有嵌入代都有相应的假响应,你可以使用 preventStrayEmbeddings。如果在没有定义假响应的情况下生成嵌入,则会引发异常:
Embeddings::fake()->preventStrayEmbeddings();重排序
可以通过调用 Reranking 类上的 fake 方法来伪造重新排名操作:
use Laravel\Ai\Reranking;
use Laravel\Ai\Prompts\RerankingPrompt;
use Laravel\Ai\Responses\Data\RankedDocument;
// Automatically generate a fake reranked responses...
Reranking::fake();
// Provide custom responses...
Reranking::fake([
[
new RankedDocument(index: 0, document: 'First', score: 0.95),
new RankedDocument(index: 1, document: 'Second', score: 0.80),
],
]);重新排名后,你可以对所执行的操作做出断言:
Reranking::assertReranked(function (RerankingPrompt $prompt) {
return $prompt->contains('Laravel') && $prompt->limit === 5;
});
Reranking::assertNotReranked(
fn (RerankingPrompt $prompt) => $prompt->contains('Django')
);
Reranking::assertNothingReranked();文件
文件操作可以通过调用 Files 类上的 fake 方法来伪造:
use Laravel\Ai\Files;
Files::fake();一旦文件操作被伪造,你就可以对发生的上传和删除做出断言:
use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;
// Store files...
Document::fromString('Hello, Laravel!', mimeType: 'text/plain')
->as('hello.txt')
->put();
// Make assertions...
Files::assertStored(fn (StorableFile $file) =>
(string) $file === 'Hello, Laravel!' &&
$file->mimeType() === 'text/plain';
);
Files::assertNotStored(fn (StorableFile $file) =>
(string) $file === 'Hello, World!'
);
Files::assertNothingStored();为了断言不删除文件,你可以传递文件 ID:
Files::assertDeleted('file-id');
Files::assertNotDeleted('file-id');
Files::assertNothingDeleted();向量商店
向量存储操作可以通过调用 Stores 类上的 fake 方法来伪造。伪造商店也会自动伪造 file operations:
use Laravel\Ai\Stores;
Stores::fake();一旦商店操作被伪造,你可以对创建或删除的商店做出断言:
use Laravel\Ai\Stores;
// Create store...
$store = Stores::create('Knowledge Base');
// Make assertions...
Stores::assertCreated('Knowledge Base');
Stores::assertCreated(fn (string $name, ?string $description) =>
$name === 'Knowledge Base'
);
Stores::assertNotCreated('Other Store');
Stores::assertNothingCreated();为了断言不删除商店,你可以提供商店 ID:
Stores::assertDeleted('store_id');
Stores::assertNotDeleted('other_store_id');
Stores::assertNothingDeleted();要断言文件已从存储中添加或删除,请在给定的 Store 实例上使用断言方法:
Stores::fake();
$store = Stores::get('store_id');
// Add / remove files...
$store->add('added_id');
$store->remove('removed_id');
// Make assertions...
$store->assertAdded('added_id');
$store->assertRemoved('removed_id');
$store->assertNotAdded('other_file_id');
$store->assertNotRemoved('other_file_id');如果文件存储在提供程序的 file storage 中并在同一请求中添加到向量存储中,你可能不知道该文件的提供程序 ID。在这种情况下,你可以将闭包传递给 assertAdded 方法,以针对所添加文件的内容进行断言:
use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;
$store->add(Document::fromString('Hello, World!', 'text/plain')->as('hello.txt'));
$store->assertAdded(fn (StorableFile $file) => $file->name() === 'hello.txt');
$store->assertAdded(fn (StorableFile $file) => $file->content() === 'Hello, World!');事件
Laravel AI SDK 调度各种 events,包括:
AddingFileToStoreAgentFailedAgentFailedOverAgentPromptedAgentStreamedAudioGeneratedCreatingStoreEmbeddingsGeneratedFileAddedToStoreFileDeletedFileRemovedFromStoreFileStoredGeneratingAudioGeneratingEmbeddingsGeneratingImageGeneratingTranscriptionImageGeneratedInvokingToolPromptingAgentProviderFailedOverRemovingFileFromStoreRerankedRerankingStartingStepStepCompletedStepFailedStoreCreatedStoreDeletedStoringFileStreamingAgentToolApprovalRequestedToolApprovalResolvedToolFailedToolInvokedTranscriptionGenerated
你可以监听这些事件中的任何一个来记录或存储 AI SDK 使用信息。