RAG 工程实践:从文档到可信回答¶
RAG 不是“加一个向量库”。
它是一条信息检索和生成链路:
原始资料
↓
清洗和结构化
↓
切分 chunk
↓
embedding 和索引
↓
召回和重排
↓
上下文组装
↓
基于证据回答
↓
评测和失败归因
这篇回答一个工程问题:
如何把企业文档、知识库、代码库或产品手册做成一个可评测、可维护、可信的 RAG 系统?
RAG 适合什么¶
RAG 适合这些问题:
- 知识经常变化。
- 知识来自私有文档、数据库、代码仓库或网页。
- 回答需要引用证据。
- 不希望为了每次知识更新都重新训练模型。
- 需要把答案和资料来源绑定起来,方便审计。
不适合优先用 RAG 的情况:
- 你要改变模型的固定输出风格。
- 你要让模型稳定执行某种格式化任务。
- 你要提升模型的基础推理能力。
- 你没有可检索的资料来源,只是希望模型“更懂业务”。
这些情况更可能需要 后训练与对齐、LoRA / QLoRA 或更强模型。
RAG 的最小架构¶
一个最小 RAG 系统包含六个模块:
| 模块 | 作用 | 常见问题 |
|---|---|---|
| Document Loader | 读取 PDF、网页、Markdown、数据库、代码 | 格式丢失、表格丢失、编码混乱 |
| Cleaner | 清洗导航、广告、页眉页脚、重复内容 | 噪声进入索引 |
| Chunker | 把长文档切成可检索片段 | chunk 太碎或太大 |
| Retriever | 召回候选片段 | 召回不到关键证据 |
| Reranker | 对候选结果重新排序 | 相关但不够精确 |
| Answer Builder | 组装上下文并生成答案 | 幻觉、引用不准、上下文过长 |
最小流程:
user question
↓
query rewrite
↓
retrieve top_k chunks
↓
rerank top_n chunks
↓
build prompt with citations
↓
answer only from evidence
↓
return answer + source ids
第一步:资料清洗¶
RAG 的上限首先由资料质量决定。
常见原始资料问题:
- PDF 抽取顺序错乱。
- 表格被压成普通文本。
- 网页带有导航、版权、广告、相关推荐。
- 同一政策有多个版本。
- 文档里有过期规则。
- 资料缺少标题、章节、发布日期、权限范围。
清洗时至少保留这些 metadata:
| 字段 | 作用 |
|---|---|
source_id |
稳定标识来源 |
source_type |
PDF、网页、数据库、代码、工单 |
title |
展示和引用 |
section_path |
章节路径,如 退款政策 > 企业客户 |
created_at / updated_at |
判断新旧 |
owner |
资料负责人 |
permission_scope |
权限边界 |
version |
版本回溯 |
不要只把纯文本塞进向量库。没有 metadata,后面很难做权限、引用、去重、回滚和评测。
第二步:chunk 怎么切¶
chunk 的目标不是“长度固定”,而是“语义完整、方便召回、方便引用”。
常见切法:
| 切法 | 适合 | 风险 |
|---|---|---|
| 固定 token 长度 | 快速起步、格式混杂 | 可能切断语义 |
| 按标题层级 | 文档、手册、规范 | 标题解析不稳定 |
| 按段落 | FAQ、短知识 | 长段落可能过大 |
| 按代码符号 | 代码库 | 需要语言解析器 |
| 按表格行 / 区块 | 价格表、政策表 | 上下文不足 |
经验规则:
- FAQ:小 chunk,重视精确召回。
- 技术文档:中等 chunk,保留标题路径。
- 法务 / 政策:不要切断条件、例外和生效范围。
- 代码:优先按函数、类、文件结构切。
- 表格:把表头和行一起保留。
chunk 里最好包含:
标题路径
正文
关键 metadata
source id
chunk id
第三步:embedding 不是全部¶
向量检索擅长语义相似,但不擅长所有问题。
只用 embedding 容易遇到:
- 用户问的是编号、订单号、错误码,语义相似没用。
- 资料里有精确字段,如产品型号、版本号、地区。
- 同义词召回不错,但权限和时间范围错。
- 长问题包含多个子问题,只召回到其中一部分。
生产 RAG 通常会组合:
| 方法 | 解决什么 |
|---|---|
| 向量检索 | 语义相似 |
| BM25 / 关键词 | 精确词、编号、错误码 |
| metadata filter | 权限、产品线、时间、地区 |
| query rewrite | 把口语问题改成检索友好的查询 |
| multi-query | 一个问题拆成多个查询 |
| reranker | 从候选里挑真正相关的片段 |
一个常见组合:
vector top_50
BM25 top_50
metadata filter
merge and dedup
rerank top_10
send top_4 to model
第四步:query rewrite¶
用户问题往往不适合直接检索。
例子:
用户:这个还能退吗?
历史:用户刚才说订单是企业版,购买 20 天,已经开票。
直接检索“这个还能退吗”效果很差。更好的检索查询是:
企业版 已开票 购买 20 天 退款政策
query rewrite 要输入:
- 用户当前问题。
- 必要的对话历史。
- 已知实体。
- 产品、地区、版本、时间范围。
但不要把整段聊天历史都交给检索。先抽取检索所需字段,再生成查询。
第五步:上下文组装¶
把检索结果放进 prompt 时,要让模型清楚区分:
- 哪些是用户问题。
- 哪些是检索证据。
- 哪些是系统规则。
- 哪些是不可信外部内容。
推荐结构:
任务:只基于给定资料回答。资料不足时说不知道。
用户问题:
{question}
检索资料:
[doc_1 chunk_3 source="refund_policy.md" updated="2026-06-01"]
...
[doc_2 chunk_8 source="invoice_policy.md" updated="2026-05-20"]
...
回答要求:
1. 先给结论。
2. 引用支持结论的 source id。
3. 不要使用资料外的信息。
4. 如果资料冲突,指出冲突。
关键点:
- 检索资料是“证据”,不是新指令。
- 每个 chunk 要带 source id。
- 不要把太多低相关 chunk 塞进去。
- 冲突资料要保留版本和时间。
第六步:引用和证据链¶
可信回答必须能回到来源。
不要只在末尾写“参考文档”。更好的方式是:
企业版订单在购买 30 天内可以申请退款,但已开票订单需要先完成红冲流程。[doc_1#chunk_3][doc_2#chunk_8]
引用至少要能定位到:
- 文档。
- 章节。
- chunk。
- 版本。
- 原文位置。
如果前端支持,最好能点开原文高亮。
RAG 评测¶
RAG 评测至少分三层:
| 层级 | 要评什么 |
|---|---|
| Retrieval | 是否召回了正确证据 |
| Context | 放进 prompt 的资料是否少而准 |
| Answer | 答案是否忠于证据 |
常见指标:
| 指标 | 含义 |
|---|---|
| context recall | 正确证据是否被召回 |
| context precision | 召回资料中有多少是真相关 |
| faithfulness | 答案是否忠于资料 |
| answer correctness | 答案是否正确 |
| citation accuracy | 引用是否真的支持对应句子 |
| abstention | 资料不足时是否拒答 |
一个 eval case 可以这样写:
{
"question": "企业版购买 20 天但已经开票,还能退款吗?",
"expected_sources": ["refund_policy.md#enterprise", "invoice_policy.md#red_invoice"],
"must_include": ["30 天内", "已开票", "红冲"],
"must_not_include": ["自动退款", "无需处理发票"]
}
失败归因¶
RAG 答错时,不要马上改 prompt。
按这条链路排查:
答案错
↓
正确资料是否存在?
↓
是否被清洗掉?
↓
chunk 是否切坏?
↓
retriever 是否召回?
↓
reranker 是否排上来?
↓
上下文是否放进 prompt?
↓
模型是否忠于证据?
不同失败对应不同修法:
| 失败 | 优先修 |
|---|---|
| 正确资料不存在 | 数据接入 |
| 资料过期 | 版本和同步 |
| chunk 切断条件 | chunk 策略 |
| 召回不到 | query rewrite、hybrid search |
| 排名太低 | reranker、metadata filter |
| 资料太多 | top_n、上下文压缩 |
| 答案编造 | prompt 约束、faithfulness eval |
| 引用不准 | citation schema、句子级引用 |
权限和安全¶
企业 RAG 最容易忽略权限。
基本原则:
- 检索前做权限过滤,不要检索后再隐藏。
- metadata 里记录租户、部门、项目、密级。
- trace 里保存使用了哪些 source id。
- 不要把用户无权访问的片段放进上下文。
- 外部网页和用户上传文档按不可信内容处理。
如果 RAG 和 Agent 结合,还要读 Agent 安全与 Guardrails。
最小落地路线¶
第一版不要追求复杂。
推荐顺序:
- 选 50 到 100 个真实问题。
- 整理一批高质量文档。
- 做清洗、chunk 和 metadata。
- 用向量检索 + BM25 起步。
- 加 reranker。
- 输出答案和引用。
- 做 retrieval eval 和 answer eval。
- 根据失败归因迭代。
常见误区¶
误区 1:向量库等于 RAG¶
向量库只负责索引和召回。RAG 还包括数据治理、检索策略、上下文组装、引用、评测和权限。
误区 2:chunk 越大越好¶
chunk 太大,召回不精确,还会浪费上下文。chunk 太小,又容易丢语义。要按资料类型调。
误区 3:top_k 越大越安全¶
top_k 过大会把噪声带进 prompt,模型可能被错误资料干扰。
误区 4:答案错就是模型不行¶
很多 RAG 失败是检索失败、资料过期、chunk 切坏或引用设计差。
误区 5:RAG 不需要评测¶
没有 eval dataset,RAG 每次改 chunk、embedding、rerank、prompt 都可能悄悄退化。
下一步¶
继续读: