Agent 开发实战 · 第 6 篇

给 Agent 接入可追溯知识库

完成有权限过滤、有引用、会处理冲突与证据不足的 RAG 链路。

38 分钟开发进阶

学完能做什么

你会搭建一条从文档入库、检索、重排到带引用回答的最小 RAG 链路,并为“没有证据”“材料冲突”和“权限不够”设计明确的处理方式。

RAG 不是把整库文档塞进提示词

RAG 的核心是:收到问题后,只取出少量相关且有权限的材料,与问题一起交给模型,并把来源带回答案。

text
离线:文档 → 解析 → 切分 → 元数据 → 向量/关键词索引

在线:问题 → 权限过滤 → 召回 → 重排 → 组装证据
                                     ↓
                             模型回答 → 引用与校验

检索提高“找到依据”的机会,不保证模型一定正确。最终结果仍要经过引用、冲突和风险检查。

入库前先治理材料

知识库质量主要由原始材料决定。为每份文档保存:

  • 稳定的文档 ID 和版本;
  • 标题、作者、更新时间和生效状态;
  • 业务范围、语言与文档类型;
  • 权限标签和保密等级;
  • 原始位置与内容哈希;
  • 失效、替代或删除关系。

过期制度和现行制度同时出现时,没有版本元数据的检索很容易给出错误答案。

切分要保留语义和定位

不要只按固定字符数机械切。优先按标题、段落、列表和表格边界切分,再控制块大小和少量重叠。

每个片段至少包含:

json
{
  "chunk_id": "policy-17#section-3#chunk-2",
  "document_id": "policy-17",
  "document_version": 5,
  "heading_path": ["报销制度", "差旅", "住宿标准"],
  "text": "……",
  "source_url": "/docs/policy-17#section-3",
  "acl": ["finance", "employees"],
  "effective_at": "2026-07-01"
}

片段离开原文后仍应知道自己属于哪一节、哪个版本,才能正确引用和删除。

混合检索比单一检索更稳

语义向量适合近义表达,关键词适合精确型号、错误码和专有名词。常见流程是:

  1. 对问题做必要的规范化,不改写关键实体;
  2. 先按用户身份和业务范围过滤;
  3. 并行进行关键词与向量召回;
  4. 合并去重后重排;
  5. 选择少量证据进入上下文;
  6. 对低相关结果直接返回“材料不足”。

权限过滤必须在返回文本给模型之前完成,不能先召回全部内容再让模型自己忽略。

为回答定义证据协议

可以要求模型返回结构化结果:

json
{
  "answer": "……",
  "claims": [
    {
      "text": "住宿上限为……",
      "citations": ["policy-17#section-3#chunk-2"]
    }
  ],
  "conflicts": [],
  "insufficient_evidence": false
}

程序随后检查每个引用 ID 是否确实来自本轮证据、用户是否有权访问、引用片段是否支持对应结论。引用不存在时拒绝发布,而不是生成一个看起来像链接的字符串。

面对材料不足和冲突

没有足够证据

回答应说明缺少什么材料,并给出下一步,例如补充日期、项目编号或具体制度范围。不要用模型常识填补内部事实。

多份材料冲突

列出冲突来源、版本和生效时间。如果业务规则不能自动决定优先级,就交给材料负责人确认。

用户问题超出权限

不要暗示存在某份机密文档。只说明当前可用材料无法回答,并给出正式的权限申请或人工咨询路径。

最小在线流程伪代码

python
def answer_with_knowledge(question, actor):
    filters = build_acl_filters(actor)
    keyword_hits = keyword_index.search(question, filters=filters, top_k=20)
    vector_hits = vector_index.search(question, filters=filters, top_k=20)

    candidates = deduplicate(keyword_hits + vector_hits)
    ranked = rerank(question, candidates)[:6]

    if not evidence_is_sufficient(question, ranked):
        return {"answer": None, "reason": "insufficient_evidence"}

    response = model_answer(question, evidence=ranked)
    validate_citations(response, allowed_chunks=ranked, actor=actor)
    return response

生产实现还要加入超时、缓存、文档版本、日志、内容长度限制和提示注入防护。

评测不要只看“像不像答案”

建立一组经过人工确认的问题,分别衡量:

  • 召回率:关键证据是否进入候选集;
  • 排序质量:关键证据是否排在前面;
  • 引用正确率:引用是否真正支持结论;
  • 拒答准确率:没有材料时是否拒答;
  • 权限隔离:不同身份是否只看到允许内容;
  • 新鲜度:新版本发布后旧材料是否退出;
  • 延迟和成本:链路能否满足业务预算。

检索实验

准备 10 份小文档,其中包含两个版本冲突、一个无权限文档和三个相近术语:

  1. 为文档补齐版本、权限和标题路径;
  2. 比较纯关键词、纯向量和混合检索;
  3. 记录关键证据在不同方案中的排名;
  4. 测试无证据、冲突和越权问题;
  5. 检查答案中的每个引用是否能打开并定位原文;
  6. 删除一个文档,确认索引和缓存同步失效。

完成检查清单

  • 入库材料有版本、权限、生效状态和删除关系。
  • 切分后仍保留标题路径与原文定位。
  • 权限过滤发生在内容进入模型之前。
  • 每条关键结论都能映射到真实证据。
  • 无证据、材料冲突和越权都有明确结果。
  • 评测覆盖检索、引用、拒答、权限和新鲜度。

下一步

下一篇会讨论什么时候使用固定工作流、单 Agent 或多 Agent,以及如何避免为了“看起来高级”而增加不必要的复杂度。