AB
AiBoss站
教程

用 Decision Model 做单次前向推理的分类决策:从请求构造到概率阈值

教程

用 Decision Model 做单次前向推理的分类决策:从请求构造到概率阈值

Decision Model 把分类、路由、Yes/No 判定和分级评分统一成「一次前向推理返回概率分布」的任务。本教程讲清三类问题(Choice / Noul / Score)的请求结构、概率与期望值的读法、四种概率读出方式的差异,以及选型时必须核对的许可证、语言支持与校准问题。

业务系统里有一大类任务其实不需要模型「写」任何东西:把工单分到既定类别、决定 agent 调用哪个工具、判断某个条件是否成立、给候选打一个有序等级。用聊天型模型做这些事,每次都要走完整的解码循环,输出格式还可能跑偏;更麻烦的是,想用概率阈值在「自动处理」和「转人工」之间切换时,从生成文本里拿不到可信的概率。Decision Model 就是针对这类场景设计的模型:给它一段 state(输入)和若干带类型的 question,它在一次前向推理中返回每个选项的概率。本教程按请求结构、概率读法、模型差异、选型核对四条线展开,帮你把这类模型接进自己的判定流程。

准备工作

动手之前,先把下面几件事确认清楚。Decision Model 生态目前的特点是模型多、命名杂、字段不统一,准备工作做得越细,后面踩坑越少。

明确你要的是「选择」而不是「生成」

先判断任务是否落在下面四类里:

  • 把咨询、案件、工单归入既定类别
  • 决定 agent 调用哪个工具、哪个模型
  • 判断「是否满足某条件」的 Yes/No 问题
  • 给候选做有序的等级评定

如果任务需要输出解释性文字、需要多轮追问、需要自由格式的长答案,那它不适合 Decision Model。这类模型刻意不输出自由文本;需要理由时,常规做法是另接一个聊天模型,让它基于判定结果去写说明。

确认访问方式

公开的 Decision Model 大致分三种获取形态,准备工作的内容完全不同:

  • 开放权重:可以从模型仓库下载权重自行部署,部分还公开了训练代码或 recipe。
  • 仅权重与推理代码:能跑,但训练 pipeline 不公开,无法用自己的标签重训。
  • 托管 API:只能通过服务方接口调用,权重不公开,也无法本地部署。

如果你的场景涉及敏感业务文本,第三种形态通常直接出局。先定形态,再谈选型。

核对许可证与训练数据条件

这是最容易在 PoC 之后才被发现的问题。确认清单:

  • 模型权重本身是商用许可、非商用许可,还是尚未确定。
  • 即使权重是宽松许可(例如 Apache-2.0),部分模型的卡片仍会注明「训练数据另有许可,模型许可不替代数据许可」。商用前要一并核对数据侧条件。
  • 托管型服务按各家服务条款执行,与开放权重模型的许可不是一回事。

许可证、可用地区、价格与配额都会变动,一律以官网与模型卡当前 revision 为准。

确认语言支持是否被明确写出

「backbone 是多语言模型」和「这个 Decision Model 在你的语言上可用」是两件事。选型时要区分三种情况:

  • 卡片明确写了支持你的语言;
  • 卡片声称多语言,但没有给出该语言单语的精度;
  • 卡片只写英语,或干脆没有语言说明。

第三种情况不能靠推断补齐,只能用自己的评估数据验证。

准备自己的标注样本

Decision Model 输出的概率是模型输出,不是正确率的保证。要设阈值,就必须有一批带标签的真实样本,用来做温度重拟合和阈值校准。样本量、类别分布按你的业务实际来定,但这一步不能省。

操作步骤

第一步:把任务拆成带类型的 question

目前多数模型共用三种问题类型:

类型用途返回内容
Choice从多个选项中选一个每个选项的概率
NoulYes/No 判定P(true)
Score从有序等级(评分量表)中选一档各档概率与期望值

一个请求里可以同时放多个 question,一次推理全部返回。这既是性能优势,也意味着你要在拆题时想清楚:哪些问题彼此独立,哪些问题需要放在一起看。

第二步:构造 state 与 questions

state 可以是纯文本,也可以是 JSON。questions 是一个以问题名为键的对象,每个问题带 type、instructions 和 criteria。下面是一个把三类问题合并到一个请求里的示例,state 是一段客服来信:

{
  "state": "上周收到的商品有破损,希望退款,请尽快处理。",
  "questions": {
    "is_refund_request": {
      "type": "noul",
      "instructions": "这条咨询是否在要求退款?",
      "criteria": {
        "true": "明确要求退款",
        "false": "没有要求退款"
      }
    },
    "department": {
      "type": "choice",
      "instructions": "应转交哪个部门?请选一个。",
      "criteria": [
        { "id": "billing", "description": "账单与付款相关咨询" },
        { "id": "returns", "description": "退货、退款、换货相关咨询" },
        { "id": "technical", "description": "产品用法与故障相关咨询" }
      ]
    },
    "urgency": {
      "type": "score",
      "instructions": "请选择处理紧急度。",
      "criteria": [
        { "id": "low", "description": "不急", "value": 0.0 },
        { "id": "normal", "description": "常规处理即可", "value": 0.3 },
        { "id": "high", "description": "需要尽快处理", "value": 0.7 },
        { "id": "critical", "description": "需要立即处理", "value": 1.0 }
      ]
    }
  }
}

几个构造要点:

  • Noul 的 criteria 用 true / false 两个键,分别写清成立与不成立的含义。判定标准写得越具体,概率越有意义。
  • Choice 与 Score 的 criteria 是数组,每项带 id 与 description;Score 的每项还要带一个数值 value。
  • instructions 是给模型的自然语言说明,不是注释,要写清楚「选一个」「判断是否」这类动作。
  • 字段名在不同模型之间并不统一。上面的结构只是说明用,接入具体模型时必须回到它的模型卡核对入参格式。

第三步:读取返回的概率分布

返回体按问题名组织,每个问题给出该类型的判定结果和完整概率分布:

{
  "answers": {
    "is_refund_request": {
      "noul": true,
      "probabilities": { "false": 0.04, "true": 0.96 }
    },
    "department": {
      "choice": "returns",
      "probabilities": { "billing": 0.02, "returns": 0.97, "technical": 0.01 }
    },
    "urgency": {
      "score": 2.48,
      "expected_value": 0.834,
      "numeric_values": [0.0, 0.3, 0.7, 1.0],
      "probabilities": { "low": 0.02, "normal": 0.08, "high": 0.30, "critical": 0.60 }
    }
  }
}

读法要分清三个层次:

  • Noul 的 probabilities 是 true 与 false 两个概率。
  • Choice 的 probabilities 是每个选项各自的概率。
  • Score 的 probabilities 是每一档的概率;score 是「以 0 起始的档位索引」的期望值,即用各档概率加权平均索引;expected_value 则是各档所赋 value 的期望值。

以上面的数字为例,score = 0×0.02 + 1×0.08 + 2×0.30 + 3×0.60 = 2.48;expected_value = 0.3×0.08 + 0.7×0.30 + 1.0×0.60 = 0.834。注意 score 与 expected_value 是两套刻度,前者基于档位序号,后者基于你自定义的 value,不要混用。

示例中的数值仅用于说明计算方式,不是任何模型的真实输出。

第四步:用概率设自动处理与转人工的阈值

这是 Decision Model 相对生成式方案最直接的收益。典型写法是:取该问题概率最高的选项,若其概率低于阈值,就不自动处理,转人工复核。例如「department 的最大概率低于 0.9 就转人工」。

但阈值不能拍脑袋定。设阈值之前必须完成两件事:

  1. 用你自己的标注样本,检查概率与正确率是否对齐;
  2. 按模型卡的建议做温度重拟合,再重新评估。

多个模型卡都明确提醒:概率是模型输出,不是正确率保证,且会随 state、instructions、criteria 的写法变化。有的模型卡还特别说明,其置信区间只在留出数据上验证过,实际流量上要先测再用阈值。

第五步:理解概率是从哪里读出来的

「一次前向推理出概率」是所有这类模型的共同点,但概率的读出位置不同,直接决定了模型在候选数量、上下文长度、能否复用 embedding 上的表现。已公开的做法大致分四类。

A. 读选项代码的 logit

把选项编码成 A、B、C 之类的字符,取最终 token 位置的 logit,只在这些选项字符上做归一化。有论文报告,即使不做额外训练,仅从指令微调过的 LLM 的最终 token logit 里读选项字母,就能接近专用模型的水平;再叠加 LoRA 与 decision head 微调还能继续提升。也有模型卡描述为:把每个问题格式化后,限定只生成一个选项代码 token,再把它转成概率。这类实现通常对选项数量有上限,超过上限就分组处理。

B. 读每个选项的标记位置

给每个选项安排一个专用标记位置(例如各自的 mask token),在该位置上取分,再在问题内的选项之间做 softmax。因为答案空间是每次请求时定义的,新增选项不需要重新训练。也有实现用「选项结束 token 与问题最终 token 比对」的 pointer head 打分,并把 state 的计算结果缓存起来,使多个问题互不影响。还有把 state 与多个问题拼成一条序列、在每个问题的选项后放标记来分别读出的做法,以及用一个小的 transformer head 从 backbone 最终隐状态一次性给所有问题的所有选项打分。

C. 共享前缀 + 每个候选单独分支

用 encoder 把指令和 state 编码成共享前缀,只编码一次;每个候选只 attend 这个前缀和自己的 token,候选之间互不可见;对每个候选做 mean pooling 后,用按决策类型区分的 head 打分,再在候选间做 softmax。这类结构在候选很多时优势明显,因为前缀只算一次。

D. 比较 embedding

在冻结的 LLM embedding 之上,分别训练「把 state 映射过去」和「把 action 映射过去」的 head,用双向 InfoNCE 损失训练。因为 state 与 action 分开编码,候选的 embedding 可以复用。有模型卡报告在约一千个候选的条件下比对照模型快一个数量级。代价是概率只在给定候选集合内有相对意义,换一批候选,数值就不可比。

一般性的取舍是:候选多、需要复用 embedding 时,C 与 D 更合适;上下文长、需要处理图像等复杂输入时,以 decoder LLM 为 backbone 的 A 与 B 更合适。这只是从各模型卡描述推出的判断,具体差距要在自己的数据上测。

第六步:按结构选模型族

公开模型可以粗分为 encoder 系(小模型为主)与 decoder 系(带小型 head)。选型时看四个维度:读出方式、backbone、校准处理、许可证与语言。

encoder 系体积小、推理便宜,适合高并发、低延迟的分类与路由。已公开的规格里,规模从千万级参数到数亿参数不等,backbone 有 reranker 类模型、多语言 encoder、单语 ModernBERT 等。语言支持差异很大:有的明确写英语,有的明确写某单一语言,有的声称多语言但未给出单语精度。

decoder 系 + 小型 head规模从不到 1B 到 27B 不等,backbone 多为 Qwen 系或 Gemma 系,常见做法是 LoRA 加专用 readout head,也有全层微调后合并 adapter 的。这类模型在长上下文和复杂输入上更有余地,部分版本已支持图像与视频输入。代价是部署成本高,且部分大尺寸版本采用非商用许可。

embedding 系与托管型适合排序与路由,候选 embedding 可复用;托管型则省去部署,但权重不公开、无法重训,且受服务方条款约束。

无论选哪一族,都要回到模型卡确认三件事:入参字段名、选项数量上限、以及是否要求特定的推理框架构建。

一个完整示例

下面把前面的步骤串成一个可执行的最小流程。示例用伪代码表示调用,具体 SDK 与字段名按你所选模型的卡片替换。

1. 定义问题模板

questions = {
  "is_refund_request": {
    "type": "noul",
    "instructions": "这条咨询是否在要求退款?",
    "criteria": {"true": "明确要求退款", "false": "没有要求退款"}
  },
  "department": {
    "type": "choice",
    "instructions": "应转交哪个部门?请选一个。",
    "criteria": [
      {"id": "billing", "description": "账单与付款相关咨询"},
      {"id": "returns", "description": "退货、退款、换货相关咨询"},
      {"id": "technical", "description": "产品用法与故障相关咨询"}
    ]
  },
  "urgency": {
    "type": "score",
    "instructions": "请选择处理紧急度。",
    "criteria": [
      {"id": "low", "description": "不急", "value": 0.0},
      {"id": "normal", "description": "常规处理即可", "value": 0.3},
      {"id": "high", "description": "需要尽快处理", "value": 0.7},
      {"id": "critical", "description": "需要立即处理", "value": 1.0}
    ]
  }
}

2. 发起请求并解析

state = "上周收到的商品有破损,希望退款,请尽快处理。"
resp = model.decide(state=state, questions=questions)

answers = resp["answers"]

# Noul:取 true 的概率
p_refund = answers["is_refund_request"]["probabilities"]["true"]

# Choice:取最大概率选项及其概率
dept_probs = answers["department"]["probabilities"]
dept = max(dept_probs, key=dept_probs.get)
p_dept = dept_probs[dept]

# Score:取期望值与各档概率
urgency_ev = answers["urgency"]["expected_value"]
urgency_probs = answers["urgency"]["probabilities"]

3. 按阈值分流

THRESHOLD = 0.9  # 先用占位值,校准后再定

if p_dept < THRESHOLD:
    route = "human_review"
else:
    route = dept

if p_refund >= THRESHOLD:
    tag = "refund"
else:
    tag = "other"

# 紧急度用期望值做排序键,而不是用单一档位
priority = urgency_ev

4. 校准阈值

把上面流程跑在你的标注样本上,收集每个样本的最大概率与是否正确,然后:

  1. 按模型卡建议的温度参数做重拟合(有的模型自带默认温度,仍建议用自己的数据重拟);
  2. 重算概率后,画出「最大概率区间」与「实际正确率」的对应关系;
  3. 选一个正确率满足业务要求的区间下界作为阈值,而不是直接沿用示例里的 0.9。

如果发现高概率区间并不比中概率区间更准,说明该模型在你的数据上校准不佳,此时要么换模型,要么放弃阈值分流、改为全量人工复核关键类别。

5. 上线后持续监控

把每次请求的最大概率、最终路由结果、以及人工复核的纠正记录一起落库。校准会随流量分布漂移,阈值需要定期重估。

注意事项

概率不等于正确率

多个模型卡都明确写了这一点:概率是模型输出,会随 state、instructions、criteria 的写法变化,不构成正确率保证。有的模型卡还指出,其置信度只在留出数据上验证过,实际流量上要先测再用阈值。因此「温度重拟合 + 自己数据上重评估」是使用前提,不是可选项。

Score 类型普遍偏弱

有模型卡明确指出,Score 是三类问题中最弱的一类,并测到了位置偏置;也有模型卡报告在四档以上的 Score 中存在位置偏置。这意味着档位顺序、档位数量都可能影响结果。使用 Score 时建议:档位数不要过多;对档位顺序做打乱测试;用期望值而非单一最高档做下游排序。

Noul 可能低估 true

有模型卡报告 Noul 在某些情况下会低报 true。如果你的业务对漏判更敏感(例如合规判定),阈值要相应下调,并用召回率而非准确率来评估。

选项数量存在上限

采用「读选项代码 logit」方式的模型通常有固定的选项槽位,超过上限会分组处理。分组会改变概率的归一化范围,跨组比较概率时要格外小心。

多问题合并处理有副作用

有的模型把多个问题放进一条序列处理,问题之间会互相 attend。模型卡对此的说明是:把相关的方面放在一起有利,把彼此独立的候选放在一起则不利。如果你的多个问题逻辑上互不相关,考虑拆成多次请求。

输入长度限制

部分小型 encoder 模型使用局部注意力窗口,输入过长时精度会下降。长文本场景要么截断,要么换用支持长上下文的 decoder 系模型。

特殊推理模式可能报错

有的模型版本被设计为「不思考」的即时模式,对它请求推理模式会直接报错。接入前确认该版本支持哪些调用模式。

访问条件可能受限

个别模型的仓库需要令牌才能访问,或权重公开情况不明确。选型时把「能否稳定获取权重」作为硬性条件之一。

评估集不能直接当采用依据

公开的评估集适合做一次初筛,但它们通常不是你的业务分类任务本身。另外要注意训练数据与评估数据的重叠:有的模型在训练中包含了某些公开评估集的训练分片,在这些集合上的分数会偏高。用公开集筛掉明显不合适的模型,最终采用与否仍取决于你自己标注数据上的表现。

分数刻度不是正确率

部分公开基准的分数是经过基准值调整的 0 到 100 分,不是正确率;Score 类任务按期望值计分,即使最高概率选项正确,分数也可能变化。跨模型比较时先确认分数口径一致。

易变信息以官网为准

模型规模、许可证、可用地区、价格与配额更新很快。本文涉及的规格仅用于说明选型维度,实际接入前请核对模型卡当前 revision 与服务方的最新条款。