加载中...
  • RAG 全流程:从一堆文档到 Agent 能用的知识库 loading

    吃透 AI Agent 开发 · 第 20 篇 · 第四章 · Context Engineering

    RAG 不只是向量检索。切分、元数据、召回、重排、引用和过期处理共同决定知识能不能被可靠使用。

    RAG 不只是向量检索。切分、元数据、召回、重排、引用和过期处理共同决定知识能不能被可靠使用。

    从“搜到一段文字”开始怀疑

    团队经常把文档丢进向量库,看到搜索结果就宣布 RAG 完成。但 Agent 面对的不是“这段文字像不像问题”,而是“这条证据能不能支持当前动作”。一个过期的权限文档,可能比没有结果更危险,因为它看上去非常相关。

    知识进入 Agent 的生产线

    flowchart LR
      Source[文档来源] --> Parse[解析与清洗]
      Parse --> Chunk[按语义切分]
      Chunk --> Metadata[版本/权限/来源]
      Metadata --> Index[索引]
      Query[任务查询] --> Retrieve[召回]
      Index --> Retrieve
      Retrieve --> Rerank[重排与过滤]
      Rerank --> Evidence[带引用证据]
      Evidence --> Context[上下文注入]
    

    每条边都可能改变答案质量。解析丢了标题,切分打断了条件,元数据缺少版本,重排只看相似度,最终模型拿到的是“看起来像答案”的碎片。

    切分应该服务于使用方式

    接口文档适合按端点和参数切分,故障复盘适合按时间线切分,政策文件则要把例外条件和适用范围放在同一个片段里。固定按字符数切割,最容易把“除非”“仅当”“不适用于”切到下一块。

    interface KnowledgeChunk {
      id: string
      text: string
      source: string
      version: string
      acl: string[]
      validFrom?: string
      validUntil?: string
    }
    
    function citation(chunk: KnowledgeChunk): string {
      return `[${chunk.source}#${chunk.id} v${chunk.version}]`
    }
    

    引用不是装饰,它让模型的结论可以被人回到原文检查,也让检索失败能被区分为“没有证据”还是“证据没有权限”。

    召回、重排和注入各有责任

    阶段 主要问题 不该做什么
    召回 有没有漏掉候选 直接当最终答案
    重排 哪些候选更贴合任务 删除来源和版本
    过滤 是否过期、越权、互相冲突 用模型一句话代替规则
    注入 怎样控制顺序和预算 把整库原文塞进 Prompt

    q-code 参考:从知识操作追到上下文

    阅读顺序 路径 要找的证据
    1. 知识入口 src/gitlab-kb/index.ts 搜索和发布接口如何分开
    2. 知识操作 src/gitlab-kb/operations.ts 来源、权限和失败如何表达
    3. 运行上下文 src/context/runtime-context.ts 检索结果如何带预算进入请求
    4. 搜索工具 src/tools/search-tools.ts 模型拿到的是候选还是最终事实

    失败反例:把最相似的片段当成真相

    把最相似的片段当成真相,可能返回一篇旧的部署说明,模型据此建议已经废弃的命令;也可能召回一段只适用于管理员的内部政策,却没有在注入前检查当前用户。

    修复时为检索结果增加拒答条件:没有足够新鲜的版本、没有可见权限、多个来源互相冲突时,不自动生成确定答案。Agent 可以提出下一步查证动作,但不能用语言流畅掩盖证据不足。

    用一组“知道答案”的问题做验收

    练习:从一个真实知识库挑选十个问题,给每个问题标出正确来源、版本、权限和必须引用的句子。分别测量只看向量相似度、加入元数据过滤、再加入重排后的命中率。至少放入一个过期文档和一个相似但无关的文档,检查系统是否能拒绝误用。

    先建立 Source Registry,再谈 embedding

    知识库的入口可能是 Git 仓库、Wiki、网盘、数据库和工单系统。同一份发布手册会被复制到三个地方,标题稍有差异。若没有来源登记,索引会把副本当成三份独立证据,模型看到“多数文档都这样说”时产生虚假的共识。

    Source Registry 可以记录 canonicalId、原始 URL、拥有者、同步方式、权限源、内容 hash 和版本。同步时先判断是同一来源更新、镜像副本还是新文档;副本可以保留检索入口,但引用回 canonical source。

    {
      "canonicalId": "release-guide/prod/v4",
      "source": "gitlab-wiki:platform/release-guide",
      "mirrors": ["drive://ops/release-guide-copy"],
      "owner": "platform-team",
      "aclSource": "gitlab-project-membership",
      "contentHash": "b7f1...",
      "validFrom": "2026-05-01",
      "supersedes": "release-guide/prod/v3"
    }
    

    embedding 可以重算,来源关系若一开始丢掉,后面很难补。

    解析阶段要保留文档结构

    把 HTML 转纯文本时删除导航很合理,连标题、表格表头、代码语言和警告框一起删掉就会损失语义。政策里的“仅适用于生产环境”可能是小标题,表格某列的“例外”决定后续每个单元格含义。

    解析产物可以是一棵 block tree,再根据文档类型切 chunk。chunk 保存 heading path,例如“生产发布 > 回滚 > 数据库迁移”,引用时用户能快速回到位置。代码块与解释段尽量放在同一 chunk,超长时用 sibling links 关联。

    扫描 PDF 还要记录 OCR 质量和页码。识别置信度低的数字、金额和命令不能直接作为高风险操作依据;结果里显示页码和低质量提示,必要时要求看原页截图。

    切分一次发布手册

    假设原文有三个章节:发布前检查、执行步骤、失败回滚。“执行步骤”第 4 条写数据库迁移,“失败回滚”说明只有迁移未提交时才能自动回滚。按 500 字固定窗口切分,条件可能落到不同 chunk。

    更好的切分以任务单元为中心:迁移步骤 chunk 带上对应前置条件和回滚限制;通用检查单单独成块;大段命令输出示例不进入索引正文,只保存 artifact 引用。

    chunk overlap 不是越大越好。过大 overlap 会让相邻块重复命中,挤掉其他互补证据。离线评测应检查一个问题是否同时召回步骤和限制,而不是只看任一块包含关键词。

    ACL 过滤应该尽量早,也要在返回前再做

    先召回所有文档再过滤权限,可能在日志、缓存或重排模型里暴露无权内容。能在索引查询层应用租户、项目和用户组过滤,就不要把敏感候选拿到应用层。

    权限会变化,索引 metadata 可能滞后。返回正文前再向权限源确认高敏文档,尤其是跨租户和人员离职场景。缓存 key 包含访问范围,不能让管理员查询结果被普通用户复用。

    “没有权限”和“没有结果”对最终用户可能都需要模糊处理,避免泄露文档存在;对审计则保留拒绝原因和策略版本。

    从用户问题生成检索任务

    用户问“今晚发布失败后数据库怎样回滚”,查询里至少有环境、时间、动作和风险。直接 embedding 原句可能被“今晚”这类低价值词影响,也可能漏掉内部术语 migration rollback。

    查询规划可以生成多个受控子查询:关键词检索精确命令,语义检索找回滚解释,metadata 限定 production 和当前版本。重排时优先同时包含失败条件和恢复步骤的证据。

    查询扩展由模型生成时要保留原始问题,防止改写漂移。若模型把“数据库回滚”改成“代码回滚”,系统仍能从原始实体检查偏差。

    引用必须和生成内容对齐

    把三条文档链接统一放在回答末尾,不足以证明每个结论来源。生成上下文给每个 chunk 稳定 citationId,要求模型在具体断言后引用;生成后验证引用 ID 存在,并检查引用 chunk 是否包含支持句。

    模型可能引用一个真实 chunk,却给出 chunk 没说的结论。确定性检查能发现 ID 伪造,语义支持还需要规则、人工或 Judge。高风险答案可直接展示关键原文短句,让用户判断。

    没有足够证据时,回答“当前资料无法确认”,并列出已查范围和下一步。这是 RAG 的成功路径,不是系统失败。

    新鲜度是一项持续任务

    文档同步不能只在建库时跑一次。Registry 记录 lastFetched、sourceVersion 和同步错误;周期任务增量更新,删除或权限变化写 tombstone,使旧 chunk 从可见索引撤下。

    若源站暂时不可用,旧内容可以继续作为 stale 候选,但注入时标明最后验证时间。对于发布、权限和价格文档,可以设置硬过期,超过期限宁可拒答。

    内容更新后,先构建新 chunk 与索引快照,全部完成再原子切换版本。逐块覆盖会让一次查询混合新旧章节。

    用端到端问题定位管线哪一段坏了

    Golden set 不只保存问题和答案,还保存 expected source、required passage、用户权限、as-of 时间和允许拒答条件。失败时逐层看:源文档是否同步、解析是否保留句子、chunk 是否把条件切断、召回是否命中、过滤是否误删、重排是否降权、生成是否正确引用。

    如果正确 chunk 已在 top-3,答案仍错,继续调 embedding 没有意义;若 chunk 根本未入库,换生成模型也不会有帮助。RAG 全流程的价值,就是让“知识库答错了”变成一个可定位的生产线故障。

    拒答也要带一份“查过什么”的证据包

    用户问“生产退款最多多久到账”,系统只召回测试环境说明和一份已过期政策。直接回答一个最像的数字危险,简单说“不知道”又让用户无法继续。

    可以返回证据包:查询时间、访问范围、命中来源、拒绝原因、缺少的权威来源与建议动作。正文不必暴露无权限文档标题,只说当前权限下没有有效生产政策。

    result: insufficient-evidence
    searched: policy wiki + support handbook
    accepted: 0 current production sources
    rejected: 1 expired, 1 environment mismatch
    next: ask policy owner or refresh source release-guide/prod
    

    这种拒答仍可被 Eval 验证:过期片段不得进入确定答案,环境不匹配要被过滤,最终不虚构时限,同时给出可执行查证。系统从“必须每问必答”转为“有证据才定论”。

    线上还可以统计 insufficient-evidence 的主题分布。某类问题频繁拒答,可能不是模型差,而是 Source Registry 缺负责人、同步任务失败或权限映射过严。RAG 团队据此修资料生产线,而不是只调 top-k。

    增量重建要保证查询只看到一个版本

    一份 200 页手册更新第 12 章,不必重算全部 embedding。同步器按 block/chunk hash 找变化,重建受影响 chunk 与相邻关系;删除的旧 chunk 写 tombstone。新快照完成解析、索引和抽样校验后,查询别名一次切换。

    若边构建边对外可见,用户可能拿到新第 12 章和旧目录,引用定位失败;更糟的是 ACL 更新只完成一半。Snapshot ID 要贯穿候选、引用和回答,本轮检索不混版本。

    构建失败时继续服务上一个已知好版本,并标记 stale/lastSuccessfulSync。对于有硬新鲜度要求的政策库,超过阈值停止确定回答;普通历史文档可带警告继续查询。

    表格和代码的检索单元需要单独设计

    表格一行脱离表头就失去含义。解析时每个 row chunk 附表名、列名与单位,相关行可合并注入;跨页表格先复原结构。代码文档按符号、签名、说明和示例组织,错误栈里的函数名可精确召回。

    模型回答价格或限制时,引用表格具体行与版本,不能只引用整页。代码片段则带 language、path、symbol 和 source commit,避免把旧示例当当前实现。

    这类专用 chunk 往往比统一 800 token 切片提升更大。RAG 设计应从资料的使用方式出发,而不是让所有文档迁就同一个向量库输入框。

    生产管线要能从索引重建回原件

    抽样从检索结果反查 canonical source,验证 chunk text、heading path、ACL、version 和 citation anchor。找不到原件的孤儿 chunk 立即撤下,不让模型引用无法核验的内容。

    每天统计同步延迟、解析失败、孤儿、tombstone 积压、ACL 拒绝、空召回和引用失效率。一个回答错误只是表象,这些管线指标能更早发现知识库正在腐烂。

    本文目录
    本文目录