AIsaAIsaStudio English 打开 AIsa Studio

Hugging Face 介绍 Decision Models:以概率输出替代文本生成

原标题:Decision Models in Hugging Face

Hugging Face 发文介绍 decision models,其接口为输入 state 与 questions,返回带概率分布的结构化决策,支持 choice、score 和 noul 三种原语,不生成文本。Transformers 已支持 DecisionPipeline、AutoModelForDecision 和 /v1/systemone 端点,llama.cpp 也支持同一 API,Hub 上线专用 decision-model 标签,并可用 Trainer 微调(TRL 的原生支持 PR 尚在开放中)。

为什么值得看

原文系统讲清了 decision model 的三类原语、概率接口与 Transformers、llama.cpp 的实际用法,可直接迁移到路由和分类场景。

阅读原文 ↗11月11日 AI 日报

本页的摘要、推荐理由与解读出自 AIsa Studio 的 AI 风向智能体,原文版权归原作者所有。

深度解读

发生了什么

Hugging Face 在官方博客介绍了「Decision Models」(决策模型)这一新的模型类别与配套生态支持。核心思路是:不再让模型逐 token 生成文本来回答结构化问题,而是让模型「直接评估可用选项并返回其上的概率分布,不生成任何文本」。Hugging Face 给出了统一接口:State(状态)+ Questions(问题)+ Allowed Answers(允许的答案)→ Decisions + Probabilities(决策与概率)。

关键细节

  • 接口包含三个原语:Choice(从多个选项中选择,输出选项上的概率分布)、Score(在有序量表上打分,返回概率分布的期望值,例如紧急度打分 1.7)、Noul(二元判断,直接返回「是」的概率参数本身,源自 Bernoulli 分布)。同一请求可包含多个不同类型的问题。
  • Choice 和 Score 还会返回 confidence 字段,计算方式为 (p_max − 1/K)/(1 − 1/K),即把最大概率相对均匀分布归一化;完全均匀时置信度为 0,完全集中时为 1。
  • Transformers 已支持 DecisionPipeline、AutoModelForDecision、DecisionConfig,以及 /v1/systemone 服务端点;llama.cpp 也支持同一 API。Hub 上新增了决策模型专用标签,下有 ModernBERT 等编码器架构模型,也有源自更大生成式骨干的模型。可用 Trainer API 微调(choice 问题用交叉熵损失),TRL 的一等支持 PR 尚在开放中,Unsloth 也提供了用 LoRA 加决策打分头改造 Qwen 等生成模型的配方。
  • 值得注意的是,该接口也兼容自回归语言模型:Transformers 会构造决策提示、批量前向计算各选项的 logit 并做 softmax,无需生成回复文本,因此可以不训练新模型就实验这套接口。
  • 社区曾用 TypeSafe AI 提出的「System One model」称呼这类模型,后经投票统一为「decision model」,因其描述计算角色而非某种认知理论。

为什么重要

传统分类器的输出类别固定在分类头里,新增一个部门(如 Legal)就需要改结构、收集数据、重新训练;零样本分类(如基于 NLI 的 bart-large-mnli)支持推理时定义标签,但只解决单次归类。决策模型的优势不在于发明了运行时定义标签或概率分类,而在于把这些能力组织进一个带类型的统一接口:支持多个类型化问题、有序打分、是非判断,并为可靠、校准良好的决策提供专门训练的可能。相比之下,用 LLM 生成 JSON 既有不遵循 schema 的风险,即使用 Outlines、XGrammar 这类受限解码也带来额外开销。正如文中总结:「决策模型使决策与概率成为软件的一等输出」。Hugging Face 的定位是「约定共同接口而不强制共同实现」,使推理引擎、Hub、训练工具可以一次性适配所有决策模型。

对谁有影响

对在应用层做分类、路由、意图识别、紧急度评估等结构化任务的工程师影响最直接:客服分流、工单路由等场景可以从生成式方案切换到更轻、带概率与置信度的决策接口。模型作者与训练者则获得了一套从推理、服务(Transformers / llama.cpp)、Hub 发现到微调的完整工具链。

全文翻译

以下翻译只覆盖原文的前一部分,其余内容请看下方原文。

大型语言模型(LLMs)在生成文本方面表现出色。我们利用它们来回答问题、编写代码、推理问题等等。但并非所有问题都需要生成文本。

考虑一个客户支持系统收到以下消息:

"我的订阅被扣了两次费。我今天需要退款。"

系统需要确定哪个团队应该处理此请求,其紧急程度如何,以及客户是否在要求退款。

我们可以要求 LLM 返回一个包含这些答案的 JSON 对象。

llama cli -hf ggml-org/gemma-4-e4b-it-GGUF:Q4_0

> 我们有以下客户支持工单,“我的订阅被扣了两次费。我今天需要退款。”,哪个团队应该处理此请求,其紧急程度如何,以及客户是否在要求退款。请以 JSON 格式给出答案。

[开始思考]
...
[结束思考]
```json
{
  "team_to_handle": "Billing",
  "urgency": "High",
  "request_refund": true
}
```

但如果模型能够直接评估可用选项并返回概率分布,而_不生成任何文本_呢?

```
from transformers import pipeline

decide = pipeline("decision", model="autotrust/GEV-26B-Decide")
decision = decide(
    {
        "state": "我的订单被扣了两次费,我想要退款。",
        "questions": {
            "department": {
                "type": "choice",
                "instructions": "哪个团队应该处理这个?",
                "criteria": {
                    "billing": "支付和退款",
                    "technical": "错误和技术问题",
                    "sales": "定价和订阅"
                }
            },
        }
    }
)

# {
#   'department': {
#       'type': 'choice',
#       'choice': 'billing',
#       'confidence': 0.9974000453948975,
#       'probabilities': {
#         'billing': 0.9982666969299316,
#         'technical': 0.0010246571619063616,
#         'sales': 0.0007087149424478412
#       }
#     }
# }
```

这就是**决策模型**背后的核心理念。

## [](#what-are-decision-models)什么是决策模型?

决策模型接收一些输入上下文(状态),评估带有预定义可能答案(原语)的问题,并返回带有概率的结构化决策。与其让模型逐个标记地生成答案,不如让它为我们应用程序已经理解的答案分配概率。

[![决策模型的 state 和 primitives](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/decision/decision-models.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/decision/decision-models.png)

[TypeSafe AI 介绍了 Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev),将其描述为 [_System One 模型_](https://en.wikipedia.org/wiki/Thinking,_Fast_and_Slow)。这个名字借鉴了快速直觉思维(System 1)和较慢的深思熟虑思维(System 2)之间的区别。

> 社区已统一使用**决策模型**这一术语,因为它描述了模型的计算角色,而不是引用特定的认知理论。在本文的其余部分中,我们将这些模型称为“决策模型”而非“system one 模型”。([关于我们应该如何称呼这类模型的 Twitter 投票](https://x.com/ariG23498/status/2113220513158009022))

对于决策模型,基本接口是:

```
State + Questions + Allowed Answers → Decisions + Probabilities
```

## [](#why-not-use-llms-for-everything)为什么不全用 LLM 来做一切?

我们可以简单地提示 LLM 返回一个包含我们决策的 JSON 对象。事实上,许多应用程序已经使用语言模型进行分类、路由和其他结构化任务。

然而,这种方法存在一些问题。

-   LLM 不能保证遵循请求的模式:即使有明确的指示,模型也可能产生无效的 JSON、引入意外的键或返回超出允许选择的答案。
-   约束解码有帮助,但不能消除计算开销:诸如 [Outlines](https://github.com/dottxt-ai/outlines) 和 [XGrammar](https://github.com/mlc-ai/xgrammar) 之类的库可以通过限制模型在每个步骤中可以生成的标记来强制执行语法或 JSON 模式。这确保输出遵循支持的结构性约束,但引入了额外的工作。
-   为什么要生成整个响应:LLM 是为开放式生成而设计的,但当我们的应用程序只需要在预定义的答案之间进行选择时,自回归解码可能是多余的工作。

## [](#how-are-decision-models-different-from-classification-models)决策模型与分类模型有何不同?

在理解决策模型的接口之前,回顾两种密切相关的方法——传统分类和零样本分类——是有用的。

### [](#traditional-classification)传统分类

假设我们训练了一个分类器,将客户支持请求路由到三个部门:

-   账单
-   技术支持
-   销售

传统的分类模型学习从输入到预定义标签集的映射。在训练期间,模型学习从示例中预测这些标签。分类头通常为每个类产生一个 logit,softmax 将这些 logit 转换为概率分布。

这里的局限性在于_**输出类别是固化在模型的分类头中的**_。

如果我们引入第四个部门,例如法律部,现有的分类器不会自动获得相应的输出类别。我们可能需要修改头部,收集训练数据,并**重新训练**或调整模型。

### [](#zero-shot-classification)零样本分类

[零样本分类](https://huggingface.co/tasks/zero-shot-classification)解决了这一局限性的一个重要方面。我们不需要专门针对每个输出类别训练分类器,而是在推理时提供候选标签。

例如:

```
from transformers import pipeline

classifier = pipeline(
    "zero-shot-classification",
    model="facebook/bart-large-mnli",
)

classifier(
    "我的付款被扣了两次。",
    candidate_labels=["billing", "technical support", "sales"],
)

# {'sequence': 'My payment was deducted twice.',
#  'labels': ['billing', 'sales', 'technical support'],
#  'scores': [0.8466812372207642, 0.0962134450674057, 0.05710534751415253]}
```

与传统的固定标签分类器不同,类别可以从一个请求变为另一个请求。让我们添加另一个部门,例如“法律”,看看模型的输出。

```
from transformers import pipeline

classifier = pipeline(
    "zero-shot-classification",
    model="facebook/bart-large-mnli",
)

classifier(
    "我的付款被扣了两次。",
    candidate_labels=["billing", "technical support", "sales", "legal"],
)

# {'sequence': 'My payment was deducted twice.',
#  'labels': ['billing', 'sales', 'technical support', 'legal'],
#  'scores': [0.8185835480690002, 0.09302053600549698, 0.05521026626229286, 0.03318570926785469]}
```

一种常见的实现使用为自然语言推理(NLI)训练的模型。每个候选标签都转换为假设,模型测量输入在多大程度上蕴含该假设。

这听起来已经很接近决策模型了。在很多方面,确实如此。

### [](#where-decision-models-differ)决策模型的不同之处

决策模型将这个概念扩展到不仅仅是将输入分配给类别。决策模型可以接受关于同一状态的几个命名问题,具有不同的答案类型。类型可以是选择替代方案(choice)、对有序量表进行评分(score),或估计陈述为真的概率(noul)。

更重要的是,其接口围绕**可能决策的概率分布**设计。

考虑这三种方法:

属性

分类

零样本分类

决策模型

选择的概率

是

是

是

推理时定义的标签

否

是

是

多个类型化的问题

否

否

是

有序评分和是/否决策

特定于任务

通过适应可能

原生原语

决策模型的优势不在于它们发明了运行时定义的标签或概率分类。而在于它们将这些功能组织成一个一致的接口,背后可能有专门为决策优化的架构和训练程序支持。

## [](#the-decision-model-interface)决策模型接口

决策模型请求具有以下两个组件:

1.  state(状态)
2.  questions(问题)

### [](#state)状态

状态是模型应该评估的信息。它可能是一个字符串、一个结构化的 JSON 对象,或者对于支持的 omni-modal 模型,还有其他模态。

例如:

```
{
  "customer_message": "我被扣了两次费。请今天给我退款。",
  "subscription": "pro",
  "previous_tickets": 2
}
```

### [](#questions-and-their-primitives)问题及其原语

问题描述了我们想知道关于状态的什么内容。在决策模型接口中,三个基本原语代表不同类型的决策:

1.  **Choice(选择):** 从几个可能的答案中选择一个。
2.  **Score(评分):** 在有序量表上评估某事。
3.  **Noul:** 以概率方式回答是/否问题。

让我们逐一介绍它们。

#### [](#choice-a-probability-distribution-over-options)选择:选项上的概率分布

选择是最直观的原语,也是最接近传统分类的。

假设我们问:

> 哪个部门应该处理这个客户请求?

可用的选项是账单、技术支持和销售。

```
from transformers import pipeline

decide = pipeline("decision", model="autotrust/GEV-26B-Decide")
decision = decide(
    {
        "state": "我的订单被扣了两次费,我想要退款。",
        "questions": {
            "department": {
                "type": "choice",
                "instructions": "哪个团队应该处理这个?",
                "criteria": {
                    "billing": "支付和退款",
                    "technical": "错误和技术问题",
                    "sales": "定价和订阅"
                }
            },
        }
    }
)

# {
#   'department': {
#     'type': 'choice',
#     'choice': 'billing',
#     'confidence': 0.9974000453948975,
#     'probabilities': {
#       'billing': 0.9982666969299316,
#       'technical': 0.0010246571619063616,
#       'sales': 0.0007087149424478412
#     }
#   }
# }
```

模型为每个选项分配一个 logit。对于 logits ($z_1,\ldots,z_K$)(K 个类),我们可以使用 softmax 获得概率:

pi=exp⁡(zi/T)∑j=1Kexp⁡(zj) p_i=\frac{\exp(z_i/T)}{\sum_{j=1}^{K}\exp(z_j)}

这里,(K) 是选择的数量。

考虑这个说明性分布:

选择

概率

账单

0.998

技术支持

0.001

销售

0.001

概率之和为一:

∑i=1Kpi=1. \sum_{i=1}^{K}p_i=1.

模型选择概率最大的选项:

y^=arg⁡max⁡ipi \hat y=\arg\max_i p_i

在这种情况下,选择是账单,概率为 0.998,但剩余的概率也很有用。它们告诉我们模型如何在替代方案之间分配其信念。

#### [](#score-the-expectation-of-an-ordered-distribution)评分:有序分布的期望值

当有几个不同的替代方案时,选择有效。但如果我们想在有序量表上衡量某事怎么办?

考虑这个问题:

> 这个客户请求有多紧急?

我们定义三个级别:

-   0:低紧急度
-   1:中
查看原文

Large Language Models (LLMs) are exceptionally good at generating text. We use them to answer questions, write code, reason about problems, and so much more. But not every problem requires generating text.

Consider a customer support system receiving the following message:

"I was charged twice for my subscription. I need a refund today."

The system needs to determine which team should handle the request, how urgent it is, and whether the customer is requesting a refund.

We could ask an LLM to return a JSON object containing these answers.

llama cli -hf ggml-org/gemma-4-e4b-it-GGUF:Q4_0

> We have the following customer support ticket, "I was charged twice for my subscription. I need a refund today.", which team should handle the request, how urgent it is, and whether the customer is requesting a refund. Give the answers in JSON.

[Start thinking]
...
[end thinking]
```json
{
  "team_to_handle": "Billing",
  "urgency": "High",
  "request_refund": true
}

But what if the model could directly evaluate the available options and return a probability distribution over them, without generating any text?

from transformers import pipeline

decide = pipeline("decision", model="autotrust/GEV-26B-Decide")
decision = decide(
    {
        "state": "I was charged twice for my order and want a refund.",
        "questions": {
            "department": {
                "type": "choice",
                "instructions": "Which team should handle this?",
                "criteria": {
                    "billing": "Payments and refunds",
                    "technical": "Bugs and technical issues",
                    "sales": "Pricing and subscriptions"
                }
            },
        }
    }
)

# {
#   'department': {
#       'type': 'choice',
#       'choice': 'billing',
#       'confidence': 0.9974000453948975,
#       'probabilities': {
#         'billing': 0.9982666969299316,
#         'technical': 0.0010246571619063616,
#         'sales': 0.0007087149424478412
#       }
#     }
# }

This is the key idea behind decision models.

What are decision models?

A decision model takes some input context (state), evaluates a question with a set of predefined possible answers (primitives), and returns a structured decision along with probabilities. Instead of asking a model to generate an answer token by token, we ask it to assign probabilities to the answers that our application already understands.

state and primitives for the decision models

TypeSafe AI introduced Jev, describing it as a System One model. The name draws inspiration from the distinction between fast, intuitive thinking (System 1) and slower, deliberative thinking (System 2).

The community has converged to the term decision model, because it describes the model's computational role rather than invoking a particular theory of cognition. In the rest of the post we will refer to the models as "decision models" instead of "system one models". (A poll on twitter deciding what we should be calling such models)

For a decision model, the fundamental interface is:

State + Questions + Allowed Answers → Decisions + Probabilities

Why not use LLMs for everything?

We could simply prompt an LLM to return a JSON object containing our decisions. In fact, this is how many applications already use language models for classification, routing, and other structured tasks.

However, there are a few problems with this approach.

  • LLMs are not guaranteed to follow the requested schema: Even with explicit instructions, a model might produce invalid JSON, introduce unexpected keys, or return an answer outside the allowed choices.
  • Constrained decoding helps, but does not eliminate the computational overhead: Libraries such as Outlines and XGrammar can enforce grammars or JSON schemas by restricting which tokens the model is allowed to generate at each step. This ensures the output follows the supported structural constraints, but introduces additional work.
  • Why generate an entire response: LLMs are designed for open-ended generation, but when our application only needs to choose between predefined answers, autoregressive decoding can be unnecessary work.

How are decision models different from classification models?

Before understanding the interface of a decision model, it is useful to revisit two closely related approaches namely traditional classification and zero-shot classification.

Traditional classification

Suppose we train a classifier that routes customer-support requests to three departments:

  • Billing
  • Technical support
  • Sales

A conventional classification model learns a mapping from an input to a predefined set of labels. During training, the model learns to predict these labels from examples. A classification head typically produces one logit per class, and softmax turns those logits into a probability distribution.

The limitation here is that the output classes are baked into the model's classification head.

If we introduce a fourth department, such as Legal, the existing classifier does not automatically acquire a corresponding output class. We may need to modify the head, collect training data, and retrain or adapt the model.

Zero-shot classification

Zero-shot classification addresses an important part of this limitation. Instead of requiring the classifier to be trained specifically on every output category, we supply the candidate labels at inference time.

For example:

from transformers import pipeline

classifier = pipeline(
    "zero-shot-classification",
    model="facebook/bart-large-mnli",
)

classifier(
    "My payment was deducted twice.",
    candidate_labels=["billing", "technical support", "sales"],
)

# {'sequence': 'My payment was deducted twice.',
#  'labels': ['billing', 'sales', 'technical support'],
#  'scores': [0.8466812372207642, 0.0962134450674057, 0.05710534751415253]}

Unlike a conventional fixed-label classifier, the categories can change from one request to another. Let’s add another department, such as “Legal”, and see the model outputs.

from transformers import pipeline

classifier = pipeline(
    "zero-shot-classification",
    model="facebook/bart-large-mnli",
)

classifier(
    "My payment was deducted twice.",
    candidate_labels=["billing", "technical support", "sales", "legal"],
)

# {'sequence': 'My payment was deducted twice.',
#  'labels': ['billing', 'sales', 'technical support', 'legal'],
#  'scores': [0.8185835480690002, 0.09302053600549698, 0.05521026626229286, 0.03318570926785469]}

One common implementation uses a model trained for natural language inference (NLI). Each candidate label is converted into a hypothesis, and the model measures how strongly the input entails that hypothesis.

This already sounds quite similar to a decision model. And in many ways, it is.

Where decision models differ

Decision models extend this idea beyond simply assigning an input to a category. A decision model can accept several named questions about the same state, with different answer types. The types can be choosing between alternatives (choice), scoring an ordered scale (score), or estimating the probability that a statement is true (noul).

More importantly, its interface is designed around the probability distribution over possible decisions.

Consider these three approaches:

Property

Classification

Zero-shot classification

Decision model

Probability over choices

Yes

Yes

Yes

Labels defined at inference

No

Yes

Yes

Multiple typed questions

No

No

Yes

Ordered scoring and yes/no decisions

Task-specific

Possible with adaptation

Native primitives

The advantage of decision models is not that they invented runtime-defined labels or probabilistic classification. It is that they organize these capabilities into a consistent interface, potentially backed by architectures and training procedures optimized specifically for decisions.

The decision model interface

A decision model request has the following two components:

  1. state
  2. questions

State

The state is the information the model should evaluate. It may be a string, a structured JSON object, or, other modalities for supported omni-modal models.

For example:

{
  "customer_message": "I was charged twice. Please refund me today.",
  "subscription": "pro",
  "previous_tickets": 2
}

Questions and their primitives

Questions describe what we want to know about the state. In the decision models interface, three fundamental primitives represent different forms of decisions:

  1. Choice: Select one of several possible answers.
  2. Score: Evaluate something on an ordered scale.
  3. Noul: Answer a yes/no question probabilistically.

Let's go over them one by one.

Choice: A probability distribution over options

Choice is the most intuitive primitive and the closest to traditional classification.

Suppose we ask:

Which department should handle this customer request?

The available options are Billing, Technical support, and Sales.

from transformers import pipeline

decide = pipeline("decision", model="autotrust/GEV-26B-Decide")
decision = decide(
    {
        "state": "I was charged twice for my order and want a refund.",
        "questions": {
            "department": {
                "type": "choice",
                "instructions": "Which team should handle this?",
                "criteria": {
                    "billing": "Payments and refunds",
                    "technical": "Bugs and technical issues",
                    "sales": "Pricing and subscriptions"
                }
            },
        }
    }
)

# {
#   'department': {
#     'type': 'choice',
#     'choice': 'billing',
#     'confidence': 0.9974000453948975,
#     'probabilities': {
#       'billing': 0.9982666969299316,
#       'technical': 0.0010246571619063616,
#       'sales': 0.0007087149424478412
#     }
#   }
# }

The model assigns a logit to each option. For logits ($z_1,\ldots,z_K$), (with K classes) we can obtain the probabilities using softmax:

pi=exp⁡(zi/T)∑j=1Kexp⁡(zj) p_i=\frac{\exp(z_i/T)}{\sum_{j=1}^{K}\exp(z_j)}

Here, (K) is the number of choices.

Consider this illustrative distribution:

Choice

Probability

Billing

0.998

Technical support

0.001

Sales

0.001

The probabilities sum to one:

∑i=1Kpi=1. \sum_{i=1}^{K}p_i=1.

The model chooses the option with the largest probability:

y^=arg⁡max⁡ipi \hat y=\arg\max_i p_i

In this case, the choice is Billing, with the probability of 0.998, but the remaining probabilities are useful too. They tell us how the model distributes its belief among the alternatives.

Score: The expectation of an ordered distribution

Choice works when there are several distinct alternatives. But what if we want to measure something on an ordered scale?

Consider the question:

How urgent is this customer request?

We define three levels:

  • 0: Low urgency
  • 1: Medium urgency
  • 2: High urgency
from transformers import pipeline

decide = pipeline("decision", model="autotrust/GEV-26B-Decide")
decision = decide(
    {
        "state": "I was charged twice for my order and want a refund.",
        "questions": {
            "urgency": {
                "type": "score",
                "instructions": "How urgent is the request?",
                "criteria": ["low", "medium", "high"]
            },
        }
    }
)

# {
#   'urgency': {
#     'type': 'score',
#     'score': 1.7053890228271484,
#     'legend': {
#       '0': 'low',
#       '1': 'medium',
#       '2': 'high'
#     },
#     'confidence': 0.5580834252759814,
#     'probabilities': {
#       '0': 0.014908230863511562,
#       '1': 0.26479458808898926,
#       '2': 0.7202972173690796
#     }
#   }
# }

The model again produces a probability distribution:

Level

Probability

0 — Low

0.10

1 — Medium

0.26

2 — High

0.72

Rather than returning only the most likely level, the Score primitive computes the expected value of the level index.

For a random variable (X) representing the level, the expectation is:

E[X]=∑i=0K−1ipi \mathbb{E}[X]=\sum_{i=0}^{K-1}ip_i

For our example:

E[X]=0(0.10)+1(0.26)+2(0.72)=1.7 \mathbb{E}[X] = 0(0.10)+1(0.26)+2(0.72)=1.7

The reported score is therefore 1.7.

This does not mean that the model selected a fictional level halfway between medium and high. It means that the expectation of its probability distribution is 1.7.

Noul: A Bernoulli decision

The third primitive is Noul, which represents a binary question.

For example:

Is the customer requesting a refund?

from transformers import pipeline

decide = pipeline("decision", model="autotrust/GEV-26B-Decide")
decision = decide(
    {
        "state": "I was charged twice for my order and want a refund.",
        "questions": {
            "refund": {
                "type": "noul",
                "instructions": "Is the customer requesting a refund?"
            }
        }
    }
)

# {
#   'refund': {
#     'type': 'noul',
#     'noul': 0.9947046637535095
#   }
# }

Rather than defining a list of choices, we ask the model to return the probability that the answer is true. We can understand this using the Bernoulli distribution (noul comes from Bernoulli).

Let (Y) represent a binary outcome:

Y∼Bernoulli⁡(p) Y\sim\operatorname{Bernoulli}(p)

where

P(Y=1)=p,P(Y=0)=1−p. P(Y=1)=p,\qquad P(Y=0)=1-p.

If the model returns:

{"type": "noul", "noul": 0.92}

it assigns a probability of 0.92 to Yes and 0.08 to No.

The model is not randomly sampling a binary answer here. It returns the probability parameter itself, leaving the application to decide how to act on it.

Confidence: How certain is the model?

So far, we have obtained probability distributions from the model. But the decision model interface also returns a field called confidence for Choice and Score.

The full distribution might contain several possible answers, and it is useful to summarize how concentrated the model's belief is. With confidence we achieve just that.

For a Choice with (K) options, the confidence is:

pmax⁡−1K1−1K \frac{p_{\max}-\frac{1}{K}}{1-\frac{1}{K}}

where ($p_{\max}$) is the largest predicted probability.

The intuition here is:

  • A uniform distribution has confidence 0. ($p_{\max} = \frac{1}{K}$). This means the model estimates that all choices are equally likely.
  • A distribution concentrated entirely on one option has confidence 1. ($p_{\max} = 1$)
  • The larger the winning probability relative to a uniform distribution, the higher the confidence.

In our previous three-choice example, Billing had probability 0.80:

C=3(0.99)−12=0.98 C=\frac{3(0.99)-1}{2}=0.98

Notice that the winning probability is 0.99, but the reported confidence is 0.98. The confidence normalizes the largest probability relative to the number of available options. In contrast, the ordered Score example only had a confidence of 0.558.

Decision models in the Hugging Face ecosystem

Decision models are a young and fast-moving space. Each one makes decisions differently under the hood (heads that score the whole schema jointly, separate readouts on top of LLM backbones, edge-sized and multimodal variants, etc). This variety is healthy, but it comes at a cost. If every model exposes its own interface, every tool around it needs model-specific adaptations.

Our goal at Hugging Face is to agree on a common interface without forcing a common implementation. A decision model should be free to work however it likes internally, as long as it accepts the same state and questions and returns the same typed decisions. Once that contract exists, the rest of the ecosystem can build on it once:

  • inference engines like llama.cpp can serve any decision model through the same API
  • the Hub can package and surface them consistently
  • and training tools can target shared primitives instead of individual architectures

The rest of this section walks through each of these pieces.

1. Serving with Transformers

Transformers now supports the DecisionPipeline, AutoModelForDecision, and the DecisionConfig classes. These follow the same overall structure of the pipeline and model classes in transformers but are tailor made for decision models.

from transformers import pipeline

decide = pipeline("decision", model="autotrust/GEV-26B-Decide")
print(decide)

# DecisionPipeline: {'model': 'JevForDecision', 'dtype': 'bfloat16', 'device': 'cuda', 'input_modalities': 'text'}

One can also inspect the configuration and the model like so:

from transformers import AutoModelForDecision, DecisionConfig

model_id = "autotrust/GEV-26B-Decide"
config = DecisionConfig.from_pretrained(model_id)
model = AutoModelForDecision.from_pretrained(model_id)

Transformers also supports the /v1/systemone endpoint.

We can start a local server using the serving dependencies:

pip install "transformers[serving]"
transformers serve

Using a compatible version of Transformers, we can then send a decision request:

curl http://localhost:8000/v1/systemone \
  -H "Content-Type: application/json" \
  -d '{
    "model": "autotrust/GEV-26B-Decide",
    "state": "I was charged twice. Please refund me today.",
    "questions": {
      "department": {
        "type": "choice",
        "instructions": "Which team should handle this?",
        "criteria": {
          "billing": "Payments and refunds",
          "technical": "Bugs and technical issues",
          "sales": "Pricing and subscriptions"
        }
      },
      "urgency": {
        "type": "score",
        "instructions": "How urgent is the request?",
        "criteria": ["low", "medium", "high"]
      },
      "refund": {
        "type": "noul",
        "instructions": "Is the customer requesting a refund?"
      }
    }
  }'

The response contains three named answers with the corresponding typed results and probabilities.

{
  "model":"autotrust/GEV-26B-Decide",
  "answers":{
    "department":{
      "type":"choice",
      "choice":"billing",
      "confidence":0.9974000453948975,
      "probabilities":{
        "billing":0.9982666969299316,
        "technical":0.0010246571619063616,
        "sales":0.0007087149424478412
        }
    },
    "urgency":{
      "type":"score",
      "score":1.7525686174631119,
      "legend":{"0":"low","1":"medium","2":"high"},
      "confidence":0.6288529792800546,
      "probabilities":{
        "0":0.014000383205711842,
        "1":0.2194305807352066,
        "2":0.7665690183639526
      }
    },
    "refund":{
      "type":"noul",
      "noul":0.9953224062919617
    }
  },
  "usage":{"input_tokens":148,"output_tokens":0}
}

An interesting detail is that the above APIs works with compatible autoregressive language models too. Transformers constructs the decision prompts, evaluates the answer label logits in a batched forward pass, and applies softmax without generating response text. This means we can experiment with the decision interface without necessarily training a new model.

2. Serving with llama.cpp

llama.cpp also supports decision models using the same /v1/systemone API.

For example, we can launch a GGUF decision model from the Hub:

llama serve -hf ggml-org/Kev-4B-GGUF

The default server runs on port 9931.

We can then send a request containing the same state and questions, using http://localhost:9931/v1/systemone. With the model selected by the server, we can omit the model field from the request.

The useful property is interface compatibility.

curl http://localhost:9931/v1/systemone \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ggml-org/Kev-4B-GGUF",
    "state": "I was charged twice. Please refund me today.",
    "questions": {
      "department": {
        "type": "choice",
        "instructions": "Which team should handle this?",
        "criteria": {
          "billing": "Payments and refunds",
          "technical": "Bugs and technical issues",
          "sales": "Pricing and subscriptions"
        }
      },
      "urgency": {
        "type": "score",
        "instructions": "How urgent is the request?",
        "criteria": ["low", "medium", "high"]
      },
      "refund": {
        "type": "noul",
        "instructions": "Is the customer requesting a refund?"
      }
    }
  }'

3. Discovering models on the Hugging Face Hub

Hugging Face introduced a dedicated decision-model tag for discovering compatible models. This is useful because decision models do not have to share a single backbone or architecture.

For example, the ecosystem includes models based on encoder architectures such as ModernBERT, as well as models derived from larger generative language-model backbones.

4. Training decision models using Hugging Face tooling

Once we understand inference, the next question is whether we can adapt a decision model. Since decision models are integrated in transformers, we can fine-tune them with the Trainer API.

Below is a toy example that fine-tunes JEV-9B to route support requests. It is intentionally small, but it shows the parts that matter:

  • each example is a decision request (a state plus questions) paired with the gold answer.
  • model.decision_config.prepare_inputs turns the request into model inputs, the same way it does at inference time.
  • the model returns one logit per option, so a choice question is trained with a plain cross-entropy loss over the options.
import os; os.environ["CUDA_VISIBLE_DEVICES"]="2"
import torch
import torch.nn.functional as F
from transformers import AutoTokenizer, AutoModelForDecision, Trainer, TrainingArguments

checkpoint = "autotrust/JEV-9B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForDecision.from_pretrained(checkpoint)

examples = [
    ("I was charged twice. Please refund me.", "refund"),
    ("My package has not arrived.", "shipping"),
    ("The application crashes when I open it.", "technical"),
    ("I want my money back.", "refund"),
    ("Where is my delivery?", "shipping"),
    ("I cannot log into my account.", "technical"),
]

options = {
    "refund": "Billing issues and refunds",
    "shipping": "Orders and deliveries",
    "technical": "Software and account issues",
}

def preprocess(text, label):
    request = {
        "state": text,
        "questions": {
            "category": {
                "type": "choice",
                "instructions": "Classify the support request.",
                "criteria": options,
            }
        },
    }
    (inputs, questions), = model.decision_config.prepare_inputs(
        request, tokenizer
    )
    inputs["marks"] = inputs["marks"].unsqueeze(0)
    inputs["num_options"] = torch.tensor([[len(questions[0].options)]])
    inputs["question_type_ids"] = torch.tensor([[1]])  # choice
    inputs["labels"] = torch.tensor(
        [questions[0].options.index(label)]
    )
    return inputs

dataset = [preprocess(text, label) for text, label in examples]


class DecisionTrainer(Trainer):
    def compute_loss(self, model, inputs, return_outputs=False, **kwargs):
        inputs = inputs.copy()
        labels = inputs.pop("labels")
        logits = model(**inputs)

        logits = logits.unsqueeze(0)  # [3] -> [1, 3]
        loss = F.cross_entropy(logits.float(), labels)

        return (loss, logits) if return_outputs else loss


trainer = DecisionTrainer(
    model=model,
    args=TrainingArguments(
        output_dir="./decision-toy",
        max_steps=30,
        per_device_train_batch_size=1,
        learning_rate=1e-3,
        logging_steps=5,
        save_strategy="no",
        report_to="none",
        remove_unused_columns=False,
    ),
    train_dataset=dataset,
    data_collator=lambda batch: batch[0],
)

trainer.train()

First-class support for training decision models is also coming to TRL (huggingface/trl#8040), so we won't have to write this plumbing by hand. At the time of writing, the PR is still open.

If we want to go further and turn a generative LLM into a decision model, Unsloth provides a recipe that fine-tunes models such as Qwen with LoRA and a decision-scoring head.

Final thoughts

Decision models are interesting not because classification is new, but because they make decisions and probabilities first-class outputs for software.

Traditional classifiers already produce probabilities, and zero-shot classifiers already allow runtime-defined categories. Decision models bring these ideas into a typed interface with multiple question primitives, non-generative scoring, and the possibility of specialized training for reliable, calibrated decisions.

原文版权归 Hugging Face Blog 及原作者所有,本站转载仅供阅读。如需删除,请联系 support@aisa.one。