全栈 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.setHeaderContent-Type: text/plain;charset=utf-8- 作用:声明货物类型。
- 解释:告诉浏览器“我发给你的是纯文本,不是 HTML 网页,也不是图片”。如果不写
charset=utf-8,中文可能会变成乱码。
Transfer-Encoding: chunked- 作用:声明发货方式(分批次)。
- 解释:这是流式传输的开关。它告诉浏览器:“这个包裹太大(或者我还没生产完),我无法在发货前告诉你总重量(
Content-Length)。我会切成一块一块地发给你,直到我说发完了为止。” - 后果:如果不写这个,浏览器可能会一直等到服务器把所有数据都准备好(
res.end)才开始显示内容,那就没有打字机效果了。
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. 封装了复杂的流式网络请求(省去了手写fetch和reader)
痛点:
手动处理流式响应非常麻烦。你需要写fetch,获取reader,写while循环读取流,用TextDecoder解码,还要处理 JSON 解析和错误。便利:
useChat内部已经写好了全套的流处理逻辑。- 你只需要配置一个
api: '/api/ai/chat'。 - 它会自动发起请求,自动监听数据流,自动解析后端发来的
0:"xxx"格式。
- 你只需要配置一个
3. 开箱即用的 UI 交互逻辑(省去了写受控组件)
痛点:
你需要自己处理输入框的onChange,自己处理表单的onSubmit,还要防止用户在生成过程中重复点击发送。便利:
- 提供
input和handleInputChange:直接绑定到输入框,实现双向绑定。 - 提供
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>的value和onChange直接绑定了 SDK 提供的input和handleInputChange。这意味着输入框的状态管理权也完全移交给了 SDK,减少了我们自己写useState的冗余代码。
总结:数据流的全景图
构建这个 AI Chatbot,本质上是搭建了一条从用户到 AI 再回到用户的高速数据管道:
- 触发:用户点击发送,
useChat携带历史消息发起请求。 - 中转:后端
req.on接收数据,向 DeepSeek 发起stream: true的请求。 - 生成:DeepSeek 逐个生成 Token。
- 清洗:后端
reader截获 Token,去除 SSE 外壳。 - 推送:后端
res.write将 Token 实时推回前端。 - 渲染:前端 SDK 接收 Token,更新状态,React 刷新界面。
通过这种模块化、分层的设计,我们不仅实现了酷炫的打字机效果,更保证了系统的可维护性和扩展性。
学习资源推荐
如果你想更深入地学习大模型,以下是一些非常有价值的学习资源,这些资源将帮助你从不同角度学习大模型,提升你的实践能力。
一、全套AGI大模型学习路线
AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!
因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取
二、640套AI大模型报告合集
这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示
因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取
三、AI大模型经典PDF籍
随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。
因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取
四、AI大模型商业化落地方案
作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。