Laravel AI SDK
介绍
Laravel AI SDK 提供了一个统一的、富有表现力的 API,用于与 OpenAI、Anthropic、Gemini 等 AI 提供方交互。使用 AI SDK,你可以构建具有工具和结构化输出的智能体,生成图像,合成和转录音频,创建向量嵌入等等——所有这些都使用一个一致的、适合 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=
COHERE_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
OPENAI_API_KEY=
JINA_API_KEY=
XAI_API_KEY=文本、图像、音频、转录和嵌入所使用的默认模型也可以在应用程序的 config/ai.php 配置文件中配置。
提供方支持
AI SDK 在其各功能中支持多种提供方。下表总结了每个功能可用的提供方:
| 功能 | 提供方 |
|---|---|
| 文本 | OpenAI, Anthropic, Gemini, Groq, xAI |
| 图像 | OpenAI, Gemini, xAI |
| TTS | OpenAI, ElevenLabs |
| STT | OpenAI, ElevenLabs |
| 嵌入 | OpenAI, Gemini, Cohere, Jina |
| 重新排序 | Cohere, Jina |
| 文件 | OpenAI, Anthropic, Gemini |
智能体
智能体是 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\Promptable;
use Stringable;
class SalesCoach implements Agent, Conversational, HasTools, HasStructuredOutput
{
use Promptable;
public function __construct(public User $user) {}
/**
* 获取智能体应遵循的指令。
*/
public function instructions(): Stringable|string
{
return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
}
/**
* 获取构成到目前为止对话的消息列表。
*/
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();
}
/**
* 获取智能体可用的工具。
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new RetrievePreviousTranscripts,
];
}
/**
* 获取智能体的结构化输出模式定义。
*/
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...');
$response = SalesCoach::make()
->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: 'anthropic',
model: 'claude-haiku-4-5-20251001',
timeout: 120,
);对话上下文
如果你的智能体实现了 Conversational 接口,你可以使用 messages 方法返回先前的对话上下文(如果适用):
use App\Models\History;
use Laravel\Ai\Messages\Message;
/**
* 获取构成到目前为止对话的消息列表。
*/
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();
}记住对话
NOTE
在使用 RemembersConversations trait 之前,你应该使用 vendor:publish Artisan 命令发布并运行 AI SDK 迁移。这些迁移将创建存储对话所需的数据库表。
如果你希望 Laravel 自动为你的智能体存储和检索对话历史,你可以使用 RemembersConversations trait。这个 trait 提供了一种简单的方法将对话消息持久化到数据库,而无需手动实现 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;
/**
* 获取智能体应遵循的指令。
*/
public function instructions(): string
{
return 'You are a sales coach...';
}
}要为用户开始新的对话,在提示之前调用 forUser 方法:
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');
$conversationId = $response->conversationId;对话 ID 会在响应中返回,可以存储以备将来参考,或者你也可以直接从 agent_conversations 表中检索用户的所有对话。
要继续现有的对话,使用 continue 方法:
$response = (new SalesCoach)
->continue($conversationId, as: $user)
->prompt('Tell me more about that.');当使用 RemembersConversations trait 时,在提示时会自动加载先前的消息并将其包含在对话上下文中。每次交互后,新消息(用户和助手的)都会自动存储。
结构化输出
如果你希望你的智能体返回结构化输出,请实现 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;
// ...
/**
* 获取智能体的结构化输出模式定义。
*/
public function schema(JsonSchema $schema): array
{
return [
'score' => $schema->integer()->required(),
];
}
}当提示返回结构化输出的智能体时,你可以像访问数组一样访问返回的 StructuredAgentResponse:
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return $response['score'];附件
提示时,你也可以随提示传递附件,以允许模型检查图像和文档:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;
$response = (new SalesCoach)->prompt(
'Analyze the attached sales transcript...',
attachments: [
Files\Document::fromStorage('transcript.pdf') // 从文件系统磁盘附加文档...
Files\Document::fromPath('/home/laravel/transcript.md') // 从本地路径附加文档...
$request->file('transcript'), // 附加上传的文件...
]
);同样,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') // 从文件系统磁盘附加图像...
Files\Image::fromPath('/home/laravel/photo.jpg') // 从本地路径附加图像...
$request->file('photo'), // 附加上传的文件...
]
);流式传输
你可以通过调用 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 流协议 来流式传输事件:
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'),
);队列
使用智能体的 queue 方法,你可以提示智能体,但允许它在后台处理响应,使你的应用程序感觉快速和响应迅速。then 和 catch 方法可用于注册在响应可用或发生异常时将调用的闭包:
use Illuminate\Http\Request;
use Laravel\Ai\Responses\AgentResponse;
use Throwable;
Route::post('/coach', function (Request $request) {
return (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
{
/**
* 获取工具用途的描述。
*/
public function description(): Stringable|string
{
return 'This tool may be used to generate cryptographically secure random numbers.';
}
/**
* 执行工具。
*/
public function handle(Request $request): Stringable|string
{
return (string) random_int($request['min'], $request['max']);
}
/**
* 获取工具的模式定义。
*/
public function schema(JsonSchema $schema): array
{
return [
'min' => $schema->integer()->min(0)->required(),
'max' => $schema->integer()->required(),
];
}
}一旦你定义了你的工具,你可以从任何智能体的 tools 方法返回它:
use App\Ai\Tools\RandomNumberGenerator;
/**
* 获取智能体可用的工具。
*
* @return Tool[]
*/
public function tools(): iterable
{
return [
new RandomNumberGenerator,
];
}相似性搜索
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.'),提供方工具
提供方工具是由 AI 提供方原生实现的特殊工具,提供诸如网络搜索、URL 获取和文件搜索等功能。与常规工具不同,提供方工具由提供方本身执行,而不是由你的应用程序执行。
提供方工具可以由你的智能体的 tools 方法返回。
网络搜索
WebSearch 提供方工具允许智能体搜索网络以获取实时信息。这对于回答关于当前事件、最近数据或自模型训练截止日期以来可能已发生变化的话题非常有用。
支持的提供方: Anthropic, OpenAI, Gemini
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
use Laravel\Ai\Providers\Tools\WebFetch;
public function tools(): iterable
{
return [
new WebFetch,
];
}你可以配置网络获取工具以限制获取次数或限制到特定域:
(new WebFetch)->max(3)->allow(['docs.laravel.com']),文件搜索
FileSearch 提供方工具允许智能体搜索存储在向量存储中的文件。这通过允许智能体在你的上传文档中搜索相关信息来实现检索增强生成(RAG)。
支持的提供方: OpenAI, Gemini
use Laravel\Ai\Providers\Tools\FileSearch;
public function tools(): iterable
{
return [
new FileSearch(stores: ['store_id']),
];
}你可以提供多个向量存储 ID 以跨多个存储进行搜索:
new FileSearch(stores: ['store_1', 'store_2']);如果你的文件有元数据,你可以通过提供 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'])
);中间件
智能体支持中间件,允许你在提示发送给提供方之前拦截和修改它们。要向智能体添加中间件,实现 HasMiddleware 接口并定义一个 middleware 方法,该方法返回一个中间件类的数组:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasMiddleware
{
use Promptable;
// ...
/**
* 获取智能体的中间件。
*/
public function middleware(): array
{
return [
new LogPrompts,
];
}
}每个中间件类应该定义一个 handle 方法,该方法接收 AgentPrompt 和一个 Closure 以将提示传递给下一个中间件:
<?php
namespace App\Ai\Middleware;
use Closure;
use Laravel\Ai\Prompts\AgentPrompt;
class LogPrompts
{
/**
* 处理传入的提示。
*/
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: 模型可以生成的最大令牌数。Provider: 用于智能体的 AI 提供方(或多个提供方用于故障转移)。Temperature: 用于生成的采样温度(0.0 到 1.0)。Timeout: 智能体请求的 HTTP 超时时间(秒)(默认:60)。UseCheapestModel: 使用提供方最便宜的文本模型以进行成本优化。UseSmartestModel: 使用提供方能力最强的文本模型以处理复杂任务。
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\MaxTokens;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Provider('anthropic')]
#[Temperature(0.7)]
#[Timeout(120)]
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;
// 将使用最便宜的模型(例如,Haiku)...
}
#[UseSmartestModel]
class ComplexReasoner implements Agent
{
use Promptable;
// 将使用能力最强的模型(例如,Opus)...
}图像
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 a 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;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 类提供的新的 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('openai', 'text-embedding-3-small');查询嵌入
一旦你生成了嵌入,通常会将其存储在数据库的 vector 列中以供后续查询。Laravel 通过 pgvector 扩展为 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 模型上,你应该将向量列转换为 array:
protected function casts(): array
{
return [
'embedding' => 'array',
];
}要查询相似记录,使用 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();如果你想给智能体提供执行相似性搜索的能力作为工具,请查看相似性搜索工具文档。
NOTE
目前仅在 PostgreSQL 连接上支持向量查询,且需要使用 pgvector 扩展。
缓存嵌入
可以缓存嵌入生成以避免对相同输入进行冗余 API 调用。要启用缓存,将 ai.caching.embeddings.cache 配置选项设置为 true:
'caching' => [
'embeddings' => [
'cache' => true,
'store' => env('CACHE_STORE', 'database'),
// ...
],
],启用缓存后,嵌入会被缓存 30 天。缓存键基于提供方、模型、维度和输入内容,确保相同的请求返回缓存结果,而不同的配置生成新的嵌入。
你也可以使用 cache 方法为特定请求启用缓存,即使全局缓存被禁用:
$response = Embeddings::for(['Napa Valley has great wine.'])
->cache()
->generate();你可以指定自定义的缓存持续时间(秒):
$response = Embeddings::for(['Napa Valley has great wine.'])
->cache(seconds: 3600) // 缓存 1 小时
->generate();toEmbeddings Stringable 方法也接受一个 cache 参数:
// 使用默认持续时间缓存...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);
// 缓存特定持续时间...
$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');
// 访问顶部结果...
$response->first()->document; // "Laravel is a PHP web application framework."
$response->first()->score; // 0.95
$response->first()->index; // 1 (原始位置)limit 方法可用于限制返回的结果数量:
$response = Reranking::of($documents)
->limit(5)
->rerank('search query');重新排序集合
为了方便起见,Laravel 集合可以使用 rerank 宏进行重新排序。第一个参数指定用于重新排序的字段,第二个参数是查询:
// 按单个字段重新排序...
$posts = Post::all()
->rerank('body', 'Laravel tutorials');
// 按多个字段重新排序(作为 JSON 发送)...
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');
// 使用闭包构建文档进行重新排序...
$reranked = $posts->rerank(
fn ($post) => $post->title.': '.$post->body,
'Laravel tutorials'
);你也可以限制结果数量并指定提供方:
$reranked = $posts->rerank(
by: 'content',
query: 'Laravel tutorials',
limit: 10,
provider: 'cohere'
);文件
Laravel\Ai\Files 类或单独的文件类可用于将文件存储在你的 AI 提供方处,以便在以后的对话中使用。这对于大文档或你希望多次引用而不必重新上传的文件很有用:
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;
// 从本地路径存储文件...
$response = Document::fromPath('/home/laravel/document.pdf')->put();
$response = Image::fromPath('/home/laravel/photo.jpg')->put();
// 存储在文件系统磁盘上的文件...
$response = Document::fromStorage('document.pdf', disk: 'local')->put();
$response = Image::fromStorage('photo.jpg', disk: 'local')->put();
// 存储在远程 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;
// 存储原始内容...
$stored = Document::fromString('Hello, World!', 'text/plain')->put();
// 存储上传的文件...
$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') // 附加已存储的文档...
]
);要检索先前存储的文件,在文件实例上使用 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: 'anthropic');在对话中使用存储的文件
一旦文件存储在提供方处,你可以使用 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;
// 创建新的向量存储...
$store = Stores::create('Knowledge Base');
// 使用附加选项创建存储...
$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;
// 按 ID 删除...
Stores::delete('store_id');
// 或通过存储实例删除...
$store = Stores::get('store_id');
$store->delete();向存储添加文件
一旦你有了向量存储,你可以使用 add 方法向其添加文件。添加到存储中的文件会自动索引,以便使用文件搜索提供方工具进行语义搜索:
use Laravel\Ai\Files\Document;
use Laravel\Ai\Stores;
$store = Stores::get('store_id');
// 添加已存储在提供方处的文件...
$document = $store->add('file_id');
$document = $store->add(Document::fromId('file_id'));
// 或者,一步存储并添加文件...
$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;NOTE
通常,当将先前存储的文件添加到向量存储时,返回的文档 ID 将与文件先前分配的 ID 匹配;但是,某些向量存储提供方可能会返回一个新的、不同的“文档 ID”。因此,建议你在数据库中同时存储两个 ID 以备将来参考。
向存储添加文件时,你可以附加元数据。稍后在使用文件搜索提供方工具时,可以使用此元数据过滤搜索结果:
$store->add(Document::fromPath('/path/to/document.pdf'), metadata: [
'author' => 'Taylor Otwell',
'department' => 'Engineering',
'year' => 2026,
]);要从存储中删除文件,使用 remove 方法:
$store->remove('file_id');从向量存储中删除文件并不会将其从提供方的文件存储中删除。要从向量存储中删除文件并永久地从文件存储中删除,使用 deleteFile 参数:
$store->remove('file_abc123', deleteFile: true);故障转移
当提示或生成其他媒体时,你可以提供一个提供方/模型数组,以便在主提供方遇到服务中断或速率限制时自动故障转移到备份提供方/模型:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Image;
$response = (new SalesCoach)->prompt(
'Analyze this sales transcript...',
provider: ['openai', 'anthropic'],
);
$image = Image::of('A donut sitting on the kitchen counter')
->generate(provider: ['gemini', 'xai']);测试
智能体
要在测试期间伪造智能体的响应,在智能体类上调用 fake 方法。你可以选择性地提供一个响应数组或一个闭包:
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;
// 为每个提示自动生成固定响应...
SalesCoach::fake();
// 提供提示响应列表...
SalesCoach::fake([
'First response',
'Second response',
]);
// 基于传入的提示动态处理提示响应...
SalesCoach::fake(function (AgentPrompt $prompt) {
return 'Response for: '.$prompt->prompt;
});NOTE
当在返回结构化输出的智能体上调用 Agent::fake() 时,Laravel 会自动生成符合智能体定义输出模式的伪造数据。
在提示智能体之后,你可以对接收到的提示进行断言:
use Laravel\Ai\Prompts\AgentPrompt;
SalesCoach::assertPrompted('Analyze this...');
SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
return $prompt->contains('Analyze');
});
SalesCoach::assertNotPrompted('Missing prompt');
SalesCoach::assertNeverPrompted();对于排队的智能体调用,使用排队的断言方法:
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;
// 为每个提示自动生成固定响应...
Image::fake();
// 提供提示响应列表...
Image::fake([
base64_encode($firstImage),
base64_encode($secondImage),
]);
// 基于传入的提示动态处理提示响应...
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;
// 为每个提示自动生成固定响应...
Audio::fake();
// 提供提示响应列表...
Audio::fake([
base64_encode($firstAudio),
base64_encode($secondAudio),
]);
// 基于传入的提示动态处理提示响应...
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;
// 为每个提示自动生成固定响应...
Transcription::fake();
// 提供提示响应列表...
Transcription::fake([
'First transcription text.',
'Second transcription text.',
]);
// 基于传入的提示动态处理提示响应...
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;
// 为每个提示自动生成具有适当维度的伪造嵌入...
Embeddings::fake();
// 提供提示响应列表...
Embeddings::fake([
[$firstEmbeddingVector],
[$secondEmbeddingVector],
]);
// 基于传入的提示动态处理提示响应...
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;
// 自动生成伪造的重新排序响应...
Reranking::fake();
// 提供自定义响应...
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;
// 存储文件...
Document::fromString('Hello, Laravel!', mime: 'text/plain')
->as('hello.txt')
->put();
// 进行断言...
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 方法来伪造向量存储操作。伪造存储也会自动伪造文件操作:
use Laravel\Ai\Stores;
Stores::fake();一旦存储操作被伪造,你可以对创建或删除的存储进行断言:
use Laravel\Ai\Stores;
// 创建存储...
$store = Stores::create('Knowledge Base');
// 进行断言...
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');
// 添加/移除文件...
$store->add('added_id');
$store->remove('removed_id');
// 进行断言...
$store->assertAdded('added_id');
$store->assertRemoved('removed_id');
$store->assertNotAdded('other_file_id');
$store->assertNotRemoved('other_file_id');如果一个文件在提供方的文件存储中存储并同时添加到向量存储中,你可能不知道文件的提供方 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 派发各种事件,包括:
AddingFileToStoreAgentPromptedAgentStreamedAudioGeneratedCreatingStoreEmbeddingsGeneratedFileAddedToStoreFileDeletedFileRemovedFromStoreFileStoredGeneratingAudioGeneratingEmbeddingsGeneratingImageGeneratingTranscriptionImageGeneratedInvokingToolPromptingAgentRemovingFileFromStoreRerankedRerankingStoreCreatedStoringFileStreamingAgentToolInvokedTranscriptionGenerated
你可以监听这些事件中的任何一个来记录或存储 AI SDK 使用信息。