利用 SSE 实现流式 AI 聊天交互(三)

在 AI 赋能的时代,即时交互式对话体验成为众多应用的核心功能之一。本文将介绍如何使用 流式 SSE (Server-Sent Events) 技术,实现高效的 AI 聊天交互,提供更加丝滑的用户体验。


一、SSE 介绍

SSE (Server-Sent Events) 是一种基于 HTTP 的服务器推送技术,适用于需要实时更新但数据流量较小的场景,例如 AI 对话、股票行情、新闻推送等。它的主要特点包括:

  • 单向通信:服务器可以主动向客户端推送消息,而客户端仅能接收。

  • 轻量级:相比 WebSocket,SSE 使用 HTTP 连接,无需额外协议支持。

  • 自动重连:连接断开时,浏览器会自动尝试重新建立连接。

相比 WebSocket,SSE 更适合 AI 聊天等对实时性要求较高但数据方向单一的场景。


二、实现思路

传统的 AI 聊天一般是用户发送请求后,后端计算完成后一次性返回结果。但对于长文本生成,这种方式可能导致较长的响应延迟。

相比之下,流式响应 (Streaming) 可以让 AI 逐步输出生成内容,从而提升用户体验。

我们的实现基于 Odoo 18 + OpenAI API,通过 SSE (Server-Sent Events) 让前端实时接收 AI 生成的内容,达到流畅的交互效果。


三、后端实现

在 Odoo 的 @http.route 中,我们定义了一个流式接口,负责与 AI 进行通信,并通过 SSE 将生成内容逐步推送到前端。

后端代码:请求 AI 生成内容

@http.route('/ai/stream_chat', type='http', auth='public', cors='*')
def ai_stream_chat(self, **kwargs):
    user_id = request.session.uid
    user_message = kwargs.get('user_message')
    file_content = kwargs.get('file_content', '')
    api_url = request.env['ir.config_parameter'].sudo().get_param('ai_chat_url')
    api_key = request.env['ir.config_parameter'].sudo().get_param('ai_chat_api_key')
    ai_model = request.env['ir.config_parameter'].sudo().get_param('ai_chat_model')

    # ✅ 初始化 OpenAI 客户端
    client = OpenAI(base_url=api_url, api_key=api_key)

    messages = [
        {"role": "system", "content": "我是一个AI助手,我的名字叫小加!"},
        {"role": "user", "content": file_content[:5000]},
        {"role": "user", "content": user_message}
    ]

    def event_stream():
        try:
            completion = client.chat.completions.create(
                model=ai_model,
                messages=messages,
                stream=True,
                extra_headers={
                    "HTTP-Referer": "DeepSeek R1",
                    "X-Title": "Odoo AIChat"
                }
            )
            response_text = ""
            for chunk in completion:
                delta = chunk.choices[0].delta
                if delta and delta.content:
                    response_text += delta.content
                    yield f"data: {chunk.model_dump_json()}\n\n"
            yield "data: [DONE]\n\n"
        except Exception as e:
            yield f"data: {{\"error\": \"{str(e)}\"}}\n\n"

    return Response(event_stream(), content_type='text/event-stream')

实现细节:

  1. 参数获取:前端传递 user_messagefile_content

  2. AI 请求:使用 OpenAI API 发起流式请求。

  3. SSE 事件流:遍历 AI 生成结果并逐步推送。

  4. 异常处理:防止请求失败导致 SSE 断开。

 

四、前端实现

前端使用 EventSource 监听 SSE 事件流,逐步渲染 AI 回复内容。

前端代码:接收 AI 数据并实时渲染

function startAIStream(userMessage, fileContent, base64File, fileName) {
    const chatBox = document.getElementById('chat-box');

    const botMessageDiv = document.createElement('div');
    botMessageDiv.classList.add('message', 'bot');
    botMessageDiv.innerHTML = `
        <img src="./image/icon1.gif" alt="Bot">
        <span id="bot-response">AI 正在思考...</span>
        <span id="bot-time" style="font-size: 10px; color: #888; margin-left: 10px;"></span>`;
    chatBox.appendChild(botMessageDiv);
    chatBox.scrollTop = chatBox.scrollHeight;

    const botResponseSpan = botMessageDiv.querySelector("#bot-response");
    const botTimeSpan = botMessageDiv.querySelector("#bot-time");

    const params = new URLSearchParams({
        user_message: userMessage,
        file_content: fileContent
    });

    const eventSource = new EventSource(`/ai/stream_chat?${params.toString()}`);
    let fullBotMessage = "";
    botResponseSpan.innerText = "";

    eventSource.onmessage = function (event) {
        if (event.data === "[DONE]") {
            eventSource.close();
            botTimeSpan.innerText = new Date().toLocaleTimeString();
            createHistoryRecord(userMessage, fullBotMessage, base64File, fileName);
            return;
        }

        try {
            const jsonData = JSON.parse(event.data);
            const deltaContent = jsonData.choices[0]?.delta?.content;
            if (deltaContent) {
                fullBotMessage += deltaContent;
                requestAnimationFrame(() => {
                    botResponseSpan.innerHTML = marked.parse(fullBotMessage);
                    chatBox.scrollTop = chatBox.scrollHeight;
                });
            }
        } catch (e) {
            console.error("解析数据出错:", e);
        }
    };

    eventSource.onerror = function (err) {
        console.error("SSE 连接错误:", err);
        eventSource.close();
        botResponseSpan.innerText += "\n[连接已断开]";
    };
}

 

五、效果展示

          当用户发送消息后,前端立即显示 AI 正在思考...,随后 AI 逐步返回内容,并以 Markdown 解析进行美化。如下图所示:

 

 

六、项目特点

  • 流式传输:AI 逐步生成内容,提升用户体验。

  • 轻量级实现:基于 SSE,避免复杂的 WebSocket 连接管理。

  • Markdown 支持:使用 marked.js 美化 AI 回复。

  • 自动重连:确保长时间使用时连接稳定。

如果你也在开发 AI 聊天功能,不妨尝试这种流式优化方案!

 

posted @ 2025-04-02 17:33  何双新  阅读(1065)  评论(0)    收藏  举报