news 2026/6/22 17:21:44

全栈实现流式 AI 聊天机器人:从后端代理到前端实时渲染

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
全栈实现流式 AI 聊天机器人:从后端代理到前端实时渲染

全栈 AI Chatbot 实战:从后端流式转发到前端即时响应

在当今 AI 驱动的应用开发中,构建一个类似 ChatGPT 的对话界面已成为“Hello World”级别的必修课。然而,要实现一个丝滑的、具备打字机效果的聊天机器人,并非简单的 API 调用,而是涉及 前后端流式数据传输(Streaming) 、 服务端代理(Proxying) 以及 前端状态管理 的综合工程。

本文将通过模块化的方式,拆解一个全栈 AI Chatbot 的核心实现流程,带你深入理解数据是如何从 DeepSeek 大模型流向用户屏幕的。


后端部分(Mock 模拟)

后端的职责非常明确:它是一个“流式代理中间件”
它需要同时维护两条连接:

  • 左手与前端保持长连接,
  • 右手与大模型保持流式连接,
    并在中间进行数据的清洗和格式转换。

定义路由以及处理请求

当用户向 Chatbot 发送请求时,后端需要接收前端的请求体(包含用户消息),并将其转发给大模型。

import { config } from 'dotenv' config(); export default [ { url: '/api/ai/chat', method: 'post', rawResponse: async (req, res) => { let body = ''; req.on('data', (chunk) => { body += chunk }) req.on('end', async () => { try { const { messages } = JSON.parse(body); // ... 后续逻辑 } catch (err) { res.end(); } }) } } ]
核心代码与逻辑:
  • rawResponse
    使用rawResponse就像是从“自动挡”切换到了“手动挡”。
    虽然你需要自己处理req.on('data')res.end()等底层琐事,但它给了你控制时间的能力——让你能够决定什么时候发送数据,以及分多少次发送数据。这正是实现打字机效果的唯一途径。

  • req.on()
    这就是一个监听事件:

    • 'data'事件:当请求体有数据到达时触发。这里每当有数据传输就进行拼接。
    • 'end'事件:当请求体数据接收完毕时触发。表示用户的问题已经接收完全,可以开始调用大模型了。
  • res.end()
    因为我们使用rawResponse获得了绝对的控制权,所以当大模型返回完所有数据后,需要手动调用res.end()来结束响应。

通过字符串拼接,将二进制数据转化为字符串,因此可以通过JSON.parse(body)将字符串解析为 JSON 对象。


流式调用大模型 API

这里负责调用大模型的 API,获取流式响应。

// ... 在 req.on('end') 内部 res.setHeader('Content-Type', 'text/plain;charset=utf-8') res.setHeader('Transfer-Encoding', 'chunked') res.setHeader('x-vercel-ai-data-stream', 'v1') const response = await fetch('https://api.deepseek.com/v1/chat/completions', { method: 'post', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.VITE_DEEPSEEK_API_KEY}` }, body: JSON.stringify({ model: 'deepseek-chat', messages: messages, stream: true }) });
核心代码与逻辑:
  • 三个res.setHeader

    1. Content-Type: text/plain;charset=utf-8

      • 作用:声明货物类型。
      • 解释:告诉浏览器“我发给你的是纯文本,不是 HTML 网页,也不是图片”。如果不写charset=utf-8,中文可能会变成乱码。
    2. Transfer-Encoding: chunked

      • 作用:声明发货方式(分批次)。
      • 解释:这是流式传输的开关。它告诉浏览器:“这个包裹太大(或者我还没生产完),我无法在发货前告诉你总重量(Content-Length)。我会切成一块一块地发给你,直到我说发完了为止。”
      • 后果:如果不写这个,浏览器可能会一直等到服务器把所有数据都准备好(res.end)才开始显示内容,那就没有打字机效果了。
    3. x-vercel-ai-data-stream: v1

      • 作用:对暗号。
      • 解释:这是前端使用的 Vercel AI SDK(useChat)特有的自定义协议头。前端 SDK 收到响应时,会检查有没有这个头。如果有,它就知道:“哦!这是自家兄弟发的流式数据,格式我懂(0:"xxx"),我可以自动解析它。”
      • 后果:如果不写,前端 SDK 可能会认为这是一个普通的文本响应,导致无法正确解析流内容或者报错。
  • const response = await fetch(...)
    调用大模型 API。


SSE 数据清洗与实时转发

将大模型回复生成的数据进行流式的清洗与转发。

// ... 接上文 const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (let line of lines) { if (line.startsWith('data: ') && line !== 'data: [DONE]') { try { const data = JSON.parse(line.slice(6)); const content = data.choices[0]?.delta?.content || ''; if (content) { res.write(`0:${JSON.stringify(content)}\n`) } } catch (err) {} } } }
核心代码与逻辑:
  • const reader = response.body.getReader();
    用于流式读取数据的对象。

  • const decoder = new TextDecoder();
    因为大模型返回的是二进制数据,所以需要使用TextDecoder来将其解码为文本。

  • const { done, value } = await reader.read();
    每次读取数据块,done表示是否读取完毕,value表示读取到的二进制数据。

  • const chunk = decoder.decode(value);
    const lines = chunk.split('\n');
    将每次读取的数据块进行解码与分块。

  • for(){...}—— 数据的清洗
    通过解码我们已经能够拿到数据,但是要想像聊天一样获得纯文本,就必须经过数据清洗:

    • if (line.startsWith('data: ') && line !== 'data: [DONE]')
      大模型返回的每个数据块以data:开头代表的就是我们需要的数据;当输出[data: [DONE]]时,代表大模型已经返回完所有数据。通过条件判断当前数据块是我们的有效数据。
    • const data = JSON.parse(line.slice(6));
      切割掉每条数据前的data:,将剩余的字符串解析为 JSON 对象。
    • const content = data.choices[0]?.delta?.content || '';
      拿到每次新增的内容字段(delta.content)。
    • res.write(0:${JSON.stringify(content)}\n)
      将新增的字段内容转发给前端。

前端部分

这里负责接收后端转发的流式数据,并将其显示在前端的聊天框中。


useChat

useChat是 Vercel AI SDK 提供的一个 React 钩子函数,用于在 React 组件中调用大模型 API 并处理流式响应。

import { useChat } from '@ai-sdk/react' export const useChatbot = () => { return useChat({ api: '/api/ai/chat', onError: (err) => { console.error("Chat Error:", err); } }) }

它带来的便利主要体现在以下三个方面:


1. 自动化的状态管理(省去了手动维护数组)
  • 痛点
    如果不使用useChat,你需要自己创建一个messages数组状态。每当用户发消息,你要手动push进去;每当 AI 回复,你又要手动更新最后一条消息。

  • 便利
    useChat自动维护messages

    • 用户发消息 → 自动追加到列表。
    • AI 流式回复 → 自动拼接到最后一条消息里,无需你写任何拼接逻辑。

2. 封装了复杂的流式网络请求(省去了手写fetchreader
  • 痛点
    手动处理流式响应非常麻烦。你需要写fetch,获取reader,写while循环读取流,用TextDecoder解码,还要处理 JSON 解析和错误。

  • 便利
    useChat内部已经写好了全套的流处理逻辑。

    • 你只需要配置一个api: '/api/ai/chat'
    • 它会自动发起请求,自动监听数据流,自动解析后端发来的0:"xxx"格式。

3. 开箱即用的 UI 交互逻辑(省去了写受控组件)
  • 痛点
    你需要自己处理输入框的onChange,自己处理表单的onSubmit,还要防止用户在生成过程中重复点击发送。

  • 便利

    • 提供inputhandleInputChange:直接绑定到输入框,实现双向绑定。
    • 提供handleSubmit:直接绑定到表单,自动处理提交。
    • 提供isLoading:自动告诉你 AI 是否正在生成,方便你禁用按钮或显示 Loading 动画。

UI 交互与渲染

通过 Vercel AI SDK 提供的useChat钩子函数,我们可以很方便地实现聊天界面的交互与渲染。我们只需要关心页面设计,其他的都交给useChat打理。

import { useChatbot } from '@/hooks/useChatBot'; // ... UI 组件导入 export default function Chat() { const { messages, input, handleInputChange, handleSubmit, isLoading } = useChatbot(); const onSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!input.trim()) return; handleSubmit(e); } return ( <div className="..."> {/* 消息列表区域 */} <ScrollArea className="..."> {messages.map((m, idx) => ( <div key={idx} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}> <div className={`... ${m.role === 'user' ? 'bg-primary' : 'bg-muted'}`}> {m.content} </div> </div> ))} </ScrollArea> {/* 输入区域 */} <form onSubmit={onSubmit} className="flex gap-2"> <Input value={input} onChange={handleInputChange} disabled={isLoading} /> <Button type="submit" disabled={isLoading}>Send</Button> </form> </div> ) }
核心代码和逻辑:
  • Optimistic UI(乐观更新)
    当你调用handleSubmit的瞬间,你的消息就会立即出现在messages列表中,无需等待服务器响应。这提供了极佳的流畅感。
  • 数据驱动视图
    注意看{m.content}这一行。我们没有写任何定时器或手动 DOM 操作来实现打字机效果。一切都是响应式的:
    后端推来一个字 → SDK 更新messages状态 → React 检测到状态变化 → 重新渲染组件 → 界面上多出一个字。
  • 受控组件
    <Input>valueonChange直接绑定了 SDK 提供的inputhandleInputChange。这意味着输入框的状态管理权也完全移交给了 SDK,减少了我们自己写useState的冗余代码。

总结:数据流的全景图

构建这个 AI Chatbot,本质上是搭建了一条从用户到 AI 再回到用户的高速数据管道

  1. 触发:用户点击发送,useChat携带历史消息发起请求。
  2. 中转:后端req.on接收数据,向 DeepSeek 发起stream: true的请求。
  3. 生成:DeepSeek 逐个生成 Token。
  4. 清洗:后端reader截获 Token,去除 SSE 外壳。
  5. 推送:后端res.write将 Token 实时推回前端。
  6. 渲染:前端 SDK 接收 Token,更新状态,React 刷新界面。

通过这种模块化、分层的设计,我们不仅实现了酷炫的打字机效果,更保证了系统的可维护性和扩展性。

学习资源推荐

如果你想更深入地学习大模型,以下是一些非常有价值的学习资源,这些资源将帮助你从不同角度学习大模型,提升你的实践能力。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!​

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示

​因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

四、AI大模型商业化落地方案

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/22 9:30:03

MGW-10000微机控制静载锚固试验机

MGW-10000微机控制静载锚固试验机 一、产品主要功能  本机主要用于混凝土结构和构件用预应力钢绞线锚具、夹具和连接器&#xff0c;预应力钢结构钢绞线锚具、夹具和连接器&#xff0c;拉索锚具、夹具和连接器&#xff0c;预应力筋锚具、夹具和连接器的静载锚固性能测试…

作者头像 李华
网站建设 2026/6/22 9:31:46

Python 异步编程实战:深入理解背压机制与 asyncio.Queue 的流量控制艺术

Python 异步编程实战:深入理解背压机制与 asyncio.Queue 的流量控制艺术 引言:当数据洪流遇见处理瓶颈 在我十多年的 Python 开发生涯中,曾亲眼见证过一个生产环境中的"惨案":一个实时数据采集系统在运行三小时后突然崩溃,原因是内存溢出。事后分析发现,数据…

作者头像 李华
网站建设 2026/6/22 9:29:57

告别低效自动化:这套五步框架,让你的Workflow进化为高阶Skill

Workflow 的天花板极低。你无法在节点里表达复杂的递归逻辑&#xff0c;难以复用模块&#xff0c;更无法进行版本管理&#xff08;GitOps&#xff09;。当你想要把一个写好的工作流分享给别人时&#xff0c;导出导入的过程充满了环境依赖的深坑。 这是典型的「低代码陷阱」在 …

作者头像 李华
网站建设 2026/6/21 6:31:11

荣耀“宣布清仓”,16GB+512GB+2亿像素,从3999元跌至2947元

手机分屏 摄像头像素越高&#xff0c;不代表拍照效果就一定越好&#xff0c;这一点很多人都明白&#xff0c;但是关注手机行业人应该能明显感知到&#xff0c;这一年时间里&#xff0c;配备2亿像素摄像头的手机数量越来越多了&#xff0c;无论是小米、OPPO、vivo还是荣耀&#…

作者头像 李华
网站建设 2026/6/21 0:25:54

多功能场馆预约系统源码,支持分时计价、节假日设置与多端预订

温馨提示&#xff1a;文末有资源获取方式 在全民健身和体育产业政策利好的大背景下&#xff0c;各类场馆迎来了发展黄金期&#xff0c;但竞争也日趋激烈。实现精细化、数字化运营&#xff0c;是场馆脱颖而出的关键。源码获取方式在源码闪购网。 详细功能列表阐述&#xff1a; …

作者头像 李华
网站建设 2026/6/21 6:32:29

学术降重新纪元:书匠策AI用“语义显微镜”破解论文查重的终极困局——当查重从“文字扫雷”升级为“逻辑重塑”,你的论文终于能自由呼吸

在学术写作的江湖里&#xff0c;查重系统曾是悬在研究者头顶的“达摩克利斯之剑”。传统工具以“文字匹配”为核心理念&#xff0c;将论文与数据库中的文本逐字比对&#xff0c;标记重复片段。这种模式虽能快速定位表面重复&#xff0c;却也催生了无数“降重陷阱”&#xff1a;…

作者头像 李华