跳转到主要内容

简介

Laravel AI SDK 为你提供了一套统一、表达力强的 API,用来与 OpenAI、Anthropic、Gemini 等 AI 提供商进行交互。借助 AI SDK,你可以构建带工具调用和结构化输出的智能体、生成图像、进行文本转语音与语音转文本、创建向量嵌入等,全部通过一致而符合 Laravel 风格的接口完成。
Laravel AI SDK 是在 Laravel 13 中新增的官方扩展包。它以 laravel/ai 的形式提供,允许你通过统一的 API 使用多家 AI 提供商。

支持的提供商一览

功能支持的提供商
文本生成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
重排序(Reranking)Cohere, Jina, VoyageAI
文件OpenAI, Anthropic, Gemini, Azure

安装

1

安装扩展包

使用 Composer 安装 Laravel AI SDK。
composer require laravel/ai
2

发布配置文件和迁移

通过 vendor:publish Artisan 命令发布配置文件和迁移。
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
3

执行迁移

运行数据库迁移。它会创建 agent_conversationsagent_conversation_messages 两张表,用于保存会话历史。
php artisan migrate

配置

环境变量

.env 文件中配置你所使用的 AI 提供商的 API 密钥。
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=
文本、图像、音频、语音转文本以及嵌入向量所使用的默认模型也可以在 config/ai.php 中配置。

自定义 Base 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'),
    ],
],
OpenAI、Anthropic、Gemini、Groq、Cohere、DeepSeek、xAI 和 OpenRouter 均支持自定义 Base URL。

OpenAI-Compatible 提供商

对于 LM Studio、vLLM、Together、Fireworks 或本地网关等提供 OpenAI 兼容 API 的服务,可以使用 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-Compatible 提供商支持文本生成、流式响应、工具调用、结构化输出以及图像附件。如果你的端点需要额外的请求体字段,请使用提供商选项

Lab 枚举

在代码中引用提供商时,可以使用 Lab 枚举。
use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::Gemini;

智能体(Agent)

智能体是 Laravel AI SDK 的基本构成单元。使用 make:agent 命令可以生成智能体类。
php artisan make:agent SalesCoach

# 生成带结构化输出的智能体
php artisan make:agent SalesCoach --structured
生成的智能体会被放到 app/Ai/Agents/ 目录下。下面是一个实现了所有主要接口的智能体示例。
<?php

namespace App\Ai\Agents;

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

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

    public function __construct(public User $user) {}

    public function instructions(): Stringable|string
    {
        return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
    }

    public function messages(): iterable
    {
        return History::where('user_id', $this->user->id)
            ->latest()
            ->limit(50)
            ->get()
            ->reverse()
            ->map(function ($message) {
                return new Message($message->role, $message->content);
            })->all();
    }

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

    public function schema(JsonSchema $schema): array
    {
        return [
            'feedback' => $schema->string()->required(),
            'score' => $schema->integer()->min(1)->max(10)->required(),
        ];
    }
}

发起提示

使用 prompt() 方法向智能体发送消息。
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return (string) $response;
使用静态方法 make() 可以让容器解析依赖并生成实例。
$agent = SalesCoach::make(user: $user);
可以在 prompt() 的参数中覆盖提供商、模型和超时时间。
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);

会话上下文

实现 Conversational 接口并定义 messages() 方法后,就可以把历史会话传给 AI。 使用 RemembersConversations trait 可以让会话历史自动保存到数据库并自动读取。
use Laravel\Ai\Concerns\RemembersConversations;

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

    public function instructions(): string
    {
        return 'You are a sales coach...';
    }
}
通过 forUser() 开始一段会话,并使用返回的 conversationId 通过 continue() 继续对话。
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');
$conversationId = $response->conversationId;

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

结构化输出

实现 HasStructuredOutput 接口,并在 schema() 方法中定义 JSON Schema,就能把 AI 响应作为结构化数据接收。
public function schema(JsonSchema $schema): array
{
    return ['score' => $schema->integer()->required()];
}

$response = (new SalesCoach)->prompt('Analyze this...');
return $response['score'];

嵌套对象

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

对象数组

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(多个 schema 中任选其一)

如果一个值可能匹配多个 schema 之一,可以使用 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(),
    ];
}

附件

通过 attachments 参数可以把文档或图像传给智能体。
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'),
    ]
);
图像附件的传入方式相同。
$response = (new ImageAnalyzer)->prompt('What is in this image?', attachments: [
    Files\Image::fromStorage('photo.jpg'),
    Files\Image::fromPath('/home/laravel/photo.jpg'),
    $request->file('photo'),
]);

流式响应

使用 stream() 方法可以按分块返回响应,非常适合把长响应实时推送到前端。
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze this sales transcript...');
});
可以通过 then() 回调在流式传输完成后执行处理逻辑。
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 协议

如果前端使用 Vercel AI SDK,可以调用 usingVercelDataProtocol()
Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze...')->usingVercelDataProtocol();
});

广播

可以把流事件推送到 Laravel Echo 等广播频道。
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 Attribute 把特定类型的事件排除在广播之外。
<?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() 方法可以把提示词放入队列进行异步处理。
use Laravel\Ai\Responses\AgentResponse;

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

    return back();
});

工具

工具允许 AI 调用你代码中的函数。使用 make:tool 命令可以生成工具类。
php artisan make:tool RandomNumberGenerator
<?php

namespace App\Ai\Tools;

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

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

    public function handle(Request $request): Stringable|string
    {
        return (string) random_int($request['min'], $request['max']);
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'min' => $schema->integer()->min(0)->required(),
            'max' => $schema->integer()->required(),
        ];
    }
}
在智能体的 tools() 方法中注册工具。
public function tools(): iterable
{
    return [new RandomNumberGenerator];
}

相似度检索工具

可以轻松添加一个基于向量嵌入的相似度检索工具。
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}
也可以指定选项。
SimilaritySearch::usingModel(
    model: Document::class,
    column: 'embedding',
    minSimilarity: 0.7,
    limit: 10,
    query: fn ($query) => $query->where('published', true),
),
还可以通过闭包定义自定义的检索逻辑。
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,可以把 Model Context Protocol 服务器暴露出的工具提供给智能体。通过 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 提供商原生实现的特殊工具。

Web 搜索

为智能体添加网页搜索能力。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']),
(new WebSearch)->location(city: 'New York', region: 'NY', country: 'US');

Web 抓取

获取指定 URL 内容的工具。Anthropic 和 Gemini 支持。
use Laravel\Ai\Providers\Tools\WebFetch;

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

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

文件检索

从向量库中搜索文档的工具。OpenAI 和 Gemini 支持。
use Laravel\Ai\Providers\Tools\FileSearch;

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

// 指定多个向量库
new FileSearch(stores: ['store_1', 'store_2']);

// 按元数据过滤
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'])
);

子智能体(Sub-agent)

智能体也可以从其他智能体的 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;

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

    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;

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

    public function name(): string
    {
        return 'refunds_specialist';
    }

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

    public function tools(): iterable
    {
        return [
            new LookupOrder,
        ];
    }
}
对于未实现 CanActAsTool 的子智能体,Laravel 会以类名作为工具名,并自动生成一段通用描述。每次调用子智能体都是独立的,不会继承父智能体的会话历史。

中间件

你可以为智能体添加中间件,用来拦截提示词或响应。
php artisan make:agent-middleware LogPrompts
在智能体上实现 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;

    public function middleware(): array
    {
        return [new LogPrompts];
    }
}
下面是一个中间件类的实现示例。
<?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() 可以在响应之后追加处理逻辑。
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;

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

智能体配置(PHP Attribute)

可以使用 PHP Attribute 以声明式的方式为智能体设置默认值。
Attribute说明
#[Provider]指定使用的提供商
#[Model]指定使用的模型
#[MaxSteps]工具调用的最大步数
#[MaxTokens]最大 token 数
#[Temperature]温度参数(0.0~1.0)
#[TopP]核采样概率(0.0~1.0)
#[Timeout]超时时间(秒)
#[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;
}
此外还提供了模型选择的快捷 Attribute。
// 使用最便宜的模型
#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
    use Promptable;
}

// 使用性能最强的模型
#[UseSmartestModel]
class ComplexReasoner implements Agent
{
    use Promptable;
}

提供商选项

实现 HasProviderOptions 接口后,可以传入提供商特有的选项。
<?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],
            ],
            default => [],
        };
    }
}

图像生成

使用 Image 类可以生成图像。OpenAI、Gemini、xAI 提供商均支持。
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')->generate();
$rawContent = (string) $image;
可以指定画质、宽高比和超时时间。
$image = Image::of('A donut sitting on the kitchen counter')
    ->quality('high')
    ->landscape()
    ->timeout(120)
    ->generate();
也可以附加参考图像进行加工。
use Laravel\Ai\Files;

$image = Image::of('Update this photo to be in the style of an impressionist painting.')
    ->attachments([Files\Image::fromStorage('photo.jpg')])
    ->landscape()
    ->generate();

保存图像

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

通过队列生成图像

use Laravel\Ai\Responses\ImageResponse;

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

语音合成(TTS)

使用 Audio 类可以把文本转换为语音。OpenAI 和 ElevenLabs 提供商支持。
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();
$rawContent = (string) $audio;
可以指定声音性别、具体的 voice ID,或者朗读的风格提示。
$audio = Audio::of('I love coding with Laravel.')->female()->generate();
$audio = Audio::of('I love coding with Laravel.')->voice('voice-id-or-name')->generate();
$audio = Audio::of('I love coding with Laravel.')->female()->instructions('Said like a pirate')->generate();

保存音频

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

通过队列生成音频

use Laravel\Ai\Responses\AudioResponse;

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

语音识别(STT)

使用 Transcription 类可以把音频文件转换为文本。OpenAI、ElevenLabs 和 Mistral 提供商支持。
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;

说话人分离(Diarization)

使用 diarize() 可以得到按说话人分离的转写结果。
$transcript = Transcription::fromStorage('audio.mp3')->diarize()->generate();

通过队列进行语音识别

use Laravel\Ai\Responses\TranscriptionResponse;

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

嵌入向量(Embeddings)

可以把文本转换成向量表示,用于相似度搜索等场景。
use Illuminate\Support\Str;

// 通过 Stringable 使用
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
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');

向量检索(pgvector)

下面是使用 PostgreSQL 和 pgvector 扩展做向量检索的配置示例。
1

创建迁移

Schema::ensureVectorExtensionExists();

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

// 添加 HNSW 索引
$table->vector('embedding', dimensions: 1536)->index();
2

配置模型

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

相似度查询

// 使用嵌入向量搜索
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();

// 传入字符串会自动生成嵌入向量
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
    ->limit(10)
    ->get();
也可以使用更底层的方法。
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();

缓存嵌入向量

可以对相同文本的嵌入向量进行缓存,避免重复生成。 config/ai.php 中设置默认的缓存行为。
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
    ],
],
也可以在每次请求时控制缓存。
$response = Embeddings::for(['Napa Valley has great wine.'])->cache()->generate();
$response = Embeddings::for(['Napa Valley has great wine.'])->cache(seconds: 3600)->generate();

// Stringable 也一样可用
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);

重排序(Reranking)

可以按照与查询的相关度对检索结果重新排序。Cohere 和 Jina 提供商支持。
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() 可以限制返回数量。
$response = Reranking::of($documents)->limit(5)->rerank('search query');

对集合进行重排序

可以直接对 Eloquent 集合进行重排序。
// 单一字段
$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');

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

文件管理

可以把文件上传到 AI 提供商,之后再引用它们。
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;
也可以从字符串或表单上传创建文件。
$stored = Document::fromString('Hello, World!', 'text/plain')->put();
$stored = Document::fromUpload($request->file('document'))->put();

引用已保存的文件

用上传得到的文件 ID 就可以把文件附加到智能体上。
use Laravel\Ai\Files;

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

获取与删除文件

// 获取
$file = Document::fromId('file-id')->get();
$file->id;
$file->mimeType();

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

指定提供商

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

向量库(Vector Stores)

向量库允许你在提供商侧管理文档。
use Laravel\Ai\Stores;

// 创建
$store = Stores::create('Knowledge Base');
$store = Stores::create(
    name: 'Knowledge Base',
    description: 'Documentation.',
    expiresWhenIdleFor: days(30),
);
return $store->id;

// 获取
$store = Stores::get('store_id');
$store->id;
$store->name;
$store->fileCounts;
$store->ready;

// 删除
Stores::delete('store_id');
$store->delete();

向向量库添加文件

$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;
也可以附加元数据。
$store->add(
    Document::fromPath('/path/to/document.pdf'),
    metadata: [
        'author' => 'Taylor Otwell',
        'department' => 'Engineering',
        'year' => 2026,
    ]
);

从向量库中移除文件

$store->remove('file_id');

// 同时删除文件本身
$store->remove('file_abc123', deleteFile: true);

故障切换(Failover)

以数组方式指定多个提供商时,如果第一个提供商失败,会自动切换到下一个。
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]);

测试

Laravel AI SDK 提供了测试用的 fake 能力,可以在不调用真实 API 的情况下进行测试。

智能体的测试

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

// 固定响应的 fake
SalesCoach::fake();
SalesCoach::fake(['First response', 'Second response']);

// 使用闭包动态返回响应
SalesCoach::fake(function (AgentPrompt $prompt) {
    return 'Response for: '.$prompt->prompt;
});

// 断言
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() 后,如果调用了 fake 中未定义的提示词,就会抛出异常。
SalesCoach::fake()->preventStrayPrompts();
对于返回结构化输出的智能体,可以用数组指定响应内容。智能体会返回包含指定数据的结构化响应。
SalesCoach::fake([
    ['score' => 87],
]);
如果对结构化输出的智能体调用 fake() 时没有显式传入 fake 数据,Laravel 会根据智能体定义的 schema 自动生成符合结构的 fake 数据。
匿名智能体的测试可以使用 AnonymousAgent::fake()
use Laravel\Ai\AnonymousAgent;

AnonymousAgent::fake(['Test response']);

图像生成的测试

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

Image::fake();
Image::fake([base64_encode($firstImage), base64_encode($secondImage)]);
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();

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

语音合成的测试

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

Audio::fake();
Audio::fake([base64_encode($firstAudio), base64_encode($secondAudio)]);
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();

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

语音识别的测试

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

Transcription::fake();
Transcription::fake(['First transcription text.', 'Second transcription text.']);
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();

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

嵌入向量的测试

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

Embeddings::fake();
Embeddings::fake([[$firstEmbeddingVector], [$secondEmbeddingVector]]);
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();

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

重排序的测试

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

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

文件的测试

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

Files::fake();

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

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

向量库的测试

use Laravel\Ai\Stores;

Stores::fake(); // 文件操作也会被 fake

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

Stores::assertDeleted('store_id');
Stores::assertNotDeleted('other_store_id');
Stores::assertNothingDeleted();
也可以对向量库上的文件操作进行断言。
$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');

// 通过闭包验证内容
$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 会派发以下事件。你可以监听这些事件用于日志记录、监控等场景。
  • PromptingAgent — 发送提示词之前
  • AgentPrompted — 发送提示词之后
  • StreamingAgent — 开始流式响应时
  • AgentStreamed — 流式响应完成后
  • InvokingTool — 工具调用之前
  • ToolInvoked — 工具调用之后
  • GeneratingImage — 生成图像之前
  • ImageGenerated — 生成图像之后
  • GeneratingAudio — 生成音频之前
  • AudioGenerated — 生成音频之后
  • GeneratingTranscription — 语音识别之前
  • TranscriptionGenerated — 语音识别之后
  • GeneratingEmbeddings — 生成嵌入向量之前
  • EmbeddingsGenerated — 生成嵌入向量之后
  • Reranking — 重排序之前
  • Reranked — 重排序之后
  • StoringFile — 保存文件之前
  • FileStored — 保存文件之后
  • FileDeleted — 删除文件之后
  • CreatingStore — 创建向量库之前
  • StoreCreated — 创建向量库之后
  • AddingFileToStore — 向量库中新增文件之前
  • FileAddedToStore — 向量库中新增文件之后
  • RemovingFileFromStore — 从向量库移除文件之前
  • FileRemovedFromStore — 从向量库移除文件之后
最后修改于 2026年7月13日