Laravel 优雅的对接 DeepSeek API,以及踩过的那些坑
前言
随着AI的发展,任何应用如果不对接AI就好像跟不上潮流。Laravel作为 WEB 开发中最为优雅的存在,也应该在这场技术革命中占据自己的一席之地。本文将分享如何在Laravel项目中优雅地集成 DeepSeek API(支持符合OpenAi API规范的所有AI服务商如:硅基流动),并记录下我在这个过程中踩过的坑,希望能为同样想要拥抱AI的Laravel开发者们提供一些参考。
一、技术依赖
在开始之前,首先你需要创建一个 Laravel 应用。如果你还没有创建过 Laravel 项目,可以参考 Laravel 官方文档 中的安装指南,使用 Composer 快速创建一个全新的 Laravel 项目。例如,运行以下命令:
composer create-project laravel/laravel laravel-deepseek
接下来,由于 DeepSeek API 兼容 OpenAI API,我们可以直接使用社区中成熟的第三方扩展包 openai-php/laravel 来简化 API 调用。这个扩展包提供了对 OpenAI API 的封装,并且与 Laravel 框架深度集成,能够帮助我们快速实现与 DeepSeek 的对接。
你可以通过 Composer 安装这个扩展包:
composer require openai-php/laravel
安装完成后,运行以下 Artisan 命令来发布扩展包的配置文件:
php artisan openai:install
这会在 config 目录下生成一个 openai.php 配置文件,你可以在其中配置 DeepSeek API 的密钥,当然你也可以通过 .env
文件来配置,将 DeepSeek 提供的 API Key 填入.env
的 OPENAI_API_KEY
字段中:
OPENAI_API_KEY=
OPENAI_ORGANIZATION=
如果你不知道 DeepSeek API_KEY 如何申请,请参考DeepSeek 文档。
踩坑点一
截至:2025年3月13日,openai-php/laravel
目前暂不支持使用 base_uri
来配置第三方URL,我们可以通过修改依赖文件的方式来配置DeepSeek API 地址,首先我们在 config/openai.php
文件中添加配置:
'base_uri' => env('OPENAI_BASE_URI', 'https://api.openai.com/v1'),
在 .env
环境中添加配置:
OPENAI_BASE_URI=https://api.deepseek.com
之后我们找到vendor/openai-php/laravel/src/ServiceProvider.php
文件,修改以下代码:
public function register(): void
{
$this->app->singleton(ClientContract::class, static function (): Client {
$apiKey = config('openai.api_key');
$organization = config('openai.organization');
// 添加 baseUri 配置
$baseUri = config('openai.base_uri');
if (! is_string($apiKey) || ($organization !== null && ! is_string($organization))) {
throw ApiKeyIsMissing::create();
}
return OpenAI::factory()
->withApiKey($apiKey)
->withOrganization($organization)
// 关联 baseUri
->withBaseUri($baseUri)
->withHttpClient(new \GuzzleHttp\Client(['timeout' => config('openai.request_timeout', 30), 'verify' => false,]))
->make();
});
$this->app->alias(ClientContract::class, 'openai');
$this->app->alias(ClientContract::class, Client::class);
}
以上内容可能会随着
openai-php/laravel
的更新来陆续支持其它 URL,具体可以参考 issues: Can we customize OPENAI_BASE_URL, just like we defined OPENAI_API_KEY?,你也可以通过这种方式来适配其它大模型。
了通过这种方式,我们可以快速搭建起 Laravel 与 DeepSeek API 的桥梁,为后续的功能开发奠定基础。
二、接口实现
在 Laravel 中,与 DeepSeek API 的对接可以通过多种方式实现,具体取决于你的应用场景和需求。Laravel 提供了强大的 Streamed Responses
,来实现流式响应(例如实时获取 AI 生成的文本)这种场景。
以下是一个简单的示例,展示如何使用 Streamed Responses
来实时获取 DeepSeek API 的响应并返回给客户端:
public function test(Request $request): StreamedResponse
{
$message = $request->getContent();
return response()->eventStream(function () use ($message) {
yield $message;
$msgData = json_decode($message, true);
$stream = OpenAI::chat()->createStreamed([
'model' => 'deepseek-chat',
'messages' => [
['role' => 'user', 'content' => $msgData['message']],
]
]);
foreach ($stream as $response) {
yield $response->choices[0];
}
}, endStreamWith: new StreamedEvent(event: 'update', data: 'end'));
}
在这个示例中,我们通过 $message = $request->getContent()
方法来接受前端提供的 JSON
信息,并通过json_decode($message, true);
来解析请求,通过 response()->eventStream()
方法创建了一个流式响应。每次从 DeepSeek API 接收到数据块时,都会通过 yield
将其推送到客户端。
踩坑点二
由于DeepSeek API 响应的内容不包含 created
字段,所以在 openai-php
解析的时候,会出现 Undefined array key created
错误,我们需要将 created
字段设置为可选字段,找到文件vendor\openai-php\client\src\Responses\Models\RetrieveResponse.php
, 修改以下代码。
public static function from(array $attributes, MetaInformation $meta): self
{
return new self(
$attributes['id'],
$attributes['object'],
// 将created 修改为可选字段
$attributes['created'] ?? 1,
$attributes['owned_by'],
$meta,
);
}
以上内容可能会随着
openai-php/client
的更新而修复,具体可以参考 Pull requests: Fix undefined array key “created”,你也可以通过这种方式来适配其它大模型。
前端实现
在前端与 Laravel 后端对接时,我们可以通过 fetch API 向 DeepSeek 的接口发送请求,并处理返回的流式数据。以下是一个完整的前端请求示例:
1. 发送请求
使用 fetch
发送 POST 请求时,可以在 headers
中配置请求头,并在 body
中传递所需的数据。以下是一个示例:
const response = await fetch('/your_api_route', {
method: 'POST',
headers: {
'Accept': '*/*',
'content-type': 'text/plain;charset=UTF-8', // 使用 JSON 格式传递数据
},
body: JSON.stringify({ message: '你的消息内容' }), // 将消息内容序列化为 JSON
});
在这个示例中:
method: 'POST'
指定请求方法为 POST。headers
中设置了Accept
和Content-Type
,确保服务器能够正确解析请求。body
中通过JSON.stringify
将消息内容序列化为 JSON 格式。
2. 处理流式响应
DeepSeek 的流式接口返回的是一个标准的ReadableStream 对象。我们可以通过 for await...of
语法对数据流进行异步迭代,逐块处理返回的数据。以下是一个完整的示例:
let totalSize = 0; // 用于记录接收到的数据总大小
try {
// 异步迭代 response.body
for await (const chunk of response.body) {
// 处理每个数据块
const textChunk = new TextDecoder().decode(chunk); // 将二进制数据解码为字符串
console.log('Received chunk:', textChunk);
// 更新总大小
totalSize += chunk.length;
}
// 输出接收到的数据总大小
console.log('Total data size received:', totalSize, 'bytes');
} catch (error) {
console.error('Error while reading the stream:', error);
}
在这个示例中:
- response.body 是一个 ReadableStream 对象,表示服务器返回的流式数据。
- 使用 for await…of 逐块读取数据流,并通过 TextDecoder 将二进制数据解码为字符串。
- 每次接收到数据块时,更新 totalSize 以记录总数据大小。
- 使用 try…catch 捕获可能的错误,确保程序的健壮性。
3. 实时更新 UI
如果你需要将流式数据实时展示在页面上,可以将每个数据块追加到 DOM 元素中。例如:
const outputElement = document.getElementById('output'); // 获取用于展示结果的 DOM 元素
for await (const chunk of response.body) {
const textChunk = new TextDecoder().decode(chunk); // 解码数据块
outputElement.textContent += textChunk; // 将数据追加到页面中
}
通过以上方式,你可以轻松实现前端与 DeepSeek API 的流式交互,并将数据实时展示给用户。
完整演示
在实际业务场景中,我们通常需要存储和记录会话内容,并利用大模型的缓存机制来实现对话上下文的管理。XinAdmin 作为一个全栈开发框架,不仅集成了 DeepSeek API,还完整实现了与大模型的对话功能。你可以轻松地将 AI 大模型能力应用到你的应用中,同时实现精细的权限控制。
以下是一个XinAdmin应用的截图示例:
- GitHub 地址: xin-laravel
- 官方网站:xinadmin.cn
- 小刘同学:xineny.cn
欢迎体验
我们致力于为开发者提供简单、高效的工具,帮助你将 AI 能力快速融入实际业务中。无论是对话系统、内容生成,还是其他 AI 应用场景,XinAdmin
都能为你提供强大的支持。
欢迎大家前往 GitHub 和官方网站体验,并提出宝贵的意见和建议!如果你有任何问题,也欢迎随时联系我。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: