
# Laravel AI SDK

- [简介](#introduction)
- [安装](#installation)
    - [配置](#configuration)
    - [自定义服务商地址](#custom-base-urls)
    - [服务商支持](#provider-support)
- [Agents](#agents)
    - [提示词](#prompting)
    - [对话上下文](#conversation-context)
    - [结构化输出](#structured-output)
    - [附件](#attachments)
    - [流式响应](#streaming)
    - [广播](#broadcasting)
    - [队列](#queueing)
    - [工具](#tools)
    - [服务商工具](#provider-tools)
    - [子 Agents](#sub-agents)
    - [中间件](#middleware)
    - [匿名 Agents](#anonymous-agents)
    - [Agent 配置](#agent-configuration)
    - [服务商选项](#provider-options)
- [图像生成](#images)
- [音频（TTS）](#audio)
- [语音转录（STT）](#transcription)
- [嵌入向量](#embeddings)
    - [查询嵌入](#querying-embeddings)
    - [缓存嵌入](#caching-embeddings)
- [重排序](#reranking)
- [文件](#files)
- [向量存储](#vector-stores)
    - [向存储中添加文件](#adding-files-to-stores)
- [故障转移](#failover)
- [测试](#testing)
    - [Agent 测试](#testing-agents)
    - [图像测试](#testing-images)
    - [音频测试](#testing-audio)
    - [转录测试](#testing-transcriptions)
    - [嵌入测试](#testing-embeddings)
    - [重排序测试](#testing-reranking)
    - [文件测试](#testing-files)
    - [向量存储测试](#testing-vector-stores)
- [事件](#events)

<a name="introduction"></a>
## 简介

「[Laravel AI SDK](https://github.com/laravel/ai)」 为开发者提供了一套统一且富有表达力的 API，用于与 OpenAI、Anthropic、Gemini 等 AI 服务商进行交互。通过该 SDK，你可以构建具备工具调用与结构化输出能力的智能 Agent，还能生成图像、合成与转录音频、创建向量嵌入等功能 —— 所有操作都通过一致且符合 Laravel 风格的接口实现。

<a name="installation"></a>
## 安装

你可以通过 Composer 安装 Laravel AI SDK：

```shell
composer require laravel/ai
```

接下来，你需要使用 `vendor:publish` Artisan 命令来发布 AI SDK 的配置文件和迁移文件：

```shell
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
```



最后，你应该运行应用程序的数据库迁移。这将创建 AI SDK 用于存储对话的 `agent_conversations` 和 `agent_conversation_messages` 表：

```shell
php artisan migrate
```

<a name="configuration"></a>
### 配置

你可以在应用程序的 `config/ai.php` 配置文件中或在 `.env` 文件中以环境变量的形式定义 AI 提供商凭据：

```ini
ANTHROPIC_API_KEY=
AZURE_OPENAI_API_KEY=
COHERE_API_KEY=
DEEPSEEK_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
GROQ_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_KEY=
OPENAI_API_KEY=
OPENROUTER_API_KEY=
JINA_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=
```

用于文本、图像、音频、转录和 embeddings 的默认模型也可以在应用程序的 `config/ai.php` 配置文件中进行配置。

<a name="custom-base-urls"></a>
### 自定义基础 URL

默认情况下，Laravel AI SDK 直接连接到每个提供商的公共 API 端点。但是，你可能需要通过不同的端点路由请求——例如，当使用代理服务来集中管理 API 密钥、实施速率限制或通过企业网关路由流量时。

你可以通过在提供商配置中添加 `url` 参数来配置自定义基础 URL：

```php
'providers' => [
    'openai' => [
        'driver' => 'openai',
        'key' => env('OPENAI_API_KEY'),
        'url' => env('OPENAI_BASE_URL'),
    ],

    'anthropic' => [
        'driver' => 'anthropic',
        'key' => env('ANTHROPIC_API_KEY'),
        'url' => env('ANTHROPIC_BASE_URL'),
    ],
],
```

当通过代理服务（如 LiteLLM 或 Azure OpenAI Gateway）路由请求或使用替代端点时，这非常有用。

以下提供商支持自定义基础 URL：OpenAI、Anthropic、Gemini、Groq、Cohere、DeepSeek、xAI 和 OpenRouter。



<a name="provider-support"></a>
### 提供商支持

AI SDK 在其各项功能中支持多种提供商。下表总结了每项功能可用的提供商：

| Feature | Providers |
|---|---|
| Text | OpenAI, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter |
| Images | OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter |
| TTS | OpenAI, ElevenLabs, Gemini |
| STT | OpenAI, ElevenLabs, Mistral, Gemini |
| Embeddings | OpenAI, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter |
| Reranking | Cohere, Jina, VoyageAI |
| Files | OpenAI, Anthropic, Gemini |

`Laravel\Ai\Enums\Lab` 枚举可用于在代码中引用提供商，而不是使用普通字符串：

```php
use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::Gemini;
// ...
```

<a name="agents"></a>
## Agents

Agent 是 Laravel AI SDK 中与 AI 提供商交互的基本构建块。每个 agent 都是一个专用的 PHP 类，封装了与大语言模型交互所需的指令、对话上下文、工具和输出模式。可以将 agent 视为一个专业助手——销售教练、文档分析器、客服机器人——你只需配置一次，即可在整个应用程序中按需使用。

你可以通过 `make:agent` Artisan 命令创建一个 agent：

```shell
php artisan make:agent SalesCoach

php artisan make:agent SalesCoach --structured
```

在生成的 agent 类中，你可以定义系统提示词/指令、消息上下文、可用工具和输出模式（如果适用）：

```php
<?php

namespace App\Ai\Agents;

use App\Ai\Tools\RetrievePreviousTranscripts;
use App\Models\History;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;

class SalesCoach implements Agent, Conversational, HasTools, HasStructuredOutput
{
    use Promptable;

    public function __construct(public User $user) {}

    /**
     * 获取 agent 应遵循的指令。
     */
    public function instructions(): Stringable|string
    {
        return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
    }

    /**
     * 获取迄今为止的对话消息列表。
     */
    public function messages(): iterable
    {
        return History::where('user_id', $this->user->id)
            ->latest()
            ->limit(50)
            ->get()
            ->reverse()
            ->map(function ($message) {
                return new Message($message->role, $message->content);
            })->all();
    }

    /**
     * 获取 agent 可用的工具。
     *
     * @return Tool[]
     */
    public function tools(): iterable
    {
        return [
            new RetrievePreviousTranscripts,
        ];
    }

    /**
     * 获取 agent 的结构化输出模式定义。
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'feedback' => $schema->string()->required(),
            'score' => $schema->integer()->min(1)->max(10)->required(),
        ];
    }
}
```



<a name="prompting"></a>
### 提示

要提示一个 agent，首先使用 `make` 方法或标准实例化创建实例，然后调用 `prompt`：

```php
$response = (new SalesCoach)
    ->prompt('Analyze this sales transcript...');

return (string) $response;
```

`make` 方法从容器中解析你的 agent，允许自动依赖注入。你也可以向 agent 的构造函数传递参数：

```php
$agent = SalesCoach::make(user: $user);
```

通过向 `prompt` 方法传递额外参数，你可以在提示时覆盖默认的提供商、模型或 HTTP 超时时间：

```php
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);
```

<a name="conversation-context"></a>
### 对话上下文

如果你的 agent 实现了 `Conversational` 接口，你可以使用 `messages` 方法返回之前的对话上下文（如果适用）：

```php
use App\Models\History;
use Laravel\Ai\Messages\Message;

/**
 * 获取迄今为止的对话消息列表。
 */
public function messages(): iterable
{
    return History::where('user_id', $this->user->id)
        ->latest()
        ->limit(50)
        ->get()
        ->reverse()
        ->map(function ($message) {
            return new Message($message->role, $message->content);
        })->all();
}
```

<a name="remembering-conversations"></a>
#### 记忆对话

> **Note:** 在使用 `RemembersConversations trait` 之前，你应该使用 `vendor:publish` Artisan 命令发布并运行 AI SDK 迁移。这些迁移将创建存储对话所需的数据库表。

如果你希望 Laravel 自动存储和检索 agent 的对话历史，你可以使用 `RemembersConversations` trait。该 trait 提供了一种简单的方式将对话消息持久化到数据库，无需手动实现 `Conversational` 接口：

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, Conversational
{
    use Promptable, RemembersConversations;

    /**
     * 获取 agent 应遵循的指令。
     */
    public function instructions(): string
    {
        return 'You are a sales coach...';
    }
}
```



要为用户开始新对话，请在提示之前调用 `forUser` 方法：

```php
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');

$conversationId = $response->conversationId;
```

对话 ID 会在响应中返回，可以存储以供将来引用。如果你想使用 Eloquent 检索用户的所有对话，可以将 `HasConversations` trait 添加到你的用户模型：

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Ai\Concerns\HasConversations;

class User extends Authenticatable
{
    use HasConversations;
}
```

将该 trait 添加到模型后，你可以通过 `conversations` 关联检索和查询用户的对话：

```php
$conversations = $user->conversations()
    ->latest('updated_at')
    ->paginate(20);
```

要继续现有对话，请使用 `continue` 方法：

```php
$response = (new SalesCoach)
    ->continue($conversationId, as: $user)
    ->prompt('Tell me more about that.');
```

使用 `RemembersConversations` trait 时，之前的消息会在提示时自动加载并包含在对话上下文中。每次交互后，新消息（用户和助手消息）都会自动存储。

<a name="structured-output"></a>
### 结构化输出

如果你希望 agent 返回结构化输出，请实现 `HasStructuredOutput` 接口，该接口要求你的 agent 定义一个 `schema` 方法：

```php
<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasStructuredOutput
{
    use Promptable;

    // ...

    /**
     * 获取 agent 的结构化输出模式定义。
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required(),
        ];
    }
}
```



当提示一个返回结构化输出的 agent 时，你可以像数组一样访问返回的 `StructuredAgentResponse`：

```php
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

return $response['score'];
```

<a name="structured-output-nested-objects"></a>
#### 嵌套对象

要定义嵌套的结构化输出，可以结合闭包使用 `object` 方法：

```php
<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasStructuredOutput
{
    use Promptable;

    // ...

    /**
     * 获取 agent 的结构化输出模式定义。
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required(),
            'metadata' => $schema->object(fn ($schema) => [
                'confidence' => $schema->string()->enum(['low', 'medium', 'high'])->required(),
                'language' => $schema->string()->required(),
            ])->required(),
        ];
    }
}
```

<a name="structured-output-arrays-of-objects"></a>
#### 对象数组

如果你的 agent 应返回由结构化条目组成的列表，可以组合 `array` 和 `object` 方法：

```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(),
    ];
}
```

<a name="attachments"></a>
### 附件

在提示时，你还可以传递附件以允许模型检查图像和文档：

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
    'Analyze the attached sales transcript...',
    attachments: [
        Files\Document::fromStorage('transcript.pdf') // 从文件系统磁盘附加文档...
        Files\Document::fromPath('/home/laravel/transcript.md') // 从本地路径附加文档...
        $request->file('transcript'), // 附加上传的文件...
    ]
);
```



同样，`Laravel\Ai\Files\Image` 类可用于将图像附加到提示：

```php
use App\Ai\Agents\ImageAnalyzer;
use Laravel\Ai\Files;

$response = (new ImageAnalyzer)->prompt(
    'What is in this image?',
    attachments: [
        Files\Image::fromStorage('photo.jpg') // 从文件系统磁盘附加图像...
        Files\Image::fromPath('/home/laravel/photo.jpg') // 从本地路径附加图像...
        $request->file('photo'), // 附加上传的文件...
    ]
);
```

<a name="streaming"></a>
### 流式传输

你可以通过调用 `stream` 方法流式传输 agent 的响应。返回的 `StreamableAgentResponse` 可以从路由返回，以自动向客户端发送流式响应（SSE）：

```php
use App\Ai\Agents\SalesCoach;

Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze this sales transcript...');
});
```

`then` 方法可用于提供一个闭包，该闭包将在整个响应流式传输到客户端后被调用：

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Responses\StreamedAgentResponse;

Route::get('/coach', function () {
    return (new SalesCoach)
        ->stream('Analyze this sales transcript...')
        ->then(function (StreamedAgentResponse $response) {
            // $response->text, $response->events, $response->usage...
        });
});
```

或者，你可以手动遍历流式事件：

```php
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
    // ...
}
```

<a name="streaming-using-the-vercel-ai-sdk-protocol"></a>
#### 使用 Vercel AI SDK 协议进行流式传输

你可以通过在流式响应上调用 `usingVercelDataProtocol` 方法，使用 [Vercel AI SDK 流协议](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol) 来流式传输事件：

```php
use App\Ai\Agents\SalesCoach;

Route::get('/coach', function () {
    return (new SalesCoach)
        ->stream('Analyze this sales transcript...')
        ->usingVercelDataProtocol();
});
```



<a name="broadcasting"></a>
### 广播

你可以通过几种不同的方式广播流式事件。首先，你可以简单地在流式事件上调用 `broadcast` 或 `broadcastNow` 方法：

```php
use App\Ai\Agents\SalesCoach;
use Illuminate\Broadcasting\Channel;

$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
    $event->broadcast(new Channel('channel-name'));
}
```

或者，你可以调用 agent 的 `broadcastOnQueue` 方法将 agent 操作加入队列，并在流式事件可用时广播它们：

```php
(new SalesCoach)->broadcastOnQueue(
    'Analyze this sales transcript...'
    new Channel('channel-name'),
);
```

<a name="queueing"></a>
### 队列

使用 agent 的 `queue` 方法，你可以提示 agent，但允许它在后台处理响应，使你的应用程序保持快速和响应。`then` 和 `catch` 方法可用于注册闭包，这些闭包将在响应可用时或发生异常时被调用：

```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();
});
```

<a name="tools"></a>
### 工具

工具可用于为 agent 提供额外的功能，agent 在响应提示时可以使用这些功能。可以使用 `make:tool` Artisan 命令创建工具：

```shell
php artisan make:tool RandomNumberGenerator
```

生成的工具将放置在应用程序的 `app/Ai/Tools` 目录中。每个工具包含一个 `handle` 方法，当 agent 需要使用该工具时会调用此方法：

```php
<?php

namespace App\Ai\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class RandomNumberGenerator implements Tool
{
    /**
     * 获取工具用途的描述。
     */
    public function description(): Stringable|string
    {
        return 'This tool may be used to generate cryptographically secure random numbers.';
    }

    /**
     * 执行工具。
     */
    public function handle(Request $request): Stringable|string
    {
        return (string) random_int($request['min'], $request['max']);
    }

    /**
     * 获取工具的模式定义。
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'min' => $schema->integer()->min(0)->required(),
            'max' => $schema->integer()->required(),
        ];
    }
}
```



定义工具后，你可以从任何 agent 的 `tools` 方法中返回它：

```php
use App\Ai\Tools\RandomNumberGenerator;

/**
 * 获取 agent 可用的工具。
 *
 * @return Tool[]
 */
public function tools(): iterable
{
    return [
        new RandomNumberGenerator,
    ];
}
```

<a name="similarity-search"></a>
#### 相似性搜索

`SimilaritySearch` 工具允许 agent 使用存储在数据库中的向量 embeddings 搜索与给定查询相似的文档。当你想让 agent 能够搜索应用程序数据时，这对于检索增强生成（RAG）非常有用。

创建相似性搜索工具最简单的方法是使用 `usingModel` 方法，传入一个具有向量 embeddings 的 Eloquent 模型：

```php
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}
```

第一个参数是 Eloquent 模型类，第二个参数是包含向量 embeddings 的列。

你还可以提供 `0.0` 到 `1.0` 之间的最小相似度阈值和一个闭包来自定义查询：

```php
SimilaritySearch::usingModel(
    model: Document::class,
    column: 'embedding',
    minSimilarity: 0.7,
    limit: 10,
    query: fn ($query) => $query->where('published', true),
),
```

如需更多控制，你可以使用自定义闭包创建相似性搜索工具来返回搜索结果：

```php
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        new SimilaritySearch(using: function (string $query) {
            return Document::query()
                ->where('user_id', $this->user->id)
                ->whereVectorSimilarTo('embedding', $query)
                ->limit(10)
                ->get();
        }),
    ];
}
```

你可以使用 `withDescription` 方法自定义工具的描述：

```php
SimilaritySearch::usingModel(Document::class, 'embedding')
    ->withDescription('Search the knowledge base for relevant articles.'),
```



<a name="provider-tools"></a>
### 提供商工具

提供商工具是由 AI 提供商原生实现的特殊工具，提供网页搜索、URL 抓取和文件搜索等功能。与常规工具不同，提供商工具由提供商自身执行，而非你的应用程序。

提供商工具可以从 agent 的 `tools` 方法中返回。

<a name="web-search"></a>
#### 网页搜索

`WebSearch` 提供商工具允许 agent 搜索网页以获取实时信息。这对于回答有关时事、最新数据或自模型训练截止后可能已发生变化的主题的问题非常有用。

**支持的供应商:** Anthropic, OpenAI, Gemini

```php
use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
    return [
        new WebSearch,
    ];
}
```

你可以配置网页搜索工具以限制搜索次数或将结果限制到特定域名：

```php
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
```

要根据用户位置细化搜索结果，请使用 `location` 方法：

```php
(new WebSearch)->location(
    city: 'New York',
    region: 'NY',
    country: 'US'
);
```

<a name="web-fetch"></a>
#### 网页抓取

`WebFetch` 提供商工具允许 agent 抓取和读取网页内容。当你需要 agent 分析特定 URL 或从已知网页检索详细信息时，这非常有用。

**支持的供应商:** Anthropic, Gemini

```php
use Laravel\Ai\Providers\Tools\WebFetch;

public function tools(): iterable
{
    return [
        new WebFetch,
    ];
}
```

你可以配置网页抓取工具以限制抓取次数或限制到特定域名：

```php
(new WebFetch)->max(3)->allow(['docs.laravel.com']),
```



<a name="file-search"></a>
#### 文件搜索

`FileSearch` 提供商工具允许 agent 搜索存储在[向量存储](#vector-stores)中的[文件](#files)。这通过允许 agent 搜索你上传的文档中的相关信息来实现检索增强生成（RAG）。

**支持的提供商：** OpenAI, Gemini

```php
use Laravel\Ai\Providers\Tools\FileSearch;

public function tools(): iterable
{
    return [
        new FileSearch(stores: ['store_id']),
    ];
}
```

你可以提供多个向量存储 ID 以跨多个存储进行搜索：

```php
new FileSearch(stores: ['store_1', 'store_2']);
```

如果你的文件有[元数据](#adding-files-to-stores)，你可以通过提供 `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'])
);
```

<a name="sub-agents"></a>
### 子 Agents

Agent 也可以从另一个 agent 的 `tools` 方法中返回。当一个 agent 作为工具返回时，父 agent 可以将特定任务委派给子 agent，并在回答原始提示时使用子 agent 的响应。当通用型 agent 需要访问拥有独立指令、工具、模型配置或提供商偏好的专用 agent 时，这会很有用。

例如，客服 agent 可以将退款资格问题委派给专门的退款 agent：

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

class CustomerSupportAgent implements Agent, HasTools
{
    use Promptable;

    /**
     * 获取 agent 应遵循的指令。
     */
    public function instructions(): string
    {
        return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.';
    }

    /**
     * 获取 agent 可用的工具。
     *
     * @return Tool[]
     */
    public function tools(): iterable
    {
        return [
            new RefundsAgent,
        ];
    }
}
```



要自定义子 agent 暴露给父 agent 的方式，请在子 agent 上实现 `CanActAsTool` 接口，并定义面向工具的名称和描述：

```php
<?php

namespace App\Ai\Agents;

use App\Ai\Tools\LookupOrder;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\CanActAsTool;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

#[Provider(Lab::Anthropic)]
class RefundsAgent implements Agent, CanActAsTool, HasTools
{
    use Promptable;

    /**
     * 获取 agent 应遵循的指令。
     */
    public function instructions(): string
    {
        return 'You are a refunds specialist. Use order details and the refund policy to give concise eligibility guidance.';
    }

    /**
     * 获取 agent 的工具名称。
     */
    public function name(): string
    {
        return 'refunds_specialist';
    }

    /**
     * 获取 agent 的工具描述。
     */
    public function description(): string
    {
        return 'Determine whether an order is eligible for a refund and explain the next step.';
    }

    /**
     * 获取 agent 可用的工具。
     *
     * @return Tool[]
     */
    public function tools(): iterable
    {
        return [
            new LookupOrder,
        ];
    }
}
```

如果子 agent 未实现 `CanActAsTool`，Laravel 将根据 agent 的类名生成默认工具名称和描述。

<a name="middleware"></a>
### 中间件

Agent 支持中间件，允许你在提示发送到提供商之前拦截和修改提示。可以使用 `make:agent-middleware` Artisan 命令创建中间件：

```shell
php artisan make:agent-middleware LogPrompts
```

生成的中间件将放置在应用程序的 `app/Ai/Middleware` 目录中。要向 agent 添加中间件，请实现 `HasMiddleware` 接口并定义一个返回中间件类数组的 `middleware` 方法：

```php
<?php

namespace App\Ai\Agents;

use App\Ai\Middleware\LogPrompts;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasMiddleware
{
    use Promptable;

    // ...

    /**
     * 获取 agent 的中间件。
     */
    public function middleware(): array
    {
        return [
            new LogPrompts,
        ];
    }
}
```



每个中间件类应定义一个 `handle` 方法，该方法接收 `AgentPrompt` 和一个 `Closure` 以将提示传递给下一个中间件：

```php
<?php

namespace App\Ai\Middleware;

use Closure;
use Laravel\Ai\Prompts\AgentPrompt;

class LogPrompts
{
    /**
     * 处理传入的提示。
     */
    public function handle(AgentPrompt $prompt, Closure $next)
    {
        Log::info('Prompting agent', ['prompt' => $prompt->prompt]);

        return $next($prompt);
    }
}
```

你可以在响应上使用 `then` 方法在 agent 完成处理后执行代码。这适用于同步和流式响应：

```php
public function handle(AgentPrompt $prompt, Closure $next)
{
    return $next($prompt)->then(function (AgentResponse $response) {
        Log::info('Agent responded', ['text' => $response->text]);
    });
}
```

<a name="anonymous-agents"></a>
### 匿名 Agent

有时你可能希望快速与模型交互而无需创建专用的 agent 类。你可以使用 `agent` 函数创建一个临时的匿名 agent：

```php
use function Laravel\Ai\{agent};

$response = agent(
    instructions: 'You are an expert at software development.',
    messages: [],
    tools: [],
)->prompt('Tell me about Laravel')
```

匿名 agent 也可以生成结构化输出：

```php
use Illuminate\Contracts\JsonSchema\JsonSchema;

use function Laravel\Ai\{agent};

$response = agent(
    schema: fn (JsonSchema $schema) => [
        'number' => $schema->integer()->required(),
    ],
)->prompt('Generate a random number less than 100')
```

<a name="agent-configuration"></a>
### Agent 配置

你可以使用 PHP 属性为 agent 配置文本生成选项。以下属性可用：

- `MaxSteps`: agent 使用工具时可以执行的最大步骤数。
- `MaxTokens`: 模型可以生成的最大 token 数。
- `Model`: agent 应使用的模型。
- `Provider`: 用于 agent 的 AI 提供商（或用于故障转移的多个提供商）。
- `Temperature`: 用于生成的采样温度（0.0 到 1.0）。
- `Timeout`: agent 请求的 HTTP 超时秒数（默认：60）。
- `TopP`: 用于生成的 nucleus sampling 概率（0.0 到 1.0）。
- `UseCheapestModel`: 使用提供商最便宜的文本模型以优化成本。
- `UseSmartestModel`: 使用提供商最强大的文本模型以处理复杂任务。

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\MaxTokens;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Attributes\TopP;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

#[Provider(Lab::Anthropic)]
#[Model('claude-haiku-4-5-20251001')]
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Temperature(0.7)]
#[Timeout(120)]
#[TopP(0.9)]
class SalesCoach implements Agent
{
    use Promptable;

    // ...
}
```



`UseCheapestModel` 和 `UseSmartestModel` 属性允许你自动选择给定提供商中最具成本效益或最强大的模型，而无需指定模型名称。当你想在不同提供商之间优化成本或能力时，这非常有用：

```php
use Laravel\Ai\Attributes\UseCheapestModel;
use Laravel\Ai\Attributes\UseSmartestModel;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;

#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
    use Promptable;

    // 将使用最便宜的模型（例如 Haiku）...
}

#[UseSmartestModel]
class ComplexReasoner implements Agent
{
    use Promptable;

    // 将使用最强大的模型（例如 Opus）...
}
```

> [!NOTE]
> `UseCheapestModel` 和 `UseSmartestModel` 选择的底层模型可能会随着 Laravel AI SDK 发布版本和提供商推出新模型而变化。切换模型可能引入行为变化、已弃用参数和显著的成本差异。如果你需要稳定、可预测的模型和定价，请使用 `Model` 属性明确指定模型。

<a name="provider-options"></a>
### 提供商选项

如果你的 agent 需要传递提供商特定的选项（如 OpenAI 推理强度或惩罚设置），请实现 `HasProviderOptions` 契约并定义一个 `providerOptions` 方法：

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasProviderOptions;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasProviderOptions
{
    use Promptable;

    // ...

    /**
     * 获取提供商特定的生成选项。获取提供商特定的生成选项。
     */
    public function providerOptions(Lab|string $provider): array
    {
        return match ($provider) {
            Lab::OpenAI => [
                'reasoning' => ['effort' => 'low'],
                'frequency_penalty' => 0.5,
                'presence_penalty' => 0.3,
            ],
            Lab::Anthropic => [
                'thinking' => ['budget_tokens' => 1024],
                'cache_control' => ['type' => 'ephemeral'],
            ],
            default => [],
        };
    }
}
```

`providerOptions` 方法接收当前正在使用的提供商（`Lab` 枚举或字符串），允许你为每个提供商返回不同的选项。当使用[故障转移](#failover)时，这特别有用，因为每个备用提供商可以接收自己的配置。



上面的 Anthropic 示例还通过 `cache_control` 启用了 [prompt caching](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching)。

<a name="images"></a>
## 图像

`Laravel\Ai\Image` 类可用于使用 `openai`、`gemini` 或 `xai` 提供商生成图像：

```php
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 超时秒数：

```php
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')
    ->quality('high')
    ->landscape()
    ->timeout(120)
    ->generate();
```

你可以使用 `attachments` 方法附加参考图像：

```php
use Laravel\Ai\Files;
use Laravel\Ai\Image;

$image = Image::of('Update this photo of me to be in the style of an impressionist painting.')
    ->attachments([
        Files\Image::fromStorage('photo.jpg'),
        // Files\Image::fromPath('/home/laravel/photo.jpg'),
        // Files\Image::fromUrl('https://example.com/photo.jpg'),
        // $request->file('photo'),
    ])
    ->landscape()
    ->generate();
```

生成的图像可以轻松存储到应用程序 `config/filesystems.php` 配置文件中配置的默认磁盘上：

```php
$image = Image::of('A donut sitting on the kitchen counter');

$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');
```

图像生成也可以加入队列：

```php
use Laravel\Ai\Image;
use Laravel\Ai\Responses\ImageResponse;

Image::of('A donut sitting on the kitchen counter')
    ->portrait()
    ->queue()
    ->then(function (ImageResponse $image) {
        $path = $image->store();

        // ...
    });
```



<a name="audio"></a>
## 音频

`Laravel\Ai\Audio` 类可用于从给定文本生成音频：

```php
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();

$rawContent = (string) $audio;
```

你也可以通过 Laravel 的 `Stringable` 类提供的 `toAudio` 方法从字符串生成音频：

```php
use Illuminate\Support\Str;

$audio = Str::of('I love coding with Laravel.')->toAudio();
```

`male`、`female` 和 `voice` 方法可用于确定生成音频的声音：

```php
$audio = Audio::of('I love coding with Laravel.')
    ->female()
    ->generate();

$audio = Audio::of('I love coding with Laravel.')
    ->voice('voice-id-or-name')
    ->generate();
```

同样，`instructions` 方法可用于动态指导模型生成音频的听觉效果：

```php
$audio = Audio::of('I love coding with Laravel.')
    ->female()
    ->instructions('Said like a pirate')
    ->generate();
```

生成的音频可以轻松存储到应用程序 `config/filesystems.php` 配置文件中配置的默认磁盘上：

```php
$audio = Audio::of('I love coding with Laravel.')->generate();

$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');
```

音频生成也可以加入队列：

```php
use Laravel\Ai\Audio;
use Laravel\Ai\Responses\AudioResponse;

Audio::of('I love coding with Laravel.')
    ->queue()
    ->then(function (AudioResponse $audio) {
        $path = $audio->store();

        // ...
    });
```

<a name="transcription"></a>
## 转录

`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) {
        // ...
    });
```

<a name="embeddings"></a>
## Embeddings

你可以使用 Laravel `Stringable` 类提供的新 `toEmbeddings` 方法轻松地为任何给定字符串生成向量 embeddings：

```php
use Illuminate\Support\Str;

$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
```

或者，你可以使用 `Embeddings` 类一次为多个输入生成 embeddings：

```php
use Laravel\Ai\Embeddings;

$response = Embeddings::for([
    'Napa Valley has great wine.',
    'Laravel is a PHP framework.',
])->generate();

$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]
```

你可以指定 `embeddings` 的维度和提供商：

```php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->dimensions(1536)
    ->generate(Lab::OpenAI, 'text-embedding-3-small');
```

<a name="querying-embeddings"></a>
### 查询 Embeddings

生成 embeddings 后，你通常会将它们存储在数据库的 `vector` 列中以供后续查询。Laravel 通过 `pgvector` 扩展为 PostgreSQL 上的向量列提供原生支持。首先，在迁移中定义一个 `vector` 列，指定维度数：

```php
Schema::ensureVectorExtensionExists();

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->vector('embedding', dimensions: 1536);
    $table->timestamps();
});
```



你还可以添加向量索引以加速相似性搜索。在向量列上调用 `index` 时，Laravel 将自动创建一个使用余弦距离的 HNSW 索引：

```php
$table->vector('embedding', dimensions: 1536)->index();
```

在你的 Eloquent 模型上，你应该将向量列转换为 `array`：

```php
protected function casts(): array
{
    return [
        'embedding' => 'array',
    ];
}
```

要查询相似记录，请使用 `whereVectorSimilarTo` 方法。此方法按最小余弦相似度（`0.0` 到 `1.0` 之间，`1.0` 表示完全相同）过滤结果，并按相似度排序：

```php
use App\Models\Document;

$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();
```

`$queryEmbedding` 可以是浮点数组或纯字符串。当给定字符串时，Laravel 会自动为其生成 embeddings：

```php
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
    ->limit(10)
    ->get();
```

如果你需要更多控制，可以独立使用底层的 `whereVectorDistanceLessThan`、`selectVectorDistance` 和 `orderByVectorDistance` 方法：

```php
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();
```

如果你想让 agent 能够作为工具执行相似性搜索，请查看[相似性搜索](https://v13-laravel-docs.catchadmin.com/ai-sdk#similarity-search)工具文档。

> [!NOTE]
> 向量查询目前仅在使用 `pgvector` 扩展的 PostgreSQL 连接上受支持。

<a name="caching-embeddings"></a>
### 缓存 Embeddings

Embedding 生成可以被缓存以避免对相同输入的冗余 API 调用。要启用缓存，请将 `ai.caching.embeddings.cache` 配置选项设置为 `true`：

```php
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
        // ...
    ],
],
```



启用缓存后，embeddings 将被缓存 30 天。缓存键基于提供商、模型、维度和输入内容，确保相同的请求返回缓存结果，而不同的配置会生成新的 embeddings。

你也可以使用 `cache` 方法为特定请求启用缓存，即使全局缓存已禁用：

```php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->cache()
    ->generate();
```

你可以指定自定义的缓存持续时间（秒）：

```php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->cache(seconds: 3600) // Cache for 1 hour
    ->generate();
```

`toEmbeddings` Stringable 方法也接受 `cache` 参数：

```php
// 使用默认持续时间缓存...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);

// 缓存指定持续时间...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);
```

<a name="reranking"></a>
## 重排序

重排序允许你根据文档与给定查询的相关性重新排列文档列表。这对于通过语义理解来改善搜索结果非常有用：

`Laravel\Ai\Reranking` 类可用于重排序文档：

```php
use Laravel\Ai\Reranking;

$response = Reranking::of([
    'Django is a Python web framework.',
    'Laravel is a PHP web application framework.',
    'React is a JavaScript library for building user interfaces.',
])->rerank('PHP frameworks');

// 访问排名最高的结果...
$response->first()->document; // "Laravel is a PHP web application framework."
$response->first()->score;    // 0.95
$response->first()->index;    // 1 (原始位置)
```

`limit` 方法可用于限制返回的结果数量：

```php
$response = Reranking::of($documents)
    ->limit(5)
    ->rerank('search query');
```



<a name="reranking-collections"></a>
### 重排序集合

为方便起见，Laravel 集合可以使用 `rerank` 宏进行重排序。第一个参数指定用于重排序的字段，第二个参数是查询：

```php
// 按单个字段重排序...
$posts = Post::all()
    ->rerank('body', 'Laravel tutorials');

// 按多个字段重排序（以 JSON 形式发送）...
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');

// 使用闭包构建文档进行重排序...
$reranked = $posts->rerank(
    fn ($post) => $post->title.': '.$post->body,
    'Laravel tutorials'
);
```

你还可以限制结果数量并指定提供商：

```php
$reranked = $posts->rerank(
    by: 'content',
    query: 'Laravel tutorials',
    limit: 10,
    provider: Lab::Cohere
);
```

<a name="files"></a>
## 文件

`Laravel\Ai\Files` 类或各个文件类可用于将文件存储到你的 AI 提供商，以便稍后在对话中使用。这对于大型文档或你想多次引用而不需要重新上传的文件非常有用：

```php
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;

// 从本地路径存储文件...
$response = Document::fromPath('/home/laravel/document.pdf')->put();
$response = Image::fromPath('/home/laravel/photo.jpg')->put();

// 存储文件系统磁盘上的文件...
$response = Document::fromStorage('document.pdf', disk: 'local')->put();
$response = Image::fromStorage('photo.jpg', disk: 'local')->put();

// 存储远程 URL 上的文件...
$response = Document::fromUrl('https://example.com/document.pdf')->put();
$response = Image::fromUrl('https://example.com/photo.jpg')->put();

return $response->id;
```



你还可以存储原始内容或上传的文件：

```php
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;

// 存储原始内容...
$stored = Document::fromString('Hello, World!', 'text/plain')->put();

// 存储上传的文件...
$stored = Document::fromUpload($request->file('document'))->put();
```

文件存储后，你可以在通过 agent 生成文本时引用该文件，而无需重新上传：

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
    'Analyze the attached sales transcript...'
    attachments: [
        Files\Document::fromId('file-id') // Attach a stored document...
    ]
);
```

要检索之前存储的文件，请在文件实例上使用 `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);
```

<a name="using-stored-files-in-conversations"></a>
### 在对话中使用已存储的文件

文件存储到提供商后，你可以使用 `Document` 或 `Image` 类的 `fromId` 方法在 agent 对话中引用它：

```php
use App\Ai\Agents\DocumentAnalyzer;
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;

$stored = Document::fromPath('/path/to/report.pdf')->put();

$response = (new DocumentAnalyzer)->prompt(
    'Summarize this document.',
    attachments: [
        Document::fromId($stored->id),
    ],
);
```



同样，已存储的图像可以使用 `Image` 类引用：

```php
use Laravel\Ai\Files;
use Laravel\Ai\Files\Image;

$stored = Image::fromPath('/path/to/photo.jpg')->put();

$response = (new ImageAnalyzer)->prompt(
    'What is in this image?',
    attachments: [
        Image::fromId($stored->id),
    ],
);
```

<a name="vector-stores"></a>
## 向量存储

向量存储允许您创建可搜索的文件集合，用于检索增强生成（RAG）。`Laravel\Ai\Stores` 类提供了创建、检索和删除向量存储的方法：

```php
use Laravel\Ai\Stores;

// 创建新的向量存储...
$store = Stores::create('Knowledge Base');

// 使用额外选项创建存储...
$store = Stores::create(
    name: 'Knowledge Base',
    description: 'Documentation and reference materials.',
    expiresWhenIdleFor: days(30),
);

return $store->id;
```

要通过 ID 检索现有的向量存储，请使用 `get` 方法：

```php
use Laravel\Ai\Stores;

$store = Stores::get('store_id');

$store->id;
$store->name;
$store->fileCounts;
$store->ready;
```

要删除向量存储，请在 `Stores` 类或存储实例上使用 `delete` 方法：

```php
use Laravel\Ai\Stores;

// 通过 ID 删除...
Stores::delete('store_id');

// 或通过存储实例删除...
$store = Stores::get('store_id');

$store->delete();
```

<a name="adding-files-to-stores"></a>
### 向存储添加文件

拥有向量存储后，您可以使用 `add` 方法向其中添加[文件](#files)。添加到存储的文件会自动索引，以便使用[文件搜索提供商工具](#file-search)进行语义搜索：

```php
use Laravel\Ai\Files\Document;
use Laravel\Ai\Stores;

$store = Stores::get('store_id');

// 添加已存储到提供商的文件...
$document = $store->add('file_id');
$document = $store->add(Document::fromId('file_id'));

// 或者，一步完成存储和添加文件...
$document = $store->add(Document::fromPath('/path/to/document.pdf'));
$document = $store->add(Document::fromStorage('manual.pdf'));
$document = $store->add($request->file('document'));

$document->id;
$document->fileId;
```

> **Note:** 通常，将之前存储的文件添加到向量存储时，返回的文档 ID 将与文件之前分配的 ID 匹配；但是，某些向量存储提供商可能会返回一个新的、不同的"文档 ID"。因此，建议您始终在数据库中存储两个 ID 以供将来引用。



您可以在将文件添加到存储时附加元数据。此元数据稍后可用于在使用[文件搜索提供商工具](#file-search)时过滤搜索结果：

```php
$store->add(Document::fromPath('/path/to/document.pdf'), metadata: [
    'author' => 'Taylor Otwell',
    'department' => 'Engineering',
    'year' => 2026,
]);
```

要从存储中移除文件，请使用 `remove` 方法：

```php
$store->remove('file_id');
```

从向量存储中移除文件不会将其从提供商的[文件存储](#files)中删除。要从向量存储中移除文件并从文件存储中永久删除它，请使用 `deleteFile` 参数：

```php
$store->remove('file_abc123', deleteFile: true);
```

<a name="failover"></a>
## 故障转移

在提示或生成其他媒体时，您可以提供一个提供商/模型数组，以便在主要提供商遇到服务中断或速率限制时自动故障转移到备用提供商/模型：

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Image;

$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: [Lab::OpenAI, Lab::Anthropic],
);

$image = Image::of('A donut sitting on the kitchen counter')
    ->generate(provider: [Lab::Gemini, Lab::xAI]);
```

<a name="testing"></a>
## 测试

<a name="testing-agents"></a>
### 代理

要在测试中伪造代理的响应，请在代理类上调用 `fake` 方法。您可以选择性地提供响应数组或闭包：

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;

// 自动为每个提示生成固定响应...
SalesCoach::fake();

// 提供提示响应列表...
SalesCoach::fake([
    'First response',
    'Second response',
]);

// 根据传入的提示动态处理响应...
SalesCoach::fake(function (AgentPrompt $prompt) {
    return 'Response for: '.$prompt->prompt;
});
```

> **Note:** 当在返回结构化输出的代理上调用 `Agent::fake()` 时，Laravel 将自动生成与代理定义的输出模式匹配的伪造数据。



提示代理后，您可以对收到的提示进行断言：

```php
use Laravel\Ai\Prompts\AgentPrompt;

SalesCoach::assertPrompted('Analyze this...');

SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});

SalesCoach::assertNotPrompted('Missing prompt');

SalesCoach::assertNeverPrompted();
```

对于队列化的代理调用，请使用队列断言方法：

```php
use Laravel\Ai\QueuedAgentPrompt;

SalesCoach::assertQueued('Analyze this...');

SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});

SalesCoach::assertNotQueued('Missing prompt');

SalesCoach::assertNeverQueued();
```

要确保所有代理调用都有对应的伪造响应，您可以使用 `preventStrayPrompts`。如果代理在没有定义伪造响应的情况下被调用，将抛出异常：

```php
SalesCoach::fake()->preventStrayPrompts();
```

<a name="testing-images"></a>
### 图像

图像生成可以通过在 `Image` 类上调用 `fake` 方法来伪造。一旦图像被伪造，可以对记录的图像生成提示执行各种断言：

```php
use Laravel\Ai\Image;
use Laravel\Ai\Prompts\ImagePrompt;
use Laravel\Ai\Prompts\QueuedImagePrompt;

// 自动为每个提示生成固定响应...
Image::fake();

// 提供提示响应列表...
Image::fake([
    base64_encode($firstImage),
    base64_encode($secondImage),
]);

// 根据传入的提示动态处理响应...
Image::fake(function (ImagePrompt $prompt) {
    return base64_encode('...');
});
```

生成图像后，您可以对收到的提示进行断言：

```php
Image::assertGenerated(function (ImagePrompt $prompt) {
    return $prompt->contains('sunset') && $prompt->isLandscape();
});

Image::assertNotGenerated('Missing prompt');

Image::assertNothingGenerated();
```

对于队列化的图像生成，请使用队列断言方法：

```php
Image::assertQueued(
    fn (QueuedImagePrompt $prompt) => $prompt->contains('sunset')
);

Image::assertNotQueued('Missing prompt');

Image::assertNothingQueued();
```



要确保所有图像生成都有对应的伪造响应，您可以使用 `preventStrayImages`。如果在没有定义伪造响应的情况下生成图像，将抛出异常：

```php
Image::fake()->preventStrayImages();
```

<a name="testing-audio"></a>
### 音频

音频生成可以通过在 `Audio` 类上调用 `fake` 方法来伪造。一旦音频被伪造，可以对记录的音频生成提示执行各种断言：

```php
use Laravel\Ai\Audio;
use Laravel\Ai\Prompts\AudioPrompt;
use Laravel\Ai\Prompts\QueuedAudioPrompt;

// 自动为每个提示生成固定响应...
Audio::fake();

// 提供提示响应列表...
Audio::fake([
    base64_encode($firstAudio),
    base64_encode($secondAudio),
]);

// 根据传入的提示动态处理响应...
Audio::fake(function (AudioPrompt $prompt) {
    return base64_encode('...');
});
```

生成音频后，您可以对收到的提示进行断言：

```php
Audio::assertGenerated(function (AudioPrompt $prompt) {
    return $prompt->contains('Hello') && $prompt->isFemale();
});

Audio::assertNotGenerated('Missing prompt');

Audio::assertNothingGenerated();
```

对于队列化的音频生成，请使用队列断言方法：

```php
Audio::assertQueued(
    fn (QueuedAudioPrompt $prompt) => $prompt->contains('Hello')
);

Audio::assertNotQueued('Missing prompt');

Audio::assertNothingQueued();
```

要确保所有音频生成都有对应的伪造响应，您可以使用 `preventStrayAudio`。如果在没有定义伪造响应的情况下生成音频，将抛出异常：

```php
Audio::fake()->preventStrayAudio();
```

<a name="testing-transcriptions"></a>
### 转录

转录生成可以通过在 `Transcription` 类上调用 `fake` 方法来伪造。一旦转录被伪造，可以对记录的转录生成提示执行各种断言：

```php
use Laravel\Ai\Transcription;
use Laravel\Ai\Prompts\TranscriptionPrompt;
use Laravel\Ai\Prompts\QueuedTranscriptionPrompt;

// 自动为每个提示生成固定响应...
Transcription::fake();

// 提供提示响应列表...
Transcription::fake([
    'First transcription text.',
    'Second transcription text.',
]);

// 根据传入的提示动态处理响应...
Transcription::fake(function (TranscriptionPrompt $prompt) {
    return 'Transcribed text...';
});
```



生成转录后，您可以对收到的提示进行断言：

```php
Transcription::assertGenerated(function (TranscriptionPrompt $prompt) {
    return $prompt->language === 'en' && $prompt->isDiarized();
});

Transcription::assertNotGenerated(
    fn (TranscriptionPrompt $prompt) => $prompt->language === 'fr'
);

Transcription::assertNothingGenerated();
```

对于队列化的转录生成，请使用队列断言方法：

```php
Transcription::assertQueued(
    fn (QueuedTranscriptionPrompt $prompt) => $prompt->isDiarized()
);

Transcription::assertNotQueued(
    fn (QueuedTranscriptionPrompt $prompt) => $prompt->language === 'fr'
);

Transcription::assertNothingQueued();
```

要确保所有转录生成都有对应的伪造响应，您可以使用 `preventStrayTranscriptions`。如果在没有定义伪造响应的情况下生成转录，将抛出异常：

```php
Transcription::fake()->preventStrayTranscriptions();
```

<a name="testing-embeddings"></a>
### 嵌入

嵌入生成可以通过在 `Embeddings` 类上调用 `fake` 方法来伪造。一旦嵌入被伪造，可以对记录的嵌入生成提示执行各种断言：

```php
use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\EmbeddingsPrompt;
use Laravel\Ai\Prompts\QueuedEmbeddingsPrompt;

// 自动为每个提示生成正确维度的伪造嵌入...
Embeddings::fake();

// 提供提示响应列表...
Embeddings::fake([
    [$firstEmbeddingVector],
    [$secondEmbeddingVector],
]);

// 根据传入的提示动态处理响应...
Embeddings::fake(function (EmbeddingsPrompt $prompt) {
    return array_map(
        fn () => Embeddings::fakeEmbedding($prompt->dimensions),
        $prompt->inputs
    );
});
```

生成嵌入后，您可以对收到的提示进行断言：

```php
Embeddings::assertGenerated(function (EmbeddingsPrompt $prompt) {
    return $prompt->contains('Laravel') && $prompt->dimensions === 1536;
});

Embeddings::assertNotGenerated(
    fn (EmbeddingsPrompt $prompt) => $prompt->contains('Other')
);

Embeddings::assertNothingGenerated();
```

对于队列化的嵌入生成，请使用队列断言方法：

```php
Embeddings::assertQueued(
    fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Laravel')
);

Embeddings::assertNotQueued(
    fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Other')
);

Embeddings::assertNothingQueued();
```



要确保所有嵌入生成都有对应的伪造响应，您可以使用 `preventStrayEmbeddings`。如果在没有定义伪造响应的情况下生成嵌入，将抛出异常：

```php
Embeddings::fake()->preventStrayEmbeddings();
```

<a name="testing-reranking"></a>
### 重排序

重排序操作可以通过在 `Reranking` 类上调用 `fake` 方法来伪造：

```php
use Laravel\Ai\Reranking;
use Laravel\Ai\Prompts\RerankingPrompt;
use Laravel\Ai\Responses\Data\RankedDocument;

// 自动生成伪造的重排序响应...
Reranking::fake();

// 提供自定义响应...
Reranking::fake([
    [
        new RankedDocument(index: 0, document: 'First', score: 0.95),
        new RankedDocument(index: 1, document: 'Second', score: 0.80),
    ],
]);
```

重排序后，您可以对执行的操作进行断言：

```php
Reranking::assertReranked(function (RerankingPrompt $prompt) {
    return $prompt->contains('Laravel') && $prompt->limit === 5;
});

Reranking::assertNotReranked(
    fn (RerankingPrompt $prompt) => $prompt->contains('Django')
);

Reranking::assertNothingReranked();
```

<a name="testing-files"></a>
### 文件

文件操作可以通过在 `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();
```

<a name="testing-vector-stores"></a>
### 向量存储



向量存储操作可以通过在 `Stores` 类上调用 `fake` 方法来伪造。伪造存储也会自动伪造[文件操作](#files)：

```php
use Laravel\Ai\Stores;

Stores::fake();
```

一旦存储操作被伪造，您可以对创建或删除的存储进行断言：

```php
use Laravel\Ai\Stores;

// 创建存储...
$store = Stores::create('Knowledge Base');

// 进行断言...
Stores::assertCreated('Knowledge Base');

Stores::assertCreated(fn (string $name, ?string $description) =>
    $name === 'Knowledge Base'
);

Stores::assertNotCreated('Other Store');

Stores::assertNothingCreated();
```

对于存储删除的断言，您可以提供存储 ID：

```php
Stores::assertDeleted('store_id');
Stores::assertNotDeleted('other_store_id');
Stores::assertNothingDeleted();
```

要断言文件已添加到存储或从存储中移除，请在给定的 `Store` 实例上使用断言方法：

```php
Stores::fake();

$store = Stores::get('store_id');

// 添加/移除文件...
$store->add('added_id');
$store->remove('removed_id');

// 进行断言...
$store->assertAdded('added_id');
$store->assertRemoved('removed_id');

$store->assertNotAdded('other_file_id');
$store->assertNotRemoved('other_file_id');
```

如果文件在同一请求中存储到提供商的[文件存储](#files)并添加到向量存储，您可能不知道文件的提供商 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!');
```

<a name="events"></a>
## 事件

Laravel AI SDK 会分发多种[事件](/docs/laravel/13.x/events)，包括：

- `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 使用信息。

