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 配置文件中定义 AI 提供商凭证,或者作为环境变量定义在应用的 .env 文件中:

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=

用于文本、图像、音频、转录和嵌入的默认模型也可以在应用的 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'),
        ],
    ],
],

您可以通过在其配置中定义一个 headers 数组,来为提供程序的每个传出请求添加自定义 HTTP 标头。当端点需要除 Bearer 令牌之外的额外标识或身份验证标头时,这非常有用:

php
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'headers' => [
        'X-Tenant-Id' => env('LOCAL_AI_TENANT_ID'),
    ],
],

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

OpenAI 兼容的嵌入

由于任意端点没有已知模型,您必须配置一个默认嵌入模型,以便在 OpenAI 兼容的提供程序上使用 embeddings()。您还可以配置一个固定的维度值;如果省略,则请求发送时不带 dimensions 参数,并使用模型的原始维度。

php
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'embeddings' => [
            'default' => 'text-embedding-qwen3-embedding-0.6b',
            'dimensions' => 1024, // 可选
        ],
    ],
],

OpenAI 兼容转录

同样,您必须配置一个默认转录模型,以便在 OpenAI 兼容提供商中使用 Transcription。音频将作为标准 multipart 请求上传到端点的 /audio/transcriptions 路由:

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

注意

OpenAI 兼容提供商和 Groq 提供商不支持说话人分离。在使用这些提供商时调用 diarize 方法将抛出异常。

提供商支持

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

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

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

php
use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::OpenAiCompatible;
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 '你是一名销售教练,负责分析对话记录并提供反馈和整体销售实力评分。';
    }

    /**
     * 获取构成到目前为止对话的消息列表。
     */
    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('分析这段销售对话记录...');

return (string) $response;

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

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

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

php
$response = (new SalesCoach)->prompt(
    '分析这段销售对话记录...',
    provider: Lab::Anthropic,
    model: 'claude-sonnet-5',
    timeout: 120,
);

原始 HTTP 响应

每个从文本生成代理返回的响应,都会通过 raw 属性公开来自底层提供程序 API 调用的原始 HTTP 响应。这使您可以访问不属于 AI SDK 通用响应的提供程序特定信息——速率限制标头、请求 ID 或其他确切的负载字段:

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

$response->raw; // Illuminate\Http\Client\Response|null

$response->raw->header('X-RateLimit-Remaining-Requests');
$response->raw->json('id');

在工具调用循环中,每一步都保留其自身请求的原始响应:

php
foreach ($response->steps as $step) {
    $step->raw?->header('X-RateLimit-Remaining-Requests');
}

注意

当流式传输响应时、使用 Bedrock 提供程序时(该提供程序通过 AWS SDK 而非 HTTP 客户端执行其 API 调用),以及对于伪造的响应,raw 属性为 null,除非通过 withRawResponse 显式提供了一个。

对话上下文

如果你的智能体实现了 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 '你是一名销售教练...';
    }
}

使用 RemembersConversations 特征时,不要在你的代理类中手动定义 messages 方法。如果存在 messages 方法,它将优先于特征的实现,并且对话历史将不会从数据库中加载。

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

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

$conversationId = $response->conversationId;

会话 ID 会在响应中返回,并可存储以供将来参考。如果你想使用 Eloquent 检索某个用户的所有会话,可以将 HasConversations 特征添加到你的用户模型中:

php
<?php

namespace App\Models;

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

class User extends Authenticatable
{
    use HasConversations;
}

一旦将特征添加到模型中,你就可以通过 conversations 关联来检索和查询用户的会话:

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

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

php
$response = (new SalesCoach)
    ->continue($conversationId, as: $user)
    ->prompt('再多告诉我一些。');

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

对话参与者

尽管用户是最常见的对话参与者,但对话可以属于任何 Eloquent 模型。使用 forParticipant 方法为其他类型的模型启动对话:

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

参与者的多态类名和主键会随对话一起存储。因此,具有相同主键的不同类型模型,例如 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 模型上。生成的 conversations 关联是一个多态关联,其作用域限定为该模型的类型和主键。您还可以通过其反向关联访问拥有该对话的参与者:

php
$conversations = $team->conversations;

$participant = $conversation->participant;

如果您的应用程序使用了多种参与者模型类型,应考虑定义一个 Eloquent 多态映射,这样存储的参与者类型就不会与您的模型类名耦合。

警告

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

结构化输出

如果你希望智能体返回结构化输出,请实现 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('分析这段销售对话记录...');

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;

    // ...

    /**
     * 获取代理的结构化输出模式定义。
     */
    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(),
        ];
    }
}

对象数组

如果您的代理应返回一组结构化的条目,请结合使用 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(),
    ];
}

如果一个值可能匹配多个模式之一,可以使用 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(
    '分析附带的销售对话记录...',
    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(
    '这张图片里有什么?',
    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('分析这段销售对话记录...');
});

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

php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Responses\StreamedAgentResponse;

Route::get('/coach', function () {
    return (new SalesCoach)
        ->stream('分析这段销售对话记录...')
        ->then(function (StreamedAgentResponse $response) {
            // $response->text, $response->events, $response->usage...
        });
});

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

php
$stream = (new SalesCoach)->stream('分析这段销售对话记录...');

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('分析这段销售对话记录...')
        ->usingVercelDataProtocol();
});

广播

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

php
use App\Ai\Agents\SalesCoach;
use Illuminate\Broadcasting\Channel;

$stream = (new SalesCoach)->stream('分析这段销售对话记录...');

foreach ($stream as $event) {
    $event->broadcast(new Channel('channel-name'));
}

或者,你可以调用智能体的 broadcastOnQueue 方法,将智能体操作排队,并在事件可用时广播它们:

php
(new SalesCoach)->broadcastOnQueue(
    '分析这段销售对话记录...'
    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)两种方式。

队列

使用智能体的 queue 方法,你可以提示智能体,但允许它在后台处理响应,从而使你的应用程序保持快速和响应。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();
});

工具

工具可用于为智能体提供额外的功能,以便它们在响应提示时可以利用这些功能。可以使用 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 '此工具可用于生成加密安全的随机数。';
    }

    /**
     * 执行工具。
     */
    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,
    ];
}

验证工具参数

尽管你的工具模式约束了模型可能提供的参数,你仍然可以使用请求的 validate 方法来验证传入的参数:

php
public function handle(Request $request): Stringable|string
{
    $validated = $request->validate([
        'city' => 'required|string',
        'days' => 'required|integer|max:7',
    ]);

    return $this->forecast($validated['city'], $validated['days']);
}

当验证失败时,验证消息会作为工具的结果返回给模型,使其能够纠正参数并再次调用该工具。

修复工具调用

使用 RepairToolCalls 属性可以让智能体在模型调用未知本地工具时进行恢复。Laravel 会将失败的调用返回给模型,并附上可用本地工具的名称,使其能够更正调用:

php
use Laravel\Ai\Attributes\RepairToolCalls;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

#[RepairToolCalls]
class SupportAgent implements Agent, HasTools
{
    use Promptable;

    // ...
}

当 Laravel 自动推导最大步骤数时,该属性会为修复后的调用增加一步。显式的 MaxSteps 限制保持不变。

相似性搜索

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

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

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('搜索知识库以查找相关文章。'),

延迟工具加载

默认情况下,智能体公开的每个工具都会随每个请求发送给提供商。当智能体提供大量工具时,这会消耗令牌并可能降低模型工具选择的准确性。使用 ToolSearch 提供商工具(适用于 OpenAI 或 Anthropic),您可以延迟工具定义,以便提供商仅在需要时才加载它们:

php
use App\Ai\Tools\RefundOrder;
use App\Ai\Tools\SearchInvoices;
use App\Ai\Tools\Weather;
use Laravel\Ai\Providers\Tools\ToolSearch;

public function tools(): iterable
{
    return [
        new Weather,
        new ToolSearch(tools: [
            new SearchInvoices,
            new RefundOrder,
        ]),
    ];
}

被包装的工具无需任何修改。当它们与提示相关时,提供商将搜索并加载它们,之后智能体可以像调用任何其他工具一样调用它们。

使用 Anthropic 时,可以使用 strategy 参数来确定提供商应如何搜索延迟工具。支持的策略为 regex(默认)和 bm25

php
new ToolSearch(tools: [new SearchInvoices], strategy: 'bm25'),

使用 Anthropic 时,可以使用 withProviderOptions 方法向搜索工具传递额外的提供商特定选项:

php
(new ToolSearch(tools: [new SearchInvoices]))
    ->withProviderOptions(['cache_control' => ['type' => 'ephemeral']]),

警告

不支持工具搜索的提供商将抛出异常,而不是静默丢弃延迟工具。此外,Anthropic 要求至少有一个工具在 ToolSearch 包装器之外提供。

文件存储工具

FileStorage 工具工厂允许你让代理访问 Laravel 的 文件系统磁盘all 方法返回的工具允许代理在给定的磁盘上列出、读取、检查、生成文件 URL、写入、删除和复制文件:

php
use Laravel\Ai\Tools\FileStorage;

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

如果你的代理只需要能够检查文件,请使用 readOnly 方法:

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

这些方法返回一个 Illuminate\Support\Collection,允许你进一步筛选提供给代理的工具:

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

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

MCP 工具

如果您的应用使用了 Laravel MCP,您可以为智能体提供由 模型上下文协议 服务器暴露的工具。借助 Laravel MCP 客户端,您可以连接到远程或本地 MCP 服务器,并将其工具直接传递给智能体。

注意

MCP 工具需要在您的应用中安装 Laravel MCP 包。

因为 MCP 客户端的 tools 方法会返回一个集合,所以您需要使用 ... 运算符将其展开到智能体的 tools 数组中:

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

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

        new RandomNumberGenerator,
    ];
}

AI SDK 会自动包装每个 MCP 工具,以便智能体可以像调用其他工具一样调用它。您也可以使用 命名 MCP 客户端

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 客户端(包括 bearer 令牌和 OAuth)的更多信息,请查阅 MCP 客户端文档

提供商工具

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

提供商工具可以由你的智能体的 tools 方法返回。

网络搜索

WebSearch 提供商工具允许智能体搜索网络以获取实时信息。这对于回答关于当前事件、最近数据或自模型训练截止日期以来可能发生变化的话题非常有用。

支持的提供商: Anthropic、OpenAI、Azure、Gemini、xAI、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 提供商工具允许智能体获取并读取网页的内容。当您需要智能体分析特定 URL 或从已知网页检索详细信息时,这很有用。

支持的提供商: Anthropic, Gemini, OpenRouter

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, xAI

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

子智能体

智能体也可以从另一个智能体的 tools 方法中返回。当一个智能体作为工具被返回时,父智能体可以将特定任务委托给子智能体,并在回答原始提示时使用子智能体的响应。当通用智能体需要访问具有自身指令、工具、模型配置或供应商偏好的专用智能体时,这非常有用。

例如,一个客户支持智能体可以将退款资格问题委托给专用的退款智能体:

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;

    /**
     * 获取智能体应遵循的指令。
     */
    public function instructions(): string
    {
        return '您需要帮助客户解答账户、订单和账单问题。将退款政策问题委托给退款专员。';
    }

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

要自定义子智能体如何暴露给父智能体,可在子智能体上实现 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;

    /**
     * 获取智能体应遵循的指令。
     */
    public function instructions(): string
    {
        return '您是退款专员。使用订单详情和退款政策来提供简洁的资格指导。';
    }

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

    /**
     * 获取智能体的工具描述。
     */
    public function description(): string
    {
        return '确定订单是否符合退款条件并说明下一步操作。';
    }

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

如果子智能体未实现 CanActAsTool,Laravel 将使用智能体的类基名作为工具名称,并使用一个通用描述,要求父智能体传递清晰且自包含的任务描述。每次子智能体调用都在隔离环境中运行,不会接收父智能体的对话历史。

中间件

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

shell
php artisan make:agent-middleware LogPrompts

生成的中间件将放在你的应用程序的 app/Ai/Middleware 目录中。要向智能体添加中间件,请实现 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;

    // ...

    /**
     * 获取智能体的中间件。
     */
    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('正在提示智能体', ['prompt' => $prompt->prompt]);

        return $next($prompt);
    }
}

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

php
public function handle(AgentPrompt $prompt, Closure $next)
{
    return $next($prompt)->then(function (AgentResponse $response) {
        Log::info('智能体已响应', ['text' => $response->text]);
    });
}

匿名智能体

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

php
use function Laravel\Ai\{agent};

$response = agent(
    instructions: '你是一名软件开发专家。',
    messages: [],
    tools: [],
)->prompt('告诉我关于 Laravel 的事情')

匿名智能体也可以产生结构化输出:

php
use Illuminate\Contracts\JsonSchema\JsonSchema;

use function Laravel\Ai\{agent};

$response = agent(
    schema: fn (JsonSchema $schema) => [
        'number' => $schema->integer()->required(),
    ],
)->prompt('生成一个小于 100 的随机数')

智能体配置

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

  • MaxSteps:智能体在使用工具时可能采取的最大步数。
  • MaxTokens:模型可能生成的最大令牌数。
  • Model:智能体应使用的模型。
  • Provider:用于智能体的 AI 提供商(或用于故障转移的多个提供商)。
  • Temperature:用于生成内容的采样温度(0.0 到 1.0)。
  • Timeout:智能体请求的 HTTP 超时时间(秒,默认:60)。
  • TopP:生成时使用的核采样概率(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)...
}

注意

UseCheapestModelUseSmartestModel 选择的底层模型可能会在 Laravel AI SDK 的版本发布之间发生变化,因为提供商会发布新模型。切换模型可能引发行为变更、引入已弃用的参数,并带来显著的成本差异。如果你需要稳定、可预测的模型和定价,请使用 Model 属性显式指定模型。

提供商选项

如果你的智能体需要传递特定于提供商的选项(例如 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 启用了提示缓存

提示缓存

大多数提供商自动缓存重复的提示前缀,并对缓存部分按折扣计费。OpenAI、Gemini、Groq、DeepSeek 和 xAI 无需配置,你可以通过响应的 usage 查看节省情况:

php
$response->usage->cacheReadInputTokens;
$response->usage->cacheWriteInputTokens;

anthropicbedrock 提供商只有在被要求时才会缓存。CacheInstructionsCacheToolDefinitions 属性会在你的代理指令和工具定义的末尾放置一个缓存断点,这样每次对话都会从缓存中读取该前缀,而不是再次写入:

php
use Laravel\Ai\Attributes\CacheInstructions;
use Laravel\Ai\Attributes\CacheToolDefinitions;

#[CacheInstructions]
#[CacheToolDefinitions]
class SalesCoach implements Agent
{
    use Promptable;

    // ...
}

如果你的指令在每次请求时都会变化,例如其中嵌入了当前日期,请仅使用 CacheToolDefinitions。缓存一个每次请求都变化的前缀每次都会创建一个新的缓存条目,因此你只支付了写入缓存的费用,却从未复用它。

不支持这些属性的提供商会忽略它们,因此代理可以在使用故障转移时安全地声明这些属性。

默认情况下,缓存的前缀会保留五分钟。如果你向属性传递一个 TTL,Anthropic 可以将其保留一小时:

php
#[CacheInstructions('1h')]
#[CacheToolDefinitions('1h')]

或者,可以通过顶层的 cache_control 提供商选项 启用 Anthropic 的自动缓存。这会在请求的最后一个块之后放置一个单一的断点,因此随着对话的增长,断点会向前推进,每一轮都会从缓存中读取之前的轮次。这两种机制可以结合使用。

警告

由于提供商按照工具、指令和消息的顺序构建提示,将指令缓存一小时也需要将工具定义缓存一小时。混合使用两者会抛出 InvalidArgumentException

人工工具审批

警告

工具审批要求使用一个会话历史被持久化的 Conversational 代理,以便暂停的调用能够恢复。RemembersConversations trait 提供了所需的持久化能力。

执行敏感或不可逆操作的工具可能需要在运行前获得人工审批。要使工具可审批,请实现 Approvable 契约并使用 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.');
}

在代理的 tools 方法中返回工具时,你可以覆盖工具的审批要求:

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

当可审批工具被调用时,代理会在执行前暂停。你可以检查响应的待处理审批,其中包含每个工具调用的 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
    }
}

要恢复代理,请继续对话并提供一个 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 方法均支持工具审批。

在流式传输和广播期间,暂停会以 tool_approval_request 事件表示。当使用 Vercel AI SDK 流协议 时,审批请求和结果会使用该协议原生的工具审批部件进行发送。

对于队列化代理,生成的响应会传递给 then 回调,Laravel 同时还会分发 ToolApprovalRequested 事件。

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

完整审批流程

以下路由演示了一个完整的审批流程。GET 路由返回聊天界面,而 POST 路由接受来自聊天界面的新文本提示或审批决策。此示例假设应用的 User 模型使用了 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', 'prohibits:decisions'],
        'decisions' => ['nullable', 'array', 'required_without:message', 'prohibits:message'],
        'decisions.*.action' => ['required_with:decisions', Rule::in(['approve', 'reject'])],
        'decisions.*.result' => ['nullable', 'string'],
    ]);

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

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

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

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

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('一个放在厨房柜台上的甜甜圈')->generate();

$rawContent = (string) $image;

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

php
use Laravel\Ai\Image;

$image = Image::of('一个放在厨房柜台上的甜甜圈')
    ->quality('high')
    ->landscape()
    ->timeout(120)
    ->generate();

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

php
use Laravel\Ai\Files;
use Laravel\Ai\Image;

$image = Image::of('把这张我的照片更新为印象派绘画的风格。')
    ->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('一个放在厨房柜台上的甜甜圈');

$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('一个放在厨房柜台上的甜甜圈')
    ->portrait()
    ->queue()
    ->then(function (ImageResponse $image) {
        $path = $image->store();

        // ...
    });

音频

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

php
use Laravel\Ai\Audio;

$audio = Audio::of('我喜欢用 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('我喜欢用 Laravel 编码。')
    ->female()
    ->generate();

$audio = Audio::of('我喜欢用 Laravel 编码。')
    ->voice('voice-id-or-name')
    ->generate();

类似地,instructions 方法可用于动态地指导模型生成的音频听起来应该是什么样子:

php
$audio = Audio::of('我喜欢用 Laravel 编码。')
    ->female()
    ->instructions('像海盗一样说')
    ->generate();

生成的音频可以很容易地存储在应用程序的 config/filesystems.php 配置文件中配置的默认磁盘上:

php
$audio = Audio::of('我喜欢用 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('我喜欢用 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 类提供的 summarize 方法来对文本进行摘要。默认情况下,摘要最多包含三句话,并将使用已配置提供商的最便宜文本模型生成:

php
use Illuminate\Support\Str;

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

您可以指定用于生成摘要的最大句子数、提供商、模型和超时时间。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);

嵌入

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

php
use Illuminate\Support\Str;

$embeddings = Str::of('纳帕谷有很棒的葡萄酒。')->toEmbeddings();

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

php
use Laravel\Ai\Embeddings;

$response = Embeddings::for([
    '纳帕谷有很棒的葡萄酒。',
    'Laravel 是一个 PHP 框架。',
])->generate();

$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]

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

php
$response = Embeddings::for(['纳帕谷有很棒的葡萄酒。'])
    ->dimensions(1536)
    ->generate(Lab::OpenAI, 'text-embedding-3-small');

多模态嵌入

除了字符串之外,Embeddings::for 方法还接受图像、音频、文档和视频输入,允许您为非文本内容生成嵌入。Gemini 支持图像、音频、文档和视频嵌入,而 VoyageAI 支持图像和视频嵌入:

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

多模态输入使用与附件相同的文件类。这些文件可以从本地路径、文件系统磁盘、远程 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'));

注意

VoyageAI 不允许在单个请求中混合使用远程 URL 媒体和 Base64 编码的媒体。本地文件、存储的文件和上传的文件将作为 Base64 编码的内容发送,文本输入可以与任一媒体源组合。请查阅您的提供商文档,了解哪些多模态模型和输入可用。

查询嵌入

生成嵌入后,通常会将其存储在数据库的 vector 列中,以便后续查询。Laravel 通过 pgvector 扩展为 PostgreSQL 以及 MariaDB 提供了对向量列的原生支持。开始时,在迁移中定义一个 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 模型上,你应该使用 AsVector 转换来转换向量列:

php
use Illuminate\Database\Eloquent\Casts\AsVector;

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

要查询相似记录,请使用 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', '纳帕谷最好的酒庄')
    ->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();

如果你想让智能体能够将相似性搜索作为工具使用,请查看相似性搜索工具文档。

注意

向量查询目前在使用 pgvector 扩展的 PostgreSQL 连接以及 MariaDB 11.7 或更高版本上受支持。

缓存嵌入

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

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

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

默认情况下,每个输入的嵌入都以其各自的键进行缓存,因此后续请求即使输入集合或顺序发生变化,也可能命中之前见过的输入的缓存。若要改为将整个输入集合缓存到单个键下,请将 ai.caching.embeddings.individually 配置选项设置为 false

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

php
$response = Embeddings::for(['纳帕谷有很棒的葡萄酒。'])
    ->cache()
    ->generate();

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

php
$response = Embeddings::for(['纳帕谷有很棒的葡萄酒。'])
    ->cache(seconds: 3600) // 缓存 1 小时
    ->generate();

toEmbeddings 字符串方法也接受一个 cache 参数:

php
// 使用默认持续时间缓存...
$embeddings = Str::of('纳帕谷有很棒的葡萄酒。')->toEmbeddings(cache: true);

// 缓存特定持续时间...
$embeddings = Str::of('纳帕谷有很棒的葡萄酒。')->toEmbeddings(cache: 3600);

重排序

重排序允许你根据文档与给定查询的相关性重新排序文档列表。这对于通过使用语义理解来改进搜索结果非常有用:

Laravel\Ai\Reranking 类可用于重排序文档:

php
use Laravel\Ai\Reranking;

$response = Reranking::of([
    'Django 是一个 Python Web 框架。',
    'Laravel 是一个 PHP Web 应用程序框架。',
    'React 是一个用于构建用户界面的 JavaScript 库。',
])->rerank('PHP 框架');

// 访问最相关的结果...
$response->first()->document; // "Laravel 是一个 PHP Web 应用程序框架。"
$response->first()->score;    // 0.95
$response->first()->index;    // 1 (原始位置)

limit 方法可用于限制返回的结果数量:

php
$response = Reranking::of($documents)
    ->limit(5)
    ->rerank('搜索查询');

重排序集合

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

php
// 按单个字段重排序...
$posts = Post::all()
    ->rerank('body', 'Laravel 教程');

// 按多个字段重排序(作为 JSON 发送)...
$reranked = $posts->rerank(['title', 'body'], 'Laravel 教程');

// 使用闭包构建文档进行重排序...
$reranked = $posts->rerank(
    fn ($post) => $post->title.': '.$post->body,
    'Laravel 教程'
);

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

php
$reranked = $posts->rerank(
    by: 'content',
    query: 'Laravel 教程',
    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(
    '分析附带的销售对话记录...'
    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 方法引用它:

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(
    '总结这个文档。',
    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(
    '这张图片里有什么?',
    attachments: [
        Image::fromId($stored->id),
    ],
);

向量存储

向量存储允许你创建可搜索的文件集合,这些集合可用于检索增强生成(RAG)。Laravel\Ai\Stores 类提供了创建、检索和删除向量存储的方法:

php
use Laravel\Ai\Stores;

// 创建一个新的向量存储...
$store = Stores::create('知识库');

// 创建带有额外选项的存储...
$store = Stores::create(
    name: '知识库',
    description: '文档和参考资料。',
    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' => '工程部',
    '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(
    '分析这段销售对话记录...',
    provider: [Lab::OpenAI, Lab::Anthropic],
);

$image = Image::of('一个放在厨房柜台上的甜甜圈')
    ->generate(provider: [Lab::Gemini, Lab::xAI]);

仅当抛出 FailoverableException 异常时才会触发故障转移——例如达到速率限制(RateLimitedException)、服务商过载或不可用(ProviderOverloadedException)或余额不足(InsufficientCreditsException)。普通的错误,如校验错误或错误的请求错误,不会触发故障转移。

当你传入一个普通的服务商列表时,例如 [Lab::OpenAI, Lab::Anthropic],每个服务商会使用其默认模型。若要为故障转移链中的每个服务商指定特定模型,可以传入一个以服务商为键的关联数组,并使用 Lab 枚举的 value 作为键(枚举成员无法直接用作 PHP 数组的键):

php
use Laravel\Ai\Enums\Lab;

$response = (new SalesCoach)->prompt(
    '分析这段销售对话记录...',
    provider: [
        Lab::Gemini->value => 'gemini-3-flash-preview',
        Lab::DeepSeek->value => 'deepseek-v4-pro',
    ],
);

测试

当伪造队列化的图像、音频、转录或嵌入生成时,任何在队列化生成上注册的 then 回调都会以伪造的响应被调用,从而允许您测试回调中包含的逻辑。如果您希望这些回调不被调用,也可以使用 Queue::fake() 来伪造队列。

智能体

要在测试中伪造智能体的响应,请在智能体类上调用 fake 方法。你可以选择提供响应数组或闭包:

php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;

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

// 提供一系列提示响应...
SalesCoach::fake([
    '第一个响应',
    '第二个响应',
]);

// 根据传入的提示动态处理响应...
SalesCoach::fake(function (AgentPrompt $prompt) {
    return '对以下内容的响应:'.$prompt->prompt;
});

当模拟一个返回结构化输出的代理时,你可以提供数组作为响应。该代理将返回一个包含给定数据的结构化响应:

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

注意

当在一个返回结构化输出的代理上调用 Agent::fake() 且未显式提供模拟输出时,Laravel 将自动生成与代理定义的输出结构相匹配的模拟数据。

在提示智能体之后,你可以对接收到的提示进行断言:

php
use Laravel\Ai\Prompts\AgentPrompt;

SalesCoach::assertPrompted('分析这个...');

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

SalesCoach::assertPromptedTimes(3);

SalesCoach::assertNotPrompted('缺失的提示');

SalesCoach::assertNeverPrompted();

在断言审批延续时,您可以检查提示的审批决策:

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

对于排队的智能体调用,请使用排队断言方法:

php
use Laravel\Ai\QueuedAgentPrompt;

SalesCoach::assertQueued('分析这个...');

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

SalesCoach::assertNotQueued('缺失的提示');

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('日落') && $prompt->isLandscape();
});

Image::assertNotGenerated('缺失的提示');

Image::assertNothingGenerated();

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

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

Image::assertNotQueued('缺失的提示');

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('你好') && $prompt->isFemale();
});

Audio::assertNotGenerated('缺失的提示');

Audio::assertNothingGenerated();

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

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

Audio::assertNotQueued('缺失的提示');

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([
    '第一段转录文本。',
    '第二段转录文本。',
]);

// 根据传入的提示动态处理响应...
Transcription::fake(function (TranscriptionPrompt $prompt) {
    return '转录的文本...';
});

在生成转录之后,你可以对接收到的提示进行断言:

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('其他')
);

Embeddings::assertNothingGenerated();

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

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

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

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: '第一个', score: 0.95),
        new RankedDocument(index: 1, document: '第二个', 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('知识库');

// 进行断言...
Stores::assertCreated('知识库');

Stores::assertCreated(fn (string $name, ?string $description) =>
    $name === '知识库'
);

Stores::assertNotCreated('其他存储');

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
  • AgentFailed
  • AgentFailedOver
  • AgentPrompted
  • AgentStreamed
  • AudioGenerated
  • CreatingStore
  • EmbeddingsGenerated
  • FileAddedToStore
  • FileDeleted
  • FileRemovedFromStore
  • FileStored
  • GeneratingAudio
  • GeneratingEmbeddings
  • GeneratingImage
  • GeneratingTranscription
  • ImageGenerated
  • InvokingTool
  • PromptingAgent
  • ProviderFailedOver
  • RemovingFileFromStore
  • Reranked
  • Reranking
  • StartingStep
  • StepCompleted
  • StepFailed
  • StoreCreated
  • StoreDeleted
  • StoringFile
  • StreamingAgent
  • ToolApprovalRequested
  • ToolApprovalResolved
  • ToolFailed
  • ToolInvoked
  • TranscriptionGenerated

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