文章链接:https://blog.csdn.net/qq_43201350/article/details/163399030
本文来自一个前端工程师在智慧社区项目中落地 AI 对话系统的真实经验。不聊模型训练,只聊工程化——怎么把大模型接入业务系统,让它真正能用。
一、为什么要做"AI 中间层"?
去年我在一个智慧社区项目里接到需求:给物业管理系统接入 AI 对话助手,让用户能用自然语言查工单、查缴费、报修。
一开始我想得简单——前端直接调大模型API 不就行了?结果踩了三个坑:
-
供应商切换 :领导说先用便宜的,后面效果好再换贵的。直接调 API 意味着每次换模型都要改前端代码
-
流式响应 :大模型生成一个字就回一个字,前端不做处理的话用户要等 10 秒才能看到完整回复
-
业务融合 :AI 不能只聊天——用户说"查 3 栋 501 的物业费",AI 得真的去数据库查
这三个问题指向同一个答案: 在前后端之间加一层 AI 中间层 。架构长这样:
二、多模型路由:三级降级设计
生产环境不能只有一个模型——挂了就全挂了。我设计了一个三级路由:
function routeModel(userId, requestPrefix) {
// 第1级:请求里带了模型名前缀,直接路由
// 比如用户输入 "claude:帮我查工单"
if (requestPrefix === 'claude:') return { model: 'claude-3-sonnet', provider: 'anthropic' }
if (requestPrefix === 'deepseek:') return { model: 'deepseek-chat', provider: 'deepseek' }
if (requestPrefix === 'qwen:') return { model: 'qwen-turbo', provider: 'aliyun' }
// 第2级:读数据库里的用户偏好配置
const userConfig = db.getUserAIConfig(userId)
if (userConfig?.preferredModel) return userConfig
// 第3级:环境变量默认值(最便宜的通义千问兜底)
return { model: process.env.DEFAULT_AI_MODEL || 'qwen-turbo', provider: 'aliyun' }
}三个层级的逻辑 :
-
第1级让运营人员可以在输入框里临时指定模型(适用于效果对比测试)
-
第2级让系统管理员在后台配置默认模型(适用于正式上线后的全局策略)
-
第3级兜底保证服务永远不挂(环境变量不改就不会出问题)
配合 try-catch + 自动降级:
async function chatWithFallback(options) {
try {
return await callModel(options.model)
} catch (err) {
console.warn(`Model ${options.model} failed:`, err.message)
// 自动降级到通义千问
return await callModel('qwen-turbo')
}
}三、SSE流式对话:让 AI “边想边说”
大模型生成文本是一字一字出来的。如果不做流式处理,前端要等完整回复生成完才能显示——10 秒白屏谁也受不了。
后端推送 (Express):
app.post('/api/chat', async (req, res) => {
// 设置 SSE 响应头
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
res.setHeader('X-Accel-Buffering', 'no') // 禁用 Nginx 缓冲
const { message, role } = req.body
// 调用大模型 API 获取流式响应
const stream = await aiClient.chat({
messages: buildHistory(message, role),
stream: true
})
// 逐块推送
for await (const chunk of stream) {
if (chunk.type === 'content') {
res.write(`data: ${JSON.stringify({ type: 'text', content: chunk.text })}\n\n`)
} else if (chunk.type === 'tool_call') {
// AI 要调用工具了,推送一个特殊事件
res.write(`data: ${JSON.stringify({ type: 'tool_call', name: chunk.name })}\n\n`)
// 执行工具调用...
}
}
res.write('data: [DONE]\n\n')
res.end()
})前端消费 (Vue3 CompositionAPI):
async function sendMessage(msg: string) {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: msg, role: currentRole.value })
})
const reader = response.body!.getReader()
const decoder = new TextDecoder()
let fullText = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value, { stream: true })
const lines = chunk.split('\n')
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6))
if (data.type === 'text') {
fullText += data.content
messages.value[messages.value.length - 1].content = fullText
}
if (data.type === 'card') {
// 业务数据卡片——直接渲染到聊天界面
messages.value.push({ type: 'card', data: data.payload })
}
}
}
}
}为什么用 SSE 不用 WebSocket?
-
对话场景是单向的(服务端推,客户端只需要发一次请求)
-
SSE 比 WebSocket 更轻量:不需要心跳保活、不需要升级协议
-
浏览器原生支持
EventSource(虽然我们用了更灵活的 fetch 方案) -
如果需要双向通信(比如用户中途打断 AI),可以补一个 WebSocket 做打断指令
四、Docker 部署:一行命令上线
中间层做完,部署也不能拖后腿:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]配合Nginx反向代理 + PM2 进程管理,整个 AI 中间层跑在 1 核 2G 的服务器上就能支撑日均千级的对话量。
location /api/chat {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off; # 关键:关闭缓冲,否则 SSE 变成一次性返回
proxy_read_timeout 300s; # 长连接超时保护
}五、总结
前端工程师做 AI 应用开发,不是去训练模型,而是做四件事:
-
路由 ——让请求走到合适的模型
-
流控 ——让 AI 的回答丝滑流畅地呈现
-
工具 ——让 AI 能真正操作系统
-
部署 ——让这一切在生产环境稳定运行
这四件事,恰好都是前端工程化的基本功。