Skip to content

Laravel AI SDK

简介

Laravel AI SDK 提供了一套统一且富有表现力的 API,用于与 OpenAI、Anthropic、Gemini 等 AI 提供商进行交互。借助 AI SDK,你可以构建拥有工具和结构化输出的智能 agent,生成图像,合成和转录音频,创建向量 embeddings 等等——所有这些都使用一致的、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 配置文件中或在 .env 文件中以环境变量的形式定义 AI 提供商凭据:

ini
ANTHROPIC_API_KEY=
AZURE_OPENAI_API_KEY=
COHERE_API_KEY=
DEEPSEEK_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
GROQ_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_KEY=
OPENAI_API_KEY=
OPENAI_COMPATIBLE_API_KEY=
OPENAI_COMPATIBLE_URL=
OPENROUTER_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=

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

自定义基础 URL

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

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

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

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

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

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

OpenAI 兼容提供商

如果你使用的是 OpenAI 兼容 API,例如 LM Studio、vLLM、Together、Fireworks 或本地网关,则可以配置一个 openai-compatible 提供商。url 选项是必需的,而 key 选项是可选的;提供该选项时,它会作为 bearer token 发送:

php
'providers' => [
    'local' => [
        'driver' => 'openai-compatible',
        'url' => env('LOCAL_AI_URL'),
        'key' => env('LOCAL_AI_API_KEY'),
    ],
],

配置完成后,你可以像使用其他提供商一样使用这个具名提供商:

php
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');

你也可以为该提供商配置默认文本模型,这样就无需显式传入模型:

php
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'text' => [
            'default' => env('LOCAL_AI_MODEL'),
        ],
    ],
],

OpenAI 兼容提供商支持文本生成、流式传输、工具、结构化输出和图像附件。如果你的端点需要额外的请求体字段,请使用提供商选项提供它们。

提供商支持

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

功能提供商
文本OpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter
图像OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter
TTSOpenAI, ElevenLabs, Gemini
STTOpenAI, ElevenLabs, Mistral, Gemini
EmbeddingsOpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter
重排序Cohere, Jina, VoyageAI
文件OpenAI, Anthropic, Gemini, Azure

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

php
use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::OpenAiCompatible;
Lab::Gemini;
// ...

Agent

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

你可以通过 make:agent Artisan 命令创建一个 agent:

shell
php artisan make:agent SalesCoach

php artisan make:agent SalesCoach --structured

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

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

    /**
     * 获取 agent 应遵循的指令。
     */
    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();
    }

    /**
     * 获取 agent 可用的工具。
     *
     * @return Tool[]
     */
    public function tools(): iterable
    {
        return [
            new RetrievePreviousTranscripts,
        ];
    }

    /**
     * 获取 agent 的结构化输出模式定义。
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'feedback' => $schema->string()->required(),
            'score' => $schema->integer()->min(1)->max(10)->required(),
        ];
    }
}

提示

要提示一个 agent,首先使用 make 方法或标准实例化创建实例,然后调用 prompt

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

return (string) $response;

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

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

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

php
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model: 'claude-sonnet-5',
    timeout: 120,
);

对话上下文

如果你的 agent 实现了 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 自动存储和检索 agent 的对话历史,你可以使用 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;

    /**
     * 获取 agent 应遵循的指令。
     */
    public function instructions(): string
    {
        return 'You are a sales coach...';
    }
}

使用 RemembersConversations trait 时,让该 trait 提供 messages 方法实现。如果 agent 类中存在 messages 方法,该方法会优先于 trait 的实现,导致对话历史无法从数据库加载。

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

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

$conversationId = $response->conversationId;

对话 ID 会在响应中返回,可以存储以供将来引用。如果你想使用 Eloquent 检索用户的所有对话,可以将 HasConversations trait 添加到你的用户模型:

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Ai\Concerns\HasConversations;

class User extends Authenticatable
{
    use HasConversations;
}

将该 trait 添加到模型后,你可以通过 conversations 关联检索和查询用户的对话:

php
$conversations = $user->conversations()
    ->latest('updated_at')
    ->paginate(20);

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

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

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

对话参与者

虽然用户是最常见的对话参与者,但对话可以属于任何 Eloquent model。使用 forParticipant 方法为另一种类型的 model 开始对话:

php
$response = (new SalesCoach)
    ->forParticipant($team)
    ->prompt('Review our latest sales results.');

参与者的 morph class 和主键会随对话一起存储。因此,具有相同主键的不同类型 model,例如 User ID 1Team ID 1,会拥有各自独立的对话历史。forUser 方法是 forParticipant 的别名。

你可以使用 continueLastConversation 方法继续该参与者最近的对话:

php
$response = (new SalesCoach)
    ->continueLastConversation($team)
    ->prompt('Tell me more about that.');

继续特定对话时,请将参与者传递给 continue 方法:

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

HasConversations trait 可以添加到任何参与对话的 Eloquent model。生成的 conversations 关系是一个按该 model 类型和主键限定范围的多态关系。你也可以通过其反向关系访问拥有某个对话的参与者:

php
$conversations = $team->conversations;

$participant = $conversation->participant;

如果你的应用使用多种参与者 model 类型,应考虑定义 Eloquent morph map,使存储的参与者类型与 model 类名解耦。

WARNING

continue 方法不会验证给定参与者是否拥有该对话。你的应用应在继续对话前授权访问该对话。

结构化输出

如果你希望 agent 返回结构化输出,请实现 HasStructuredOutput 接口,该接口要求你的 agent 定义一个 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;

    // ...

    /**
     * 获取 agent 的结构化输出模式定义。
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required(),
        ];
    }
}

当提示一个返回结构化输出的 agent 时,你可以像数组一样访问返回的 StructuredAgentResponse

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

return $response['score'];

嵌套对象

要定义嵌套的结构化输出,可以结合闭包使用 object 方法:

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;

    // ...

    /**
     * 获取 agent 的结构化输出模式定义。
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required(),
            'metadata' => $schema->object(fn ($schema) => [
                'confidence' => $schema->string()->enum(['low', 'medium', 'high'])->required(),
                'language' => $schema->string()->required(),
            ])->required(),
        ];
    }
}

对象数组

如果你的 agent 应返回由结构化条目组成的列表,可以组合 arrayobject 方法:

php
public function schema(JsonSchema $schema): array
{
    return [
        'feedback' => $schema->array()
            ->items(
                $schema->object(fn ($schema) => [
                    'comment' => $schema->string()->required(),
                    'score' => $schema->integer()->required(),
                ])
            )
            ->required(),
    ];
}

如果某个值可能匹配多个 schema 中的任意一个,请使用 anyOf 方法:

php
public function schema(JsonSchema $schema): array
{
    return [
        'content' => $schema->anyOf([
            $schema->object(fn ($schema) => [
                'type' => $schema->string()->enum(['article'])->required(),
                'title' => $schema->string()->required(),
            ]),
            $schema->object(fn ($schema) => [
                'type' => $schema->string()->enum(['image'])->required(),
                'url' => $schema->string()->required(),
            ]),
        ])->required(),
    ];
}

附件

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

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 方法流式传输 agent 的响应。返回的 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'));
}

或者,你可以调用 agent 的 broadcastOnQueue 方法将 agent 操作加入队列,并在流式事件可用时广播它们:

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

跳过过大的事件

某些广播平台会将 WebSocket 消息限制在约 10KB。包含大量数据的流式事件(例如大型工具结果)可能会超过该限制并导致广播失败。你可以使用 WithoutBroadcasting 属性从广播中排除特定事件类型:

php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Attributes\WithoutBroadcasting;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use Laravel\Ai\Streaming\Events\ToolCall;
use Laravel\Ai\Streaming\Events\ToolResult;

#[WithoutBroadcasting(ToolCall::class, ToolResult::class)]
class SearchAgent implements Agent, HasTools
{
    use Promptable;

    // ...
}

被排除的事件不会被广播,但仍会持久化到 agent_conversation_messages 表,因此你的前端可以在流结束后加载完整的工具数据。这同时适用于队列广播(broadcastOnQueue)和同步广播(broadcast / broadcastNow)。

队列

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

php
use Illuminate\Http\Request;
use Laravel\Ai\Responses\AgentResponse;
use Throwable;

Route::post('/coach', function (Request $request) {
    (new SalesCoach)
        ->queue($request->input('transcript'))
        ->then(function (AgentResponse $response) {
            // ...
        })
        ->catch(function (Throwable $e) {
            // ...
        });

    return back();
});

工具

工具可用于为 agent 提供额外的功能,agent 在响应提示时可以使用这些功能。可以使用 make:tool Artisan 命令创建工具:

shell
php artisan make:tool RandomNumberGenerator

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

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

定义工具后,你可以从任何 agent 的 tools 方法中返回它:

php
use App\Ai\Tools\RandomNumberGenerator;

/**
 * 获取 agent 可用的工具。
 *
 * @return Tool[]
 */
public function tools(): iterable
{
    return [
        new RandomNumberGenerator,
    ];
}

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

创建相似性搜索工具最简单的方法是使用 usingModel 方法,传入一个具有向量 embeddings 的 Eloquent 模型:

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

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

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

你还可以提供 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.'),

文件存储工具

FileStorage 工具工厂允许你为 agent 提供访问 Laravel filesystem disk 的能力。all 方法会返回一组工具,使 agent 能够列出、读取、检查、生成 URL、写入、删除和复制给定 disk 上的文件:

php
use Laravel\Ai\Tools\FileStorage;

public function tools(): iterable
{
    return FileStorage::all('local');
}

如果你的 agent 只应检查文件,请使用 readOnly 方法:

php
return FileStorage::readOnly('local');

这些方法会返回一个 Illuminate\Support\Collection,让你可以进一步过滤提供给 agent 的工具:

php
use Laravel\Ai\Tools\Filesystem\DeleteFile;

return FileStorage::all('s3')
    ->reject(fn ($tool) => $tool instanceof DeleteFile);

MCP 工具

如果你的应用使用 Laravel MCP,你可以为 agent 提供由 Model Context Protocol 服务器公开的工具。使用 Laravel MCP client,你可以连接到远程或本地 MCP 服务器,并将其工具直接传递给你的 agent。

NOTE

MCP 工具要求你的应用中已安装 Laravel MCP 包。

由于 MCP client 的 tools 方法会返回一个集合,因此请使用 ... 运算符将其展开到 agent 的 tools 数组中:

php
use App\Ai\Tools\RandomNumberGenerator;
use Laravel\Mcp\Client;

/**
 * 获取 agent 可用的工具。
 *
 * @return Tool[]
 */
public function tools(): iterable
{
    return [
        ...Client::web('https://mcp.example.com')
            ->withToken($token)
            ->tools(),

        new RandomNumberGenerator,
    ];
}

AI SDK 会自动包装每个 MCP 工具,使 agent 可以像调用任何其他工具一样调用它。你也可以使用命名 MCP client

php
use Laravel\Mcp\Facades\Mcp;

public function tools(): iterable
{
    return [
        ...Mcp::client('github')->tools(),
    ];
}

或者连接到本地 MCP 服务器

php
use Laravel\Mcp\Client;

public function tools(): iterable
{
    return [
        ...Client::local('php', ['artisan', 'mcp:start'])->tools(),
    ];
}

如需了解如何创建和认证 MCP client,包括 bearer tokens 和 OAuth,请查阅 MCP client 文档

提供商工具

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

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

WebSearch 提供商工具允许 agent 搜索网页以获取实时信息。这对于回答有关时事、最新数据或自模型训练截止后可能已发生变化的主题的问题非常有用。

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

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 提供商工具允许 agent 抓取和读取网页内容。当你需要 agent 分析特定 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 提供商工具允许 agent 搜索存储在向量存储中的文件。这通过允许 agent 搜索你上传的文档中的相关信息来实现检索增强生成(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'])
);

子 Agent

Agent 也可以从另一个 agent 的 tools 方法中返回。当一个 agent 作为工具返回时,父 agent 可以将特定任务委派给子 agent,并在回答原始提示时使用子 agent 的响应。当通用型 agent 需要访问拥有独立指令、工具、模型配置或提供商偏好的专用 agent 时,这会很有用。

例如,客服 agent 可以将退款资格问题委派给专门的退款 agent:

php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

class CustomerSupportAgent implements Agent, HasTools
{
    use Promptable;

    /**
     * 获取 agent 应遵循的指令。
     */
    public function instructions(): string
    {
        return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.';
    }

    /**
     * 获取 agent 可用的工具。
     *
     * @return Tool[]
     */
    public function tools(): iterable
    {
        return [
            new RefundsAgent,
        ];
    }
}

要自定义子 agent 暴露给父 agent 的方式,请在子 agent 上实现 CanActAsTool 接口,并定义面向工具的名称和描述:

php
<?php

namespace App\Ai\Agents;

use App\Ai\Tools\LookupOrder;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\CanActAsTool;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

#[Provider(Lab::Anthropic)]
class RefundsAgent implements Agent, CanActAsTool, HasTools
{
    use Promptable;

    /**
     * 获取 agent 应遵循的指令。
     */
    public function instructions(): string
    {
        return 'You are a refunds specialist. Use order details and the refund policy to give concise eligibility guidance.';
    }

    /**
     * 获取 agent 的工具名称。
     */
    public function name(): string
    {
        return 'refunds_specialist';
    }

    /**
     * 获取 agent 的工具描述。
     */
    public function description(): string
    {
        return 'Determine whether an order is eligible for a refund and explain the next step.';
    }

    /**
     * 获取 agent 可用的工具。
     *
     * @return Tool[]
     */
    public function tools(): iterable
    {
        return [
            new LookupOrder,
        ];
    }
}

如果子 agent 未实现 CanActAsTool,Laravel 将根据 agent 的类名生成默认工具名称和描述。

中间件

Agent 支持中间件,允许你在提示发送到提供商之前拦截和修改提示。可以使用 make:agent-middleware Artisan 命令创建中间件:

shell
php artisan make:agent-middleware LogPrompts

生成的中间件将放置在应用程序的 app/Ai/Middleware 目录中。要向 agent 添加中间件,请实现 HasMiddleware 接口并定义一个返回中间件类数组的 middleware 方法:

php
<?php

namespace App\Ai\Agents;

use App\Ai\Middleware\LogPrompts;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasMiddleware
{
    use Promptable;

    // ...

    /**
     * 获取 agent 的中间件。
     */
    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
{
    /**
     * 处理传入的提示。
     */
    public function handle(AgentPrompt $prompt, Closure $next)
    {
        Log::info('Prompting agent', ['prompt' => $prompt->prompt]);

        return $next($prompt);
    }
}

你可以在响应上使用 then 方法在 agent 完成处理后执行代码。这适用于同步和流式响应:

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

匿名 Agent

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

php
use function Laravel\Ai\{agent};

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

匿名 agent 也可以生成结构化输出:

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

Agent 配置

你可以使用 PHP 属性为 agent 配置文本生成选项。以下属性可用:

  • MaxSteps:agent 使用工具时可以执行的最大步骤数。
  • MaxTokens:模型可以生成的最大 token 数。
  • Model:agent 应使用的模型。
  • Provider:用于 agent 的 AI 提供商(或用于故障转移的多个提供商)。
  • Temperature:用于生成的采样温度(0.0 到 1.0)。
  • Timeout:agent 请求的 HTTP 超时秒数(默认:60)。
  • TopP:用于生成的 nucleus sampling 概率(0.0 到 1.0)。
  • UseCheapestModel:使用提供商最便宜的文本模型以优化成本。
  • UseSmartestModel:使用提供商最强大的文本模型以处理复杂任务。
php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\MaxTokens;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Attributes\TopP;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

#[Provider(Lab::Anthropic)]
#[Model('claude-sonnet-5')]
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Temperature(0.7)]
#[Timeout(120)]
#[TopP(0.9)]
class SalesCoach implements Agent
{
    use Promptable;

    // ...
}

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

NOTE

UseCheapestModelUseSmartestModel 选择的底层模型可能会随着 Laravel AI SDK 发布版本和提供商推出新模型而变化。切换模型可能引入行为变化、已弃用参数和显著的成本差异。如果你需要稳定、可预测的模型和定价,请使用 Model 属性明确指定模型。

提供商选项

如果你的 agent 需要传递提供商特定的选项(如 OpenAI 推理强度或惩罚设置),请实现 HasProviderOptions 契约并定义一个 providerOptions 方法:

php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasProviderOptions;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasProviderOptions
{
    use Promptable;

    // ...

    /**
     * 获取提供商特定的生成选项。
     */
    public function providerOptions(Lab|string $provider): array
    {
        return match ($provider) {
            Lab::OpenAI => [
                'reasoning' => ['effort' => 'low'],
                'frequency_penalty' => 0.5,
                'presence_penalty' => 0.3,
            ],
            Lab::Anthropic => [
                'thinking' => ['budget_tokens' => 1024],
                'cache_control' => ['type' => 'ephemeral'],
            ],
            default => [],
        };
    }
}

providerOptions 方法接收当前正在使用的提供商(Lab 枚举或字符串),允许你为每个提供商返回不同的选项。当使用故障转移时,这特别有用,因为每个备用提供商可以接收自己的配置。

上面的 Anthropic 示例还通过 cache_control 启用了 prompt caching

人工工具审批

WARNING

工具审批需要使用持久化对话历史的 Conversational agent,这样暂停的调用才能恢复。RemembersConversations trait 提供所需的持久化能力。

执行敏感或不可逆操作的工具在执行前可能需要人工审批。要让工具支持审批,请实现 Approvable contract 并使用 InteractsWithApprovals trait。可审批工具默认需要审批:

php
<?php

namespace App\Ai\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Storage;
use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\Approvable;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class DeleteFile implements Approvable, Tool
{
    use InteractsWithApprovals;

    /**
     * 获取工具用途的描述。
     */
    public function description(): Stringable|string
    {
        return 'Delete a file from storage.';
    }

    /**
     * 执行工具。
     */
    public function handle(Request $request): Stringable|string
    {
        Storage::delete($request['path']);

        return "Deleted [{$request['path']}].";
    }

    /**
     * 获取工具的 schema 定义。
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'path' => $schema->string()->required(),
        ];
    }
}

要根据工具调用的参数判断是否需要审批,请在工具上定义 needsApproval 方法。此方法可以返回布尔值,也可以返回包含审批请求原因的 Approval 实例:

php
use Laravel\Ai\Approvals\Approval;

/**
 * 判断该工具是否需要针对给定请求进行审批。
 */
protected function needsApproval(Request $request): Approval|bool
{
    return str_starts_with($request['path'], 'temporary/')
        ? false
        : Approval::required('This will permanently delete a file.');
}

从 agent 的 tools 方法返回工具时,你可以覆盖工具的审批要求:

php
public function tools(): iterable
{
    return [
        (new SendNotification)->withoutApproval(),
        (new DeleteFile)->requireApproval('Deletion review required.'),
    ];
}

当调用可审批工具时,agent 会在执行前暂停。你可以检查响应中的待审批项,其中包含每个工具调用的 ID、工具名称、参数和审批原因:

php
$response = (new FileAssistant)
    ->forUser($user)
    ->prompt('Delete the old invoice.');

if ($response->hasPendingApprovals()) {
    foreach ($response->pendingApprovals as $approval) {
        // $approval->id
        // $approval->tool
        // $approval->arguments
        // $approval->reason
    }
}

要恢复 agent,请继续该对话,并提供一个 Decisions 实例,为每个待处理工具调用包含一项决定。决定可以批准调用、拒绝调用,或在执行前编辑其参数:

php
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;

$response = (new FileAssistant)
    ->continue($conversationId, as: $user)
    ->prompt(Decisions::from([
        'call_abc' => Decision::approve(),
        'call_ghi' => Decision::reject('The invoice must be retained.'),
    ]));

布尔值 truefalse 可分别作为批准和拒绝的简写。每个待处理工具调用都必须收到一项决定。未知、缺失或之前已解决的工具调用 ID 会导致抛出 ApprovalMismatchException。你可以使用 approveRemainingrejectRemaining 方法为缺少显式决定的调用提供默认决定:

php
$decisions = Decisions::from([
    'call_abc' => true,
])->rejectRemaining('Not approved.');

$response = (new FileAssistant)
    ->continue($conversationId, as: $user)
    ->prompt($decisions);

带结果的拒绝,例如 Decision::reject('Not approved.'),会返回给模型,使其可以继续响应。无结果的拒绝会在记录拒绝后停止生成循环。

promptstreamqueuebroadcastbroadcastNowbroadcastOnQueue 方法都支持工具审批。

在 streaming 和 broadcasting 期间,暂停会表示为 tool_approval_request 事件。使用 Vercel AI SDK stream protocol 时,审批请求和结果会使用该协议原生的工具审批片段发出。

对于 queued agent,生成的响应会传递给 then 回调,Laravel 也会分发 ToolApprovalRequested 事件。

Laravel 会在要求模型继续之前存储已批准工具的结果。如果随后生成失败,该审批已经解决。请使用普通文本 prompt 继续对话,而不是再次提交相同的审批决定。

完整审批流程

以下路由演示了完整的审批流程。GET 路由返回聊天界面,而 POST 路由接受来自聊天界面的新文本 prompt 或审批决定。此示例假设应用的 User model 使用了 HasConversations trait:

php
use App\Ai\Agents\FileAssistant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\Rule;
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;
use Laravel\Ai\Models\Conversation;

Route::get('/chat/{conversation}', function (Request $request, Conversation $conversation) {
    Gate::authorize('view', $conversation);

    return view('chat', [
        'conversation' => $conversation,
    ]);
})->middleware('auth');

Route::post('/chat/{conversation}', function (Request $request, Conversation $conversation) {
    Gate::authorize('view', $conversation);

    $validated = $request->validate([
        'message' => ['nullable', 'string', 'required_without:decisions', 'prohibited_with:decisions'],
        'decisions' => ['nullable', 'array', 'required_without:message', 'prohibited_with:message'],
        'decisions.*.action' => ['required_with:decisions', Rule::in(['approve', 'reject'])],
        'decisions.*.result' => ['nullable', 'string'],
    ]);

    $prompt = isset($validated['decisions'])
        ? Decisions::from($validated->collect('decisions')->map(
            fn (array $decision) => match ($decision['action']) {
                'approve' => Decision::approve(),
                'reject' => Decision::reject($decision['result'] ?? null),
            }
        )->all())
        : $validated['message'];

    $response = (new FileAssistant)
        ->continue($conversation->id, as: $request->user())
        ->prompt($prompt);

    return [
        'conversation_id' => $response->conversationId,
        'status' => $response->hasPendingApprovals() ? 'awaiting_approval' : 'complete',
        'message' => $response->text,
        'approvals' => $response->pendingApprovals,
    ];
})->middleware('auth');

当响应状态为 awaiting_approval 时,聊天界面应渲染待审批项,并使用工具调用 ID 作为每项决定的键,将用户选择提交到同一个端点:

json
{
    "decisions": {
        "call_abc": {
            "action": "approve"
        },
        "call_def": {
            "action": "reject",
            "result": "The invoice must be retained."
        }
    }
}

对于普通聊天消息,界面可以改为提交 message 值:

json
{
    "message": "Delete the old invoice."
}

图像

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 an impressionist painting.')
    ->attachments([
        Files\Image::fromStorage('photo.jpg'),
        // Files\Image::fromPath('/home/laravel/photo.jpg'),
        // Files\Image::fromUrl('https://example.com/photo.jpg'),
        // $request->file('photo'),
    ])
    ->landscape()
    ->generate();

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

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

        // ...
    });

音频(TTS)

Laravel\Ai\Audio 类可用于从给定文本生成音频:

php
use Laravel\Ai\Audio;

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

$rawContent = (string) $audio;

你也可以通过 Laravel 的 Stringable 类提供的 toAudio 方法从字符串生成音频:

php
use Illuminate\Support\Str;

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

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

        // ...
    });

转录(STT)

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 类提供的 summarize 方法对文本进行摘要。默认情况下,摘要最多包含三个句子,并使用已配置 provider 中最便宜的文本模型生成:

php
use Illuminate\Support\Str;

$summary = Str::of($article)->summarize();

你可以指定用于生成摘要的最大句子数、provider、model 和 timeout。Str 类也提供了该方法的静态版本:

php
use Laravel\Ai\Enums\Lab;

$summary = Str::of($article)->summarize(
    sentences: 4,
    provider: Lab::Anthropic,
    model: 'claude-sonnet-5',
    timeout: 30,
);

$summary = Str::summarize($article, sentences: 4);

Embeddings

你可以使用 Laravel Stringable 类提供的新 toEmbeddings 方法轻松地为任何给定字符串生成向量 embeddings:

php
use Illuminate\Support\Str;

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

或者,你可以使用 Embeddings 类一次为多个输入生成 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, ...]]

你可以指定 embeddings 的维度和提供商:

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

多模态 Embeddings

除了字符串,Embeddings::for 方法还接受图像、音频、文档和视频输入,让你可以为非文本内容生成 embeddings。Gemini 支持图像、音频、文档和视频 embeddings,而 VoyageAI 支持图像和视频 embeddings:

php
use Laravel\Ai\Embeddings;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Files\Image;
use Laravel\Ai\Files\Video;

$response = Embeddings::for([
    'A vineyard at sunset.',
    Image::fromStorage('vineyard.jpg'),
    Video::fromPath('/home/laravel/tour.mp4'),
])->generate(Lab::Gemini);

多模态输入使用与附件所用文件类相同的文件类。这些文件可以从本地路径、filesystem disk、远程 URL 或 Base64 编码内容创建。图像、文档和视频也可以从上传文件创建,而文档还可以从原始字符串内容创建:

php
use Laravel\Ai\Files\Audio;
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;
use Laravel\Ai\Files\Video;

Image::fromPath('/home/laravel/photo.jpg');
Image::fromStorage('photo.jpg');
Image::fromUpload($request->file('photo'));

Audio::fromPath('/home/laravel/clip.mp3');
Audio::fromStorage('clip.mp3');
Audio::fromUpload($request->file('clip.mp3'));

Video::fromPath('/home/laravel/video.mp4');
Video::fromStorage('video.mp4');
Video::fromUpload($request->file('video'));

Document::fromUrl('https://example.com/report.pdf');
Document::fromString('Laravel is a PHP framework.', 'text/plain');
Document::fromUpload($request->file('report'));

NOTE

VoyageAI 不允许在同一个请求中混用远程 URL 媒体和 Base64 编码媒体。本地文件、已存储文件和上传文件会作为 Base64 编码内容发送,而文本输入可以与任一媒体来源组合使用。请查阅你的 provider 文档,确认可用的多模态模型和输入类型。

查询 Embeddings

生成 embeddings 后,你通常会将它们存储在数据库的 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 会自动为其生成 embeddings:

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

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

NOTE

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

缓存 Embeddings

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

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

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

你也可以使用 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: 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();

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

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

你可以使用 withProviderOptions 方法传入提供商特定的上传选项。例如,你可以设置 OpenAI 文件的 purpose

php
use Laravel\Ai\Files\Document;

$response = Document::fromPath('/home/laravel/knowledge.txt')
    ->withProviderOptions(['purpose' => 'assistants'])
    ->put();

要按提供商限定选项,请传入一个接收当前提供商的闭包:

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

$response = Document::fromPath('/home/laravel/training.jsonl')
    ->withProviderOptions(fn (Lab|string $provider) => match ($provider) {
        Lab::OpenAI => ['purpose' => 'fine-tune'],
        default => [],
    })
    ->put();

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

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

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\Enums\Lab;
use Laravel\Ai\Image;

$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: [Lab::OpenAI, Lab::Anthropic],
);

$image = Image::of('A donut sitting on the kitchen counter')
    ->generate(provider: [Lab::Gemini, Lab::xAI]);

只有在抛出 FailoverableException 时才会触发故障转移,例如速率限制(RateLimitedException)、提供商过载或不可用(ProviderOverloadedException),或额度不足(InsufficientCreditsException)。普通错误,例如验证错误或错误请求错误,不会触发故障转移。

当你传入普通的提供商列表(例如 [Lab::OpenAI, Lab::Anthropic])时,每个提供商都会使用其默认模型。要为故障转移链中的每个提供商指定特定模型,请传入以提供商为键的关联数组,并使用 Lab 枚举的 value 作为键(enum case 不能直接用作 PHP 数组键):

php
use Laravel\Ai\Enums\Lab;

$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: [
        Lab::Gemini->value => 'gemini-3-flash-preview',
        Lab::DeepSeek->value => 'deepseek-v4-pro',
    ],
);

测试

Agent

要在测试中模拟 agent 的响应,请在 agent 类上调用 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 时,你可以提供数组作为响应。agent 将返回包含给定数据的结构化响应:

php
SalesCoach::fake([
    ['score' => 87],
]);

你也可以伪造一个正在等待工具审批的响应:

php
use Laravel\Ai\Approvals\PendingApproval;
use Laravel\Ai\Responses\AgentResponse;

FileAssistant::fake([
    AgentResponse::fakeWithPendingApprovals([
        new PendingApproval(
            id: 'call_abc',
            tool: 'DeleteFile',
            arguments: ['path' => 'invoice.pdf'],
            reason: 'This will permanently delete a file.',
        ),
    ]),
]);

$response = (new FileAssistant)->prompt('Delete the invoice.');

$response->hasPendingApprovals(); // true

Note: 当在返回结构化输出的 agent 上调用 Agent::fake() 且未显式提供假输出时,Laravel 会自动生成与 agent 定义的输出模式匹配的假数据。

提示 agent 后,你可以对接收到的提示进行断言:

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

断言审批继续流程时,你可以检查 prompt 的审批决定:

php
use Laravel\Ai\Approvals\Decisions;
use Laravel\Ai\Prompts\AgentPrompt;

FileAssistant::fake();

(new FileAssistant)->prompt(Decisions::from([
    'call_abc' => true,
]));

FileAssistant::assertPrompted(function (AgentPrompt $prompt) {
    return $prompt->hasApprovalDecisions()
        && $prompt->approvalDecisions->get('call_abc')->isApproved();
});

对于加入队列的 agent 调用,使用队列断言方法:

php
use Laravel\Ai\QueuedAgentPrompt;

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

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

SalesCoach::assertNotQueued('Missing prompt');

SalesCoach::assertNeverQueued();

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

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

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

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

// 自动为每个提示生成适当维度的假 embeddings...
Embeddings::fake();

// 提供提示响应列表...
Embeddings::fake([
    [$firstEmbeddingVector],
    [$secondEmbeddingVector],
]);

// 根据传入的提示动态处理响应...
Embeddings::fake(function (EmbeddingsPrompt $prompt) {
    return array_map(
        fn () => Embeddings::fakeEmbedding($prompt->dimensions),
        $prompt->inputs
    );
});

生成 embeddings 后,你可以对接收到的提示进行断言:

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

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

Embeddings::assertNothingGenerated();

对于加入队列的 embeddings 生成,使用队列断言方法:

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

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

Embeddings::assertNothingQueued();

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

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!', mimeType: '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
  • ToolApprovalRequested
  • ToolApprovalResolved
  • ToolInvoked
  • TranscriptionGenerated

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