AI SDK

简介

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

安装

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

composer require laravel/ai

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

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

最后,你应该运行应用的数据库迁移。这将创建 AI SDK 用于驱动其对话存储的 agent_conversations 和 agent_conversation_messages 表:

php artisan migrate

配置

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

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:

'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 令牌发送:

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

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

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

你还可以为提供商配置默认文本模型,这样就不需要显式传递模型:

'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
TTS OpenAI, ElevenLabs, Gemini
STT OpenAI, ElevenLabs, Mistral, Gemini
嵌入 OpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter
重排序 Cohere, Jina, VoyageAI
文件 OpenAI, Anthropic, Gemini, Azure

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

use Laravel\Ai\Enums\Lab;

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

代理

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

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

php artisan make:agent SalesCoach

php artisan make:agent SalesCoach --structured

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

<?php

namespace App\Ai\Agents;

use App\Ai\Tools\RetrievePreviousTranscripts;
use App\Models\History;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;

class SalesCoach implements Agent, Conversational, HasTools, HasStructuredOutput
{
    use Promptable;

    public function __construct(public User $user) {}

    /**
     * Get the instructions that the agent should follow.
     */
    public function instructions(): Stringable|string
    {
        return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
    }

    /**
     * Get the list of messages comprising the conversation so far.
     */
    public function messages(): iterable
    {
        return History::where('user_id', $this->user->id)
            ->latest()
            ->limit(50)
            ->get()
            ->reverse()
            ->map(function ($message) {
                return new Message($message->role, $message->content);
            })->all();
    }

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

    /**
     * Get the agent's structured output schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'feedback' => $schema->string()->required(),
            'score' => $schema->integer()->min(1)->max(10)->required(),
        ];
    }
}

提示

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

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

return (string) $response;

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

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

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

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

对话上下文

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

use App\Models\History;
use Laravel\Ai\Messages\Message;

/**
 * Get the list of messages comprising the conversation so far.
 */
public function messages(): iterable
{
    return History::where('user_id', $this->user->id)
        ->latest()
        ->limit(50)
        ->get()
        ->reverse()
        ->map(function ($message) {
            return new Message($message->role, $message->content);
        })->all();
}

记忆对话

信息

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

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

<?php

namespace App\Ai\Agents;

use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, Conversational
{
    use Promptable, RemembersConversations;

    /**
     * Get the instructions that the agent should follow.
     */
    public function instructions(): string
    {
        return 'You are a sales coach...';
    }
}

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

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

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

$conversationId = $response->conversationId;

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

<?php

namespace App\Models;

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

class User extends Authenticatable
{
    use HasConversations;
}

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

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

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

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

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

结构化输出

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

<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasStructuredOutput
{
    use Promptable;

    // ...

    /**
     * Get the agent's structured output schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required(),
        ];
    }
}

提示返回结构化输出的代理时,你可以像数组一样访问返回的 StructuredAgentResponse:

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

return $response['score'];

嵌套对象

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

<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasStructuredOutput
{
    use Promptable;

    // ...

    /**
     * Get the agent's structured output schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required(),
            'metadata' => $schema->object(fn ($schema) => [
                'confidence' => $schema->string()->enum(['low', 'medium', 'high'])->required(),
                'language' => $schema->string()->required(),
            ])->required(),
        ];
    }
}

对象数组

如果你的代理应该返回一个结构化项目列表,请组合使用 array 和 object 方法:

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

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

附件

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

use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
    'Analyze the attached sales transcript...',
    attachments: [
        Files\Document::fromStorage('transcript.pdf') // Attach a document from a filesystem disk...
        Files\Document::fromPath('/home/laravel/transcript.md') // Attach a document from a local path...
        $request->file('transcript'), // Attach an uploaded file...
    ]
);

同样,Laravel\Ai\Files\Image 类可用于将图像附加到提示:

use App\Ai\Agents\ImageAnalyzer;
use Laravel\Ai\Files;

$response = (new ImageAnalyzer)->prompt(
    'What is in this image?',
    attachments: [
        Files\Image::fromStorage('photo.jpg') // Attach an image from a filesystem disk...
        Files\Image::fromPath('/home/laravel/photo.jpg') // Attach an image from a local path...
        $request->file('photo'), // Attach an uploaded file...
    ]
);

流式传输

你可以通过调用 stream 方法来流式传输代理的响应。返回的 StreamableAgentResponse 可以从路由返回,以自动向客户端发送流式响应(SSE):

use App\Ai\Agents\SalesCoach;

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

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

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

Route::get('/coach', function () {
    return (new SalesCoach)
        ->stream('Analyze this sales transcript...')
        ->then(function (StreamedAgentResponse $response) {
            // $response->text, $response->events, $response->usage...
        });
});

或者,你可以手动迭代流式事件:

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

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

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

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

use App\Ai\Agents\SalesCoach;

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

广播

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

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

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

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

或者,你可以调用代理的 broadcastOnQueue 方法来排队代理操作,并在流式事件可用时广播它们:

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

跳过超大事件

某些广播平台将 WebSocket 消息限制在约 10KB。数据量大的流事件,如大型工具结果,可能超过此限制并导致广播失败。你可以使用 WithoutBroadcasting 属性将特定事件类型排除在广播之外:

<?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 方法,你可以提示代理,但允许它在后台处理响应,使你的应用保持快速和响应灵敏。then 和 catch 方法可用于注册当响应可用或发生异常时将调用的闭包:

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

php artisan make:tool RandomNumberGenerator

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

<?php

namespace App\Ai\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class RandomNumberGenerator implements Tool
{
    /**
     * Get the description of the tool's purpose.
     */
    public function description(): Stringable|string
    {
        return 'This tool may be used to generate cryptographically secure random numbers.';
    }

    /**
     * Execute the tool.
     */
    public function handle(Request $request): Stringable|string
    {
        return (string) random_int($request['min'], $request['max']);
    }

    /**
     * Get the tool's schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'min' => $schema->integer()->min(0)->required(),
            'max' => $schema->integer()->required(),
        ];
    }
}

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

use App\Ai\Tools\RandomNumberGenerator;

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

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

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

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

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

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

你还可以提供 0.0 到 1.0 之间的最小相似度阈值和一个用于自定义查询的闭包:

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

要获得更多控制,你可以使用返回搜索结果的自定义闭包创建相似性搜索工具:

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

public function tools(): iterable
{
    return [
        new SimilaritySearch(using: function (string $query) {
            return Document::query()
                ->where('user_id', $this->user->id)
                ->whereVectorSimilarTo('embedding', $query)
                ->limit(10)
                ->get();
        }),
    ];
}

你可以使用 withDescription 方法自定义工具的描述:

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

文件存储工具

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

use Laravel\Ai\Tools\FileStorage;

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

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

return FileStorage::readOnly('local');

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

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 数组中:

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

/**
 * Get the tools available to the agent.
 *
 * @return Tool[]
 */
public function tools(): iterable
{
    return [
        ...Client::web('https://mcp.example.com')
            ->withToken($token)
            ->tools(),

        new RandomNumberGenerator,
    ];
}

AI SDK 会自动包装每个 MCP 工具,使代理可以像调用其他工具一样调用它。你也可以使用命名 MCP 客户端:

use Laravel\Mcp\Facades\Mcp;

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

或连接到本地 MCP 服务器:

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、Gemini、OpenRouter

use Laravel\Ai\Providers\Tools\WebSearch;

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

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

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

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

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

网页获取

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

支持的提供商: Anthropic、Gemini

use Laravel\Ai\Providers\Tools\WebFetch;

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

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

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

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

支持的提供商: OpenAI、Gemini

use Laravel\Ai\Providers\Tools\FileSearch;

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

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

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

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

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

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

use Laravel\Ai\Providers\Tools\FileSearchQuery;

new FileSearch(stores: ['store_id'], where: fn (FileSearchQuery $query) =>
    $query->where('author', 'Taylor Otwell')
        ->whereNot('status', 'draft')
        ->whereIn('category', ['news', 'updates'])
);

子代理

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

例如,客户支持代理可以将退款资格问题委托给专门的退款代理:

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

    /**
     * Get the instructions that the agent should follow.
     */
    public function instructions(): string
    {
        return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.';
    }

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

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

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

    /**
     * Get the instructions that the agent should follow.
     */
    public function instructions(): string
    {
        return 'You are a refunds specialist. Use order details and the refund policy to give concise eligibility guidance.';
    }

    /**
     * Get the agent's tool name.
     */
    public function name(): string
    {
        return 'refunds_specialist';
    }

    /**
     * Get the agent's tool description.
     */
    public function description(): string
    {
        return 'Determine whether an order is eligible for a refund and explain the next step.';
    }

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

如果子代理没有实现 CanActAsTool,Laravel 将使用代理的类基本名称作为工具名称,并使用一个通用描述来要求父代理传递清晰、自包含的任务描述。每个子代理调用都在隔离中运行,不会接收父代理的对话历史。

中间件

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

php artisan make:agent-middleware LogPrompts

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

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

    // ...

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

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

<?php

namespace App\Ai\Middleware;

use Closure;
use Laravel\Ai\Prompts\AgentPrompt;

class LogPrompts
{
    /**
     * Handle the incoming prompt.
     */
    public function handle(AgentPrompt $prompt, Closure $next)
    {
        Log::info('Prompting agent', ['prompt' => $prompt->prompt]);

        return $next($prompt);
    }
}

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

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

匿名代理

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

use function Laravel\Ai\{agent};

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

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

use Illuminate\Contracts\JsonSchema\JsonSchema;

use function Laravel\Ai\{agent};

$response = agent(
    schema: fn (JsonSchema $schema) => [
        'number' => $schema->integer()->required(),
    ],
)->prompt('Generate a random number less than 100')

代理配置

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

  • MaxSteps:代理使用工具时可以采取的最大步骤数。
  • MaxTokens:模型可以生成的最大令牌数。
  • Model:代理应该使用的模型。
  • Provider:用于代理的 AI 提供商(或多个提供商用于故障转移)。
  • Temperature:用于生成的采样温度(0.0 到 1.0)。
  • Timeout:代理请求的 HTTP 超时时间(秒)(默认:60)。
  • TopP:用于生成的核采样概率(0.0 到 1.0)。
  • UseCheapestModel:使用提供商最便宜的文本模型以优化成本。
  • UseSmartestModel:使用提供商最强大的文本模型来处理复杂任务。
<?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-haiku-4-5-20251001')]
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Temperature(0.7)]
#[Timeout(120)]
#[TopP(0.9)]
class SalesCoach implements Agent
{
    use Promptable;

    // ...
}

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

use Laravel\Ai\Attributes\UseCheapestModel;
use Laravel\Ai\Attributes\UseSmartestModel;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;

#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
    use Promptable;

    // Will use the cheapest model (e.g., Haiku)...
}

#[UseSmartestModel]
class ComplexReasoner implements Agent
{
    use Promptable;

    // Will use the most capable model (e.g., Opus)...
}

信息

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

提供商选项

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

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

    // ...

    /**
     * Get provider-specific generation options.
     */
    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 启用了提示缓存。

图像

Laravel\Ai\Image 类可用于使用 openai、gemini 或 xai 提供商生成图像:

use Laravel\Ai\Image;

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

$rawContent = (string) $image;

square、portrait 和 landscape 方法可用于控制图像的纵横比,而 quality 方法可用于指导模型的最终图像质量(high、medium、low)。timeout 方法可用于指定 HTTP 超时时间(秒):

use Laravel\Ai\Image;

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

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

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

$image = Image::of('Update this photo of me to be in the style of 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 配置文件中配置的默认磁盘上:

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

$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');

图像生成也可以排队:

use Laravel\Ai\Image;
use Laravel\Ai\Responses\ImageResponse;

Image::of('A donut sitting on the kitchen counter')
    ->portrait()
    ->queue()
    ->then(function (ImageResponse $image) {
        $path = $image->store();

        // ...
    });

音频

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

use Laravel\Ai\Audio;

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

$rawContent = (string) $audio;

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

use Illuminate\Support\Str;

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

male、female 和 voice 方法可用于确定生成音频的声音:

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

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

同样,instructions 方法可用于动态指导模型生成的音频应该如何听起来:

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

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

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

$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');

音频生成也可以排队:

use Laravel\Ai\Audio;
use Laravel\Ai\Responses\AudioResponse;

Audio::of('I love coding with Laravel.')
    ->queue()
    ->then(function (AudioResponse $audio) {
        $path = $audio->store();

        // ...
    });

转录

Laravel\Ai\Transcription 类可用于生成给定音频的转录:

use Laravel\Ai\Transcription;

$transcript = Transcription::fromPath('/home/laravel/audio.mp3')->generate();
$transcript = Transcription::fromStorage('audio.mp3')->generate();
$transcript = Transcription::fromUpload($request->file('audio'))->generate();

return (string) $transcript;

diarize 方法可用于指示你希望响应除了原始文本转录外还包含说话人分离的转录,允许你按说话者访问分段的转录:

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

转录生成也可以排队:

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

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

嵌入

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

use Illuminate\Support\Str;

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

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

use Laravel\Ai\Embeddings;

$response = Embeddings::for([
    'Napa Valley has great wine.',
    'Laravel is a PHP framework.',
])->generate();

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

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

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

查询嵌入

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

Schema::ensureVectorExtensionExists();

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->vector('embedding', dimensions: 1536);
    $table->timestamps();
});

你还可以添加向量索引以加速相似性搜索。在向量列上调用 index 时,Laravel 将自动创建一个使用余弦距离的 HNSW 索引:

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

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

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

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

use App\Models\Document;

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

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

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

如果你需要更多控制,可以独立使用更低级的 whereVectorDistanceLessThan、selectVectorDistance 和 orderByVectorDistance 方法:

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

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

信息

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

缓存嵌入

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

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

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

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

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

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

$response = Embeddings::for(['Napa Valley has great wine.'])
    ->cache(seconds: 3600) // Cache for 1 hour
    ->generate();

toEmbeddings Stringable 方法也接受 cache 参数:

// Cache with default duration...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);

// Cache for a specific duration...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);

重排序

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

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

use Laravel\Ai\Reranking;

$response = Reranking::of([
    'Django is a Python web framework.',
    'Laravel is a PHP web application framework.',
    'React is a JavaScript library for building user interfaces.',
])->rerank('PHP frameworks');

// Access the top result...
$response->first()->document; // "Laravel is a PHP web application framework."
$response->first()->score;    // 0.95
$response->first()->index;    // 1 (original position)

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

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

重排序集合

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

// Rerank by a single field...
$posts = Post::all()
    ->rerank('body', 'Laravel tutorials');

// Rerank by multiple fields (sent as JSON)...
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');

// Rerank using a closure to build the document...
$reranked = $posts->rerank(
    fn ($post) => $post->title.': '.$post->body,
    'Laravel tutorials'
);

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

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

文件

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

use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;

// Store a file from a local path...
$response = Document::fromPath('/home/laravel/document.pdf')->put();
$response = Image::fromPath('/home/laravel/photo.jpg')->put();

// Store a file that is stored on a filesystem disk...
$response = Document::fromStorage('document.pdf', disk: 'local')->put();
$response = Image::fromStorage('photo.jpg', disk: 'local')->put();

// Store a file that is stored on a remote URL...
$response = Document::fromUrl('https://example.com/document.pdf')->put();
$response = Image::fromUrl('https://example.com/photo.jpg')->put();

return $response->id;

你也可以存储原始内容或上传的文件:

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

// Store raw content...
$stored = Document::fromString('Hello, World!', 'text/plain')->put();

// Store an uploaded file...
$stored = Document::fromUpload($request->file('document'))->put();

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

use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
    'Analyze the attached sales transcript...'
    attachments: [
        Files\Document::fromId('file-id') // Attach a stored document...
    ]
);

要检索之前存储的文件,请在文件实例上使用 get 方法:

use Laravel\Ai\Files\Document;

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

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

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

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

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

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

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

use Laravel\Ai\Files\Document;

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

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

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

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

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

use App\Ai\Agents\DocumentAnalyzer;
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;

$stored = Document::fromPath('/path/to/report.pdf')->put();

$response = (new DocumentAnalyzer)->prompt(
    'Summarize this document.',
    attachments: [
        Document::fromId($stored->id),
    ],
);

同样,已存储的图像可以使用 Image 类引用:

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

$stored = Image::fromPath('/path/to/photo.jpg')->put();

$response = (new ImageAnalyzer)->prompt(
    'What is in this image?',
    attachments: [
        Image::fromId($stored->id),
    ],
);

向量存储

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

use Laravel\Ai\Stores;

// Create a new vector store...
$store = Stores::create('Knowledge Base');

// Create a store with additional options...
$store = Stores::create(
    name: 'Knowledge Base',
    description: 'Documentation and reference materials.',
    expiresWhenIdleFor: days(30),
);

return $store->id;

要通过 ID 检索现有向量存储,请使用 get 方法:

use Laravel\Ai\Stores;

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

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

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

use Laravel\Ai\Stores;

// Delete by ID...
Stores::delete('store_id');

// Or delete via a store instance...
$store = Stores::get('store_id');

$store->delete();

向存储添加文件

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

use Laravel\Ai\Files\Document;
use Laravel\Ai\Stores;

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

// Add a file that has already been stored with the provider...
$document = $store->add('file_id');
$document = $store->add(Document::fromId('file_id'));

// Or, store and add a file in one step...
$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 以供将来参考。

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

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

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

$store->remove('file_id');

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

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

故障转移

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

use App\Ai\Agents\SalesCoach;
use Laravel\Ai\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 作为键(枚举用例不能直接用作 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',
    ],
);

测试

代理

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

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

// Automatically generate a fixed response for every prompt...
SalesCoach::fake();

// Provide a list of prompt responses...
SalesCoach::fake([
    'First response',
    'Second response',
]);

// Dynamically handle prompt responses based on the incoming prompt...
SalesCoach::fake(function (AgentPrompt $prompt) {
    return 'Response for: '.$prompt->prompt;
});

伪造返回结构化输出的代理时,你可以提供数组作为响应。代理将返回包含给定数据的结构化响应:

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

信息

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

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

use Laravel\Ai\Prompts\AgentPrompt;

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

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

SalesCoach::assertNotPrompted('Missing prompt');

SalesCoach::assertNeverPrompted();

对于排队的代理调用,使用排队断言方法:

use Laravel\Ai\QueuedAgentPrompt;

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

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

SalesCoach::assertNotQueued('Missing prompt');

SalesCoach::assertNeverQueued();

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

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

图像

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

use Laravel\Ai\Image;
use Laravel\Ai\Prompts\ImagePrompt;
use Laravel\Ai\Prompts\QueuedImagePrompt;

// Automatically generate a fixed response for every prompt...
Image::fake();

// Provide a list of prompt responses...
Image::fake([
    base64_encode($firstImage),
    base64_encode($secondImage),
]);

// Dynamically handle prompt responses based on the incoming prompt...
Image::fake(function (ImagePrompt $prompt) {
    return base64_encode('...');
});

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

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

Image::assertNotGenerated('Missing prompt');

Image::assertNothingGenerated();

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

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

Image::assertNotQueued('Missing prompt');

Image::assertNothingQueued();

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

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

音频

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

use Laravel\Ai\Audio;
use Laravel\Ai\Prompts\AudioPrompt;
use Laravel\Ai\Prompts\QueuedAudioPrompt;

// Automatically generate a fixed response for every prompt...
Audio::fake();

// Provide a list of prompt responses...
Audio::fake([
    base64_encode($firstAudio),
    base64_encode($secondAudio),
]);

// Dynamically handle prompt responses based on the incoming prompt...
Audio::fake(function (AudioPrompt $prompt) {
    return base64_encode('...');
});

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

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

Audio::assertNotGenerated('Missing prompt');

Audio::assertNothingGenerated();

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

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

Audio::assertNotQueued('Missing prompt');

Audio::assertNothingQueued();

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

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

转录

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

use Laravel\Ai\Transcription;
use Laravel\Ai\Prompts\TranscriptionPrompt;
use Laravel\Ai\Prompts\QueuedTranscriptionPrompt;

// Automatically generate a fixed response for every prompt...
Transcription::fake();

// Provide a list of prompt responses...
Transcription::fake([
    'First transcription text.',
    'Second transcription text.',
]);

// Dynamically handle prompt responses based on the incoming prompt...
Transcription::fake(function (TranscriptionPrompt $prompt) {
    return 'Transcribed text...';
});

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

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

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

Transcription::assertNothingGenerated();

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

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

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

Transcription::assertNothingQueued();

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

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

嵌入

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

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

// Automatically generate fake embeddings of the proper dimensions for every prompt...
Embeddings::fake();

// Provide a list of prompt responses...
Embeddings::fake([
    [$firstEmbeddingVector],
    [$secondEmbeddingVector],
]);

// Dynamically handle prompt responses based on the incoming prompt...
Embeddings::fake(function (EmbeddingsPrompt $prompt) {
    return array_map(
        fn () => Embeddings::fakeEmbedding($prompt->dimensions),
        $prompt->inputs
    );
});

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

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

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

Embeddings::assertNothingGenerated();

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

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

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

Embeddings::assertNothingQueued();

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

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

重排序

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

use Laravel\Ai\Reranking;
use Laravel\Ai\Prompts\RerankingPrompt;
use Laravel\Ai\Responses\Data\RankedDocument;

// Automatically generate a fake reranked responses...
Reranking::fake();

// Provide custom responses...
Reranking::fake([
    [
        new RankedDocument(index: 0, document: 'First', score: 0.95),
        new RankedDocument(index: 1, document: 'Second', score: 0.80),
    ],
]);

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

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

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

Reranking::assertNothingReranked();

文件

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

use Laravel\Ai\Files;

Files::fake();

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

use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;

// Store files...
Document::fromString('Hello, Laravel!', mimeType: 'text/plain')
    ->as('hello.txt')
    ->put();

// Make assertions...
Files::assertStored(fn (StorableFile $file) =>
    (string) $file === 'Hello, Laravel!' &&
        $file->mimeType() === 'text/plain';
);

Files::assertNotStored(fn (StorableFile $file) =>
    (string) $file === 'Hello, World!'
);

Files::assertNothingStored();

对于文件删除的断言,你可以传递文件 ID:

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

向量存储

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

use Laravel\Ai\Stores;

Stores::fake();

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

use Laravel\Ai\Stores;

// Create store...
$store = Stores::create('Knowledge Base');

// Make assertions...
Stores::assertCreated('Knowledge Base');

Stores::assertCreated(fn (string $name, ?string $description) =>
    $name === 'Knowledge Base'
);

Stores::assertNotCreated('Other Store');

Stores::assertNothingCreated();

对于存储删除的断言,你可以提供存储 ID:

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

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

Stores::fake();

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

// Add / remove files...
$store->add('added_id');
$store->remove('removed_id');

// Make assertions...
$store->assertAdded('added_id');
$store->assertRemoved('removed_id');

$store->assertNotAdded('other_file_id');
$store->assertNotRemoved('other_file_id');

如果文件存储在提供商的文件存储中并在同一请求中添加到向量存储,你可能不知道文件的提供商 ID。在这种情况下,你可以向 assertAdded 方法传递一个闭包来对添加文件的内容进行断言:

use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;

$store->add(Document::fromString('Hello, World!', 'text/plain')->as('hello.txt'));

$store->assertAdded(fn (StorableFile $file) => $file->name() === 'hello.txt');
$store->assertAdded(fn (StorableFile $file) => $file->content() === 'Hello, World!');

事件

Laravel AI SDK 会派发各种事件,包括:

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

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