大多数 RAG 教程都止步于 Notebook。它们加载几个精美的 PDF,每隔一千个字符切分一次文本,把片段塞进向量数据库,然后就称之为架构了。在周五的下午,这个演示运行得非常完美。但在生产环境中,同样的流水线会悄无声息地变成一个隐患。

检索系统中真正的瓶颈很少是模型或提示词(prompt)。而是数据摄取(ingestion)。RAG 流水线只能检索它被喂入的内容,如果喂入的内容充满噪音、陈旧或不完整,模型就会给出充满自信的胡言乱语。当用户抱怨机器人产生幻觉时,问题往往出在没人密切监控的上游数据流水线中。

白板陷阱

架构图让数据摄取看起来就像一个标有“文档 $\rightarrow$ 向量数据库”的单向箭头。现实情况要复杂得多。源系统会在不通知的情况下发生变化。HTML 布局会重新设计。URL 会重定向到通用的落地页。JavaScript 框架会在初始 HTTP 响应后更换内容。将数据摄取视为一次性的设置任务是第一个错误。它是一个持续的数据工程问题,值得像任何 ETL 流水线一样严谨对待。

为什么 RAG 的失败通常是数据喂入的失败

想象一下:用户向你的内部助手询问当前的退款政策。模型从向量库中提取了最相关的片段,并声称退款窗口为 30 天。而实际政策在上个季度已改为 60 天。LLM 并没有捏造错误的答案,它只是信任了错误的信息。检索层提供了一个旧页面,由于嵌入(embedding)在语义上看起来足够接近,模型将其视为真值(ground truth)。

这种模式不断重复。当语料库中充满了导航页脚、重复的新闻稿以及将表格切成两半的片段时,团队却在浪费数小时去微调 temperature 和 top-k。在优化生成(generation)之前,请先审计你的系统被允许知道哪些内容。

破坏数据摄取的七大陷阱

1. 第一次运行是骗人的

初始爬取时的绿色对勾几乎没有任何意义。生产数据是动态的。文档页面会被重构,博客永久链接会失效,站点地图(sitemaps)会悄无声息地丢失章节。如果你只验证流水线是否在没有报错的情况下完成,那你就是在盲目飞行。你需要验证输出。检查预期的文档是否存在,它们的结构是否仍能解析,以及总文本量是否因为某个源更改了分页方式而大幅缩减。

2. 爬取不等于摄取

获取 HTML 是最简单的部分。原始爬取会抓取一切:Cookie 横幅、“相关文章”侧边栏、广告块和页脚版权声明。如果你天真地对这些原始 HTML 进行分块,每一段文本都会携带导航菜单的碎片。当用户询问 API 速率限制时,检索器可能会返回一个包含 40% 侧边栏链接的片段。清洗提取至关重要。你需要识别主要内容区域,剥离样板内容(boilerplate),并移除在每个页面中重复出现的元素。否则,你不是在构建知识库,而是在为网站的界面元素(chrome)构建搜索引擎。

3. 分块破坏了语义

固定大小的分块是几乎所有快速入门指南中的默认做法,而这是危险的。如果纯粹按字符数切分文档,你会把表格从中间切断,把编号步骤中的第 4 步和第 5 步分开,并让列表项脱离其标题。一个仅包含价格表后半部分的块在语义上是毫无用处的。感知结构的(structure-aware)切分会尊重原始格式。解析标题层级。尽可能保持表格完整。在同一个 H2 或 H3 标题下按段落边界进行切分。如果列表足够短,请将其保留在单个块内。目标不是大小均匀的块,而是具有连贯意义的单元。

4. 时效性问题

对内部维基进行静态快照抓取属于简单模式。而从实时网络中持续摄取数据则非常困难。你需要知道页面最后一次抓取的时间、自那时以来是否发生了变化,以及信息在多长时间内保持有效。数据陈旧并不总是意味着日期看起来很旧。有时页面更新了文本但保留了相同的 URL,如果没有内容哈希(content hashing),你的系统永远不会察觉到变化。根据数据源的波动性建立清晰的刷新规则。金融数据源可能需要每小时检查一次,而公司的“关于我们”页面可能只需要每季度检查一次。记录时间戳并设置生存时间(TTL)边界,特别是如果你的领域涉及受监管或安全关键型的指导信息,因为过时的事实可能会造成实际伤害。

5. 重复污染

网站充满了重复内容。同一个产品描述会出现在分类页面、产品页面和促销落地页上。同一份新闻稿可能同时存在于 /news//press//blog/ 路径下。向量搜索不会自动去重。如果你的数据库中存有十个几乎相同的分块(chunks),它们可能会在 top-k 检索中挤掉那些多样且相关的结果。在进行嵌入(embedding)之前,你需要进行规范化追踪或内容去重。如果两个分块表达的意思相同,请保留权威来源并丢弃副本。你的检索器槽位有限,不要浪费它们。

6. 缺失元数据

没有元数据的向量数据库仅仅是一个没有上下文记忆的密集文本搜索引擎。智能检索依赖于过滤和排序信号,而原始嵌入无法提供这些信号。存储源 URL、抓取日期、文档类别和版本号。如果你摄取 API 文档,版本管理至关重要。否则,查询可能会将 v1 和 v2 的规范混杂在同一个答案中。如果你摄取人力资源政策,按地区或部门进行标记可以让你在结果到达模型之前对其进行过滤。元数据能将文本堆砌转化为经过策划的知识系统。

7. JavaScript 缺口

现代网站不会在第一个 HTML 负载中发送全部内容。它们先发送一个骨架,然后通过 JavaScript 调用进行“注水”(hydrate)。一个基础的 HTTP 请求可能只能看到加载图标和布局外壳。如果你的流水线无法执行 JavaScript,你摄取的将是空白页面或残缺片段,而且你甚至不会意识到出了问题。使用无头浏览器(headless browser)可以解决渲染问题,但也会引入新问题:更高的内存占用、更慢的吞吐量以及反爬虫检测墙。请审慎权衡,不要假装简单的 curl 等效操作对所有数据源都足够。

实用摄取清单

如果你正在构建或审查 RAG 数据流,请从这里开始:

  • 验证数据源覆盖范围和分页情况。 站点地图(sitemap)可能只列出了某个分类下的前十篇文章。请进行深度爬取,并验证分页或动态加载的内容是否确实被抓取到了。
  • 在分块前去除模板内容。 剔除导航栏、广告、页脚和重复的法律免责声明。如果一个短语出现在每个页面上,它就是噪声。
  • 使用感知结构的分块方式。 尊重标题、项目符号列表和表格。根据语义边界进行切分,而不是根据字符数。
  • 附加丰富的元数据。 包括 URL、抓取日期、内容类别和版本。确保这些字段在检索查询中是可过滤的。
  • 根据数据波动性设置刷新频率。 高频变动的数据源需要频繁重新爬取,而静态存档则不需要。
  • 监控语料库,而不仅仅是任务状态。 流水线可能以状态码 0 退出,但产出的却是垃圾数据。定期审计存储的分块样本,检查是否存在偏移(drift)和质量问题。
  • 定义版本管理和删除规则。 当源页面被移除时,删除其对应的分块。当页面更新时,覆盖它们或进行版本化处理。孤立数据(Orphaned data)是一个隐形的杀手。

关于嵌入的残酷真相

没有任何嵌入模型(无论多么先进)能够修复缺失的文档。如果你的数据流中仍保留着去年的副本,它无法猜到页面上周已经更新了。如果一个表格行因为糟糕的分块边界而与其标题分离,它也无法推断出其上下文。嵌入压缩了含义,但如果摄取层未能保留含义,嵌入也无法凭空创造含义。

检索质量始于摄取层。这一层决定了你的 RAG 系统是一个有用的工具,还是一个背后仅靠向量数据库支撑的、言之凿凿的骗子。

核心启示

不要仅仅依靠流水线仪表板来衡量数据摄取的健康状况。任务状态显示为绿色且日志干净,并不意味着语料库是干净的。打开数据库,去读一读用户实际会检索到的那些数据块。如果文本中充斥着版权声明、破碎的表格和过时的政策页面,那么问题不在于 LLM。先修复数据源。除此之外的一切,都只是在垃圾之上进行调优。