日期:2026-10-29 · 主题:检索系统、RAG、评估与可靠性
用户问:“按今年的企业版合同,欧洲客户提前解约,是否需要支付剩余合同金额?”
系统检索到一份高度相关的合同条款,模型流畅地回答“需要”。但那份条款属于去年的北美标准合同;今年欧洲企业版合同中,另有一条特殊退出条款。
这个错误可能发生在知识接入、版本过滤、候选召回、重排,也可能发生在上下文截断或模型推理阶段。只看最终答案,无法确定该换 Embedding、调 Top-K,还是修改生成提示词。
RAG 的深度,体现在能否追踪一条证据从原始来源进入系统,到最终支持某个结论的全过程,并通过实验判断错误发生在哪一层。
本文以虚构的企业合同知识助手为主线。合同问题只用于系统设计,不构成法律意见;所有参数与数字示例都是教学设定,未宣称生产收益或完整模型基准测试。文中将公开文档能力与自定义系统设计分开说明。
1. 为 RAG 定义一个更严格的目标
RAG 通常被概括为“先检索,再生成”。这个描述解释了调用顺序,但没有定义正确性。
对于企业知识助手,更严格的目标是:
在用户有权访问、适用版本和时间范围内,找到回答问题所需的证据,在有限预算下交给模型,并生成可以被这些证据支持的回答。
这个目标包含四个经常被忽略的条件:权限、时效、证据完整性、结论可支持性。
OpenAI 的 Retrieval 文档说明,语义检索可以找到关键词重合较少但语义相近的内容;File Search 则提供托管的文件检索能力。它们解决了部分检索执行问题,但应用仍需定义自己的业务适用条件与评测标准。Retrieval;File search
将系统写成两个阶段:
离线或增量阶段:
源数据 → 解析 → 规范化 → 切块 → 表征 → 索引 → 发布
在线阶段:
问题与身份 → 约束解析 → 候选召回 → 融合 → 重排
→ 证据组装 → 生成 → 引用与支持性检查
系统还需要一条旁路:监控、评估、删除与权限变更传播。缺少这条旁路,一个今天答对的系统,明天可能因为源文档更新而稳定答错。
flowchart LR
A[源文档与权限] --> B[解析与版本化]
B --> C[结构化切块]
C --> D[稀疏与稠密索引]
Q[问题与可信身份] --> F[范围与权限约束]
D --> R[候选召回]
F --> R
R --> S[融合与重排]
S --> P[预算内证据组装]
P --> G[生成与主张检查]
G --> E[结果与分层评估]
E -. 定位失败与回归验证 .-> B
E -. 定位失败与回归验证 .-> R
E -. 定位失败与回归验证 .-> P2. 知识接入首先是数据工程问题
假设源文件是一份扫描合同。页面包含正文、表格、脚注和盖章。简单 OCR 把表格的行列关系打乱,将“适用地区:欧洲”与另一列中的“标准版”拼成一句话。
后续 Embedding、ANN 和 Reranker 再强,也只能处理已经被破坏的内容。
接入需要分别保存三种东西:
| 层 | 保存内容 | 作用 |
|---|---|---|
| 原始层 | 文件、原始 API 响应、来源 ID、内容哈希 | 重放、审计与重新解析 |
| 规范层 | 章节、段落、表格、页码、字符或坐标定位 | 保留结构和出处 |
| 索引层 | 检索文本、Embedding、过滤字段、索引版本 | 支持高效查询 |
三层不一定要部署成三个独立服务,但不能丢掉它们之间的映射。只保存向量和一段文本,会使后续纠错、重建和引用验证非常困难。
对 PDF,文本提取和页面视觉分析可能提供不同信息。OpenAI 的相关 Cookbook 展示了两种处理路径;该示例已归档,本文仅引用其文档解析思路,不采用其中旧模型和 API 代码。How to parse PDF docs for RAG
解析后的质量检查应该覆盖:
- 章节标题与段落是否保持从属关系。
- 表格单元格是否仍能关联列名、行名、单位和脚注。
- 页眉页脚是否被重复当成正文。
- OCR 的数字、否定词和小数点是否存在高风险误识别。
- 附件和主合同之间的引用关系是否保留。
- 无法解析的页面是否明确标记,而不是静默跳过。
对于重要文档,可以把低质量页面送入补充解析或人工复核。不要让解析器的“调用成功”成为内容正确的代理指标。
3. 文档身份、版本和时间必须成为一等公民
一个可用的 chunk 记录可以包含:
{
"chunk_id": "contract_42:v8:section_12:part_2",
"document_id": "contract_42",
"source_version": "v8",
"content_hash": "sha256:example",
"tenant_id": "tenant_a",
"acl_policy_id": "policy_17",
"effective_from": "2026-01-01",
"effective_to": null,
"region": "EU",
"product": "enterprise",
"section_path": ["Termination", "Early exit"],
"source_locator": {"page": 18, "paragraph": 3},
"parser_version": "parser_v3",
"chunker_version": "chunker_v2",
"embedding_version": "embedding_profile_v1"
}
这是自定义数据模型示意,不是某个托管检索接口的完整 schema。
document_id 表示逻辑文档,source_version 表示其版本,chunk_id 表示某一版本中的检索单元。不要把源文件名当成唯一身份:文件可以重命名,两个文件也可能同名。
时间还需要区分:
- 业务有效时间:条款什么时候适用。
- 系统获知时间:该版本什么时候被接入。
“现在适用什么规定”和“去年决策时系统知道什么”不是同一个查询。需要历史回溯时,必须保留这两个维度,并明确查询语义。
更新与删除的正确顺序
可以为新版本构建独立索引 generation,待解析、向量和关键词索引就绪后,再通过发布清单或别名切换到新 generation。这降低了查询同时看到新正文和旧向量的风险。
如果稀疏索引和向量索引分属不同系统,通常没有跨系统原子切换。需要在候选返回时校验 generation,或在服务层绑定发布清单。不能仅因为两个任务都启动了,就声称更新已原子完成。
删除要传播到向量索引、关键词索引、父文档存储、上下文缓存和答案缓存。权限撤销同样需要传播。对不能接受传播延迟的系统,应在返回证据或生成前进行权威状态复核;具体一致性等级必须写进系统契约。
4. 切块的目标是保存可回答性
把每 500 个 Token 切成一块,很容易实现,却可能把一般规则和例外拆开:
第 12 条:提前解约需支付剩余合同金额。
第 12.1 条:若发生特定服务违约,欧洲企业版客户可以免付该费用。
问题是,第一块单独看非常像完整答案。
切块时需要区分三个粒度:
| 粒度 | 例子 | 设计目标 |
|---|---|---|
| 检索单元 | 条款片段或段落 | 足够聚焦,便于匹配 |
| 生成证据单元 | 条款及其适用条件、例外 | 足够完整,便于判断 |
| 引用单元 | 页码、条款号、原文范围 | 足够精确,便于核对 |
三者可以不同。可以检索短 chunk,再按结构扩展到父章节或相邻条款。但扩展必须计入 Token 预算,也要保留权限、版本和来源校验。
切块策略的常见取舍包括:
- 小块更聚焦,但可能缺少前提和指代对象。
- 大块信息完整,但相似度可能被无关内容稀释。
- Overlap 减少边界断裂,也会制造近重复候选并占用预算。
- 标题与路径能补充语义,但不能代替缺失正文。
- 表格需要保留字段关系,不能按普通段落随意截断。
用模型生成“检索摘要”或背景补充时,应把它标为派生字段,并保留原文。摘要用于帮助找到证据,不应在未经核对时成为合同事实的权威来源。
选择 chunk 大小时,应比较不同问题类型下的证据覆盖、上下文占用和最终答案,而不是只看向量检索分数。
5. 索引:Embedding 质量与搜索近似误差是两件事
稠密检索通常把问题和文档映射到向量,再计算相似度:
score(q, d) = dot(normalize(embed_query(q)),
normalize(embed_document(d)))
这里使用归一化后的点积,对应余弦相似度。实际系统必须遵守模型要求,包括查询与文档的编码方式、前缀、归一化和距离度量,不能任意混搭。
更换 Embedding 模型,即使维度相同,也不意味着新旧向量位于兼容空间。通常应重建文档向量,并绑定查询编码配置。
生产语料大时,常用近似最近邻搜索 ANN 降低查询开销。需要区分两种召回:
ANN Recall@K:
近似检索找回了多少“同一向量空间下精确搜索的 Top-K”。
任务证据 Recall@K:
检索找回了多少“回答当前问题真正需要的证据”。
ANN Recall 很高,业务证据召回仍可能很差,因为 Embedding 把错误条款排在了前面。反过来,Embedding 在精确搜索中表现很好,但 ANN 配置过于激进,也会漏掉本来可找到的证据。
定位时可以先在可控样本上做精确向量搜索:如果精确搜索也找不到,优先检查数据、切块和表征;如果精确搜索能找到而 ANN 找不到,再调整近似搜索预算、索引结构和过滤策略。
对于非常小的语料,精确搜索可能已经足够。不要默认系统必须使用复杂的向量数据库。
6. 权限过滤会改变你的检索空间
假设全库最相似的前 100 个候选都属于另一个租户。先取 Top-10 再过滤,最后可能什么也不剩,即使用户有权访问的集合里存在答案。
正确的检索目标应该是:
在 Authorized(user) ∩ Applicable(time, region, product)
定义的集合内,找到相关证据。
这与“全库检索之后把不允许看的候选删掉”不是等价操作。
可以使用预过滤、租户分区或支持过滤的 ANN 方案,但过滤本身可能影响 ANN 质量和性能。应按选择性、租户规模和数据分布测试,而不是只在全库无过滤条件下测一次。
权限条件必须从可信身份和策略服务派生,不能相信模型传来的 tenant_id。允许模型帮助理解“欧洲企业版”这样的业务条件,不代表允许它自行扩大访问范围。
托管检索服务可能提供属性过滤,OpenAI Retrieval 文档也描述了相关能力;但复杂 ACL 的同步、撤销与最终授权复核仍需应用明确设计。Retrieval:Attributes 与 filtering
更不能把未授权候选先送给 Reranker 或生成模型,再只过滤最终答案。此时内容已经越过访问边界。日志、调试页面和缓存也属于可能泄露证据的路径。
7. 混合召回:语义相关与精确匹配各有盲区
用户可能问:
“ERR_AUTH_042 到底是什么意思?”
这类问题很依赖精确标识符。另一个用户问:
“客户不想继续合作,什么情况下能免掉后面的费用?”
这又依赖语义匹配,关键词可能与合同原文完全不同。
因此,稀疏检索与稠密检索可以互补:
- 关键词检索适合产品名、错误码、条款编号、稀有术语。
- 稠密检索适合语义改写、表达差异和跨词面匹配。
以 BM25 这类关键词排序为例,稀有词通常具有更高区分度,词频增加的收益逐渐饱和,并通过文档长度归一化避免长文档仅凭重复出现占优。这也解释了为什么专有标识符可能很有效,以及为什么把大量无关正文塞进一个 chunk 会改变排序。中文分词、大小写、符号保留和代码标识符切分都需要针对语料检查。
召回应扩大有价值的候选集合,再由后续阶段精筛。直接把 BM25 分数与向量分数相加通常没有清楚含义,因为两种分数的尺度不同。
一种简单融合方法是 Reciprocal Rank Fusion:
RRF(d) = Σ_i weight_i / (k0 + rank_i(d))
rank 从 1 开始。
某通道没有返回 d,则该通道贡献为 0。
k0 为平滑参数,不是候选数量 K。
它依据名次而非原始分数融合,减少尺度不一致的问题,但也丢弃了原始分差。k0、权重和候选数都应通过任务数据验证。
下面是一个仅依赖 Python 标准库、可以本地运行的教学实现。输入必须已完成授权过滤;chunk ID 必须在参与融合的通道中具有一致身份。
from collections import defaultdict
def rrf(rankings, k0=60):
if k0 <= 0:
raise ValueError("k0 must be positive")
scores = defaultdict(float)
for ranking in rankings:
# 同一通道的重复条目不能重复投票。
unique = list(dict.fromkeys(ranking))
for rank, chunk_id in enumerate(unique, start=1):
scores[chunk_id] += 1.0 / (k0 + rank)
return sorted(scores.items(), key=lambda item: (-item[1], item[0]))
if __name__ == "__main__":
result = rrf([["A", "B", "C"], ["B", "D", "A"]])
assert result[0][0] == "B"
assert rrf([["A", "A", "B"]]) == rrf([["A", "B"]])
assert rrf([]) == []
print([chunk_id for chunk_id, _ in result])
OpenAI Retrieval 文档也提供了混合检索相关配置,包括稠密与文本通道的融合权重;不同托管接口的可调范围应以对应文档为准,不要把这个自定义实现当成其内部源码。Retrieval:Ranking
查询改写何时有帮助?
“它可以提前结束吗?”需要结合会话还原“它”指哪份合同。但改写也可能把用户没说过的条件加进去。
一个稳妥起点是保留原始查询,与改写查询并行召回,再记录改写来源。型号、否定词、金额、日期和条款号尤其需要检查。
多跳问题可以拆解。例如先确定客户适用的合同,再检索对应退出条款。每次检索都应有明确的信息缺口、预算和停止条件,否则 Agentic RAG 容易变成无限搜索。
8. 重排:判断候选是否真正回答了问题
召回器关注“别漏掉重要证据”,重排器关注“在这批候选里,把更能回答问题的排到前面”。
Bi-encoder 通常分别编码问题和文档,便于预计算与检索;Cross-encoder 则联合处理问题和候选,可以更细地判断两者关系,但逐候选计算的成本更高。
常见设计是先取较大的候选集合,再对有限候选重排。OpenAI Cookbook 展示了这种两阶段思路;其旧模型与旧接口示例已归档,不能直接当成当前接入指南。Search reranking with cross-encoders
重排器输入不应只有孤立正文,还可以包括标题、结构路径、版本和适用条件。但硬性权限和适用性约束应先执行,不能寄希望于重排分数把非法候选降下去。
一个关键上限是:重排器只能调整已召回的集合,不能凭空补回从未进入候选的证据。 如果例外条款在召回阶段就丢了,换更强 Reranker 可能毫无帮助。
还要注意:
- 重排输入长度限制可能截掉真正关键的后半段。
- 模型分数未必是“答案正确概率”,跨模型阈值通常不能直接复用。
- Listwise LLM 重排可能受候选顺序影响,可用换序测试检查。
- 重排很高的十个片段可能全是同一条款的重叠切块。
- 对多跳问题,最重要的下一条证据不一定单独最像问题。
因此排序目标可以加入覆盖与去重。需要保留“版本不同且内容冲突”的证据,不能把它们误当成重复文本删除。
如果重排服务故障,可以降级到融合排序,但必须记录降级状态,并根据业务风险决定能否继续生成。降级是一种质量与可用性的取舍,不是无损替换。
9. 上下文组装:Top-K 不是最终答案
假设召回 100 条、重排后保留 20 条,其中 15 条来自同一份合同的重叠段落。直接把前 10 条塞进模型,很可能遗漏唯一的例外条款。
上下文组装应考虑证据集合的价值,而不只是逐条分数。
可以用一个简化优化目标表达:
在 Σ token_cost(d) ≤ B 的约束下,选择集合 S:
maximize 相关性(S)
+ 必要事实覆盖(S)
- 重复度(S)
- 未解决冲突带来的风险(S)
这里是设计视角,不是宣称存在一个通用、可直接计算的完美目标函数。冲突也不能靠扣分后简单删掉;重要冲突应被明确呈现,并根据来源与版本规则解决,无法解决时应说明不确定。
预算 B 需要扣除指令、问题、工具 schema、必要历史、预留输出与所用模型需要的其他空间。计数应使用对应模型的实际 Token 工具,不能用字符数替代精确预算。
一个实用流程是:
- 过滤失效、无权访问和不适用的证据。
- 合并同版本的近重复片段,保留多个来源定位。
- 根据结构补齐前提、定义、例外和必要相邻段落。
- 选择覆盖不同必需事实的证据集合。
- 对打包后的最终输入重新计数,而非只统计正文。
- 生成稳定的引用 ID,并保留原文映射。
给模型的证据可以采用这样的结构:
[E3]
来源:合同 contract_42,版本 v8,第 18 页,第 12.1 条
适用范围:欧洲 / 企业版 / 2026-01-01 起
原文:……
用途:与提前退出费用相关的例外条件。
说明:“用途”是检索系统附加信息,不是合同原文。
不要让生成的“用途”混入原文后再被引用。不要用相似度分数证明法律或业务结论。不要以为加了 XML 标签,就能保证模型不受其中恶意指令影响。
如果上下文过长,可以做抽取式裁剪或摘要,但要检查摘要是否保留否定、适用条件、数字和例外。证据压缩后的正确性需要单独评估。
10. 从证据到回答:有引用不等于被支持
下面的回答看起来很专业:
“欧洲企业版客户可以无条件提前退出。[E3]”
如果 E3 只允许在特定服务违约时免除费用,这个引用是真实存在的,结论却过度扩张了。
应把生成结果拆成可检查的主张:
主张 1:提前解约通常需要支付剩余金额。
支持:E2 的一般条款。
主张 2:特定服务违约情况下存在例外。
支持:E3 的例外条款。
主张 3:本客户满足例外条件。
支持:当前缺失;需要客户事实或业务查询。
这里必须区分文档规则与当前业务事实。RAG 找到“什么条件下允许退款或退出”,不代表已经证明“这个用户满足条件”。实时状态、余额、权限等应通过可信业务工具获取和校验。
对证据不足的情况,系统至少有三种行为:补充检索、向用户澄清、明确无法确认。选择哪一种取决于缺失信息来自语料、用户还是外部系统。
判断证据充分性时,可以检查必需字段与来源、相关性模型分数和模型辅助判断,但不能只依赖模型自己报出的置信度。拒答过多同样是失败,需要与错误作答一起评估。
11. 效果评测必须分层,否则指标无法指导改进
如果只有一个“回答满意度”,发现退化后很难定位。应该为每一层定义可观察指标。
| 层 | 关键问题 | 可观察指标 |
|---|---|---|
| 接入 | 重要内容是否成功进入系统 | 解析覆盖、结构正确率、更新延迟 |
| 索引 | 当前可访问语料是否可搜 | 索引就绪率、删除传播延迟、ANN Recall |
| 召回 | 必需证据是否进入候选 | Evidence Recall@K、完整证据集命中率 |
| 重排 | 重要证据是否排到可用位置 | MRR、nDCG、固定预算下的证据覆盖 |
| 组装 | 证据是否真的进入最终输入 | Packed Evidence Recall、重复 Token 比例 |
| 生成 | 结论是否正确并被支持 | 正确性、支持性、引用准确与完整性 |
| 产品 | 系统是否帮助用户完成任务 | 任务成功、拒答质量、延迟、成本 |
OpenAI 的评估建议强调任务相关数据集、持续评估以及用人类判断校准自动评分。文档里的数值目标只是示例,不能直接作为所有企业 RAG 系统的上线标准。Evaluation best practices
检索指标的分母必须说清楚
对一个问题 q,设人工标注的相关证据集合为 R_q,返回前 K 条为 C_q:
Recall@K = |R_q ∩ C_q| / |R_q|
Precision@K = |R_q ∩ C_q| / |C_q|
RR(q) = 1 / 第一个相关结果的名次
MRR = 对所有问题的 RR 取平均;无相关结果时 RR = 0。
DCG@K = Σ_(i=1..K) (2^rel_i - 1) / log2(i + 1)
nDCG@K = DCG@K / 该问题理想排序的 DCG@K
无可用相关证据的问题需要单独定义计分,避免除以零;不应随意把它混进可回答问题的召回平均值。
这些公式还隐含一个条件:你知道相关集合。真实语料很难穷尽标注,因此可以通过多种检索器的候选池加专家补充来构建 judged pool,但必须承认未标注相关证据可能使指标有偏差。
切块策略变化时,原有 chunk ID 标注会失效。更稳妥的是以原始文档版本中的证据范围或事实单元标注,再映射到各版本 chunk。否则“召回率提高”可能只因为把同一段切成了更多块。
多跳问题需要完整证据集指标
合同问题至少需要一般条款、例外条款,以及当前客户适用版本。
只找回其中一个,Hit@K 已经可以为 1;MRR 也可能很好,但系统仍无法可靠作答。
因此可以标注若干最小充分证据集 G1、G2……,只要候选包含其中任意一套,就记为完整命中。这样能允许不同来源支持同一答案,又不把“找到一条相关内容”误当成“证据齐全”。
12. 一个数字例子:如何定位证据丢失?
下面使用一组刻意构造的数据,不代表真实系统表现:
回答必需证据:{一般条款 A,例外条款 B,适用版本 C}
召回 Top-20:包含 A、B、C。
重排 Top-5:包含 A、B、C。
最终上下文:只包含 A、C;B 因预算被裁掉。
模型回答:必须支付剩余金额。
此时:
- 候选证据 Recall 是 3/3。
- 重排保留的必需证据仍是 3/3。
- 打包后的证据 Recall 是 2/3。
- 最终回答错误。
调整向量模型无法直接修复这个例子,因为错误发生在上下文组装。
如果 B 从一开始就没有进入候选,则应该先检查接入、过滤和召回;如果 B 完整进入最终输入,模型却忽略它,则应检查生成模型、提示、证据布局和推理。
可以定义一个严格的、证据驱动成功事件:
I:权威语料中存在可访问且已正确接入的充分证据
R:候选检索保留充分证据
K:重排与上下文打包保留充分证据
G:生成正确且可支持的答案
P(I ∩ R ∩ K ∩ G)
= P(I) × P(R | I) × P(K | I,R) × P(G | I,R,K)
这是概率链式法则,不要求各阶段独立。它表达的是这条证据路径上的联合成功率,不等于所有可能回答正确的概率:模型可能凭参数知识猜对,另一条工具路径也可能得到答案。
工程上的意义是,某一层质量下降可能限制整体收益。盲目增强生成模型,不一定能弥补接入遗漏和证据截断。
13. 用 Oracle 实验区分检索失败和生成失败
只比较“换模型前后准确率”很难解释原因。更有帮助的实验是固定其他环节,替换一个中间产物。
| 实验 | 替换什么 | 帮助回答什么 |
|---|---|---|
| Gold evidence → Generator | 直接给人工确认的充分证据 | 模型在证据齐全时能否正确作答 |
| Gold candidates → Reranker | 候选中人工加入必需证据 | 重排能否保留正确证据 |
| Gold ranking → Packer | 按理想顺序提供证据 | 预算与扩展策略是否仍丢关键信息 |
| Exact search → ANN | 在同一向量空间比较 | 近似搜索损失有多大 |
| Current index → Fresh snapshot | 使用完整最新快照 | 错误是否来自更新与版本滞后 |
Oracle 不是无条件上界。例如人工证据过于干净,与实际上下文分布不同;不同错误还可能存在交互。因此这些实验用于诊断,不应被当成绝对因果证明。
随后做消融:稀疏、稠密、混合、加重排、加结构扩展分别测试。比较时同时控制最终上下文预算、候选规模和成本,避免一个方案只是因为读了更多文本而看起来更好。
14. 测试集应该覆盖系统真正会遇到的困难
一个有用的样本不只包含问题和标准答案,还应该包含:
问题、用户角色、查询时间、地区与产品条件
语料快照和版本
可回答性标签
必要事实与可替代充分证据集
预期引用范围
需要澄清或拒答的条件
风险类别
测试集至少包括:
- 直接查事实的问题。
- 表达与原文差异很大的问题。
- 产品编号、条款号、错误码等精确匹配问题。
- 需要多个条款共同判断的问题。
- 新旧版本冲突和历史时点问题。
- 文档确实没有答案的问题。
- 用户没有权限访问答案的问题。
- 表格、OCR、脚注和跨页内容问题。
- 恶意内容试图指挥模型越权的问题。
- 撤销权限、删除来源后再次查询的问题。
合成问题可以扩充覆盖,但如果全部由 chunk 自动反向生成,问题往往会贴近文档措辞,从而高估召回质量。应加入真实、脱敏的用户问题和专家构造的困难案例。
训练、调参和测试划分不能只随机拆问题。来自同一模板、同一文档或同一合同家族的近重复问题,可能造成泄漏。可以按文档家族、时间或业务实体分组划分,并保留冻结测试集。
15. 自动评审需要校准,线上实验需要结果指标
LLM-as-a-judge 可以评估主张是否被证据支持,但也可能偏好更长的回答,被格式影响,或重复生成模型的错误。
应提供明确评分标准、必要证据和原子主张;用专家标注集校准自动评分。做成对比较时随机交换答案顺序,检查位置偏差。对高风险问题保留人工复核。
至少分开报告:
- 正确性:答案是否符合权威事实。
- 支持性:给出的证据是否足以推出答案。
- 引用准确性:引用的片段是否支持对应主张。
- 引用完整性:需要支持的主张是否都有证据。
- 拒答表现:该答时是否答、不能答时是否克制。
一个回答可能正确但无证据,也可能忠实复述了一份过期文档而在业务上错误。单一分数会掩盖这些差异。
比较版本时,应在同一问题集上做配对评估,记录分项差值和不确定区间。若样本按文档或客户聚集,统计方法也应考虑这种相关性,不能把所有问题当成完全独立样本。
线上实验则需要关注任务完成、用户纠正、升级人工、延迟、成本和安全事件。点击引用不一定意味着回答正确,用户可能只是因为怀疑才点击;满意度也可能偏好自信但错误的回答。
缓存必须包含与正确性相关的条件,例如租户、权限版本、语料 generation、时间范围与模型配置。只按查询文本缓存答案,可能把一个用户的答案返回给另一个用户,或在政策更新后继续提供旧结论。
16. 端到端运行链路应该如何记录?
推荐为一次请求生成可追踪记录:
request_id / principal_scope / policy_version
raw_query / resolved_constraints / rewritten_queries
corpus_generation / parser_chunker_embedding_versions
每个召回通道的候选 ID、名次和耗时
融合与重排后的候选 ID、分数和截断情况
最终打包的证据 ID、来源、Token 数和丢弃原因
生成模型与提示版本
答案、主张与引用映射
各阶段延迟、费用、降级与错误分类
这些记录可以帮助回答:例外条款究竟从未接入、没有召回、被重排压下去,还是最后被预算裁掉?
日志本身可能包含敏感查询、合同信息和权限上下文,需要最小化记录、脱敏、访问控制与合理保留期。诊断便利不能成为复制整个私有知识库到普通日志的理由。
17. 一个可以执行的优化顺序
面对“RAG 回答不好”,建议按下面的顺序排查:
- 证明答案存在:在正确版本和授权范围内,是否确实有充分证据?
- 检查接入:原文、表格和例外是否被正确解析?
- 检查候选:稀疏、稠密和过滤分别在哪一步丢掉证据?
- 检查排名:候选中有证据,但能否进入最终预算范围?
- 检查打包:扩展、去重和裁剪是否损坏完整含义?
- 检查生成:有充分证据时,模型是否仍然犯同样错误?
- 验证收益:在冻结测试集和线上指标中,改善是否成立?
这套顺序不会保证每次都找到唯一原因,但能避免把所有问题都归结为“模型不够强”或“向量库不好”。
18. 真正理解 RAG,需要能回答哪些追问?
- 为什么 ANN Recall 很高,任务证据召回仍然很差?
- 为什么先取 Top-K 再做权限过滤会损害可回答性?
- 为什么换 Embedding 模型通常需要重建向量,而不仅是修改查询端?
- 为什么命中一般条款不能证明已经拥有充分证据?
- 为什么重排器无法解决候选阶段的遗漏?
- 为什么 chunk 级 Recall 不能直接横向比较不同切块策略?
- 如何证明错误来自上下文组装,而不是模型推理?
- 如何在权限撤销、文档删除和缓存命中之间保持约束?
- 如何验证提升来自算法,而不是更大的 Token 预算?
- 如何分别衡量答案正确、引用真实、证据足以支持结论?
RAG 的核心工程能力,是把来源、版本、权限、检索、上下文和答案连成一条可以检查的证据链。系统每增加一个优化,都应该说明它改善了哪一环、引入了什么代价,以及通过什么实验判断有效。
参考资料
- OpenAI — Retrieval:语义检索、属性过滤、排序与混合检索配置。
- OpenAI — File search:托管的文件搜索与生成集成。
- OpenAI Cookbook — How to parse PDF docs for RAG:文档结构提取思路;归档示例,旧 API 不作为当前接入依据。
- OpenAI Cookbook — Search reranking with cross-encoders:候选召回与联合重排的两阶段思路;归档示例,本文未采用其旧接口。
- OpenAI — Evaluation best practices:任务相关评估集、持续评估与人工校准原则。本文使用方法论,不依赖某个评测平台的生命周期。