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 填入.envOPENAI_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 中设置了 AcceptContent-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应用的截图示例:
Laravel 优雅的对接 DeepSeek API,以及踩过的哪些坑

欢迎体验

我们致力于为开发者提供简单、高效的工具,帮助你将 AI 能力快速融入实际业务中。无论是对话系统、内容生成,还是其他 AI 应用场景,XinAdmin 都能为你提供强大的支持。

欢迎大家前往 GitHub 和官方网站体验,并提出宝贵的意见和建议!如果你有任何问题,也欢迎随时联系我。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 4

你好,没有代码源码吗

3个月前 评论

没有推理内容字段吧 reasoning_content

2个月前 评论
xineny (楼主) 2个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!