4.3RAG 系统设计
本课只解决一个主问题:本单元只解决一个主问题:如果要做一个真正可用的 RAG 问答系统,应该如何设计?
学习目标
学完本单元后,学习者应该能够:
- 画出一个基础 RAG 系统架构。
- 理解文档清洗、切分、索引、检索、重排、生成和引用的关系。
- 解释查询改写和多轮对话检索为什么重要。
- 识别 RAG 系统设计中的关键取舍。
- 为一个小型知识库问答产品设计第一版方案。
先给直觉
很多人以为 RAG 系统就是:
文档丢进向量数据库,用户提问,模型回答。
这只是最粗糙的想象。
一个更真实的 RAG 系统像一个资料助理团队:
- 有人负责整理资料。
- 有人负责给资料建索引。
- 有人负责理解问题。
- 有人负责找相关资料。
- 有人负责筛掉无关内容。
- 有人负责根据资料写回答。
- 有人负责标注引用和检查质量。
RAG 的质量不是由某一个组件决定,而是由整条链路决定。
基础架构
一个基础 RAG 系统可以分成两条链路:
离线链路:资料进入系统
在线链路:用户提问并得到回答
离线链路
原始文档
↓
文档解析
↓
清洗与结构化
↓
Chunk 切分
↓
生成 Embedding
↓
存入索引
在线链路
用户问题
↓
查询理解 / 改写
↓
检索候选 Chunk
↓
重排
↓
组装上下文
↓
LLM 生成回答
↓
引用与质量检查
↓
返回用户
文档解析
原始资料可能来自:
- Markdown
- Word
- 网页
- 代码仓库
- 表格
- 产品文档
- 企业制度
- 聊天记录
文档解析要把这些资料变成系统能处理的文本和结构。
难点:
- PDF 顺序错乱。
- 表格丢失结构。
- 网页有导航和广告噪音。
- 扫描件需要 OCR。
- 代码和注释需要保留层级。
如果解析阶段就错了,后面检索再努力也只是认真地找错资料。
文档清洗
清洗不是把内容删干净,而是去掉干扰信息,保留有价值结构。
常见清洗动作:
- 删除页眉页脚。
- 删除重复导航。
- 去除广告和无关按钮。
- 保留标题层级。
- 保留列表结构。
- 标准化空格和换行。
- 标记表格、代码块和引用。
清洗目标是让文档更适合检索和生成。
Chunk 切分策略
切分不是按固定字数一刀切。
更好的切分应考虑:
- 标题层级。
- 段落边界。
- 条款完整性。
- 代码块完整性。
- 表格结构。
- 概念是否完整。
常见策略:
固定长度切分
简单,但容易切断语义。
适合快速原型。
按结构切分
按标题、段落、条款、列表切分。
更适合课程、文档、制度、说明书。
滑动窗口切分
相邻 Chunk 保留一定重叠,避免重要信息被切断。
代价是索引体积和重复内容增加。
元数据设计
元数据决定系统能不能做过滤、权限、引用和调试。
建议至少包含:
- 文档 ID
- 文档标题
- 来源路径
- 章节标题
- 创建或更新时间
- 内容类型
- 权限等级
- 语言
- 课程模块或业务领域
- Chunk 序号
元数据不是可有可无的装饰。没有元数据,系统出了问题很难定位。
检索策略
检索可以分几类。
向量检索
适合语义相似。
例如:
“模型为什么会胡说”
可以找到:
“幻觉与可靠性”
关键词检索
适合精确匹配。
例如:
- 错误码
- 文件名
- 法条编号
- 产品型号
- 人名
混合检索
把向量检索和关键词检索结合起来。
很多实际系统会使用混合检索,因为用户问题既可能包含语义表达,也可能包含精确实体。
重排
初次检索会返回一批候选结果,但候选不一定排序最好。
重排的目标是:在候选结果中重新判断哪些最适合回答当前问题。
为什么需要重排?
- 向量相似不等于答案相关。
- 关键词命中不等于上下文正确。
- 用户问题可能需要多个片段组合。
- 一些片段看起来相似,但实际不支持结论。
重排可以使用专门模型、规则、元数据或 LLM 辅助。
上下文组装
检索到资料后,不能随便塞进 Prompt。
要考虑:
- 按相关性排序。
- 保留来源信息。
- 控制总 Token 数。
- 去除重复片段。
- 合并同一章节相邻片段。
- 明确告诉模型如何使用资料。
一个基础上下文模板:
你只能根据以下资料回答问题。
如果资料不足,请说明无法确定。
资料 1:
来源:...
内容:...
资料 2:
来源:...
内容:...
用户问题:
...
查询改写
用户问题常常不适合直接检索。
例子:
那它和微调有什么区别?
如果这是多轮对话,系统需要知道“它”指的是 RAG。
查询改写会把问题改成更适合检索的形式:
RAG 和微调有什么区别?
查询改写适合:
- 多轮对话。
- 代词很多的问题。
- 问法口语化的问题。
- 需要补全上下文的问题。
多轮对话中的检索
多轮对话比单轮问答更复杂。
系统需要判断:
- 当前问题是否依赖历史对话。
- 是否要重新检索。
- 是否可以复用上轮资料。
- 用户是否改变了主题。
- 历史对话是否会引入错误上下文。
错误做法是把全部历史对话都塞给模型。
更好的做法是:
- 对当前问题做上下文补全。
- 判断是否需要检索。
- 检索最相关资料。
- 只保留必要历史。
引用与可追溯性
RAG 系统应该尽量给出引用。
引用的价值:
- 用户能检查来源。
- 系统能调试错误。
- 内容更可信。
- 高风险场景更容易审计。
引用要注意:
- 引用必须真实存在。
- 引用内容必须支持结论。
- 不要把不相关资料硬塞成依据。
- 引用粒度要合适,最好到章节或片段。
权限控制
企业 RAG 不能只考虑“能不能搜到”,还要考虑“用户有没有权看”。
权限控制应该发生在检索前或检索中,而不是最后靠模型自觉不说。
风险例子:
- 普通员工问制度,检索到高管薪酬文件。
- 客服问客户问题,检索到其他客户的隐私资料。
- 学员问课程内容,系统返回内部未发布讲义。
模型不是权限系统。权限必须由系统设计保证。
基础评估
RAG 系统至少要评估两个层面:
检索评估
问题:
- 正确资料有没有被找回来?
- 排名靠前吗?
- 是否有无关资料混入?
回答评估
问题:
- 回答是否基于资料?
- 是否遗漏关键点?
- 是否引用正确?
- 是否承认资料不足?
- 是否出现幻觉?
没有评估的 RAG 系统,很容易只在 Demo 时看起来不错。
案例:课程问答系统第一版
目标:
让学员基于 LLMCourse 课程内容提问。
第一版设计:
- 文档来源:只读取课程
lesson.md和模块README.md。 - 文档解析:保留标题层级。
- Chunk:按二级/三级标题切分,必要时保留重叠。
- 元数据:模块、单元、标题、路径、难度。
- 检索:向量检索 + 标题关键词匹配。
- 重排:优先同模块内容,优先课程单元正文。
- 生成:要求只根据课程资料回答。
- 引用:返回课程文件路径和小标题。
- 失败处理:资料不足时明确说明。
这比“把所有 Markdown 塞进模型”更可维护。
常见误区
误区 1:RAG 就是向量数据库
向量数据库只是一个组件,不是整个系统。
误区 2:切分越细越好
太细会丢上下文。切分要服务问题和文档结构。
误区 3:检索到资料就一定能回答
还要看资料是否支持结论、模型是否正确理解、引用是否准确。
误区 4:权限可以交给模型判断
不能。权限必须在系统层控制。
误区 5:Demo 能回答几个问题,就说明系统可靠
真实系统要有评估集、日志、错误分析和持续优化。
动手练习
为本课程设计一个 RAG 问答系统第一版。
填写:
| 项目 | 你的设计 |
|---|---|
| 文档范围 | |
| Chunk 切分方式 | |
| 元数据字段 | |
| 检索方式 | |
| 是否需要重排 | |
| 引用粒度 | |
| 权限规则 | |
| 资料不足时怎么回答 | |
| 如何评估 |
检查题(自测)
- RAG 的离线链路和在线链路分别做什么?
- 为什么元数据设计很重要?
- 为什么权限控制不能交给模型自己处理?
参考答案
- 离线链路:资料收集、清洗、切分、向量化、入库,把知识变成可检索的资产;在线链路:查询改写、检索、重排、组装 Prompt、生成回答、标注引用。
- 元数据(来源、时间、权限、类型)支撑三件事:过滤检索范围、生成可追溯引用、控制访问权限;没有元数据的知识库难以治理。
- 模型会幻觉、可能被 Prompt Injection 影响,无法可靠地执行权限逻辑。权限必须在系统层用代码强制,而不是指望模型"守规矩"。
Takeaway
RAG 系统不是“文档 + 向量库 + LLM”这么简单。真正可用的 RAG 需要文档处理、切分、元数据、检索、重排、上下文组装、引用、权限和评估一起工作。哪一环偷懒,哪一环就可能在用户面前替你表演。