Skip to content
Laravel 技术公众号,欢迎关注

AI SDK

简介

Laravel AI SDK 提供了一个统一且富有表现力的 API,用于与 OpenAI、Anthropic、Gemini 等 AI 提供商进行交互。借助 AI SDK,您可以构建具有工具和结构化输出的智能代理、生成图像、合成和转录音频、创建向量嵌入等——所有这些都使用一致的、Laravel 风格的接口。

安装

您可以通过 Composer 安装 Laravel AI SDK:

shell
composer require laravel/ai

接下来,您应该使用 vendor:publish Artisan 命令发布 AI SDK 的配置和迁移文件:

shell
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"

最后,您应该运行应用程序的数据库迁移。这将创建 AI SDK 用于支持其对话存储的 agent_conversationsagent_conversation_messages 表:

shell
php artisan migrate

配置

您可以在应用程序的 config/ai.php 配置文件中定义 AI 提供商凭据,或在应用程序的 .env 文件中设置环境变量:

ini
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
TTSOpenAI、ElevenLabs
STTOpenAI、ElevenLabs
嵌入OpenAI、Gemini、Cohere、Jina
重排序Cohere、Jina
文件OpenAI、Anthropic、Gemini

代理

代理是 Laravel AI SDK 中与 AI 提供商交互的基本构建块。每个代理都是一个专用的 PHP 类,封装了与大型语言模型交互所需的指令、对话上下文、工具和输出模式。可以将代理视为一个专业助手——销售教练、文档分析器、客服机器人——您只需配置一次,即可在整个应用程序中按需提示。

您可以通过 make:agent Artisan 命令创建代理:

shell
php artisan make:agent SalesCoach

php artisan make:agent SalesCoach --structured

在生成的代理类中,您可以定义系统提示/指令、消息上下文、可用工具和输出模式(如适用):

php
<?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) {}

    /**
     * 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

php
$response = (new SalesCoach)
    ->prompt('Analyze this sales transcript...');

$response = SalesCoach::make()
    ->prompt('Analyze this sales transcript...');

return (string) $response;

make 方法从容器中解析您的代理,允许自动依赖注入。您也可以向代理的构造函数传递参数:

php
$agent = SalesCoach::make(user: $user);

通过向 prompt 方法传递额外参数,您可以在提示时覆盖默认的提供商、模型或 HTTP 超时:

php
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: 'anthropic',
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);

对话上下文

如果您的代理实现了 Conversational 接口,您可以使用 messages 方法返回之前的对话上下文(如适用):

php
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();
}

记忆对话

NOTE

在使用 RemembersConversations trait 之前,您应该使用 vendor:publish Artisan 命令发布并运行 AI SDK 迁移。这些迁移将创建存储对话所需的数据库表。

如果您希望 Laravel 自动存储和检索代理的对话历史,可以使用 RemembersConversations trait。该 trait 提供了一种简单的方式将对话消息持久化到数据库,而无需手动实现 Conversational 接口:

php
<?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...';
    }
}

要为用户开始新对话,请在提示之前调用 forUser 方法:

php
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');

$conversationId = $response->conversationId;

对话 ID 会在响应中返回,可以存储以供将来引用,您也可以直接从 agent_conversations 表中检索用户的所有对话。

要继续现有对话,请使用 continue 方法:

php
$response = (new SalesCoach)
    ->continue($conversationId, as: $user)
    ->prompt('Tell me more about that.');

使用 RemembersConversations trait 时,之前的消息会在提示时自动加载并包含在对话上下文中。每次交互后,新消息(用户和助手的消息)都会自动存储。

结构化输出

如果您希望代理返回结构化输出,请实现 HasStructuredOutput 接口,该接口要求您的代理定义一个 schema 方法:

php
<?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

php
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

return $response['score'];

附件

在提示时,您还可以传递附件以允许模型检查图像和文档:

php
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 类可用于将图像附加到提示中:

php
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):

php
use App\Ai\Agents\SalesCoach;

Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze this sales transcript...');
});

then 方法可用于提供一个闭包,当整个响应已流式传输到客户端时将调用该闭包:

php
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...
        });
});

或者,您可以手动遍历流式事件:

php
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
    // ...
}

使用 Vercel AI SDK 协议进行流式传输

您可以通过在可流式响应上调用 usingVercelDataProtocol 方法来使用 Vercel AI SDK 流协议 进行事件流式传输:

php
use App\Ai\Agents\SalesCoach;

Route::get('/coach', function () {
    return (new SalesCoach)
        ->stream('Analyze this sales transcript...')
        ->usingVercelDataProtocol();
});

广播

您可以通过几种不同的方式广播流式事件。首先,您可以简单地在流式事件上调用 broadcastbroadcastNow 方法:

php
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 方法将代理操作加入队列,并在流式事件可用时广播它们:

php
(new SalesCoach)->broadcastOnQueue(
    'Analyze this sales transcript...'
    new Channel('channel-name'),
);

队列

使用代理的 queue 方法,您可以提示代理,但允许它在后台处理响应,使您的应用程序保持快速和响应。thencatch 方法可用于注册闭包,当响应可用时或发生异常时将调用这些闭包:

php
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 命令创建:

shell
php artisan make:tool RandomNumberGenerator

生成的工具将放置在应用程序的 app/Ai/Tools 目录中。每个工具包含一个 handle 方法,当代理需要使用该工具时将调用该方法:

php
<?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 方法中返回它:

php
use App\Ai\Tools\RandomNumberGenerator;

/**
 * Get the tools available to the agent.
 *
 * @return Tool[]
 */
public function tools(): iterable
{
    return [
        new RandomNumberGenerator,
    ];
}

相似性搜索

SimilaritySearch 工具允许代理使用存储在数据库中的向量嵌入搜索与给定查询相似的文档。这对于检索增强生成(RAG)非常有用,当您想让代理访问搜索应用程序数据时。

创建相似性搜索工具最简单的方式是使用 usingModel 方法,配合具有向量嵌入的 Eloquent 模型:

php
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}

第一个参数是 Eloquent 模型类,第二个参数是包含向量嵌入的列。

您还可以提供 0.01.0 之间的最小相似度阈值和一个闭包来自定义查询:

php
SimilaritySearch::usingModel(
    model: Document::class,
    column: 'embedding',
    minSimilarity: 0.7,
    limit: 10,
    query: fn ($query) => $query->where('published', true),
),

如需更多控制,您可以使用自定义闭包创建相似性搜索工具,该闭包返回搜索结果:

php
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 方法自定义工具的描述:

php
SimilaritySearch::usingModel(Document::class, 'embedding')
    ->withDescription('Search the knowledge base for relevant articles.'),

提供商工具

提供商工具是由 AI 提供商原生实现的特殊工具,提供网页搜索、URL 获取和文件搜索等功能。与常规工具不同,提供商工具由提供商本身执行,而非您的应用程序。

提供商工具可以从代理的 tools 方法中返回。

网页搜索

WebSearch 提供商工具允许代理搜索网页以获取实时信息。这对于回答有关当前事件、最新数据或自模型训练截止日期以来可能已更改的主题的问题非常有用。

支持的提供商: Anthropic、OpenAI、Gemini

php
use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
    return [
        new WebSearch,
    ];
}

您可以配置网页搜索工具以限制搜索次数或将结果限制在特定域名:

php
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),

要根据用户位置优化搜索结果,请使用 location 方法:

php
(new WebSearch)->location(
    city: 'New York',
    region: 'NY',
    country: 'US'
);

网页获取

WebFetch 提供商工具允许代理获取和读取网页内容。当您需要代理分析特定 URL 或从已知网页检索详细信息时,这非常有用。

支持的提供商: Anthropic、Gemini

php
use Laravel\Ai\Providers\Tools\WebFetch;

public function tools(): iterable
{
    return [
        new WebFetch,
    ];
}

您可以配置网页获取工具以限制获取次数或限制在特定域名:

php
(new WebFetch)->max(3)->allow(['docs.laravel.com']),

文件搜索

FileSearch 提供商工具允许代理搜索存储在向量存储中的文件。这通过允许代理搜索您上传的文档以获取相关信息来实现检索增强生成(RAG)。

支持的提供商: OpenAI、Gemini

php
use Laravel\Ai\Providers\Tools\FileSearch;

public function tools(): iterable
{
    return [
        new FileSearch(stores: ['store_id']),
    ];
}

您可以提供多个向量存储 ID 以跨多个存储进行搜索:

php
new FileSearch(stores: ['store_1', 'store_2']);

如果您的文件有元数据,您可以通过提供 where 参数来过滤搜索结果。对于简单的等值过滤,传递一个数组:

php
new FileSearch(stores: ['store_id'], where: [
    'author' => 'Taylor Otwell',
    'year' => 2026,
]);

对于更复杂的过滤,您可以传递一个接收 FileSearchQuery 实例的闭包:

php
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
<?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;

    // ...

    /**
     * Get the agent's middleware.
     */
    public function middleware(): array
    {
        return [
            new LogPrompts,
        ];
    }
}

每个中间件类应定义一个 handle 方法,该方法接收 AgentPrompt 和一个 Closure 以将提示传递给下一个中间件:

php
<?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 方法在代理完成处理后执行代码。这适用于同步和流式响应:

php
public function handle(AgentPrompt $prompt, Closure $next)
{
    return $next($prompt)->then(function (AgentResponse $response) {
        Log::info('Agent responded', ['text' => $response->text]);
    });
}

匿名代理

有时您可能希望快速与模型交互而无需创建专用的代理类。您可以使用 agent 函数创建一个临时的匿名代理:

php
use function Laravel\Ai\{agent};

$response = agent(
    instructions: 'You are an expert at software development.',
    messages: [],
    tools: [],
)->prompt('Tell me about Laravel')

匿名代理也可以产生结构化输出:

php
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
<?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;

    // ...
}

UseCheapestModelUseSmartestModel 属性允许您自动选择给定提供商最具成本效益或最强大的模型,而无需指定模型名称。当您希望在不同提供商之间优化成本或能力时,这非常有用:

php
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 类可用于使用 openaigeminixai 提供商生成图像:

php
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')->generate();

$rawContent = (string) $image;

squareportraitlandscape 方法可用于控制图像的宽高比,而 quality 方法可用于指导模型最终图像质量(highmediumlow)。timeout 方法可用于指定 HTTP 超时时间(秒):

php
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')
    ->quality('high')
    ->landscape()
    ->timeout(120)
    ->generate();

您可以使用 attachments 方法附加参考图像:

php
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 配置文件中配置的默认磁盘:

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');

图像生成也可以加入队列:

php
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 类可用于从给定文本生成音频:

php
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();

$rawContent = (string) $audio;

malefemalevoice 方法可用于确定生成音频的声音:

php
$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 方法可用于动态指导模型生成音频的效果:

php
$audio = Audio::of('I love coding with Laravel.')
    ->female()
    ->instructions('Said like a pirate')
    ->generate();

生成的音频可以轻松存储到应用程序 config/filesystems.php 配置文件中配置的默认磁盘:

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');

音频生成也可以加入队列:

php
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 类可用于生成给定音频的转录文本:

php
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 方法可用于指示您希望响应除了原始文本转录外还包含说话人分离的转录,允许您按说话人访问分段转录:

php
$transcript = Transcription::fromStorage('audio.mp3')
    ->diarize()
    ->generate();

转录生成也可以加入队列:

php
use Laravel\Ai\Transcription;
use Laravel\Ai\Responses\TranscriptionResponse;

Transcription::fromStorage('audio.mp3')
    ->queue()
    ->then(function (TranscriptionResponse $transcript) {
        // ...
    });

嵌入

您可以使用 Laravel Stringable 类上的新 toEmbeddings 方法轻松为任何给定字符串生成向量嵌入:

php
use Illuminate\Support\Str;

$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();

或者,您可以使用 Embeddings 类一次为多个输入生成嵌入:

php
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, ...]]

您可以指定嵌入的维度和提供商:

php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->dimensions(1536)
    ->generate('openai', 'text-embedding-3-small');

查询嵌入

生成嵌入后,您通常会将它们存储在数据库的 vector 列中以供后续查询。Laravel 通过 pgvector 扩展为 PostgreSQL 提供原生向量列支持。首先,在迁移中定义一个 vector 列,指定维度数:

php
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 索引:

php
$table->vector('embedding', dimensions: 1536)->index();

在 Eloquent 模型上,您应该将向量列转换为 array

php
protected function casts(): array
{
    return [
        'embedding' => 'array',
    ];
}

要查询相似记录,请使用 whereVectorSimilarTo 方法。该方法按最小余弦相似度(0.01.0 之间,其中 1.0 表示完全相同)过滤结果,并按相似度排序:

php
use App\Models\Document;

$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();

$queryEmbedding 可以是浮点数数组或纯字符串。当给定字符串时,Laravel 将自动为其生成嵌入:

php
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
    ->limit(10)
    ->get();

如果您需要更多控制,可以独立使用更底层的 whereVectorDistanceLessThanselectVectorDistanceorderByVectorDistance 方法:

php
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();

如果您想让代理能够作为工具执行相似性搜索,请查看相似性搜索工具文档。

NOTE

向量查询目前仅支持使用 pgvector 扩展的 PostgreSQL 连接。

缓存嵌入

嵌入生成可以被缓存以避免对相同输入的冗余 API 调用。要启用缓存,请将 ai.caching.embeddings.cache 配置选项设置为 true

php
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
        // ...
    ],
],

启用缓存后,嵌入将缓存 30 天。缓存键基于提供商、模型、维度和输入内容,确保相同的请求返回缓存结果,而不同的配置生成新的嵌入。

您还可以使用 cache 方法为特定请求启用缓存,即使全局缓存已禁用:

php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->cache()
    ->generate();

您可以指定自定义缓存持续时间(秒):

php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->cache(seconds: 3600) // 缓存 1 小时
    ->generate();

toEmbeddings Stringable 方法也接受 cache 参数:

php
// 使用默认持续时间缓存...
$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 类可用于重排序文档:

php
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 方法可用于限制返回的结果数量:

php
$response = Reranking::of($documents)
    ->limit(5)
    ->rerank('search query');

重排序集合

为方便起见,Laravel 集合可以使用 rerank 宏进行重排序。第一个参数指定用于重排序的字段,第二个参数是查询:

php
// 按单个字段重排序...
$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'
);

您还可以限制结果数量并指定提供商:

php
$reranked = $posts->rerank(
    by: 'content',
    query: 'Laravel tutorials',
    limit: 10,
    provider: 'cohere'
);

文件

Laravel\Ai\Files 类或各个文件类可用于将文件存储到 AI 提供商以供后续在对话中使用。这对于大型文档或您想多次引用而无需重新上传的文件非常有用:

php
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;

您还可以存储原始内容或上传的文件:

php
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;

// 存储原始内容...
$stored = Document::fromString('Hello, World!', 'text/plain')->put();

// 存储上传的文件...
$stored = Document::fromUpload($request->file('document'))->put();

文件存储后,您可以在通过代理生成文本时引用该文件,而无需重新上传:

php
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 方法:

php
use Laravel\Ai\Files\Document;

$file = Document::fromId('file-id')->get();

$file->id;
$file->mimeType();

要从提供商删除文件,请使用 delete 方法:

php
Document::fromId('file-id')->delete();

默认情况下,Files 类使用应用程序 config/ai.php 配置文件中配置的默认 AI 提供商。对于大多数操作,您可以使用 provider 参数指定不同的提供商:

php
$response = Document::fromPath(
    '/home/laravel/document.pdf'
)->put(provider: 'anthropic');

在对话中使用已存储的文件

文件存储到提供商后,您可以使用 DocumentImage 类上的 fromId 方法在代理对话中引用它:

php
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 类引用:

php
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 类提供了创建、检索和删除向量存储的方法:

php
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 方法:

php
use Laravel\Ai\Stores;

$store = Stores::get('store_id');

$store->id;
$store->name;
$store->fileCounts;
$store->ready;

要删除向量存储,请在 Stores 类或存储实例上使用 delete 方法:

php
use Laravel\Ai\Stores;

// 通过 ID 删除...
Stores::delete('store_id');

// 或通过存储实例删除...
$store = Stores::get('store_id');

$store->delete();

向存储添加文件

拥有向量存储后,您可以使用 add 方法向其中添加文件。添加到存储的文件会自动索引,以便使用文件搜索提供商工具进行语义搜索:

php
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 以供将来引用。

您可以在将文件添加到存储时附加元数据。此元数据稍后可用于在使用文件搜索提供商工具时过滤搜索结果:

php
$store->add(Document::fromPath('/path/to/document.pdf'), metadata: [
    'author' => 'Taylor Otwell',
    'department' => 'Engineering',
    'year' => 2026,
]);

要从存储中移除文件,请使用 remove 方法:

php
$store->remove('file_id');

从向量存储中移除文件不会将其从提供商的文件存储中删除。要从向量存储中移除文件并从文件存储中永久删除它,请使用 deleteFile 参数:

php
$store->remove('file_abc123', deleteFile: true);

故障转移

在提示或生成其他媒体时,您可以提供一个提供商/模型数组,以便在主要提供商遇到服务中断或速率限制时自动故障转移到备用提供商/模型:

php
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 方法。您可以选择性地提供响应数组或闭包:

php
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 将自动生成与代理定义的输出模式匹配的伪造数据。

提示代理后,您可以对收到的提示进行断言:

php
use Laravel\Ai\Prompts\AgentPrompt;

SalesCoach::assertPrompted('Analyze this...');

SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});

SalesCoach::assertNotPrompted('Missing prompt');

SalesCoach::assertNeverPrompted();

对于队列化的代理调用,请使用队列断言方法:

php
use Laravel\Ai\QueuedAgentPrompt;

SalesCoach::assertQueued('Analyze this...');

SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});

SalesCoach::assertNotQueued('Missing prompt');

SalesCoach::assertNeverQueued();

要确保所有代理调用都有对应的伪造响应,您可以使用 preventStrayPrompts。如果代理在没有定义伪造响应的情况下被调用,将抛出异常:

php
SalesCoach::fake()->preventStrayPrompts();

图像

图像生成可以通过在 Image 类上调用 fake 方法来伪造。一旦图像被伪造,可以对记录的图像生成提示执行各种断言:

php
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('...');
});

生成图像后,您可以对收到的提示进行断言:

php
Image::assertGenerated(function (ImagePrompt $prompt) {
    return $prompt->contains('sunset') && $prompt->isLandscape();
});

Image::assertNotGenerated('Missing prompt');

Image::assertNothingGenerated();

对于队列化的图像生成,请使用队列断言方法:

php
Image::assertQueued(
    fn (QueuedImagePrompt $prompt) => $prompt->contains('sunset')
);

Image::assertNotQueued('Missing prompt');

Image::assertNothingQueued();

要确保所有图像生成都有对应的伪造响应,您可以使用 preventStrayImages。如果在没有定义伪造响应的情况下生成图像,将抛出异常:

php
Image::fake()->preventStrayImages();

音频

音频生成可以通过在 Audio 类上调用 fake 方法来伪造。一旦音频被伪造,可以对记录的音频生成提示执行各种断言:

php
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('...');
});

生成音频后,您可以对收到的提示进行断言:

php
Audio::assertGenerated(function (AudioPrompt $prompt) {
    return $prompt->contains('Hello') && $prompt->isFemale();
});

Audio::assertNotGenerated('Missing prompt');

Audio::assertNothingGenerated();

对于队列化的音频生成,请使用队列断言方法:

php
Audio::assertQueued(
    fn (QueuedAudioPrompt $prompt) => $prompt->contains('Hello')
);

Audio::assertNotQueued('Missing prompt');

Audio::assertNothingQueued();

要确保所有音频生成都有对应的伪造响应,您可以使用 preventStrayAudio。如果在没有定义伪造响应的情况下生成音频,将抛出异常:

php
Audio::fake()->preventStrayAudio();

转录

转录生成可以通过在 Transcription 类上调用 fake 方法来伪造。一旦转录被伪造,可以对记录的转录生成提示执行各种断言:

php
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...';
});

生成转录后,您可以对收到的提示进行断言:

php
Transcription::assertGenerated(function (TranscriptionPrompt $prompt) {
    return $prompt->language === 'en' && $prompt->isDiarized();
});

Transcription::assertNotGenerated(
    fn (TranscriptionPrompt $prompt) => $prompt->language === 'fr'
);

Transcription::assertNothingGenerated();

对于队列化的转录生成,请使用队列断言方法:

php
Transcription::assertQueued(
    fn (QueuedTranscriptionPrompt $prompt) => $prompt->isDiarized()
);

Transcription::assertNotQueued(
    fn (QueuedTranscriptionPrompt $prompt) => $prompt->language === 'fr'
);

Transcription::assertNothingQueued();

要确保所有转录生成都有对应的伪造响应,您可以使用 preventStrayTranscriptions。如果在没有定义伪造响应的情况下生成转录,将抛出异常:

php
Transcription::fake()->preventStrayTranscriptions();

嵌入

嵌入生成可以通过在 Embeddings 类上调用 fake 方法来伪造。一旦嵌入被伪造,可以对记录的嵌入生成提示执行各种断言:

php
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
    );
});

生成嵌入后,您可以对收到的提示进行断言:

php
Embeddings::assertGenerated(function (EmbeddingsPrompt $prompt) {
    return $prompt->contains('Laravel') && $prompt->dimensions === 1536;
});

Embeddings::assertNotGenerated(
    fn (EmbeddingsPrompt $prompt) => $prompt->contains('Other')
);

Embeddings::assertNothingGenerated();

对于队列化的嵌入生成,请使用队列断言方法:

php
Embeddings::assertQueued(
    fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Laravel')
);

Embeddings::assertNotQueued(
    fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Other')
);

Embeddings::assertNothingQueued();

要确保所有嵌入生成都有对应的伪造响应,您可以使用 preventStrayEmbeddings。如果在没有定义伪造响应的情况下生成嵌入,将抛出异常:

php
Embeddings::fake()->preventStrayEmbeddings();

重排序

重排序操作可以通过在 Reranking 类上调用 fake 方法来伪造:

php
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),
    ],
]);

重排序后,您可以对执行的操作进行断言:

php
Reranking::assertReranked(function (RerankingPrompt $prompt) {
    return $prompt->contains('Laravel') && $prompt->limit === 5;
});

Reranking::assertNotReranked(
    fn (RerankingPrompt $prompt) => $prompt->contains('Django')
);

Reranking::assertNothingReranked();

文件

文件操作可以通过在 Files 类上调用 fake 方法来伪造:

php
use Laravel\Ai\Files;

Files::fake();

一旦文件操作被伪造,您可以对发生的上传和删除进行断言:

php
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:

php
Files::assertDeleted('file-id');
Files::assertNotDeleted('file-id');
Files::assertNothingDeleted();

向量存储

向量存储操作可以通过在 Stores 类上调用 fake 方法来伪造。伪造存储也会自动伪造文件操作

php
use Laravel\Ai\Stores;

Stores::fake();

一旦存储操作被伪造,您可以对创建或删除的存储进行断言:

php
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:

php
Stores::assertDeleted('store_id');
Stores::assertNotDeleted('other_store_id');
Stores::assertNothingDeleted();

要断言文件已添加到存储或从存储中移除,请在给定的 Store 实例上使用断言方法:

php
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 方法传递一个闭包来对添加的文件内容进行断言:

php
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 会分发多种事件,包括:

  • AddingFileToStore
  • AgentPrompted
  • AgentStreamed
  • AudioGenerated
  • CreatingStore
  • EmbeddingsGenerated
  • FileAddedToStore
  • FileDeleted
  • FileRemovedFromStore
  • FileStored
  • GeneratingAudio
  • GeneratingEmbeddings
  • GeneratingImage
  • GeneratingTranscription
  • ImageGenerated
  • InvokingTool
  • PromptingAgent
  • RemovingFileFromStore
  • Reranked
  • Reranking
  • StoreCreated
  • StoringFile
  • StreamingAgent
  • ToolInvoked
  • TranscriptionGenerated

您可以监听这些事件中的任何一个来记录或存储 AI SDK 使用信息。