05 · 知识检索与引用 · 落地方案
05 · 知识检索与引用 · 落地方案
对应蓝图 §6。本文解决:Agent 如何找到并使用知识?如何保证输出可信?
状态:草案(RT1.4 / RT2.2 / RT2.3 / RT3.1 待确认,见 §14;其中 RT1.4 由合规确定,技术侧不得自拟)
前置依赖:04-知识存储(索引已构建完成)
关联文档:02-知识验证(冲突检测)· 07-评测与反馈闭环(Recall@K / 拒答评测)· 08-约束执行(Hook 强制走 MCP 入口)· 09-度量评价与落地路径(P0/P1/P2 分期)
1. 定位与边界
知识检索与引用解决:Agent 如何找到并使用知识?如何保证输出可信?
检索采用混合检索架构(Exact + BM25 + Vector),通过 RRF 融合 + Cross-Encoder Rerank 输出 Top-K 证据。引用由系统生成,模型只能使用检索到的证据 ID,幻觉引用率通过机制保证为 0。
2. 混合检索架构
3. RRF 融合
3.1 公式
RRF(d) = Σ_{r ∈ 检索通道} 1 / (k + rank_r(d))rank_r(d):文档 d 在通道 r 结果列表中的名次(从 1 开始),不是相似度分数k:平滑常数,起始值取 60。依据:RRF 原论文(Cormack et al., 2009)取 k = 60;Azure AI Search 官方建议「set k to a small value, such as 60」;Elasticsearchrank_constant默认亦为 60。注意 k = 60 是算法常数,不是某个产品的可调默认值。- 每通道先取
rank_window_size条参与融合。初始值取 50–100,属本方案自拟起点而非业界默认:Elasticsearch 的rank_window_size默认等于请求的size参数(不是 100);Azure AI Search 的语义重排默认只作用于 top 50。取值须按 Recall@K 与延迟预算实测校准(见 09 §2.7.2)。 - 许可证前提:Elasticsearch 的原生 RRF 属 Relevance Engine 功能,需 Platinum/Enterprise 订阅(8.19 仍为付费);若使用 Basic 许可,RRF 须在应用层自行实现。
3.2 关键约束
- 去重键:
chunk_hash。同一 chunk 被多通道召回时合并分数 - 新鲜度校正:只作用于同一条知识的不同修订,不得跨知识比较时间
revoked的修订一律不参与召回(已撤销 = 不可信)。deprecated的修订是否参与召回,04 与本文结论冲突且尚未裁决:04 §3 侧的表述是「可召回但必须带过期警告」,本文 §8 的表述是「只有过期版本 → 拒答」。二者不可同时成立,已登记为待决策项(本文 §14 RT3.1;原写「09 §6.2 L6」,但 09 §6.2 的 LD6 是「Embedding 模型选型评测是否提前到 P1」,与本议题无关,属错引)。在裁决前,实施按更严格的一侧执行(不参与召回),理由是放宽容易收紧难:一旦 Agent 已经依赖deprecated内容作答,再改为拒答会造成能力回退。
4. Cross-Encoder Rerank
| 部署方式 | 候选数量级 | 说明 |
|---|---|---|
| 本地 GPU | Top 20-50 | 延迟可控,推荐 |
| 本地 CPU(ONNX 量化) | Top 20 | 延迟随候选数线性增长 |
| 云 Rerank API | Top 20 | 需配熔断 |
先用融合分数截断到候选窗口,再 rerank,不能对全量召回结果 rerank。
5. 延迟预算分解
检索延迟 P95 ≤ 500ms 的分解:
| 环节 | 预算 | 超预算处置 |
|---|---|---|
| 查询理解(术语/别名扩展) | 查表实现,不调用 LLM | 若引入 LLM 改写,500ms 目标不再成立 |
| 三路并行召回 | 取三路最大值,非求和 | 单通道超时降级为双通道 |
| RRF 融合 | 内存计算,可忽略 | — |
| Cross-Encoder Rerank | 候选窗口按 §4 限定 | 超时则跳过 rerank |
关键约束:「查询理解不调用 LLM」是 500ms 目标成立的前提条件。
6. 受控术语表
term: OCP
canonical_name: 内容开放平台
aliases: [开放平台, 萌视, fc-open]
historical_names: [PGC开放平台]
entity_type: domain
owner_group: ocp/backend查询同时保留原始词、标准术语、已审核别名和历史名称,不允许模型自由猜测内部缩写。
7. 引用系统
7.1 双层标识
| 标识 | 生命周期 | 生成方式 | 用途 |
|---|---|---|---|
evidence_id | 单次检索 | ev-{trace_id}-{序号} | 模型输出的引用句柄 |
chunk_ref | 长期 | knowledge_id + section_anchor + chunk_hash | 审计日志、回归集、反馈归因 |
evidence:
evidence_id: ev-a1b2c3-003
chunk_ref:
knowledge_id: "KB-PAYMENT-REFUND-LIFECYCLE"
knowledge_revision: "8f31c92"
section_anchor: "实现方案"
chunk_hash: "sha256:9c1f..."
source_uri: "gitlab://ocp/payment/src/.../RefundService.java"
line_range: "45-78"
classification: "confidential"
retrieved_at: "2026-10-21T10:30:00Z"
rerank_score: 0.877.2 幻觉引用率 = 0 的机制保证
模型输出的每个 evidence_id 必须存在于本次检索返回的集合中,否则该 claim 整体丢弃并计入「幻觉引用」——该指标因此是机制保证而非模型自觉。
7.3 知识修订后的引用可解析性
当 section_anchor 在新版本中不存在时,按以下顺序降级解析:
chunk_hash精确匹配section_anchor模糊匹配- 回落到
knowledge_id整条并标注「引用定位已降级」
8. 拒答与降级
| 情况 | 行为 |
|---|---|
| 没有可靠证据 | 拒答:「知识库中未找到相关信息」 |
| 只有过期版本 | 拒答:「相关信息已过期,请联系 Owner 确认」 |
| 来源互相冲突 | 降级:「存在多个版本的说法,以下是各方观点...」 |
| 权限判断失败 | 拒答:「您没有权限查看相关信息」 |
| 来源完整性验证失败 | 拒答 |
| 引用与当前版本不一致 | 警告:「以下信息可能已过时」 |
9. 权限、安全与审计
9.1 权限模型:检索前过滤 + Chunk 级 ACL
三条硬性要求:
- 过滤发生在检索之前,不是之后
- Agent 代替人调用时,使用发起人的权限,不使用 Agent 自身的服务账号权限
- 权限集获取失败 = 拒答,不得回落为无过滤检索
9.2 OWASP 14 类威胁映射
| # | 威胁 | 本方案控制项 | 状态 |
|---|---|---|---|
| 1 | Document Poisoning | 解析 fail-closed + 源文件校验 + 人工审批 | 部分覆盖 |
| 4 | Access Control Inheritance | 身份传播 + inherits_from 强制继承 | 已覆盖 |
| 7 | Index Integrity | 索引完整性 = 100% + 派生索引可重建 | 已覆盖 |
| 9 | Output Validation | evidence_id 白名单校验 | 已覆盖 |
| 11 | Caching Risks | 未覆盖 → 缓存键必须含权限集 | 待建设 |
| 14 | Fail-Closed Design | 拒答 + 权限失败拒答 + 解析失败不入库 | 已覆盖 |
9.3 全链路审计
| 字段 | 说明 |
|---|---|
trace_id | 全链路唯一 ID |
actor | 发起人身份(人,不是 Agent 服务账号) |
query_text | 原始查询与扩展查询 |
permission_set | 本次生效的权限集摘要(hash) |
retrieved_chunks | 召回的 chunk_ref 列表 + 分数 |
filtered_out_count | 因权限被过滤的条数(越权尝试信号) |
generated_claims | 模型输出的 claim 与 evidenceIds |
rejected_claims | 被校验丢弃的 claim |
refusal | 是否拒答 + 原因分类 |
四条纪律:
- 审计日志不可篡改(append-only)
- 被过滤的记录也要记
- 日志本身有密级
- 保留期须合规确定
9.4 本文依赖的元数据字段(须在 04 登记)
本文与 06 使用了若干 04(元数据 Schema 的唯一权威)未登记的字段。字段定义分散在使用方文档中会造成 Schema 双头——两个文档各自演进后必然分叉。下列字段已统一登记到 04 §3.7 字段字典:
| 字段 | 使用位置 | 用途 |
|---|---|---|
section_anchor | §7.1 chunk_ref | 长期引用定位 |
acl_deny | §9.1 | 显式拒绝列表(蓝图 §6.6.1 已定义,04 漏登记) |
inherits_from | §9.1 | 密级继承来源(蓝图 §6.6.1 已定义,04 漏登记) |
min_classification | §10.1 | 调用方可收紧的最低密级 |
index_version | §12 度量 | 索引版本一致率判定 |
latency_breakdown | §5 延迟预算 | 分环节延迟归因 |
degradation_flags | §8 拒答与降级 | 记录本次是否跳过 rerank / 向量通道不可用 |
degradation_flags 是本文新增字段:§8 规定 rerank 超时「跳过 rerank」,但审计日志原字段无法记录这一事实。事后无法区分「质量退化因知识」还是「因降级」,07 的离线评测结论会与线上行为系统性偏离。降级对用户可无提示,但审计必须记录。
9.5 关键决策点
| # | 决策 | 已确认 | 说明 |
|---|---|---|---|
| RT1.1 | 检索前权限过滤 | ✅ P0 必须实现 | 采集密级却不过滤等于声明不存在的控制 |
| RT1.2 | 缓存键含权限集 | ✅ 必须 | OWASP 第 11 类威胁,当前唯一完全空白项 |
| RT1.3 | 审计日志 append-only | ✅ | 不可篡改 |
| RT1.4 | 审计日志保留期 | 待确认 | 由合规要求确定,技术侧不得自拟 |
10. Agent 集成接口
10.1 MCP Server 接口
tools:
- name: search_knowledge
description: 按查询检索知识,返回带证据 ID 的 Top-K 片段
inputSchema:
query: string
domain: string # 可选
top_k: integer # 可选,服务端有硬上限
- name: get_evidence
description: 按 evidence_id 取回完整片段
- name: expand_relations
description: 取回上下游依赖、Owner、接口与表关系
- name: list_glossary
description: 查询受控术语表四条设计约束:
top_k服务端必须有硬上限min_classification只能收紧不能放宽- 渐进式加载通过工具分层实现
- 变更推送(知识废弃时主动通知)
10.2 知识分流判据
| 判据 | 归属 | 理由 |
|---|---|---|
| 每次交互都必须遵守、且违反后果严重 | AGENTS.md / 常驻上下文 | 检索有失败概率,常驻才能保证 100% 生效 |
| 可被机器确定性判定 | 编译为规则(Lint / Semgrep / CI) | 机械规则不依赖模型自觉 |
| 需要解释「为什么」、含权衡与背景 | 知识库条目 | 规则无法承载理由 |
| 只在特定模块 / 特定任务才相关 | 知识库条目 | 常驻会造成无关上下文污染 |
| 实时状态 | 不入知识库,走权威平台 API | 实时事实入库必然过期 |
冲突时的优先级:机器规则 > AGENTS.md > 知识库检索结果
10.3 关键决策点
| # | 决策 | 已确认 | 说明 |
|---|---|---|---|
| RT2.1 | Agent 集成形态 | ✅ MCP Server | 直接读文件会绕过全部控制 |
| RT2.2 | top_k 硬上限 | 待确认 | 具体数值须按延迟预算校准 |
| RT2.3 | 知识分流边界 | 待确认 | AGENTS.md vs 知识库的内容边界须按实际违规类型迭代 |
11. 实施步骤
步骤顺序即实施顺序,不得按「先做有趣的、后做安全的」排列。原表把混合检索(09 评为 P2)放在 S1、把权限过滤(09 评为 P0)放在 S2,照此施工会先产出一个无权限过滤的检索服务——这正是 OWASP RAG 威胁清单中排名最前的失控场景,且一旦有 Agent 接入就产生真实越权暴露。下表已按蓝图优先级重排,并增加「优先级」列与 09 §3 的期次对齐。
| 步骤 | 优先级 | 内容 | 产出物 | 前置条件 |
|---|---|---|---|---|
| S1 | P0 | 实现权限过滤 | 检索前 classification + ACL 过滤,权限集获取失败即拒答 | 权限模型已定义;acl_groups 已采集 |
| S2 | P0 | 实现审计日志 | append-only 写入 + 本地 WAL 兜底 | S1 完成(审计须记录 permission_set 与 filtered_out_count) |
| S3 | P0 | 实现最小可用检索 | Exact + BM25 双通道(不含向量) | 04 索引已构建;S1 完成 |
| S4 | P0 | 实现引用系统 | evidence_id 生成 + 白名单校验 | S3 完成 |
| S5 | P0 | 实现拒答逻辑 | §8 的 6 类拒答场景 | S4 完成 |
| S6 | P1 | 实现 MCP Server | 4 个 tools + 1 个 resource | S1–S5 完成 |
| S7 | P1 | 实现受控术语表 | _glossary.yaml + 查询接口 | 术语已收集 |
| S8 | P2 | 增加 Dense Vector 通道 + RRF 融合 | 三路并行召回 | S3 完成;Embedding 选型已定(04 §5.4) |
| S9 | P2 | 增加 Cross-Encoder Rerank | 候选窗口截断 + rerank | S8 完成;延迟预算已实测 |
| S10 | 每期 | 端到端验证 | 查询 → 权限过滤 → 检索 → 引用 → 审计 | 当期步骤完成 |
分期结论:P0 交付的是「带权限过滤、带审计、可溯源、会拒答的 Exact + BM25 检索」,而不是「语义检索」。语义能力(向量 + rerank)在 P2 才引入——这与 09 §3.1/§3.3 的期次划分一致,也是「先可信、后聪明」的刻意取舍。
12. 度量
| 指标 | 计算方式 | 目标 | 性质 |
|---|---|---|---|
| Recall@K | 正确证据进入 Top-K 的比例 | ≥ 90% | 阈值待实测 |
| Precision@K | Top-K 中正确证据的比例 | ≥ 70% | 阈值待实测 |
| Faithfulness | 模型引用是否真实支持对应主张 | ≥ 95% | 阈值待实测 |
| 幻觉引用率 | 模型输出的 evidence_id 不在检索结果集中的比例 | = 0 | 机制保证 |
| 拒答准确率 | 无可靠证据时正确拒答的比例 | ≥ 95% | 阈值待实测 |
| 检索延迟 P95 | 从查询到返回 Top-K 的时间 | ≤ 500ms | 阈值待实测 |
| 未携带有效权限集的请求被拒率 | 权限集获取失败时返回拒答的请求 / 权限集获取失败的请求 | = 100% | 机制保证(Fail-Closed 由代码路径强制,可脚本判定) |
| 越权测试集拦截率 | 越权用例集中被正确拦截的比例 | = 100% | 阈值待实测(须先建「权限和越权集」,见 07 §2.2;建集前不得作为门禁) |
| 审计完整率 | 有完整 trace_id 链路的交互数 / 总交互数 | 100% | 机制保证 |
| 缓存串味事件数 | 缓存返回超出调用者密级的结果的次数 | = 0 | 机制保证 |
13. 风险与依赖
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| 权限过滤拉低 Recall@K | 检索质量下降 | 评测带过滤的 Recall@K,按折扣幅度调优 |
| Rerank 延迟过高 | P95 超标 | 限定候选窗口,超时跳过 rerank |
| 审计日志写入失败 | 拒绝服务(合规优先于可用性) | 同步写入 + 本地 WAL 兜底:写入失败先落本地 WAL(有容量上限),WAL 未满则继续服务并告警;WAL 满或超过 N 分钟未落库则拒绝服务。原表述「异步写入配本地缓冲」与「拒绝服务」互斥——异步写入在调用返回时无法得知成败,据此无法同步拒答。N 与 WAL 上限须实测(09 §2.6.4) |
| MCP Server 单点故障 | Agent 无法获取知识 | 降级为 BM25 + Exact 双通道 |
14. 待确认决策汇总
编号前缀说明:本文决策编号统一使用
RT前缀(Retrieval)。原用R1.x/R2.x与 03-知识审核 的R1.x–R4.x冲突,跨文档引用时无法区分归属。
| 编号 | 决策 | 选项 | 推荐 |
|---|---|---|---|
| RT1.4 | 审计日志保留期 | 由合规确定 | — |
| RT2.2 | top_k 硬上限 | 具体数值待实测 | — |
| RT2.3 | 知识分流边界 | 须按实际违规类型迭代 | — |
| RT3.1 | deprecated 条目是否可召回 | A. 可召回 + 过期警告(04 §3 侧表述)/ B. 拒答(本文 §8 表述) | 裁决前按 B 执行(收紧不可逆性更低),见 §3.2 |