把引文核对变成可编码判断:citation check 的用法与边界

先说结论

  • 官方 citation_check 把“断言是否被原文支持”变成一个 Choice 问题,返回类型化判决和置信度,而不是自然语言解释。
  • 它适合做发布前的硬门禁或人工分流;多档 Choice 能路由不同失败类型,二分 Noul 更直接,但 Noul 不返回 confidence。
  • 它不是检索器,不会替你找到应该引用的片段;判定质量由你塞进 state 的原文片段决定。
  • 类型化判断比让通用 LLM 自己写理由更可审计;这是我的判断,类型化输出让你可以在代码里设阈值、可复算、不易自洽编造理由。
  • 阈值要按风险分级,官方示例中有 0.5 低置信度拦截、0.9 高风险自动执行,但不能当作普适准确率。

官方 cookbook 要解决什么

这篇官方 cookbook 的场景很具体:一个 LLM 回答问题时附上引用,每个断言对应一段来源文档和一句引文。但有些引用是错的或幻觉出来的:引文可能根本不在文档里,也可能逐字存在,但上下文意思与断言相反。手工核对的成本不低:找到文档、定位引文、读足够多的上下文,再判断它是否支持原来的断言。

官方 cookbook 的自动化思路分两步:先用普通字符串匹配排查“引文完全缺失”的情况;剩下的引文交给一个 Choice 问题,让模型阅读引文所在的上下文,判断它是否支持断言。根据 官方 cookbook 的示例,check_citation() 函数接收源文档和一条引用,返回四种判决之一:verifiedunsupportedcontradictedfabricated,同时返回一个置信度,用来标记哪些需要人类复核。

官方数据:该 cookbook 示例中,四条准确引用返回的置信度不低于 0.93,四条被刻意植入的失败引用全部被捕获;这些数字来自 jev-1.12 模型,运行日期为 2026-08-16(官方数据,仅针对该示例)。这个数字不能当作跨语料的准确率,但至少说明在这类核对任务上,类型化判断有能力把“通过的”和“该拦下的”分开。

state 与问题设计:断言和原文放一起

要把核对变成可编码的判定,关键是别让模型“自由发挥”。官方示例把断言、引文和来源段落一起放进 state,然后用 Choice 提问:“这段来源上下文是否支持该断言?” 根据 Primitives(Questions) 的说明,Choice 适合答案是已知选项集合且选项之间无序的场景;Score 适合等级或区间判断;Noul 适合“是否为真”的二分判断。三种问题返回不同的类型化答案:Choice 返回 choiceprobabilitiesconfidence;Score 返回 scorelegendprobabilitiesconfidence;Noul 返回 noul(0 到 1)。

在引文核对里,官方 cookbook 用的是 Choice,而且是一个多档 Choice:支持、部分支持、不支持、矛盾、无法判断。多档设计的好处是路由清晰:contradictedunsupported 不是一回事,前者意味着来源文本与断言相反,后者意味着来源文本不足以支撑;后续动作可以不同。如果需要更简单的硬门禁,只关心“能不能通过”,也可以改用 Noul。Noul 的问题是,根据 Confidence 文档,Noul 答案本身不带 confidence,所以你拿不到一个现成的置信度分数,需要直接用 noul 数值或额外设计阈值。

下面是一个状态和两种问法的精简示例,展示多档 Choice 与二分 Noul 的取舍:

# 伪代码:同一个 state,两种问法
state = {
    "claim": "If a validator does not find itself in a token's audience list, it has to reject the token.",
    "source_section": "4.1.3.  "aud" (Audience) Claim ..."
}

# 多档 Choice:适合后续路由,能区分失败类型
questions_choice = {
    "support": Choice(
        instructions="Does the source context support the claim?",
        criteria={
            "verified": "supports",
            "partial": "partially supports",
            "unsupported": "does not support",
            "contradicted": "contradicts",
            "cannot_judge": "not enough context",
        },
    )
}

# 二分 Noul:适合硬门禁,但答案不带 confidence
questions_noul = {
    "supported": Noul(
        instructions="Does the source context support the claim?"
    )
}

为什么类型化判断比 LLM 自查稳(我的判断)

这是我的判断,不是官方文档的直接结论。通用 LLM 自查引文时,通常会生成一段自然语言理由,例如“这条引文支持该断言,因为……”问题在于,这段理由本身可能是自洽的,却与原文无关。模型在生成文字时,有能力把一段不支持的引文解释得看起来很有道理。类型化判断则不同:它只返回 choiceprobabilitiesconfidence,没有“理由”字段可供模型编造。你把判决和置信度拿回代码里,按既定规则路由,审计和复算都更直接。

官方 Confidence 文档 说明,confidence 是从概率分布计算出来的一个 0 到 1 的标量,用来描述答案的确定程度。你可以基于它设置阈值,而不需要自己处理概率分布。官方示例中把置信度分成三档:高置信度自动执行、中等置信度谨慎处理、低置信度不执行并路由给人类。这种分层对引文核对特别合适:高置信度且 verified 可以自动通过,低置信度或非 verified 就进人工队列。

边界:核对器不是检索器,也不替人写解释

这篇 cookbook 只解决“给定原文片段和断言,判断支持关系”。它不负责从一堆文档里检索出应该用来支持的片段。官方 cookbook 的第一步是字符串匹配排除完全缺失的引文,第二步才是读取上下文判断支持关系。这意味着:如果你的来源片段选错了、截断了、或者只给了不相干的段落,模型只能基于你给的 state 来判断。它不会自动帮你找到正确的原文段落。

另一个边界是解释问题。官方 System One 概念页 把这套模型定位成做快速、聚焦判断,不生成解释、不产代码。因此,当判决是 unsupportedcontradicted 时,模型不会告诉你“为什么”。这一步必须由人回到你提供的 source_section 里排查。换句话说,它把“判断”和“解释”分开了:机器给类型化信号,人来查证据。这对防止幻觉有帮助,但也意味着你不能把它当成自动写作助手。

接入内容流水线的实例(我的做法)

我在内容流水线里会这样接:草稿完成后,先抽取可核对的断言;然后用检索器为每条断言找来源片段;再把断言和来源片段一起送进 Choice 判断;只有 verified 且置信度够高才自动通过,其余进人工队列。注意,检索器是独立的,citation check 只管核对。官方 cookbook 里的 AUTO_ACCEPT = 0.8 是一个起始值(官方示例),官方建议在建立信任之前保守一些。

下面是我的流水线伪代码:

def citation_gate(draft, threshold=0.85):
    claims = extract_claims(draft)
    review_queue = []

    for claim in claims:
        # 检索器:自己实现,不是 citation check 的职责
        source_section = retrieve_source(claim)
        if source_section is None:
            review_queue.append({"claim": claim, "status": "no_source", "confidence": None})
            continue

        answer = client.system_one(
            state={"claim": claim, "source_section": source_section},
            questions={
                "support": Choice(
                    instructions="Does the source section support the claim?",
                    criteria={
                        "verified": "The source supports the claim",
                        "partial": "The source partially supports the claim",
                        "unsupported": "The source does not support the claim",
                        "contradicted": "The source contradicts the claim",
                        "cannot_judge": "Not enough context to judge",
                    },
                )
            },
        )

        verdict = answer.answers["support"]
        if verdict.choice != "verified" or verdict.confidence < threshold:
            review_queue.append({
                "claim": claim,
                "status": verdict.choice,
                "confidence": verdict.confidence,
            })

    return review_queue

这里 threshold=0.85 是我的做法,不是官方普适值。你可以按内容风险调整:高风险合规内容可以提到 0.9,低风险内部草稿可以降到 0.7。官方 Quick start 里的 SDK 用法和 API 调用方式可以参考,但阈值设置、状态字段命名、失败分类策略都需要在自己的数据上验证。

自己的判断与清单

  • 先保证检索质量,再谈核对质量。如果检索器给错了段落,再好的核对器也只能在错误上下文里做判断。
  • 失败类型不要只分“通过/不通过”。至少区分 no_sourceunsupportedcontradicted,它们对应三种完全不同的修正动作。
  • 阈值要有分级。官方 Confidence 文档的高/中/低三档是很好的起点,但具体数值要按业务风险调整;高风险动作不要用同一个阈值。
  • 低置信度不等于错误。更合适的理解是“模型不确定,需要人介入”,所以把它路由到人工队列,而不是直接丢弃。
  • Noul 适合轻量硬门禁,但如果你需要置信度,就用 Choice 或 Score。这是我的判断,因为 Noul 不返回 confidence 这一事实来自官方文档。
  • 记录每次判定的原始 source_section 和模型输出。这能让复核人员快速定位问题,也能形成回归测试集。

这次没核实的

  • 官方 cookbook 示例中“四个准确、四个失败全部捕获”的结果只适用于 RFC 7519 样本和 jev-1.12,不代表其他领域或模型版本的表现;跨语料准确率未能核实。
  • 官方未给出“部分支持”的精确边界定义或标注指南;该标签在具体业务中如何划分,未能核实。
  • 未验证 Playground 或 API 的实际操作流程,本文基于文档阅读。
  • 未验证 Noul 分数与 Choice confidence 之间的数值对应关系;例如 Noul 0.8 是否等价于 Choice confidence 0.8,未能核实。
  • 未使用中文社区二手来源;本文全部依据官方文档或官方候选链接。

参考来源

评论区

0 条评论

登录后可评论。

摸鱼小队长 114 阅读