详细内容或原文请订阅后点击阅览
说“不在本文档中”的 RAG 必须出示四种证据
企业文档智能 [Vol.1 #B3] - 自信的错误答案是一个错误。没有任何理由的赤裸裸的“不回答”几乎同样糟糕。四块砖块中的每一块都有一个要显示的证据“A RAG That Says “Not in This Document” has to Show Four Kinds of Evidence”一文首先出现在《走向数据科学》上。
来源:走向数据科学RAG 系统可以给出的最有用的答案有时是“这不在本文档中。”但是当被问到问题时,模型往往会无论如何回答,因此必须内置诚实的“未找到”,而不是希望。让系统说“不”,并在正确的时候做出正确的决定,比让它说“是”更难。
本文是企业文档智能系列中的一个额外内容,该系列使用四块砖构建企业 RAG 系统。它证明了“我不知道”的合理性:一个自信的错误答案是一个错误,一个简单的不回答几乎同样糟糕,并且四块砖块中的每一块都有一个证据可以显示。
🧭该系列的新手?本系列中的每篇文章都位于我们的两个走向数据科学作者页面,Angela Shi 和 Kezhan Shi。这是查看所覆盖内容和所在位置的最短方法。
📓可运行的配套笔记本位于 GitHub 上:doc-intel/notebooks-vol1。
询问企业聊天机器人“人工智能在全球消耗多少电力?”,它会返回“我在本文档中找不到答案。”改写并再次询问:相同的答案。到那时,大多数用户就会放弃并转向谷歌。
系统做了正确的事情。它运行的文件是世界银行的《商品市场展望》(2025 年 4 月号;CC BY 3.0 IGO,如世界银行开放知识库使用条款中所声明的那样,其中规定了 2023 年之前出版物的默认许可证),这是一份关于石油、农业和金属价格的 63 页季度报告。 AI的耗电量不在里面。没有答案可寻。系统没有说谎。
但用户没有学到任何有用的东西。他们无法判断管道是否查找了应有的所有位置,或者是否在一次浅层嵌入搜索后放弃了。“我找不到答案”是一个需要辩护的结论。否则,它就会被视为失败,而不是事实。
该系列文章认为,结构化输出(第 8 条)通过迫使模型引用其证据:引文、页码、置信度,使“是”答案变得可验证。同样的逻辑适用于“否”的答案,同样的四块砖块贡献了一块证据。本文再次讨论了这四块砖块,解决了一个正确输出为“否”的问题,并展示了每块砖块必须产生什么,以便“否”答案是有道理的。
1. 四块砖,四种证据
参数压缩到一个表。
本文的其余部分按顺序遍历每一行,然后将它们在 CMO / AI 电力消耗案例上联系在一起。
2.解析:关系表就是证据
丢失解析的成本是不对称的。误报(提取噪声)会使检索变得更加混乱,但可以在下游恢复。假阴性(未能提取真实令牌)是沉默的:无答案的判决看起来是正确的,但它是错误的。答案就在那里;管道只是从未见过它。
好消息是解析模块(Article 5B,关系数据模型)已经准确生成了缺勤声明所需的数据。 parse_pdf 的输出不是一堵文本墙;它是一个小的关系型 DataFrame 集:文档包含的每种内容都有一个。line_df 是中心表:整个文档中每个文本行一行,包含页码、页面上的边界框以及检测到的行类型(散文、标题、表格行、图形标题)。其他桌子都挂在它上面。
对于无答案的判决,我们不编写新的解析函数。我们对已经存在的表进行聚合:
除 line_df 之外缺勤索赔仍需要检查的三个位置:
3.问题解析:枚举词汇
3.1 专家了解词汇
out = parse_pdf(pdf_path) # 给出 line_df, image_df, page_df, toc_df, ...# 解析砖的证据是对表的一些聚合。parse_coverage = {"pages_total": len(out["page_df"]),"pages_with_text": out["line_df"]["page_num"].nunique(),"images_total": len(out["image_df"]),"toc_entries": len(out["toc_df"]),"cross_refs_total": len(out["cross_ref_df"]),"objects_registered": len(out["object_registry"]),}# CMO 2025 年 4 月:63 页,63 个文本,426 个图像注册,# 9 个 TOC 条目,76 个体内交叉引用。聚合名称#表面缺勤索赔必须走——line_df中的文本是简单的#,其他行是图像文本和命名#对象(图、表、附件)的审计跟踪。
带有文本的图像:图表通常将其轴标签和图例作为图像嵌入文本而不是印刷跨度。 image_df 注册表列出了每个此类图像; OCR 过程将 ocr_text 列添加到该表中,而不触及 line_df。解析块暴露之前和之后的状态;报道报告列出了经过 OCR 处理的图像数量。
表格:单元格中的数字解析为一行,其邻居是同一行的其他单元格,而不是页面上其上方的单词。如果答案是“AI 在 2024 年消耗了 460 TWh”,并且该数字位于附近任何单元格中没有单词 AI 的表格中,则对 AI 的扫描会错过它。object_registry 会标记哪些页面托管表格,以便检索块可以在那里进行列感知扫描(有关表格解析的第 5 条部分)。
未解析的交叉引用。cross_ref_df 列出了正文中提及的每个“参见附件 B”、“图 3.D”、“表 11.E”。目标不在 object_registry 中的任何行都是解析间隙:主体承诺提取未找到的对象。每个缺口都是一个可能隐藏答案的候选点。
因此,解析模块的证据是从它已经生成的表中得出的一个小摘要,而不是一个新组件。无答案判决可以指出:“已解析 63 个页面,63 个包含文本,0 个未解析的交叉引用。”这足以让用户相信解析层已完成其工作。
检索步骤的好坏取决于它所扫过的关键字。如果专家列出了人工智能但忘记了人工智能,那么检索就会错过阐明该概念的每一行。问题解析砖的工作是将专家作为事实来源,并在下面设置算法安全网,将关键字集推至穷举。
在企业 RAG 中,专家几乎总是知道其领域的关键字。这是一个简单的案例,涵盖了大多数问题。他们列出了答案会提到的概念,并为每个概念列举了变体:同义词、首字母缩略词、缩写词、英语/法语/德语形式(如果语料库是多语言的)。
对于CMO/AI电力的例子,专家提供了这个表格:
这正是第 6 条中的 Concept_keywords_df 模式。驱动正常检索的相同数据结构也驱动缺勤声明。签名相同;仅输出的解释发生变化。
3.2 概念聚类作为安全网
极少数情况是专家不确定他们是否列出了每个变体。安全网是算法性的:通过嵌入相似性对语料库标记进行聚类,然后暴露专家关键词所在的聚类,并询问是否应该添加任何聚类成员。
由专家签署的关键字集是问题解析模块的证据。
4.检索:扫描,而不是top-k
检索调用的形状因此发生变化:
7. 结论
def Expand_keywords_via_clustering(seed_keywords: list[str],corpus_tokens: list[str],*,n_neighbors: int = 10,cosine_floor: float = 0.85,) -> list[KeywordCandidate]:"""对于每个种子关键字,返回其在语料库中余弦下限之上的前 N 个最近的标记。专家对候选者进行评审并且接受者拒绝每一个。"""seed_vecs = {k: embed(k) for k in seeds_keywords}corpus_vecs = {t: embed(t) for t in corpus_tokens}candidates = []for seeds, sv in seed_vecs.items():scored = [(t, cosine(sv, tv)) for t, tv in corpus_vecs.items()]scored.sort(key=lambda x: -x[1])for t, sim in Scored[:n_neighbors]:if sim >= cosine_floor and t not in seeds_keywords:candidates.append(KeywordCandidate(seed=seed, Candidates=t,相似性=sim))返回候选者
输出是一个列表,而不是一个决定。专家决定添加什么。候选人只是一个值得考虑的建议,而不是替代专家的判断。该系列的编辑立场是:扩大专家,而不是取代他们。聚类揭示了专家可能没有想到的事情;专家验证关键字集中的内容。
对于普通问题(第 7 条),检索返回 top-k:最有可能包含答案的几页或几行。对于没有答案的主张,该框架失败。系统无法通过查看前 10 页来证明答案不存在。它必须查看提到任何概念的每个页面。
def scan_for_absence(line_df: pd.DataFrame,concept_keywords_df: pd.DataFrame,) -> pd.DataFrame:"""在语料库中每个(概念、页面、行)匹配返回一行。不是 top-k。不评分。每个概念的每个变体的每次点击。"""rows = []for Concept_row in Concept_keywords_df.itertuples():pattern = r"\b(" + "|".join(map(re.escape, Concept_row.variants)) + r")\b"matched = line_df[line_df["text"].str.contains(pattern, regex=True, case=False, na=False)]for line in matched.itertuples():rows.append({"concept": Concept_row.concept,"variant": extract_first_match(pattern, line.text),"page": line.page_num,"line": line.line_num,"snippet": line.text[:120],})return pd.DataFrame(rows)
返回值是一个DataFrame,而不是一个排名列表。每行都是一个证据条目:“在 P 页,L 行,在片段 S 中找到了概念 C 的变体 V”。结果的基数是无答案判决所开启的:
某些概念的零行(任何地方都没有出现变体)→ 强有力的证据表明该文档根本没有涵盖该概念。
多行,但从未位于同一位置(概念出现在不同页面上,从不在一起)→ 中等证据:概念存在,但没有段落谈论它们的交集。
共置行(同一页面或段落中的多个概念)→ 缺席的弱证据。一代人必须查看这些片段,并决定他们是回答问题还是只触及问题。
对于 CMO April 2025 案件,结果是明确的。对上面设置的关键字进行全面扫描将返回:
命中列表是检索砖的证据。关于这个问题和这份文件,结论不言而喻。
5.一代:结构化的“没有答案”并有理由
最后一块砖获取解析覆盖率报告、经过验证的关键字集和命中列表,并将它们转换为用户看到的实际响应。该模式镜像第 8 条中的 AnswerWithEvidence,但活动字段不同。
在 CMO / AI 电力案例中,模型的输出如下所示:
6. 停止的地方
上面的框架没有完全涵盖的三种边缘情况(按重要性排序)。
8. 来源和进一步阅读
本系列的早期内容:
class AbsenceJustification(BaseModel):"""当系统无法从第 8 条中的 corpus.Companion 回答到 AnswerWithEvidence 时返回。"""answer: None = None # 对于此模式始终为 Nonereason: Literal["concept_not_found", # 任何概念的变体都不会击中任何内容"concept_found_but_off_topic", # 概念存在但不会共存"ambigously_question", # 问题本身是太模糊,无法扫描]searched_concepts: list[ConceptSearch] # 查找的内容,命中计数closest_mentions: list[Mention] # 任何附近的片段,以及why-notparse_coverage: ParseCoverage # 解析砖覆盖的内容suggestion: str | None = None # 可选重构或离语料指针类 ConceptSearch(BaseModel):concept: strvariants: list[str]pages_with_hits: list[int]line_hits: intclass Mention(BaseModel):page: intline: intsnippet: strwhy_not_an_answer: str # 一句话:为什么这段话很近但没有回答
生成步骤不会发明任何字段。该模式的前三个字段直接来自之前的砖块(解析覆盖率、关键字集、命中列表)。该模型的工作范围更窄:选择原因,选择将哪些提及标记为最接近,为每个提及写一句话“为什么这不是答案”,并可选择建议重新表述。
{"answer": null,"reason": "concept_not_found","searched_concepts": [{"concept": "AI","variants": ["AI", "人工智能", "机器学习","深度学习", "神经网络", "GenAI","生成式人工智能", "LLM", "大语言模型"],"pages_with_hits": [],"line_hits": 0},{"concept": "电力消耗","variants": ["电力", "电力消耗", "能源使用","kWh", "MWh", "TWh", "电力负荷","电力需求", "电力需求"],"pages_with_hits": [30, 31],"line_hits": 7},{"concept": "数据center","variants": ["数据中心", "数据中心", "数据中心","服务器场", "超大规模", "云基础设施"],"pages_with_hits": [],"line_hits": 0}],"closest_mentions": [{"page": 30,"line": 89,"snippet": "预计新兴市场和发展中经济体的电力需求将不断增长to","why_not_an_answer": "在煤炭需求背景下提到一个国家不断增长的电力需求;人工智能行业不是主题,也没有给出人工智能消费的数据。"}],"parse_coverage": {"pages_total": 63,"pages_with_text": 63,"pages_ocred": 0},"suggestion": "大宗商品市场展望涵盖石油、农业、金属和化肥;它没有讨论人工智能行业的能源需求。有关人工智能电力消耗的数据,请参阅 IEA 的“电力 2024”报告或专门的人工智能能源展望。”}
阅读此输出的用户可以立即学到三件事。该系统确实以 9 个不同的名称查找人工智能,并在 63 页中的任何地方发现了零命中。其中有一篇与电力相关的文章,但它涉及的是印度的煤炭电力需求,而不是人工智能。如果他们想要答案,CMO 就是不该询问的语料库。没有答案现在是一个有用的回应。
部分答案:一个问题的答案可能部分位于语料库内,部分位于语料库之外。“欧盟如何根据《人工智能法案》监管人工智能,以及与美国政策相比如何?”在仅限欧盟的法律语料库上。检索砖将为欧盟一方返回命中,而为美国一方返回任何内容。正确的回答既不是自信的“是”,也不是明确的“否”;这是一个结构化的部分答案,显示了已发现的内容,并明确说明了未发现的内容。其架构是 AnswerWithEvidence 和 AbsenceJustification 的第三个同级,具有answer_partial: str 和missing_concepts: list[ConceptSearch] 字段。
哪些有效,哪些无效
文档解析
一代
模棱两可的问题:“覆盖范围怎么样?”在没有上下文的情况下提问。由于没有定义的概念集,系统无法进行扫描。正确的回应是澄清请求,而不是拒绝回答。区分两者的信号是问题的解析(第6条):如果问题解析器无法提取概念,那么问题就是问题,而不是语料库。
敌意或超出范围的问题:在保单语料库中“生命的意义是什么?”。系统可以正确地说没有答案,但是在解析覆盖率报告和最接近的提及上花费精力是浪费的。管道应该有一个上游“这个问题是否在范围内”检查,当问题解析器将概念标记为与任何语料库标签无关时,该检查会短路全面扫描。当这种查询频繁时,管道成本很重要。
这些案例共享一个结构:上面的无答案模式是缺勤声明的正确形状,但它并不是每个“我无能为力”输出的正确形状。将其视为三个兄弟姐妹之一(是/部分/否)而不是单个后备可以干净地处理变化。
一个可以辩护的“没有答案”不是一个单独的句子;这是四块砖块共同产生的证据链。解析报告覆盖范围,问题解析报告专家关键字集,检索报告扫描,生成报告最接近的提及及其不回答的原因。用户看到作品后可以对解析、关键词设置、最接近的提及提出异议。
与本系列其余部分的对称性是更重要的一点。当模式强制模型引用其证据时,是的答案是可验证的(第 8 条);当模式强制管道公开其搜索时,没有答案是可验证的。这是同一块砖上的同一学科,并且都输出结构化的土地。
Rajpurkar 等人将“无答案”确立为一流输出的规范基准。 (SQuAD 2.0,ACL 2018)。 Choi 等人在给定段落中提出了许多问题无法回答的框架。 (QuAC,EMNLP 2018)。这里使用的管道端扫描的模型端反射令牌类似物是 Asai 等人。 (自我 RAG,ICLR 2024)。文章的框架:三兄弟答案模式(是/部分/否),在无答案分支上有一个可辩护的证据链,解析覆盖率报告,专家关键词扫描,最近提及,每一步都可审计。
文档智能:系列介绍。该系列构建了什么,一砖一瓦,以及按什么顺序。
基线企业 RAG,从 PDF 到突出显示的答案。端到端的四块管道:PDF 输入,突出显示的答案输出。
嵌入并不神奇:RAG 检索的可预测故障模式。嵌入相似性在哪里获胜(同义词、拼写错误、释义),在哪里可以预见地破坏(未知术语、否定、术语与答案相关性),以及如何使用它。
RAG 不是机器学习,ML 工具包解决了错误的问题。为什么块大小扫描和微调优化了错误的事情;而是按问题类型进行路由。
从正则表达式到视觉模型:哪种 RAG 技术适合哪个问题。两个轴,文档复杂性和问题控制,为每种情况选择技术。
我们在生产中经常看到的 10 个常见 RAG 错误。十个生产错误一一整理,并针对每个错误进行了修复。
使用循环工程构建文档结构:从 RAG 的正文版式中恢复 PDF 的轮廓。当 PDF 完全没有内容页时,从正文排版重建轮廓:六个信号,一个有界循环。
单文档管道
一个文档到一个语料库
在完全代理 RAG 之前:了解您如何决定以及您选择的解析方法。在将循环交给代理之前,将解析方法作为目录,并决定运行哪个方法。
大多数 RAG 幻觉都是提取错误:类型化生成合约的七种模式。模型提取错误的七种常见方式,以及捕获每一种错误的类型化合约。
RAG 生成的循环工程:从廉价本地模型到托管旗舰的 LLM 级联。从廉价的本地模型开始,只有当答案不成立时才升级,经过衡量。
通过减少 LLM 调用(而不是购买更快的模型)来降低企业 RAG 管道的延迟和成本。通过减少调用模型的次数和降低成本来降低管道的延迟和成本,而不是购买更快的模型。
RAG 工作流程和循环工程:决定何时循环和何时停止的调度程序。反馈循环、有界迭代和调度程序组成一个工作流程。
RAG 的循环工程:每个步骤内的小循环,整个管道的大循环。循环的两个尺度:每个砖块内的小有界循环,以及跨越它们的大的生成触发循环。
三种 RAG 语料库,以及构建错误语料库的成本。三个问题告诉您文档集合具有哪种形状,以及构建错误文档集合的成本。
