文档目录
Choice(选择题)
Choice 是 System One 的题型(question type),用于从一组预定义选项中选出一个。答案包含所选选项、每个选项的概率以及置信度。
非官方中文翻译 · 内容以 英文原文 ↗ 为准 · 同步于 2026-10-15
使用 Choice(选择题)来回答某个答案是一组固定选项之一的问题。例如,工单由哪个团队处理、产品属于哪个类别,或者代码片段是用哪种语言写的。如果答案是一个连续区间上的位置,使用 Score。如果答案是「是」或「否」,使用 Noul。怎样选题型 对三种题型做了对比。
Choice 答案中的 choice 是所选选项。模型还会返回 probabilities 中每个选项的概率,以及一个 confidence(置信度)值,表示对所选选项的置信度。
示例问题:
"What programming language is this code written in"
→ options: python, javascript, typescript, go, rust, other
"What type of meeting is this based on the title and description"
→ options: standup, planning, retrospective, one on one, brainstorm, none of the above
"Which product category does this item belong to"
→ options: electronics, clothing, home garden, food and beverage请求结构
向 TypeSafe API 发出的 POST 请求体有特定结构。顶层有三个字段:state(上下文状态),它是待评估的内容;model;以及 questions,它是从 question id(你自己选择)到 question 对象的映射。每个 Choice 问题包含以下字段:
type:始终为"choice"。instructions(指令):模型要回答的问题。criteria(判定标准):答案选项,作为一个映射。每个 key 是一个选项名,每个 value 是该选项的描述。
下面是一个请求,其中 state 是一张来自在线鞋店的客服工单,question 是应该由哪个团队处理:
{
"model": "jev-latest",
"state": "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems"
}
}
}
}question id 由你决定,本例中是 department。答案会放在同一个 id 下返回。模型看不到 question id。选项名以及它们各自的描述都会发给模型,所以要让这些描述把各选项区分开。
我们的客户端 SDK 提供了带类型的 questions。在 Python 中,同样的问题是一个 Choice:
from typesafe_sdk import Choice, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state="My running shoes arrived in the wrong size. Can I swap them for a size 10?",
questions={
"department": Choice(
instructions="Which team should handle this?",
criteria={
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems",
},
),
},
)
print(response.answers["department"].choice)使用 system_one 方法或 https://api.typesafe.ai/v1/systemone 接口(endpoint)调用 System One 模型。model 字段指定由哪个模型处理请求。如何使用 TypeSafe 构建 介绍了在代码中的什么位置调用它。
使用我们的客户端 SDK 之一,或者直接调用 HTTP API。如果由编程智能体(coding agent)为你编写集成代码,请先安装 TypeSafe 智能体技能,让它了解请求和响应的结构。
响应结构
响应的 answers 中每个 question 对应一项,question id 与请求中保持一致。这是对上面示例请求的响应:
{
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice",
"choice": "returns",
"confidence": 1.0,
"probabilities": {
"shipping": 0.0,
"returns": 1.0,
"billing": 0.0
}
}
},
"usage": {
"input_tokens": 328,
"output_tokens": 34
}
}除了 type 之外,每个 Choice 答案包含三个值:
choice:概率最高的选项。probabilities:每个选项的完整概率分布。所有值之和为 1。confidence:一个介于 0 到 1 之间的数,根据probabilities的分布形状计算。分布平坦、概率分散在多个选项上表示置信度低;只有一个尖峰集中在某个选项上表示置信度高。
这张工单是个简单的情况,所以所有概率都集中在 returns 上,置信度为 1.0。如果一张工单同时提到尺寸不对和退款未到账,probabilities 就会在 returns 和 billing 之间分散,置信度也会下降。
最佳实践:每次调用多问几个问题
尽量把所有可能用到的 Choice 问题在同一次请求中发出,而不是一个问题发一次请求。多个问题并行评估。增加问题几乎不会改变响应时间,代码可以忽略不需要的答案。额外的问题仍然要消耗 token。一次性问多个问题 详细解释这一点;下一节会在一次调用中展示五个 Choice 问题。
同样的逻辑也适用于单个 Choice 问题内部的选项。一个 Choice 问题最多支持 255 个选项,每加一个选项会多消耗少量 token,因此应该把完整的团队、品类或产品列表都提供给模型,而不是只给一个候选清单。当列表可能无法覆盖所有输入时,加一个 other 或 none of the above 选项,让模型能够说「都不匹配」。
要通过深层级或大分类体系对文档进行分类,可以逐层链接 Choice 问题。层级分类实战示例 演示了如何在 Choice 概率上做 beam search:在每一层保留最佳的 K 条候选路径,而不是只走单一的贪心路径。
一个更复杂的示例
上面的基础示例把工单分配给一个团队。更大的客服系统可能还需要知道退货原因、配送问题、客户诉求和客户语气。
下面的请求对一张比第一张更含糊的工单提出五个 Choice 问题:该工单涉及三个团队,且没有说明客户想要什么。
{
"model": "jev-latest",
"state": "Shoes arrived two weeks late and in the wrong size. Also I see two charges of $120 on my card. What are you going to do about this?",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems"
}
},
"return_reason": {
"type": "choice",
"instructions": "If the customer wants to return something, why?",
"criteria": {
"wrong_size": "The item doesn't fit",
"wrong_item": "A different product was delivered",
"damaged": "The item arrived broken or faulty",
"changed_mind": "The item is fine, the customer no longer wants it",
"other": "A return reason that fits none of the above"
}
},
"shipping_issue": {
"type": "choice",
"instructions": "If this is a shipping problem, which kind is it?",
"criteria": {
"not_delivered": "The package never arrived",
"delayed": "The package is late but still on its way",
"wrong_address": "The package went to the wrong place",
"damaged_in_transit": "The package arrived damaged",
"other": "A shipping problem that fits none of the above"
}
},
"requested_resolution": {
"type": "choice",
"instructions": "What does the customer want to happen?",
"criteria": {
"exchange": "Swap the item for a different one",
"refund": "Money back",
"replacement": "The same item sent again",
"information": "Just an answer, no action needed"
}
},
"tone": {
"type": "choice",
"instructions": "What is the customer's tone?",
"criteria": {
"calm": null,
"frustrated": null,
"angry": null
}
}
}
}其中两个 Choice 问题是推测式的:return_reason 只有在 department 为 returns 时才有意义,shipping_issue 只有在为 shipping 时才有意义。tone 问题使用 null 作为描述,因为选项名本身就足够清楚。
TypeSafe 响应:
{
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice",
"choice": "returns",
"confidence": 0.42,
"probabilities": {
"shipping": 0.04,
"billing": 0.35,
"returns": 0.61
}
},
"return_reason": {
"type": "choice",
"choice": "wrong_size",
"confidence": 1.0,
"probabilities": {
"other": 0.0,
"wrong_size": 1.0,
"changed_mind": 0.0,
"damaged": 0.0,
"wrong_item": 0.0
}
},
"shipping_issue": {
"type": "choice",
"choice": "delayed",
"confidence": 0.67,
"probabilities": {
"wrong_address": 0.0,
"other": 0.26,
"not_delivered": 0.0,
"damaged_in_transit": 0.0,
"delayed": 0.74
}
},
"requested_resolution": {
"type": "choice",
"choice": "refund",
"confidence": 0.2,
"probabilities": {
"replacement": 0.34,
"refund": 0.4,
"information": 0.02,
"exchange": 0.24
}
},
"tone": {
"type": "choice",
"choice": "frustrated",
"confidence": 0.76,
"probabilities": {
"frustrated": 0.84,
"angry": 0.16,
"calm": 0.0
}
}
},
"usage": {
"input_tokens": 589,
"output_tokens": 212
}
}每个问题都基于工单独立作答:
department的答案是returns,概率 0.61,但因为出现重复扣款,billing也有 0.35。这张工单同时属于两个团队,分散的置信度 0.42 反映了这一点。return_reason是wrong_size,置信度为 1.0,这在意料之中,因为工单里说得很清楚。shipping_issue的答案在delayed和other之间分散。它是一个推测式问题,且department的结果不是 shipping,因此代码可以忽略它,如下面的代码示例所示。requested_resolution倾向于refund,概率 0.40,replacement和exchange分走了剩下的大部分,置信度为 0.20。重复扣款暗示要退款,尺寸不对又暗示要换货,客户却没有明说要哪一个。tone的答案是frustrated,概率 0.84,置信度 0.76。
下面的示例代码读取它需要的答案,忽略其余部分,并把低置信度的答案视为「去询问」的理由,而非「直接采取行动」的理由:
from typesafe_sdk import Choice, TypeSafeClient
TRIAGE_QUESTIONS = {
"department": Choice(
instructions="Which team should handle this?",
criteria={
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems",
},
),
"return_reason": Choice(
instructions="If the customer wants to return something, why?",
criteria={
"wrong_size": "The item doesn't fit",
"wrong_item": "A different product was delivered",
"damaged": "The item arrived broken or faulty",
"changed_mind": "The item is fine, the customer no longer wants it",
"other": "A return reason that fits none of the above",
},
),
"shipping_issue": Choice(
instructions="If this is a shipping problem, which kind is it?",
criteria={
"not_delivered": "The package never arrived",
"delayed": "The package is late but still on its way",
"wrong_address": "The package went to the wrong place",
"damaged_in_transit": "The package arrived damaged",
"other": "A shipping problem that fits none of the above",
},
),
"requested_resolution": Choice(
instructions="What does the customer want to happen?",
criteria={
"exchange": "Swap the item for a different one",
"refund": "Money back",
"replacement": "The same item sent again",
"information": "Just an answer, no action needed",
},
),
"tone": Choice(
instructions="What is the customer's tone?",
criteria={"calm": None, "frustrated": None, "angry": None},
),
}
def triage(ticket: str) -> None:
with TypeSafeClient() as client:
response = client.system_one(
state=ticket,
questions=TRIAGE_QUESTIONS,
)
answers = response.answers
department = answers["department"]
if department.confidence < 0.3:
# Not clear which team to send to. Let a person decide.
send_to_manual_triage(ticket)
return
if department.choice == "returns":
# return_reason answer is only used here
assign(ticket, team="returns", issue=answers["return_reason"].choice)
elif department.choice == "shipping":
# shipping_issue answer is only used here
assign(ticket, team="shipping", issue=answers["shipping_issue"].choice)
else:
assign(ticket, team="billing")
# A second team with a real share of the probability gets a copy
for team, probability in department.probabilities.items():
if team != department.choice and probability > 0.25:
notify(ticket, team=team)
resolution = answers["requested_resolution"]
if resolution.confidence < 0.5:
# The customer hasn't said what they want. Ask, don't guess.
ask_customer_what_they_want(ticket)
elif resolution.choice == "refund":
flag_for_refund_approval(ticket)
if answers["tone"].choice == "angry":
flag_for_senior_agent(ticket)对于上面的工单,这段代码会把工单分给 returns 团队,问题为 wrong_size;因为 billing 团队 0.35 的份额超过了 0.25 的阈值,会向其抄送一份;又因为 resolution 置信度 0.20 低于 0.5,会去询问客户真正想要什么。代码没有使用 shipping_issue 这个答案。
一次请求,五个答案,路由逻辑就是普通的 if 语句。如果之后还想知道客户的语言,或者工单涉及哪个产品,只需再往 TRIAGE_QUESTIONS 里加一个 Choice 问题即可;请求次数仍然是一次。
智能家居助手演示 在一次调用中针对每个用户请求评估一长串 Choice 问题:请求类别、房间、设备和动作。其中大部分问题对某一次具体请求来说并不相关,代码会直接忽略。
结构化的 instructions(指令)与 criteria(判定标准)
每个选项先用一句话描述。当两个选项很相似、模型总是分不清时,不要用字符串,改用对象来描述。给对象设置几个字段:选项覆盖什么、相邻选项覆盖什么、以及几条示例输入。
下面的两个 answer(答案)选项 return_policy 和 return_status 很容易混淆。哪怕是关于其中任一选项的工单,都可能提到退货和退款,所以每个选项都要明确写出自己不覆盖什么。
{
"model": "jev-latest",
"state": "I sent the shoes back a week ago. When do I get my money?",
"questions": {
"return_topic": {
"type": "choice",
"instructions": {
"question": "Which returns topic is the customer asking about?",
"focus": "Classify the information the customer wants."
},
"criteria": {
"return_policy": {
"what": "Whether and how an item can be returned",
"not_for": "Progress of a return already sent",
"examples": [
"Can I return shoes I've worn once?",
"How long do I have to return an order?"
]
},
"return_status": {
"what": "Progress of a return already sent",
"not_for": "Whether and how an item can be returned",
"examples": [
"Has my return arrived yet?",
"When will my refund be paid?"
]
}
}
}
}
}return_status 的响应,置信度 1.0:
{
"model": "jev-1.13.0",
"answers": {
"return_topic": {
"type": "choice",
"choice": "return_status",
"confidence": 1.0,
"probabilities": {
"return_policy": 0.0,
"return_status": 1.0
}
}
},
"usage": {
"input_tokens": 407,
"output_tokens": 32
}
}字段名 question、focus、what、not_for 和 examples 都不属于 API,也都不是保留字段。你可以自己取名,就像取选项名一样。模型会把字段名和值一起看到,所以要取简短、能说明后面内容的名字。
本文档由 Mintlify 构建和托管,它是一个开发者文档平台。