logo

推理模型的思维链评测与结果校验

Published on

推理模型的思维链评测与结果校验

推理模型刚开始受到关注时,很多人会做同一件事:给它一道数学题,看它写出很长的过程,再数一数最后答案是否正确。过程越长,语气越坚定,大家越容易产生一种感觉——它一定“想得更深”。

但我在实际评测里很快遇到一个反例:有些回答过程写得非常完整,中间却偷换了一个条件;有些回答几乎不展示过程,最后结果反而完全正确;还有些题目答案可以自动验证,过程根本不需要由人逐字阅读。于是问题变得严肃起来:我们到底在评测推理能力,还是在欣赏一段像推理的文字?

我的结论是,思维链评测必须拆成两件事:结果是否正确,以及模型为得到结果采取的步骤是否满足任务要求。两者相关,但绝不是同一件事。更重要的是,生产系统不应该把模型生成的内部思考当成天然可信的审计记录,而应该使用可验证的中间产物、工具结果和最终答案检查来建立证据链。

一、先分清三个概念

最终结果监督:答案对不对

这是最容易做的一层。对于数学题、代码测试、数据库查询和有明确标签的分类任务,可以直接比较预测结果与标准答案。它适合快速统计准确率、通过率和回归变化。

但结果监督有一个明显限制:它只告诉我们终点,不告诉我们模型是怎么到达终点的。模型可能真正理解了规则,也可能碰巧猜对,或者利用了数据集中的表面模式。

过程监督:中间步骤是否合理

过程监督关注拆解、假设、计算和校验步骤是否符合任务约束。比如一道证明题,结论正确并不意味着每个推导都合法;一段修复代码能通过样例,也不等于没有引入资源泄漏。

过程监督可以发现“答案碰巧正确”的情况,但成本更高:需要更细的标注、更明确的标准,也要避免把某一种写法误当成唯一正确的思考方式。

可验证中间结果:能否由系统复查

这是工程上最有价值的一层。与其要求模型输出一长段自然语言过程,不如让它产出可以被程序检查的中间结果,例如公式、SQL 的执行计划、代码补丁、引用片段、单元测试结果和结构化约束。

自然语言思路 ──难以稳定复查──→ 人工凭感觉判断
结构化中间结果 ──可自动复查──→ 程序给出证据

这三层不是互相替代的关系。最终结果适合做总体指标,过程检查适合发现路径风险,可验证中间结果适合降低评测和运行成本。

二、不要把“展示思维链”当成评测的终点

思维链对模型训练和内部推理研究很有帮助,但在产品设计中,直接要求模型暴露完整内部思考并不是可靠性方案。自然语言过程可能包含猜测、错误分支和不适合展示的内容,模型也可能为了迎合格式,生成一段事后编造的解释。

更稳妥的做法是让模型输出“可核验的简要依据”:使用了哪些事实、执行了哪些工具、采用了什么公式、哪些条件仍不确定。用户需要的是能理解和验证答案的依据,系统需要的是可回放的事件,而不是一篇无法证明真实发生过的内心独白。

举个例子,财务计算任务可以要求模型返回:

{
  "formula": "本金 × 年利率 × 天数 / 365",
  "inputs": {
    "principal": 100000,
    "annualRate": 0.0365,
    "days": 30
  },
  "result": 300,
  "assumptions": ["按 365 天计息"],
  "needsReview": false
}

验证器可以重新计算 result,检查输入是否来自可信数据源,再决定是否允许模型生成最终说明。这样的证据比“我一步一步思考后得出 300”更可靠。

三、先设计可评测的任务,而不是先挑模型

一套好的推理评测集,应该说明题目需要什么能力,以及答案如何被确认。至少要覆盖下面几类:

  • 单步事实推导:检查基本规则和简单计算。
  • 多步组合:需要连续使用多个条件,观察中间状态是否丢失。
  • 约束满足:有多个限制条件,检查是否违反任意一条。
  • 反事实问题:改变一个条件后,结论是否随之改变。
  • 无答案或信息不足:测试模型会不会诚实地说证据不够。
  • 容易被表面模式误导的问题:检验模型是否真正处理了条件。

每个样本最好带上问题版本、标准结果、可接受答案集合、验证器和难度标签:

type ReasoningCase = {
  id: string
  version: string
  prompt: string
  expected: unknown
  acceptedAnswers?: unknown[]
  verifier: 'exact' | 'numeric' | 'unit_test' | 'sql' | 'human_review'
  skills: string[]
  traps: string[]
}

标准答案不一定只能有一个。规划题、代码设计题和开放式分析题,常常存在多个合理方案。此时要把“硬约束”和“软偏好”分开:硬约束必须满足,软偏好可以交给裁判模型或人工评审。否则评测集只是在奖励某个标注者的写作习惯。

四、自动校验器是推理评测的核心资产

能写验证器,就不要让另一个模型凭感觉打分。不同任务可以使用不同的确定性检查:

数值与公式

解析模型返回的表达式,重新计算并允许合理的浮点误差。不要只比较文本,因为 0.51/250% 可能表达同一个结果。

代码

运行格式检查、类型检查、单元测试、集成测试和安全扫描。只运行一个样例是不够的,边界输入、异常路径和资源释放也要进入测试。

SQL 与数据操作

在隔离数据库中执行查询,比较结果集、排序、空值处理和扫描范围。对于写操作,只允许在回滚事务或模拟环境中验证,不能让评测直接改变生产数据。

结构化计划

用 JSON Schema 验证字段,再用业务规则检查依赖关系、权限和资源上限。格式合法不代表计划可执行。

type VerificationResult = {
  passed: boolean
  score: number
  errors: Array<{
    path: string
    code: string
    message: string
  }>
  evidence: string[]
}

async function verifyAnswer(
  testCase: ReasoningCase,
  output: unknown,
): Promise<VerificationResult> {
  switch (testCase.verifier) {
    case 'numeric':
      return verifyNumeric(testCase.expected, output)
    case 'unit_test':
      return runInSandbox(testCase, output)
    case 'sql':
      return verifySqlInIsolatedDatabase(testCase, output)
    case 'exact':
      return verifyExact(testCase.expected, output)
    default:
      return requestHumanReview(testCase, output)
  }
}

验证器本身也要测试和版本化。否则你可能以为模型变好了,实际上只是验证器放宽了;或者模型没有退化,只是评测环境换了依赖版本。评测系统也是生产系统,不能因为它只服务开发者就少做工程管理。

五、如何评测中间步骤

过程评测不一定要逐 token 阅读模型输出,可以针对关键节点定义检查点。例如一个需要查询资料、计算结果、生成报告的任务,可以检查:

  1. 是否先获取了必要数据,而不是凭空计算。
  2. 是否使用了正确的字段和单位。
  3. 计算结果是否由验证器确认。
  4. 最终结论是否引用了实际使用过的来源。
  5. 发现证据冲突时,是否停下来或明确说明。

这些检查点可以记录成轨迹:

type ReasoningTrace = {
  caseId: string
  modelVersion: string
  steps: Array<{
    type: 'observation' | 'tool_call' | 'calculation' | 'decision'
    inputRef?: string
    outputRef?: string
    verified?: boolean
  }>
  finalVerification: VerificationResult
  totalTokens: number
  latencyMs: number
}

注意“步骤存在”不等于“步骤正确”。模型可以输出一百个看起来合理的步骤,但只要关键事实来源是错的,整条轨迹仍然不可信。过程指标应围绕任务风险设计,少记录无用的文字,多记录能够影响结论的证据。

六、自洽采样有用,但不能迷信多数票

对同一个问题采样多次,再观察答案是否一致,是一种常用方法。如果不同采样大多得到同一个结果,通常说明模型在这个问题上的判断更稳定。

async function sampleWithVerification(
  prompt: string,
  count = 5,
) {
  const candidates = await Promise.all(
    Array.from({ length: count }, () => model.solve(prompt)),
  )
  const verified = await Promise.all(
    candidates.map((candidate) => verifyCandidate(candidate)),
  )
  return selectBestCandidate(verified)
}

但多数票只能说明“模型重复地产生了这个答案”,不能证明答案是真的。模型可能共享同一个错误假设,也可能在数据集偏差下形成一致幻觉。正确做法是先用确定性验证器过滤,再在多个通过候选中选择质量最好的;对于不可自动验证的开放任务,才考虑使用裁判模型和人工抽检。

采样次数也会直接增加成本和延迟。不要默认所有问题都采样五次,可以根据任务难度、历史失败率和结果置信度动态分配预算。简单事实查询一次即可,复杂规划才值得多次尝试。

七、裁判模型只能当辅助裁判

当答案无法用程序完全验证时,LLM-as-a-Judge 很有用,但它有自己的偏差:偏爱更长的答案、被流畅表达影响、过度奖励某种格式、难以发现与参考答案不同但同样正确的方案。

使用裁判模型时,评分标准必须拆开写,例如事实准确性、约束满足、证据充分性、表达清晰度和安全性分别评分,并要求它指出证据位置。最好让裁判看不到不必要的模型名称,避免品牌偏见;同时保留人工标注子集,用来定期校准裁判结果。

裁判的分数不能直接当成真理,更不应该让被评测模型和裁判模型在同一个错误上互相吹捧。能自动验证的部分自动验证,不能验证的部分用裁判辅助,关键样本保留人工复核,这才是现实的组合。

八、生产环境要校验结果,而不只是评测结果

离线评测通过,并不意味着线上可以不检查。实时数据可能过期,工具可能返回空值,用户输入可能超出训练分布。生产系统至少要为高风险输出增加运行时验证:

  • 金额、日期、单位和 ID 做类型与范围检查。
  • 引用链接必须来自实际检索结果,不能只相信模型提供的 URL。
  • 代码和 SQL 在隔离环境中运行,再决定是否提交。
  • 关键结论要保留来源、版本和时间戳。
  • 结果与当前资源状态冲突时,优先停止并请求确认。

运行时校验会增加一点延迟,但它换来的是可控风险。对付款、权限变更、生产发布这类动作,几十毫秒的校验成本远小于一次错误执行的代价。

九、我在评测中最常见的三个误区

第一个误区是只看平均准确率。平均值会掩盖长尾失败,尤其是高风险样本。应该按难度、任务类型、语言、数据来源和风险等级分组看结果。

第二个误区是把过程长度当成推理能力。更长的输出会消耗更多 Token,却不一定带来更多正确性。真正要看的是关键步骤是否有证据、是否通过验证、是否减少了错误。

第三个误区是评测集长期不变。模型可能记住题型,系统也可能针对测试集过拟合。要定期加入线上脱敏失败样本、时间切分样本和对抗样本,并保留一份从不参与调参的隐藏集。

十、我的总结:让结论接受事实的审判

推理模型让我们重新认识了“想得久一点”的价值,但时间和文字都不是可靠性的同义词。评测真正要问的是:模型有没有得到正确结果?关键中间产物能不能复查?遇到证据不足时会不会停止?换一个条件、换一份数据、换一次运行环境后,它还能不能保持行为稳定?

我现在做推理评测,会先寻找能写成验证器的地方。能重新计算,就不要凭感觉看;能运行测试,就不要只读代码;能检查来源,就不要相信模型自报的引用。对于无法完全自动化的部分,再引入裁判模型和人工抽样。

思维链可以帮助我们理解模型的行为,但它本身不是证据。证据应该来自可重复的计算、真实的工具结果、明确的来源和独立的验证。把模型的推理变成一组能够被检查的中间产物,把最终答案放进事实约束里,推理模型才不会只是“更会解释”,而是真正更值得依赖。

🤪 您也可以编辑此页: