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"

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

shell
php artisan migrate

配置

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

ini
ANTHROPIC_API_KEY=
COHERE_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_KEY=
OPENAI_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=

用于文本、图片、音频、转录和嵌入的默认模型也可以在应用程序的 config/ai.php 配置文件中配置。

自定义基础 URL

默认情况下,Laravel AI SDK 直接连接到每个提供商的公共 API 端点。但是,您可能需要通过不同的端点路由请求——例如,在使用代理服务集中管理 API 密钥、实施速率限制或通过企业网关路由流量时。

您可以通过向提供商配置添加 url 参数来配置自定义基础 URL:

php
'providers' => [
    'openai' => [
        'driver' => 'openai',
        'key' => env('OPENAI_API_KEY'),
        'url' => env('OPENAI_BASE_URL'),
    ],

    'anthropic' => [
        'driver' => 'anthropic',
        'key' => env('ANTHROPIC_API_KEY'),
        'url' => env('ANTHROPIC_BASE_URL'),
    ],
],

这在通过代理服务(如 LiteLLM 或 Azure OpenAI Gateway)路由请求或使用替代端点时非常有用。

以下提供商支持自定义基础 URL:OpenAI、Anthropic、Gemini、Groq、Cohere、DeepSeek、xAI 和 OpenRouter。

提供商支持

AI SDK 在其功能中支持多种提供商。下表总结了每个功能可用的提供商:

功能提供商
文本OpenAI, Anthropic, Gemini, Azure, Groq, xAI, DeepSeek, Mistral, Ollama
图片OpenAI, Gemini, xAI
TTSOpenAI, ElevenLabs
STTOpenAI, ElevenLabs, Mistral
嵌入OpenAI, Gemini, Azure, Cohere, Mistral, Jina, VoyageAI
重排序Cohere, Jina
文件OpenAI, Anthropic, Gemini

可以使用 Laravel\Ai\Enums\Lab 枚举在整个代码中引用提供商,而不是使用普通字符串:

php
use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::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\Messages\Message;
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

php
$response = (new SalesCoach)
    ->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: Lab::Anthropic,
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);

对话上下文

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

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

记住对话

注意: 在使用 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;

    /**
     * 获取智能体应遵循的指令。
     */
    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;

    // ...

    /**
     * 获取智能体的结构化输出模式定义。
     */
    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
{
    /**
     * 获取工具用途的描述。
     */
    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 方法返回它:

php
use App\Ai\Tools\RandomNumberGenerator;

/**
 * 获取智能体可用的工具。
 *
 * @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',

文件搜索

FileSearch 提供商工具允许智能体搜索已添加到 向量存储 的文件。这对于检索增强生成(RAG)应用程序非常有用,您希望智能体能够搜索大量文档。

支持的提供商: OpenAI, Anthropic

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

public function tools(): iterable
{
    return [
        FileSearch::using($vectorStoreId),
    ];
}

您可以将多个向量存储 ID 传递给 using 方法:

php
FileSearch::using(['store-id-1', 'store-id-2']),

对于 Anthropic,您可以使用 maxResults 方法配置返回的最大结果数:

php
FileSearch::using($vectorStoreId)->maxResults(20),

中间件

智能体中间件允许您在提示前后拦截和修改智能体的行为。这在记录、修改提示或实现自定义逻辑时非常有用。

要注册中间件,请向智能体类添加一个 middleware 方法:

php
use Closure;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Middleware\AgentMiddleware;

class SalesCoach implements Agent
{
    // ...

    /**
     * 获取智能体的中间件。
     */
    public function middleware(): array
    {
        return [
            new LoggingMiddleware,
            function (Message $message, Closure $next) {
                // 在提示前执行某些操作...

                $response = $next($message);

                // 在提示后执行某些操作...

                return $response;
            },
        ];
    }
}

中间件接收消息和一个 $next 闭包,该闭包将消息传递给管道中的下一个中间件或智能体本身。

匿名智能体

对于简单的用例,您可以使用 Agent 门面创建匿名智能体,而无需定义专用的智能体类:

php
use Laravel\Ai\Facades\Agent;

$response = Agent::prompt(
    'What is the capital of France?',
    instructions: 'You are a helpful geography assistant.',
);

return (string) $response;

您还可以向匿名智能体传递工具:

php
use Laravel\Ai\Facades\Agent;
use Laravel\Ai\Tools\Tool;

$response = Agent::prompt(
    'What is the weather in Paris?',
    instructions: 'You are a helpful assistant.',
    tools: [
        Tool::make('get_weather', function (string $city) {
            return "The weather in {$city} is sunny.";
        })->description('Get the current weather for a city.'),
    ],
);

智能体配置

您可以在智能体类中定义默认配置选项:

php
class SalesCoach implements Agent
{
    protected string $provider = Lab::Anthropic;

    protected string $model = 'claude-3-5-sonnet-20241022';

    protected int $timeout = 60;

    // ...
}

这些默认值可以在提示时被覆盖:

php
$response = (new SalesCoach)->prompt(
    'Analyze this transcript...',
    provider: Lab::OpenAI,
    model: 'gpt-4o',
    timeout: 120,
);

提供商选项

某些提供商支持额外的配置选项。您可以使用 withProviderOptions 方法传递这些选项:

php
$response = (new SalesCoach)->prompt(
    'Analyze this transcript...',
    providerOptions: [
        'temperature' => 0.7,
        'max_tokens' => 1000,
    ],
);

图片

Laravel\Ai\Image 类可用于从给定提示生成图片:

php
use Laravel\Ai\Image;

$image = Image::of('A beautiful sunset over the ocean')->generate();

$rawContent = (string) $image;

squarelandscapeportrait 方法可用于确定生成图片的纵横比:

php
$image = Image::of('A beautiful sunset over the ocean')
    ->landscape()
    ->generate();

您可以使用 size 方法指定自定义尺寸:

php
$image = Image::of('A beautiful sunset over the ocean')
    ->size(1024, 768)
    ->generate();

您可以使用 model 方法指定要使用的模型:

php
$image = Image::of('A beautiful sunset over the ocean')
    ->model('dall-e-3')
    ->generate();

您还可以将参考图片传递给 of 方法以生成相似图片或使用图片作为灵感:

php
use Laravel\Ai\Files;

$image = Image::of(
    'A painting in the style of this image',
    reference: [
        Files\Image::fromStorage('reference.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(Lab::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');

重排序集合

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

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: Lab::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: Lab::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;

注意: 通常,将先前存储的文件添加到向量存储时,返回的文档 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: [Lab::OpenAI, Lab::Anthropic],
);

$image = Image::of('A donut sitting on the kitchen counter')
    ->generate(provider: [Lab::Gemini, Lab::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;
});

注意: 当在返回结构化输出的智能体上调用 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('Laravel');
});

Audio::assertNotGenerated('Missing prompt');

Audio::assertNothingGenerated();

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

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

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 transcript',
    'Second transcript',
]);

// 根据传入提示动态处理提示响应...
Transcription::fake(function (TranscriptionPrompt $prompt) {
    return 'Transcript for: '.$prompt->file;
});

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

php
Transcription::assertGenerated(function (TranscriptionPrompt $prompt) {
    return $prompt->file === 'audio.mp3';
});

Transcription::assertNotGenerated('Missing prompt');

Transcription::assertNothingGenerated();

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

php
Transcription::assertQueued(
    fn (QueuedTranscriptionPrompt $prompt) => $prompt->file === 'audio.mp3'
);

Transcription::assertNotQueued('Missing prompt');

Transcription::assertNothingQueued();

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

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

嵌入向量

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

php
use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\EmbeddingsPrompt;

// 为每个提示自动生成固定响应...
Embeddings::fake();

// 提供提示响应列表...
Embeddings::fake([
    [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]],
    [[0.7, 0.8, 0.9], [1.0, 1.1, 1.2]],
]);

// 根据传入提示动态处理提示响应...
Embeddings::fake(function (EmbeddingsPrompt $prompt) {
    return [[0.1, 0.2, 0.3]];
});

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

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

Embeddings::assertNotGenerated('Missing prompt');

Embeddings::assertNothingGenerated();

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

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

重排序

可以通过在 Reranking 类上调用 fake 方法来伪造重排序。伪造重排序后,可以对记录的重排序提示执行各种断言:

php
use Laravel\Ai\Reranking;
use Laravel\Ai\Prompts\RerankingPrompt;

// 为每个提示自动生成固定响应...
Reranking::fake();

// 提供提示响应列表...
Reranking::fake([
    [
        ['document' => 'First result', 'score' => 0.95, 'index' => 0],
        ['document' => 'Second result', 'score' => 0.85, 'index' => 1],
    ],
]);

// 根据传入提示动态处理提示响应...
Reranking::fake(function (RerankingPrompt $prompt) {
    return [
        ['document' => $prompt->documents[0], 'score' => 0.9, 'index' => 0],
    ];
});

执行重排序后,您可以对收到的提示进行断言:

php
Reranking::assertReranked(function (RerankingPrompt $prompt) {
    return $prompt->query === 'Laravel tutorials';
});

Reranking::assertNotReranked('Missing prompt');

Reranking::assertNothingReranked();

要确保所有重排序都有相应的假响应,您可以使用 preventStrayReranking。如果在没有定义假响应的情况下执行重排序,将抛出异常:

php
Reranking::fake()->preventStrayReranking();

文件

可以通过在 Files 类上调用 fake 方法来伪造文件操作。伪造文件后,可以对记录的文件操作执行各种断言:

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

// 伪造文件操作...
Files::fake();

// 或者使用特定响应伪造...
Files::fake([
    'file-id-1',
    'file-id-2',
]);

存储文件后,您可以对操作进行断言:

php
use Laravel\Ai\Files\Document;

Document::fromPath('/path/to/document.pdf')->put();

Files::assertStored('/path/to/document.pdf');

Files::assertStored(function ($file) {
    return $file->name() === 'document.pdf';
});

Files::assertNothingStored();

对于文件检索和删除,您可以使用相应的断言方法:

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

Files::assertRetrieved('file-id');

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

Files::assertDeleted('file-id');

要确保所有文件操作都有相应的假响应,您可以使用 preventStrayFiles。如果在没有定义假响应的情况下执行文件操作,将抛出异常:

php
Files::fake()->preventStrayFiles();

向量存储

可以通过在 Stores 类上调用 fake 方法来伪造向量存储操作。伪造向量存储后,可以对记录的操作执行各种断言:

php
use Laravel\Ai\Stores;

// 伪造向量存储操作...
Stores::fake();

// 或者使用特定响应伪造...
Stores::fake([
    'store-id-1',
    'store-id-2',
]);

创建向量存储后,您可以对操作进行断言:

php
Stores::create('Knowledge Base');

Stores::assertCreated('Knowledge Base');

Stores::assertCreated(function ($store) {
    return $store->name === 'Knowledge Base';
});

Stores::assertNothingCreated();

对于文件添加操作,您可以使用 assertAdded 方法:

php
use Laravel\Ai\Files\Document;

$store = Stores::get('store-id');

$store->add(Document::fromPath('/path/to/document.pdf'));

$store->assertAdded('/path/to/document.pdf');

如果文件存储在提供商的文件存储中并在同一请求中添加到向量存储,您可能不知道文件的提供商 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 使用信息。