RAG 如何补上突发文件的采集盲区
一份新发布的政府 PDF 暴露了 RAG 采集的两处盲区:来源机构不在订阅范围,正文藏在附件中。作者用临时加载器接入既有分块、嵌入和替换流程,约一小时完成补录。
AI 深度解读
一份已经冲上 X 热点的英国政府文件,为什么进不了每天更新的 RAG 知识库?作者团队发现,采集任务既找错了发布机构,也没有读取附件。真正影响回答质量的内容,藏在一份 62 页 PDF 里。
他们用约一小时补上入口,复用原有入库流程,并处理了脚注误分章节、表格错序和密钥读取错误。这篇值得读的地方,是它展示了 RAG 从「抓到网页」到「可靠回答」之间,究竟还隔着哪些工程细节。
每晚都更新,为什么还会漏掉重要文件
据原文,英国 Home Office 在 11 月 2 日英国时间 16:09 发布了 earned settlement 最终决定,涉及永久居留许可(ILR)的标准等待期从 5 年调整到 10 年。不到一小时,消息就进入 X 热点,但他们的索引里没有。
原因不是调度太慢。采集程序只通过 GOV.UK search API 查询 UK Visas and Immigration,文件却由 Home Office 发布。即使找到页面,只读正文也拿不到完整内容。
下面这个接口结构说明了正文和附件的区别:
GET https://www.gov.uk/api/content/government/consultations/earned-settlement
details.final_outcome_detail -> 2 KB of HTML summary
details.attachments[] -> the PDF, on assets.publishing.service.gov.uk
要盯住的是 details.attachments[]:页面摘要只有 2 KB HTML,实质内容在附件中。
作者因此补了一个小型加载器,把 PDF 整理成与夜间任务一致的文档结构:标题、URL 和文本章节,再交给已有的切块、向量化和替换函数。
定时任务执行成功,只能证明程序跑完了,不能证明知识覆盖完整。
复用入库函数也有实际价值:切块大小、point ID 和旧内容清理方式保持一致,重复执行才能更新已有段落,避免堆出多份重复内容。
PDF 能提取文字,为什么仍然不能直接检索
作者使用 pypdf 提取文本,但输出离可读段落还很远:大约每 40 行出现页码和页面标记,正文按 PDF 栏宽硬换行,目录又把标题重复了一遍。
他们做了三步清理:删除页码和标记;把折行重新拼成段落,空行结束段落、项目符号开启新段落;跳过第 8 页真正的 Introduction 之前的内容。
随后,章节识别又出了错。程序按 Chapter N: 切分,第一次却在第 2 章中切出了两个额外的 Chapter 7: Refugees。它们其实来自脚注中的交叉引用。作者利用这份文件的格式特点修正规则:真正的章节标题不以句号结尾,末尾有句号的匹配行继续按正文处理。
这条规则对当前文件有效,原文没有证明它适合其他 PDF。
更危险的是表格。四条定居路径被按列提取,3 years、5 years、10 years 和收入数字虽然都在,彼此的对应关系却乱了。检索可能命中全部关键词,模型仍然会拼出错误条件。
作者保留原文,在前面补了一份逐路径展开的纯文本表格,逐项照抄数字。例如,10 年路径要求过去 7 年中有 5 年收入达到 £12,571,并满足全部强制条件。
文字没有丢,不代表信息关系没有丢。
更新失败时,旧索引能不能继续用
第一次服务器运行在首个 embedding 请求就失败了,错误是 API key not valid。作者用 grep 从 shell 脚本提取密钥,拿到的却是带引号的 "${GEMINI_API_KEY}",而非环境变量的实际值。
这次失败没有影响旧索引,因为清理旧数据发生在新数据上传成功之后:
qdrant.upsert(collection_name=COLLECTION, points=points, wait=True)
qdrant.delete(collection_name=COLLECTION, points_selector=Filter(
must=[FieldCondition(key="source_url", match=MatchValue(value=url))],
must_not=[HasIdCondition(has_id=[p.id for p in points])],
))
关键是 must_not:清理同一 source_url 的旧点时,排除本次上传的 ID。point ID 使用 uuid5(url + "#section.chunk"),相同位置的段落会落到相同 ID。
本次处理得到 15 个章节、101,000 个字符,切成 91 个最多 1,500 字符的段落,另有新闻稿的 4 个段落。向量模型是 gemini-embedding-001,维度为 1,024;文档使用 RETRIEVAL_DOCUMENT,问题使用 RETRIEVAL_QUERY。作者记录的向量化和上传耗时不到一分钟。
之后,他们用 4 个贴近 X 用户提问的问题探测检索结果。不过,提供的原文没有展示具体问题和结果,只提到第三项结果出现了旧版长期居留指引。作者认为它仍是有效现行法律,因此保留。
我的判断
我认同这次修补的顺序:先补采集覆盖,再恢复文本结构,最后验证检索。单纯换模型或调整 chunk 大小,解决不了附件没采到、表格对应关系错乱的问题。
但它仍是一次经验记录。原文没有提供清理前后的检索对照,也没有给出回答准确率;「不到一分钟」只覆盖向量化和上传,不能拿来估算通用 PDF 管道的成本。
先写后删也不等于原子切换。这次错误发生在首个 embedding 请求,确实没有进入写入阶段;但仅凭这段代码,不能推导出上传中途失败时索引必然完全不变。章节和切块位置变化后,稳定 ID 是否仍代表同一段内容,也需要额外验证。
内容时效尤其不能只看发布日期。原文转述的新方案包括:过去 4 年中有 3 年收入达 £125,141 可走 3 年路径;过去 5 年中有 3 年达 £50,271,或从事按全国薪级付薪的 30 种医疗教育岗位,可走 5 年路径;指定违规或公共资金使用情形可能延至 20 年,相关审查涉及申请前 5 年。
此外还有英语从 B1 升至 B2、关闭 10 年长期居留路径并停止计入 Student 和 Graduate 时间,以及前年份未明确的「去年 5 月前」加入的护理人员,在收入 £25,000 且满足担保规则时保留 5 年路径。以上都是原文说法,且原文明确没有实施日期。检索系统必须让新方案与现行规则各自带着适用状态,否则「更新鲜」反而可能造成误答。
能拿来做什么
- 给日常 AI 编程助手维护知识库时,把发布机构、正文和附件分别检查;用一份已知重要的新文件验证覆盖范围,比只看定时任务日志更有用。
- 给 PDF 增加检索前预览,优先人工核对表格、金额、年限和章节边界;发现对应关系错乱时,保留原文并补充可追溯的结构化文本。
- 更新向量库时采用可重复执行的 ID 和先写后清理顺序,再检查部分上传失败、切块变化时的实际行为。
- 上线前准备真实问题,同时检查命中内容、来源状态和生效日期;将来方案与现行文档同时命中时,不要仅按新旧顺序决定答案。