1
月
在社区成长 1 月
LEVEL 1
1 XP
## 背景
RAG(检索增强生成)常被描述为“先搜索资料,再让模型回答”,但实际效果并不只取决于模型。答案错误可能来自文档没入库、切分丢失上下文、检索没有召回正确片段、提示词没有约束引用,也可能是模型拿到正确证据后仍然推断过头。只凭几条手工提问判断系统好坏,很容易把偶然答对当成稳定能力。
本文以一个本地或授权测试环境中的内部知识库问答为例,介绍如何建立小型离线评测集,并按检索、生成两个阶段定位问题。目标不是追求某个模型排行榜上的分数,而是让每次修改切分、嵌入模型或提示词后,都能知道改动究竟改善了什么、又引入了什么退化。
## 原理:把 RAG 拆成可观察的两段
典型流程是:文档清洗与切分 → 生成向量并建立索引 → 根据问题召回若干片段 → 将问题和片段交给生成模型 → 输出答案。评测也应分成两层。
第一层是检索评测:正确答案所需的证据有没有进入前 K 个结果?常用指标包括 Recall@K(相关证据被召回的比例)和 MRR(第一个相关结果的排名)。第二层是答案评测:答案是否正确、是否受到检索证据支持、是否在证据不足时明确说明不知道。检索正确但答案不对,通常要检查上下文拼接、提示词和模型;检索本身漏掉关键证据,则优先排查文档处理、切分、嵌入和查询方式。
需要注意,“相关”不等于“答案相同”。同一问题可能由多个片段共同回答,因此评测集应记录足以支持答案的文档或片段标识,而不要只用字符串匹配判断生成答案对错。
## 前置条件
1. 有一份可在本地或获准环境中使用的文档集合。不要把含有个人信息、密钥或未授权资料的内容上传到第三方服务。
2. 能运行现有 RAG 程序,并能导出每个问题的检索结果、排名、来源标识和最终答案。最好同时记录索引版本、嵌入模型名称、切分参数及提示词版本。
3. 准备约 20~50 个代表性问题。小规模集合足以暴露明显问题,但不能代表所有用户场景;对高风险业务应扩大样本并安排人工复核。
4. 为每个问题人工标注参考答案要点,以及一个或多个支持证据的文档或片段 ID。问题应覆盖直接查找、跨段落归纳、缩写或同义说法、资料缺失等情况。
## 分步骤实践
### 第一步:建立可复现的测试集
建议用 JSONL 保存,一行一个样本。例如:
```json
{"id":"q001","question":"离线索引每天几点更新?","answer_points":["每天02:00更新"],"relevant_doc_ids":["ops-03"],"type":"lookup"}
{"id":"q002","question":"文档没有说明保留期限时,系统应该怎么回答?","answer_points":["说明资料未提及","不要猜测具体天数"],"relevant_doc_ids":[],"type":"unanswerable"}
```
这里的 ID 应能追溯到原始文档或切片。若一个答案必须结合两个片段,`relevant_doc_ids` 就同时记录它们。不可回答问题也要纳入测试,否则系统可能通过“总是给出肯定答案”获得表面上的高命中率。
### 第二步:记录检索结果,而不只记录最终答案
让 RAG 的检索函数返回结构化结果,例如每条包含 `doc_id`、`chunk_id`、`rank`、`score` 和文本。不同向量库的分数定义可能不同,有的表示距离、有的表示相似度,不能不加确认就跨库比较数值。首要工作是检查排序和实际文本,再考虑阈值。
下面是一个与具体向量库无关的 Recall@K 计算示例:
```python
def recall_at_k(gold_ids, retrieved_ids, k=5):
gold = set(gold_ids)
if not gold:
return None # 无相关证据的问题单独统计
top = set(retrieved_ids[:k])
return len(gold & top) / len(gold)
# 每项为:问题、标注的相关文档 ID、按排名排列的检索文档 ID
cases = [
("q001", ["ops-03"], ["ops-03", "faq-02", "ops-01"]),
("q003", ["guide-01", "guide-04"], ["guide-01", "misc-02", "guide-04"]),
]
for qid, gold, retrieved in cases:
print(qid, recall_at_k(gold, retrieved, k=3))
```
若标注的是文档 ID,而系统实际检索的是切片 ID,应先建立切片到文档的映射,或直接标注相关切片。不要把两种粒度混用,否则指标会误导排查。
### 第三步:先诊断检索,再调生成
对每个失败问题查看前 3~5 个片段,分类记录原因:证据未入库、切片把标题与正文拆开、召回了相似但不支持答案的段落、正确证据排名过低,或过滤条件排除了目标文档。先处理频率最高且证据明确的问题,不要一遇到错答就换更大的模型。
常见的检索侧调整包括:
- 检查清洗步骤是否误删表格、标题、编号或脚注;对结构化文档,可将表格转成带列名的文本。
- 试验不同切片长度与重叠。切得过短容易丢失定义和上下文,过长则可能稀释相关信息、增加上下文成本。不要只凭经验选参数,固定测试集后对比结果。
- 对包含专有名词、编号和精确词项的语料,比较纯向量检索与关键词检索,必要时采用混合检索;若使用重排器,也要记录重排前后的排序变化。
- 查询扩展或改写可能提升召回,也可能改变用户原意。保留原问题,并用同一组测试问题验证改写是否引入偏差。
### 第四步:评测答案是否忠于证据
对生成结果至少检查三件事:答案要点是否覆盖、关键陈述能否在检索片段中找到依据、证据不足时是否拒绝猜测。若能使用人工评审,优先对失败样本做逐条标注;自动评审可以用于初筛,但不能把模型自评当成事实核验。
可以在提示词中明确边界,例如:
```text
你是知识库问答助手。只依据“参考资料”回答。
如果资料没有直接支持答案,请明确说“现有资料未说明”,不要补充猜测。
回答中的关键结论应标出对应的资料编号。
问题:{question}
参考资料:
{context}
```
提示词中的“只依据资料”并不能保证模型绝不产生无依据内容,因此仍需检查输出与来源的对应关系。若要求引用,应确保传给模型的片段带有稳定编号,并在后处理时验证引用编号确实出现在本次检索上下文中。
### 第五步:做基线对比并保存实验记录
每次实验只改一个主要变量,例如切片长度、召回 K、混合检索开关或提示词。记录 Recall@K、不可回答题的误答率、人工判定的答案支持率,以及延迟和上下文 token 数。按问题类型拆分结果也很重要:总体平均值可能掩盖“编号查询全错”或“不可回答问题频繁编造”等具体故障。
评测结果不必一开始就接入复杂平台。用 CSV 或 JSONL 保存输入、索引版本、召回片段、答案、耗时和人工标签即可。比较新旧方案时保持数据集和生成参数一致,并留出一部分未参与调参的问题,避免把系统只调到熟悉的样例上。
## 风险与注意事项
评测集本身可能含敏感信息,应按资料等级控制访问和留存。引用来源也不等于来源可信:过期、冲突或错误的文档仍可能被准确检索。需要为文档维护更新时间、版本和权限信息,并在检索阶段执行访问控制,不能只在回答后隐藏敏感内容。
还要防止数据泄漏:如果测试问题和参考答案被直接加入知识库,系统可能检索到测试答案而非真正解决问题。更新索引后应确认测试集没有被意外作为知识内容导入。对有副作用的工具调用,应在隔离环境中测试;本文流程只评估本地或获准的知识库,不涉及对真实第三方目标进行测试。
## 常见问题
**Recall@5 很高,答案仍然经常错,怎么办?** 逐条核查召回片段是否真正支持答案,而不只是主题相似;再检查上下文是否截断、片段顺序是否合理、提示词是否允许模型使用自身常识补全。
**分数阈值应设多少?** 不同嵌入模型、距离函数和语料的分数不可直接通用。使用标注集观察正确与错误样本的分布,再决定是否设置阈值,并验证阈值对不同问题类型的影响。
**测试问题太少,指标有意义吗?** 小集合适合发现明显回归,不足以证明生产环境可靠。先用小集快速迭代,再持续收集经脱敏、授权的问题,定期复核标签并扩充长尾场景。
**什么时候考虑更换模型?** 当检索证据稳定且提示词边界明确,模型仍不能完成必要的归纳、遵循格式或拒答要求时,再用固定测试集比较候选模型,同时纳入延迟、费用、部署条件和数据处理要求。
## 总结
RAG 调优的关键不是盲目增加上下文或更换大模型,而是让每个环节可观察、可复现。用带证据标注的小型测试集,先判断检索是否找到材料,再判断生成是否忠于材料;一次只改一个因素,并同时关注正确率、拒答质量、延迟和数据安全。这样即使系统仍有不足,也能把“答得不好”转化为可定位、可验证的工程问题。
RAG(检索增强生成)常被描述为“先搜索资料,再让模型回答”,但实际效果并不只取决于模型。答案错误可能来自文档没入库、切分丢失上下文、检索没有召回正确片段、提示词没有约束引用,也可能是模型拿到正确证据后仍然推断过头。只凭几条手工提问判断系统好坏,很容易把偶然答对当成稳定能力。
本文以一个本地或授权测试环境中的内部知识库问答为例,介绍如何建立小型离线评测集,并按检索、生成两个阶段定位问题。目标不是追求某个模型排行榜上的分数,而是让每次修改切分、嵌入模型或提示词后,都能知道改动究竟改善了什么、又引入了什么退化。
## 原理:把 RAG 拆成可观察的两段
典型流程是:文档清洗与切分 → 生成向量并建立索引 → 根据问题召回若干片段 → 将问题和片段交给生成模型 → 输出答案。评测也应分成两层。
第一层是检索评测:正确答案所需的证据有没有进入前 K 个结果?常用指标包括 Recall@K(相关证据被召回的比例)和 MRR(第一个相关结果的排名)。第二层是答案评测:答案是否正确、是否受到检索证据支持、是否在证据不足时明确说明不知道。检索正确但答案不对,通常要检查上下文拼接、提示词和模型;检索本身漏掉关键证据,则优先排查文档处理、切分、嵌入和查询方式。
需要注意,“相关”不等于“答案相同”。同一问题可能由多个片段共同回答,因此评测集应记录足以支持答案的文档或片段标识,而不要只用字符串匹配判断生成答案对错。
## 前置条件
1. 有一份可在本地或获准环境中使用的文档集合。不要把含有个人信息、密钥或未授权资料的内容上传到第三方服务。
2. 能运行现有 RAG 程序,并能导出每个问题的检索结果、排名、来源标识和最终答案。最好同时记录索引版本、嵌入模型名称、切分参数及提示词版本。
3. 准备约 20~50 个代表性问题。小规模集合足以暴露明显问题,但不能代表所有用户场景;对高风险业务应扩大样本并安排人工复核。
4. 为每个问题人工标注参考答案要点,以及一个或多个支持证据的文档或片段 ID。问题应覆盖直接查找、跨段落归纳、缩写或同义说法、资料缺失等情况。
## 分步骤实践
### 第一步:建立可复现的测试集
建议用 JSONL 保存,一行一个样本。例如:
```json
{"id":"q001","question":"离线索引每天几点更新?","answer_points":["每天02:00更新"],"relevant_doc_ids":["ops-03"],"type":"lookup"}
{"id":"q002","question":"文档没有说明保留期限时,系统应该怎么回答?","answer_points":["说明资料未提及","不要猜测具体天数"],"relevant_doc_ids":[],"type":"unanswerable"}
```
这里的 ID 应能追溯到原始文档或切片。若一个答案必须结合两个片段,`relevant_doc_ids` 就同时记录它们。不可回答问题也要纳入测试,否则系统可能通过“总是给出肯定答案”获得表面上的高命中率。
### 第二步:记录检索结果,而不只记录最终答案
让 RAG 的检索函数返回结构化结果,例如每条包含 `doc_id`、`chunk_id`、`rank`、`score` 和文本。不同向量库的分数定义可能不同,有的表示距离、有的表示相似度,不能不加确认就跨库比较数值。首要工作是检查排序和实际文本,再考虑阈值。
下面是一个与具体向量库无关的 Recall@K 计算示例:
```python
def recall_at_k(gold_ids, retrieved_ids, k=5):
gold = set(gold_ids)
if not gold:
return None # 无相关证据的问题单独统计
top = set(retrieved_ids[:k])
return len(gold & top) / len(gold)
# 每项为:问题、标注的相关文档 ID、按排名排列的检索文档 ID
cases = [
("q001", ["ops-03"], ["ops-03", "faq-02", "ops-01"]),
("q003", ["guide-01", "guide-04"], ["guide-01", "misc-02", "guide-04"]),
]
for qid, gold, retrieved in cases:
print(qid, recall_at_k(gold, retrieved, k=3))
```
若标注的是文档 ID,而系统实际检索的是切片 ID,应先建立切片到文档的映射,或直接标注相关切片。不要把两种粒度混用,否则指标会误导排查。
### 第三步:先诊断检索,再调生成
对每个失败问题查看前 3~5 个片段,分类记录原因:证据未入库、切片把标题与正文拆开、召回了相似但不支持答案的段落、正确证据排名过低,或过滤条件排除了目标文档。先处理频率最高且证据明确的问题,不要一遇到错答就换更大的模型。
常见的检索侧调整包括:
- 检查清洗步骤是否误删表格、标题、编号或脚注;对结构化文档,可将表格转成带列名的文本。
- 试验不同切片长度与重叠。切得过短容易丢失定义和上下文,过长则可能稀释相关信息、增加上下文成本。不要只凭经验选参数,固定测试集后对比结果。
- 对包含专有名词、编号和精确词项的语料,比较纯向量检索与关键词检索,必要时采用混合检索;若使用重排器,也要记录重排前后的排序变化。
- 查询扩展或改写可能提升召回,也可能改变用户原意。保留原问题,并用同一组测试问题验证改写是否引入偏差。
### 第四步:评测答案是否忠于证据
对生成结果至少检查三件事:答案要点是否覆盖、关键陈述能否在检索片段中找到依据、证据不足时是否拒绝猜测。若能使用人工评审,优先对失败样本做逐条标注;自动评审可以用于初筛,但不能把模型自评当成事实核验。
可以在提示词中明确边界,例如:
```text
你是知识库问答助手。只依据“参考资料”回答。
如果资料没有直接支持答案,请明确说“现有资料未说明”,不要补充猜测。
回答中的关键结论应标出对应的资料编号。
问题:{question}
参考资料:
{context}
```
提示词中的“只依据资料”并不能保证模型绝不产生无依据内容,因此仍需检查输出与来源的对应关系。若要求引用,应确保传给模型的片段带有稳定编号,并在后处理时验证引用编号确实出现在本次检索上下文中。
### 第五步:做基线对比并保存实验记录
每次实验只改一个主要变量,例如切片长度、召回 K、混合检索开关或提示词。记录 Recall@K、不可回答题的误答率、人工判定的答案支持率,以及延迟和上下文 token 数。按问题类型拆分结果也很重要:总体平均值可能掩盖“编号查询全错”或“不可回答问题频繁编造”等具体故障。
评测结果不必一开始就接入复杂平台。用 CSV 或 JSONL 保存输入、索引版本、召回片段、答案、耗时和人工标签即可。比较新旧方案时保持数据集和生成参数一致,并留出一部分未参与调参的问题,避免把系统只调到熟悉的样例上。
## 风险与注意事项
评测集本身可能含敏感信息,应按资料等级控制访问和留存。引用来源也不等于来源可信:过期、冲突或错误的文档仍可能被准确检索。需要为文档维护更新时间、版本和权限信息,并在检索阶段执行访问控制,不能只在回答后隐藏敏感内容。
还要防止数据泄漏:如果测试问题和参考答案被直接加入知识库,系统可能检索到测试答案而非真正解决问题。更新索引后应确认测试集没有被意外作为知识内容导入。对有副作用的工具调用,应在隔离环境中测试;本文流程只评估本地或获准的知识库,不涉及对真实第三方目标进行测试。
## 常见问题
**Recall@5 很高,答案仍然经常错,怎么办?** 逐条核查召回片段是否真正支持答案,而不只是主题相似;再检查上下文是否截断、片段顺序是否合理、提示词是否允许模型使用自身常识补全。
**分数阈值应设多少?** 不同嵌入模型、距离函数和语料的分数不可直接通用。使用标注集观察正确与错误样本的分布,再决定是否设置阈值,并验证阈值对不同问题类型的影响。
**测试问题太少,指标有意义吗?** 小集合适合发现明显回归,不足以证明生产环境可靠。先用小集快速迭代,再持续收集经脱敏、授权的问题,定期复核标签并扩充长尾场景。
**什么时候考虑更换模型?** 当检索证据稳定且提示词边界明确,模型仍不能完成必要的归纳、遵循格式或拒答要求时,再用固定测试集比较候选模型,同时纳入延迟、费用、部署条件和数据处理要求。
## 总结
RAG 调优的关键不是盲目增加上下文或更换大模型,而是让每个环节可观察、可复现。用带证据标注的小型测试集,先判断检索是否找到材料,再判断生成是否忠于材料;一次只改一个因素,并同时关注正确率、拒答质量、延迟和数据安全。这样即使系统仍有不足,也能把“答得不好”转化为可定位、可验证的工程问题。