引用、生成与拒答

本节目标

  • 把回答拆成可独立核查的 claim;
  • 区分“引用存在”“引用可访问”“引用支持主张”;
  • 用结构化契约限制生成结果并做确定性校验;
  • 为证据不足、冲突、无权限和依赖故障设计不同状态。

先定义生成契约

生成器不应只返回一段字符串。本库有两个相邻但不同的可执行教学 schema:第 8 课验证在线回答阶段,公共响应使用顶层 citations,claim 内保存较短的 document_id + fact_id 引用;第 9 课验证来源到引用的 Layer B 链,claim 内嵌完整 source/span citation。下面是采用第 9 课 citation 形状的通用目标结构,用于说明字段职责;它既不是第 8 课,也不是第 9 课 CLI 的逐字段 exact schema:

{
  "status": "answered",
  "answer": "退款审核通过后通常在一至三个工作日原路返回。",
  "claims": [
    {
      "claim_id": "C1",
      "text": "退款审核通过后通常在一至三个工作日原路返回。",
      "citations": [
        {
          "document_id": "refund-policy",
          "source_uri": "kb://tenant-a/refund-policy",
          "source_version": "refund-2026-01",
          "raw_sha256": "<64 hex>",
          "canonical_revision_id": "can_<64 hex>",
          "parse_revision_id": "par_<64 hex>",
          "element_id": "el_<64 hex>",
          "chunk_id": "chk_<64 hex>",
          "index_entry_id": "idx_<64 hex>",
          "coordinate_space": "canonical-text-lf-nfc-char-v1",
          "char_start": 120,
          "char_end": 144,
          "span_sha256": "<64 hex>"
        }
      ]
    }
  ]
}

为保持这段能被 JSON 解析器读取,逐字段说明放在块外:顶层 status 选择回答状态,answer 是面向用户的确定性渲染文本,claims 将事实拆成可审查单元;每个 citation 的 document_id/source_uri/source_version 说明来源,哈希与 revision 字段串起原始、规范化、解析、chunk 和索引身份,coordinate_space + char_start/char_end + span_sha256 精确定位并校验被引用片段。实际项目必须使用对应项目的 exact schema,而不是只复制此概念示例。

生产结构会按业务变化,但至少应表达:

  • 回答状态;
  • 用户可读答案;
  • 可核查 claim;
  • claim 与 source/span/revision 的映射;
  • 不确定、冲突或拒答原因。

JSON Schema 只能证明结构正确,不能证明语义得到支持。

若 citation 或评测 artifact 要跨服务、跨语言传递并参与签名/验签,必须另外约定精确字节、版本和消费者验证规则。json.dumps(sort_keys=True) 一类本地序列化习惯不是跨实现的规范化保证;若选择 RFC 8785 JCS,应同时遵守它对重复键、Unicode 与 IEEE-754 数值的限制并用独立实现互测。SHA-256 只有在消费者拿到可信预期摘要并实际比较时才可检测不一致;它本身不证明 producer 身份、内容事实或读取权限。

第 9 课把全局 index_generation_id 留在受保护审计投影,而不放进公共 citation:否则一篇未授权文档引起的全局 generation 变化也可能让公共响应发生变化,成为语料存在性侧信道。验证器用公共 index_entry_id 与本次受保护审计的 selected entry set 关联,再检查它指向的 generation。若业务明确选择公开快照版本,应把该信息泄露取舍写进威胁模型。

两种 schema 如何使用

第 8 课的 privileged_audit 与第 9 课的 protected_audit 都属于“受保护审计”概念,但它们是两个独立 schema 的固定 enum。第 8 课只能证明 fixture fact/revision 关联;只有第 9 课声明 canonical character span 与派生身份复算。不要把字段从一个项目复制到另一个项目后跳过相应 validator。

Claim 为什么重要

一句话“退款三天到账,而且一定免手续费”包含两个主张。一个引用可能只支持前半句。把长回答拆成 claim 后,才能逐项检查:

  1. claim 是否需要证据;
  2. 引用是否来自本次 selected context;
  3. source 的具体 span 是否支持这个 claim;
  4. source 是否当前有效且用户可访问;
  5. 多来源是否一致或冲突。

一般建议、价值判断和推断也要标明性质,不能伪装成来源中的事实。

引用校验的四层

1. 结构与 ID

  • 字段完整且类型正确;
  • document/chunk/span ID 存在;
  • ID 唯一、无未知或重复项;
  • source revision 与本次上下文一致。

2. Provenance

  • 引用必须来自本次 selected context;
  • 不能引用召回过但未进入上下文的文档;
  • 不能引用被 ACL、tenant 或有效期过滤的资料。
  • source locator、raw/canonical hash、parse/chunk/entry 身份和 coordinate space 必须能逐层复算;
  • 受保护审计必须把 selected entry 绑定到本次 published generation、authorization revision、snapshot 与 tombstone state。

3. Entailment / 支持关系

  • 引用片段是否真的支持 claim;
  • claim 中的每一条 citation 是否都支持该 claim,且没有用一条正确引用掩护其他无关或重复引用;
  • 条件、否定、数字、时间和适用范围是否一致;
  • 推断是否超过证据。

第 8 课用“claim 必须逐字等于 fixture fact statement”建立在线管线的确定性下界;第 9 课进一步让抽取式 claim 必须逐字等于 canonical source span,并重算所有派生身份。真实生成更灵活,需要人工 rubric、规则、NLI 或 LLM judge 辅助,但高风险场景仍需抽样人工核查。

还必须检查 answer 对 claims 的覆盖。只验证隐藏的 claims/citations,却允许用户可见 answer 额外写“保证当天到账且免手续费”,并没有实现 grounding。本库离线项目因此让 answer 由已验证的 status + claims 确定性渲染;真实生成若允许自由措辞,则要把答案重新拆成原子 claim,拒绝没有支持映射的新增事实,并单独评估“答案正确”与“引用忠实”。

4. 用户可核查性

  • 用户能否打开来源;
  • 链接是否定位到正确页/段;
  • 展示的标题和版本是否明确;
  • 私有来源是否按权限渲染。

用户无法打开的“神秘 S1”不能提供完整可核查体验。

citation 的显示与跳转也要重新授权

一次回答时可访问,不保证用户稍后仍可访问同一 revision。citation 的详情页、下载链接和验证 API 都应按当前主体、资源生命周期和产品允许的快照语义重新做对象级授权;撤权或删除后不能因为旧回答、缓存或引用 URL 继续提供正文。若展示层无法安全地确认可访问性,返回不泄露其它资源存在性的通用状态,而不是暴露“该文档刚被撤权”、替换成镜像副本,或根据私有候选重新生成解释。

这也说明三种说法不可混同:citation locator 说明“系统声称引用哪一段”;完整派生链说明“该段如何从某 revision 产生”;可信来源准入、签名/attestation 与授权决定“能否信任、显示或依赖这条链”。前两者都不能单独担保来源的事实正确性。

拒答不是一个状态

状态含义用户信息不应做
insufficient_evidence可访问资料不能支持答案说明资料不足、建议补充或人工核验暗示私有资料存在
conflict当前有效来源互相冲突展示冲突边界与来源擅自选一个
tool_required问的是实时状态或动作转到鉴权工具/确认流程用知识快照冒充实时值
dependency_unavailable检索依赖失败说明暂不可检索让模型凭记忆答内部事实
generation_unavailable已取证据但生成失败可按策略返回原文或稍后重试输出未校验草稿
policy_denied安全/合规规则拒绝给安全替代路径泄露检测细节

“无权访问”与“没有资料”在内部诊断上不同;对外信息要防止通过标题、ID、候选数或错误差异枚举私有内容。

参数记忆与证据边界

即使提示写“只依据上下文”,模型仍可能使用参数记忆。对内部制度、医学、法律或财务等可追溯场景:

  • 要求所有事实 claim 有引用;
  • 删除或拒绝无法对齐证据的 claim;
  • 把一般建议单独标明,不混入制度事实;
  • 对关键数字和否定做确定性检查;
  • 设置人工升级或二次核验。

“模型很自信”不是证据。

冲突回答示例

如果两个有效来源分别写“每晚 500 元”和“每晚 600 元”,合格结果不是取平均 550,也不是默认选 top-1:

状态:conflict
结论:现有有效资料不一致,暂不能给出唯一标准。
证据 A:每晚 500 元(来源 A,revision ...)
证据 B:每晚 600 元(来源 B,revision ...)
下一步:请制度负责人确认适用范围或修正文档。

若权威层级、地区或生效时间已有确定性规则,可以先按规则消解;规则及版本同样要进入 trace。

动手实践

先运行第 8 课项目,用它的公共边界检查顶层 status、claims 与 citations。这些命令不输出上面的第 9 课完整 span citation:

$env:PYTHONDONTWRITEBYTECODE = '1'  # 禁止为这次练习写入 Python bytecode 缓存。
$script = '.\docs\RAG\examples\offline_cited_qa.py'  # 保存第 8 课离线可引用问答脚本路径。
$fixture = '.\docs\RAG\examples\rag-fixture.json'  # 保存该脚本要校验的严格 fixture 路径。
 
python -B $script --fixture $fixture ask --query-id Q-refund  # 查看有充分证据时的公共 answered 响应。
python -B $script --fixture $fixture ask --query-id Q-conflict  # 查看两份有效来源冲突时的公共 conflict 响应。
python -B $script --fixture $fixture ask --query-id Q-phone-guest  # 查看 guest 无权访问时不泄露私有来源的响应。
python -B $script --fixture $fixture ask --query-id Q-order-live  # 查看实时订单问题被路由为 tool_required 的响应。

selected 只属于受保护审计面。仅在本地教学环境、确认具备 operator 权限后,再显式查看:

python -B $script --fixture $fixture inspect --query-id Q-refund --operator-view  # 在本地教学环境请求退款案例的受保护 audit trace。
python -B $script --fixture $fixture inspect --query-id Q-conflict --operator-view  # 在本地教学环境请求冲突案例的受保护 audit trace。

真实服务不能把 --operator-view 这类声明当作认证;host 必须在调用前完成身份与审计授权。

然后在测试的副本中做三次篡改:

  1. 把 citation 改成未选中的 document ID;
  2. 把 claim 改成“保证当天到账”;
  3. 把 source_revision 改成 wrong。

说明每次应在哪一层被拒绝。

常见错误

  • 只用正则确认回答里有 [S1];
  • 一个段落只有一个引用,却包含多个独立 claim;
  • 引用来自 retrieved 而非 selected context;
  • 生成器能自行编造 source ID;
  • 无答案时悄悄切换成无引用自由回答;
  • 拒答内容暴露被过滤文档的标题或 ID。

自测

  1. 引用 ID 存在为何仍不足以证明回答有据?
  2. Claim-level citation 比段落末尾统一引用解决什么问题?
  3. 结构化输出校验和语义支持校验有何区别?
  4. 为什么实时订单问题应该返回 tool_required?
  5. 权限不足时,用户可见结果为何不能包含被过滤 source ID?
  6. 为什么旧回答中的 citation URL 在用户点击时仍需对象级授权?

小结与下一步

可靠回答需要“主张—证据—版本”链和明确拒答语义。下一节从错答反向定位数据、召回、上下文、生成或引用层:失败分类与系统排查。

参考资料

来源获取日期:2026-07-22。自动 judge 是测量工具,不是无误真值;应固定 rubric/版本并与人工标注对齐。