从零搭建
大模型对话
应用工程手册
参考豆包 / 智谱清言的产品形态,完整讲解一个可落地开发的 AI 助理项目——前后端架构、技术选型、对话管理、流式输出到生产部署,带你 6 章走完一个真实 LLM 应用的全栈实现。
网上能搜到的多是 单文件 curl 调用——缺完整的工程结构。文档讲的是 单次请求,没人告诉你怎么管上下文、限流、降级。想做流式打字机效果、SSE、心跳、停止生成?教程零散。上线后 Token 成本、响应延迟、并发安全 三大坑,几乎都靠自己踩。
这本手册的目标是:交付一个 能上线的 LLM 应用——可运行的完整代码骨架、清晰的前后端架构与模块划分、对话/流式/上下文三大核心难题的解法、可直接用于生产环境的部署清单。
本教程的路线 · 6 个章节
教程按「产品 → 架构 → 后端 → 前端 → 进阶 → 部署」顺序展开,每一章给出可直接复用的代码片段与决策依据。
本教程是 线性阅读 的高密度手册。每一节末尾给出 「动手实践」 提示——配合 GitHub 上的示例仓库边读边写效果最好。
产品形态:一个完整的 AI 助理具备六大能力
豆包、智谱清言、Kimi、文心一言、ChatGPT 在产品形态上趋同——区别在于 每一项的深度。下表给出了对一个「可参考」产品的能力清单。
MVP 阶段先实现 多轮对话、会话管理、工具调用 三项,后两个章节再补 RAG / 多模态 / 记忆。
对话系统原理 · 三个核心概念
在开始写后端之前,先把这三个词刻进脑子里——你会发现后面 90% 的工程问题 都和它们有关。
Token — 模型不直接处理「字」
模型以 token 为单位。中文通常 1 字 ≈ 1.5~2 token,英文 1 词 ≈ 1.3 token。
# 例:"你好世界"
"你好世界" → ["你", "好", "世", "界"]
4 chars → 4 tokens
Context — 上下文 = 所有历史 + 系统提示 + 当前问题
上下文拼成一段字符串一起发给模型。模型 没有真正的记忆。Context window 常见值为 4K · 8K · 32K · 128K · 200K,超过会 截断 / 报错 / 失忆。
Stream — 一个 token 一个 token 推回前端
流式响应让用户看到 ChatGPT 那种「逐字打出」效果。技术实现上有三种主流协议:
- SSE · Server-Sent Events浏览器原生支持,无需 WebSocket,适合「服务端 → 客户端」单向流(推荐)。
- WebSocket双向通信。需要双向交互(协作编辑 / 实时通知)时再上 WS。
- HTTP Chunked最朴素的「分块传输」,兼容性最好但语义弱。
Token · Context · Stream 这三件事,决定你后面所有的设计。
MVP 先做 多轮对话 + 会话管理 + 工具调用 三件事;Token/Context/Stream 三个概念是后续所有工程决策的基础。
系统架构:四层模型,各司其职
从用户看到的 UI 到模型调用的 LLM API,中间被切成 四层。每一层 只依赖下层,可以独立替换、可独立伸缩、可独立测试。
单向依赖 ↓ · 可独立替换 · 可独立伸缩 · 可独立测试。
一次完整对话请求的数据流
从 「你输入一行字」 到 「屏幕上出现回答」 之间发生了什么。
端到端延迟目标: P50 < 1.5s · P95 < 5s。关键瓶颈在模型调用,次要是上下文组装。
推荐技术栈
从用户敲下第一个字,到模型吐回最后一个 token,中间涉及四层技术栈。下面是 「能跑起来」 到 「能上线」 所需的最小工具集。
选型原则:优先选 生态成熟、文档完善、可平滑替换 的组件,避免被任何一家模型厂商深度绑定。
关键技术选型 · 没有「最好」,只有「最合适」
下表是 6 个核心维度的常见选项与推荐。实际选型应当基于你的团队与场景。
| LAYER | OPTION A | OPTION B | OPTION C | 推荐 | 理由 |
|---|---|---|---|---|---|
| 前端框架 | Next.js (React) | Vue 3 + Nuxt | SvelteKit | A | 生态最大 · 流式方案最成熟 |
| 后端框架 | Node.js · NestJS | Python · FastAPI | Go · Gin | A/B | 团队熟什么选什么;Python 写 LLM 周边库更多 |
| 数据库 | PostgreSQL | MongoDB | MySQL | A | JSONB + pgvector 一库两用,够省心 |
| 向量库 | pgvector | Qdrant | Milvus | A/B | 百万级 pgvector 够用,千万级再上 Qdrant |
| 流式协议 | SSE | WebSocket | HTTP/2 Stream | A | 单向流用 SSE 最简单;需要双向再上 WS |
| LLM Provider | OpenAI | Anthropic | DeepSeek / Qwen | 多 | 抽象成统一接口,主备两三家,降级有保障 |
原则: 能标准化的标准化,不能标准化的就抽象。
四层架构 · 单向依赖 · 可替换可伸缩;技术栈选「生态成熟 · 可平滑替换」的组件,不绑定任何一家模型厂商。
后端实现:对话编排 + 模型适配 + 状态管理
后端是 「对话编排 + 模型适配 + 状态管理」 的核心——前端的体验、稳定性、成本控制,90% 在后端决定。
后端是发动机,前端只是驾驶舱。
12 个端点撑起整个对话产品
分两类:① 普通 REST(会话/用户/消息管理);② SSE 流式(对话生成)。后者的设计决定了打字机体验。REST 走 /api/v1,SSE 走 /api/v1/stream;鉴权统一用 Bearer Token。
# ===== 会话管理 (REST) =====
POST /api/v1/conversations # 新建会话
GET /api/v1/conversations # 会话列表(分页)
GET /api/v1/conversations/:id # 会话详情
PATCH /api/v1/conversations/:id # 重命名/置顶
DELETE /api/v1/conversations/:id # 删除
# ===== 消息管理 (REST) =====
GET /api/v1/conversations/:id/messages # 历史消息
DELETE /api/v1/messages/:id # 单条删除/撤回
POST /api/v1/messages/:id/feedback # 👍 / 👎
# ===== 用户 (REST) =====
GET /api/v1/me # 当前用户
POST /api/v1/me/usage # 我的 token 消耗
# ===== 对话生成 (SSE) ★核心 =====
POST /api/v1/stream/chat # 流式对话
POST /api/v1/stream/stop # 停止生成
POST /api/v1/stream/regenerate # 重新生成
/stream/* 这 3 个端点决定了用户体验。
4 张核心表 · PostgreSQL DDL
单库起步,够撑日活 10 万;再大再分库 / 分表。每张表都加 user_id + created_at 复合索引,避免全表扫。
-- 用户表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(64),
plan VARCHAR(16) DEFAULT 'free', -- free / pro / team
created_at TIMESTAMPTZ DEFAULT now()
);
-- 会话表
CREATE TABLE conversations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(128),
model VARCHAR(64) NOT NULL, -- gpt-4o / claude-3.5 ...
system_prompt TEXT,
is_pinned BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now(),
INDEX idx_user_created (user_id, created_at DESC)
);
-- 消息表
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID REFERENCES conversations(id) ON DELETE CASCADE,
role VARCHAR(16) NOT NULL, -- user / assistant / system / tool
content TEXT NOT NULL,
input_tokens INTEGER,
output_tokens INTEGER,
feedback VARCHAR(8), -- up / down / null
created_at TIMESTAMPTZ DEFAULT now(),
INDEX idx_conv_created (conversation_id, created_at)
);
-- 用量统计表
CREATE TABLE usage_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
model VARCHAR(64),
input_tokens INTEGER NOT NULL,
output_tokens INTEGER NOT NULL,
cost_cents INTEGER NOT NULL, -- 费用(分)
created_at TIMESTAMPTZ DEFAULT now()
);
SSE 流式响应 · 让模型「一个字」一个字吐回前端
SSE = Server-Sent Events。浏览器原生支持,无需 WebSocket 那一套,适合「服务端 → 客户端」单向流。长连接容易被 Nginx / 防火墙 掐断,每 15s 推一行注释占位,保持连接。
// stream.controller.ts (NestJS)
@Post('stream/chat')
@Sse() // NestJS SSE 装饰器
async chat(@Body() dto: ChatDto, @Res() res: Response) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('X-Accel-Buffering', 'no'); // 关 Nginx 缓冲
// 1) 加载历史消息 → 拼装 prompt
const messages = await this.chatService.buildMessages(dto);
// 2) 调用 LLM 流式
const stream = await openai.chat.completions.create({
model: 'gpt-4o', stream: true, messages,
});
// 3) 推 token 回前端
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content || '';
res.write(`data: ${JSON.stringify({delta})}\n\n`);
}
res.write('data: [DONE]\n\n');
res.end();
}
别忘了 异步落库 · 边推边写,关流后更新 token 统计。
上下文管理 · 4 种策略 + 推荐实践
每个模型都有 上下文窗口(GPT-4o:128K, Claude:200K)。超出就会 截断 / 报错 / 失忆。我们要在送进模型前就管好它。
// 推荐实践:02 滑动预算 + 03 兜底摘要
async function truncate(messages, maxTokens = 8000) {
let total = 0;
const result = [];
for (let i = messages.length - 1; i >= 0; i--) {
const t = await countTokens(messages[i]);
if (total + t > maxTokens) break;
result.unshift(messages[i]);
total += t;
}
return result;
}
System Prompt 永远优先 · 用户消息其次 · 历史按预算裁。
错误处理 · 重试 + 降级
生产环境一定会遇到的:超时 · 限流 · 内容审核 · 模型宕机。设计得当,用户感知不到;设计不当,直接 「网络错误」一片白屏。
01 · 重试策略:指数退避 + 抖动
- 最多重试 3 次
- 间隔 1s → 2s → 4s(+随机抖动)
- 仅对 5xx / 超时 / 429 重试
- 4xx(参数错、余额不足)不重试,直接报
用户的每一次「重新生成」都应该走 完整的重试流程,而不是又发一次同样的请求。
02 · 降级策略:主备多模型
- 主 GPT-4o → 备 Claude → 兜底 本地开源
- 不同任务用 不同等级 模型:闲聊用小模型,复杂用大模型
- 内容审核 失败:礼貌提示,不裸露「违规」字样
- 流中途断开:前端 「生成中断」 提示 + 提供 「续写」 按钮
// 多模型降级
const chain = [gpt4o, claude, deepseek];
for (const m of chain) {
try { return await m.chat(messages); }
catch (e) { logger.warn(`fallback→${m.name}`); }
}
把失败当一等公民设计 · 它不是边缘情况。
后端 = REST + SSE 12 个端点 + 4 张表;流式响应做心跳、限流、重试、降级;System Prompt 永远优先,历史按 token 预算裁。
前端实现:聊天界面 · 流式渲染 · 关键交互
如果说后端是「发动机」,前端就是「驾驶舱」。用户感知到的所有 速度感、智能感、信任感——都从你写的 React 组件开始。
组件骨架 · 别把整个对话塞进一个组件
按职责切分,每个组件只关心自己。否则会陷入 重渲染地狱。状态库推荐 Zustand——比 Redux 轻 90%,比 Context 性能好 10x。
// app/chat/[id]/page.tsx
<ChatPage>
├─ <Sidebar /> // 会话列表 · 新建按钮
│ └─ <ConversationItem /> // 单条会话 · 标题/置顶
│
├─ <ChatHeader /> // 当前模型 · 分享 · 设置
│
├─ <MessageList /> // 消息流 · 虚拟滚动
│ ├─ <UserBubble /> // 用户消息气泡
│ ├─ <AssistantBubble /> // AI 消息 · 流式渲染
│ │ └─ <MarkdownView /> // Markdown + 代码高亮
│ └─ <TypingIndicator /> // "正在输入..." 动画
│
├─ <Composer /> // 输入框 · 发送 · 停止按钮
│ └─ <ToolBar /> // 附件 · 模型切换 · 联网
│
└─ <useChatStore /> // Zustand · 唯一数据源
MessageList 用 react-virtuoso · 1000 条消息不卡。
SSE 客户端 · fetch + ReadableStream
浏览器用 EventSource 原生支持 SSE。但我们需要 POST + 取消——自己用 fetch + ReadableStream 实现。
// useChatStream.ts
export function useChatStream() {
const ctrlRef = useRef<AbortController>();
const send = async (messages: Message[]) => {
ctrlRef.current = new AbortController();
const res = await fetch('/api/v1/stream/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages }),
signal: ctrlRef.current.signal, // ★ 关键:支持取消
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// SSE 格式: "data: {...}\n\n"
for (const line of chunk.split('\n')) {
if (!line.startsWith('data: ')) continue;
const { delta } = JSON.parse(line.slice(6));
store.appendDelta(delta); // ★ 关键:只追加,不重渲染整个列表
}
}
};
const stop = () => ctrlRef.current?.abort();
return { send, stop };
}
appendDelta 走 ref,不触发 setState · 60fps 流畅打字。
5 个关键交互 · 从「能用」到「好用」
细节决定专业度。
点踩数据 比五星好评更值钱 · 它是改进的金矿。
前端按职责拆组件、用 Zustand 管状态;流式渲染走 fetch + ReadableStream;5 个关键交互决定专业度——尤其要把「点踩」做扎实。
进阶能力:RAG · 工具调用 · 多模态 · 长期记忆
基础对话能跑起来之后,真正的差异化在 「能不能回答私域问题」、「能不能调用工具」、「能不能记住用户」。
RAG 检索增强 · 让模型回答它不知道的问题
核心思路:用户问问题时,先从 「私域知识库」 里检索相关段落,再 「喂给」 模型一起回答。
// RAG 核心检索代码 · Node.js
async function answerWithRAG(userQuery: string) {
// 1) 检索 Top 5 相似段落
const queryVec = await embed(userQuery);
const chunks = await db.query(`
SELECT content, 1 - (embedding <=> $1) AS score
FROM document_chunks
WHERE user_id = $2
ORDER BY embedding <=> $1
LIMIT 5
`, [queryVec, userId]);
// 2) 拼装 prompt
const context = chunks.map(c => c.content).join('\n---\n');
const messages = [
{ role: 'system', content: `参考以下资料回答:\n${context}\n如果资料中没有,直接说不知道。` },
{ role: 'user', content: userQuery },
];
return await llm.stream(messages);
}
RAG 不是万能的 · 知识库 10 万条以上考虑 Hybrid 检索(关键词 + 向量)。
Function Calling · 让模型动手
给模型一份 「工具说明书」——它会自己判断该用哪个、传什么参数,后端负责执行并把结果回填。这就是 Agent 的雏形。适用场景:查天气 · 查订单 · 查数据库 · 发邮件 · 调内部 API · 跑代码……
// 1) 工具定义(模型可读)
const tools = [{
type: 'function',
function: {
name: 'get_weather',
description: '查询指定城市的天气',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名,例如 上海' }
},
required: ['city']
}
}
}];
// 2) 调用循环:模型可能连续调用多个工具
async function agentLoop(userMsg: string) {
const messages = [{ role: 'user', content: userMsg }];
for (let i = 0; i < 5; i++) { // 最多 5 轮
const res = await llm.chat({ messages, tools });
const call = res.choices[0].message.tool_calls?.[0];
if (!call) return res.choices[0].message.content;
// ★ 关键:由后端「真的」执行工具
const result = await executeTool(call.function.name,
JSON.parse(call.function.arguments));
messages.push({ role: 'assistant', tool_calls: [call] });
messages.push({ role: 'tool', tool_call_id: call.id, content: JSON(result) });
}
}
工具描述要清晰无歧义 · 写得烂的 schema = 调不准的工具。工具执行必须有超时 / 鉴权 / 沙箱 · 模型可能被 prompt 注入诱导。
多模态 · 看得见世界
2024 年后,主流模型都支持 图像理解;配合 语音 / 文件 输入,体验会质变。
- 图像 · 上传图片直接对话(GPT-4o vision)
- 语音 · ASR(输入) + TTS(输出)双向
- 文件 · PDF · Excel · 代码 · 解析后送模型
- 视频 · 抽帧 + Whisper 转字幕
// 多模态消息
messages = [{
role: 'user',
content: [
{ type: 'text', text: '这张图是什么?' },
{ type: 'image_url', image_url: { url: 'data:image/jpeg;base64,...' } }
]
}];
长期记忆 · 记得住用户
长期记忆则让 AI 「认识」 用户。
- 用户档案 · 职业 · 偏好 · 称呼
- 关键事实 · 团队在做 X 项目 · 用户讨厌 Y
- 行为画像 · 经常问技术问题 · 偏好详细回答
- 每次对话时 检索 + 注入 相关记忆
// 记忆注入 prompt
const memories = await mem.recall(userId, query);
const systemPrompt = `
你是用户的 AI 助理,关于用户已知:
${memories.map(m => '- ' + m.text).join('\n')}
请结合以上信息回答。`;
隐私红线 · 记忆要可查看、可删除、可关闭。
RAG 5 阶段流水线让模型回答私域问题;Function Calling 走 Agent 循环;多模态接视觉/语音/文件;长期记忆按事实档案 + 画像双轨注入 prompt。
部署与可观测性:从 git push 到生产环境
本地能跑只是 「Hello World」。生产环境要面对 并发、容灾、成本、监控、合规。本章给出 「能上线的最小配置」。
容器化流水线 · Dockerfile + Compose
最小可用流水线:Dockerfile + docker-compose + GitHub Actions + 一台云主机 / 集群。镜像原则: 多阶段构建 · 减小镜像 · 缓存依赖层 · 减小到 200MB 以内。
# Dockerfile · 多阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . . && npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]
# docker-compose.yml
services:
api:
build: .
ports: ["3000:3000"]
environment:
DATABASE_URL: postgres://...
OPENAI_API_KEY: ${OPENAI_API_KEY}
depends_on: [postgres, redis]
postgres:
image: pgvector/pgvector:pg16
volumes: [pgdata:/var/lib/postgresql/data]
redis:
image: redis:7-alpine
环境变量走 secret · API Key 永远不要进代码。
可观测性 · 三件套
LLM 应用的可观测性比传统 Web 复杂:除了 延迟 / 错误,还要追踪 Token 消耗 / 成本。否则月底账单会让你怀疑人生。
告警规则 & 成本控制
- 错误率 > 1% → 飞书 / 钉钉
- P95 延迟 > 8s → 告警
- 单用户日 token > 100w → 限流
- 按用户 / 按天 限额
- 短任务用 小模型 · 复杂任务大模型
- 启用 Prompt 缓存(Anthropic 节省 90%)
Token 成本是 LLM 应用最大的 P&L 项 · 必须每天看。
Dockerfile 多阶段 + compose + GitHub Actions 是最小流水线;Logs/Metrics/Traces 三件套 + 告警与成本规则是生产底线。
上线前 15 条「过来人的教训」清单
建议打印贴在工位。「能在本地跑」≠「能上线」 · 区分 demo 和 product。
- 所有外部调用 带超时(建议 30s)
- 用户输入 永远做长度限制(< 32K)
- 系统 prompt 版本化管理
- 关键 API 幂等设计
- 绝不 把 API key 写代码
- SSE 中断时 明确提示,不要假装没事
- 长消息用 虚拟滚动
- 暗黑模式 是必选项
- 代码块 必须有 复制按钮
- 移动端 键盘弹出 要适配
- 数据库 每天全量备份
- 所有密钥走 Secret Manager
- 上线前 压测(目标 10x 当前流量)
- 每条 LLM 调用 记成本
- 回滚方案 必须演练过
教程的尽头,是你自己的 commit。把 28 帧当作路线图,而不是答案——真正的理解,来自 写代码 · 跑挂 · 修 bug 的循环。
15 条上线前清单覆盖后端 / 前端 / 运维三端:超时、限流、压测、回滚、成本——这是「能上线」与「能跑」的真正差距。
资源与结语:开工吧,从第一行代码开始
推荐资源
- DOCS · OpenAI Cookbook · Anthropic Docs
- FRAMEWORK · LangChain · LlamaIndex · Vercel AI SDK
- VECTOR DB · pgvector · Qdrant · Milvus
- REFERENCE · OpenAI Platform · HuggingFace
- COMMUNITY · GitHub Awesome-LLM · Discord AI Dev
全书回顾 · 6 章节
从 CH 00 产品形态,到 CH 01 系统架构、CH 02 后端、CH 03 前端、CH 04 进阶能力、CH 05 部署运维——这 6 章是搭建一个 LLM 应用的最短路径。
教程的尽头,是你自己的 commit。
· 大模型对话应用工程手册 · 从零搭建你的 LLM 助理
· 技术教程 · 2026 · Vol.01 · 6 章 · 28 帧 · 约 8 小时
· 工程主题:对话管理 / 流式响应 / 上下文 / RAG / Tools / 多模态 / 记忆 / 可观测性 / 成本控制