TypeSafe 的原语是你在代码中组合使用的小型、带类型的构建块。它们成对出现:一个问题定义了让 System One 模型对某个状态做出的一项判断,而它的答案就是返回的类型化取值。你在代码中组合这些答案来做出决策。共有三种问题类型,各自返回不同形态的答案。
| 类型 | 回答什么 | 返回 |
|---|---|---|
| Choice | 这些选项中是哪一个? | choice、probabilities、confidence |
| Score | 处于哪个级别? | score、legend、probabilities、confidence |
| Noul | 这是否为真? | noul(0 到 1) |
你可以只问一个问题,也可以把多个问题一起发送。请求中的每个问题看到的是同一个状态,各自独立评估,并在你选定的 ID 下返回一个类型化答案。
每个问题只要求一次即时判断
System One 模型为快速、聚焦的判断而生。要它做的是内行的人在给定恰当上下文后一秒钟内就能做出的判断。“这条消息是否传达了紧迫性?”是个好问题。“分析这条消息并确定最佳处理方案”则不是。后者需要慢速推理,而这正是应当把任务拆成多个小问题、再在代码中组合答案的信号。
如果你想要的判断取决于多个独立因素,就针对每个因素分别提问,再用你自己的逻辑组合答案。与其“给这份创业路演打个分”,不如分别询问市场规模、技术可行性和差异化程度,然后在代码中按相对重要性为它们加权。当优先级变化时,只需修改权重值,而不必重写提示词。一起提出多个问题展示了具体做法。
定义问题
每个问题都有一个 ID、一个 type 和 instructions。Choice 和 Score 问题还接受 criteria,它为 Choice 问题定义选项,为 Score 定义级别。Noul 问题可以接受 criteria,作为对“是”与“否”含义的可选澄清。
- ID。你选定的键,例如
refund_requested。它用于在响应中标识对应的答案。 type。choice、score或noul之一。instructions。你要就状态提出的问题。你的评估逻辑就写在这里。把它写成一个清晰、具体的问题,或写成一个供模型判断的陈述。对大多数问题来说,一个字符串就够了。它也可以是对象或数组,把问题放在一个字段、把其所指的数据放在其他字段;参见在问题中使用结构。criteria。可能的答案:Choice 问题是选项的映射,Score 问题是级别的有序列表,Noul 问题是关于“是”与“否”的可选描述。每种问题类型的页面会介绍其具体形态。
下面这个问题询问客户是否请求了退款:
from typesafe_sdk import Noul
questions = {
"refund_requested": Noul(
instructions="Does the customer request a refund?",
),
}
问题 ID 是给代码用的,不会发送给模型。请把完整的问题写进 instructions,即使 ID 本身看起来已经不言自明。
选择问题类型
选择与所需答案形态相匹配的类型。
-
Choice 适用于答案是已知选项集合之一、且选项之间没有顺序的情形:把工单路由到某个部门、分类文档类型、检测编程语言。给出完整的选项列表,并在列表可能无法覆盖所有输入时,添加一个
other或none of the above选项。 -
Score 适用于答案落在一段连续区间上、且你能描述区间上每个点的含义的情形:缺陷严重程度、客户沮丧程度、技能水平。级别由你来定义,模型返回在这段级别上的一个位置。
-
Noul 适用于干净的是/否问题,且概率本身就是有用的信号:这条消息是否包含个人身份信息、客户是否在请求退款、简历是否提到了分布式系统。
用 Noul 做是/否判断,用 Score 衡量在一段连续区间上的位置。“这位候选人 Python 强吗?”需要先明确定义什么叫“强”。Noul 值为 0.5 表示模型给“是”和“否”相同的概率,并不表示候选人技能水平中等。定义不清会让这个概率难以解读。
如果你想衡量技能水平,请使用定义了级别的 Score,例如:没有经验、有一定了解、日常使用、深入专长。如果你需要的是是/否决策,就把条件定义清楚,例如“简历是否写明候选人在工作中使用过 Python?”
如果两种类型看起来都合适,优先选择答案能被你的代码直接处置的那种。在 refund、rebook 和 information 之间做选择的 Choice 可以直接对应三条代码路径。衡量客户沮丧程度的 Score 对应一个阈值。Noul 对应一个 if。
返回什么
答案也是原语。每种问题类型都返回一个类型化取值,你的代码可以对它进行比较、设阈值、排序、传入后续逻辑,或放入下一次请求的状态中(参见当一个问题依赖另一个问题时)。
| 类型 | 答案字段 | 如何解读 |
|---|---|---|
| Choice | choice、probabilities、confidence | choice 是选中的选项。probabilities 是所有选项上的分布。confidence 概括该分布的峰值程度。 |
| Score | score、legend、probabilities、confidence | score 是在你的级别上的位置,可以落在两个级别之间。legend 按编号重复列出各级别。probabilities 是各级别上的分布。 |
| Noul | noul | 答案为“是”的概率。接近 1 是强烈的“是”,接近 0 是强烈的“否”,接近 0.5 表示不确定。Noul 没有单独的 confidence。 |
这些答案有两个特性,使其可以组合:
- 每个答案都被限制在你提供的选项之内。 模型返回的是你的选项或级别上的概率分布,绝不会返回它们之外的值。你的代码永远不必从生成的文字中解析出取值。
- 每个答案都是独立的。 一个问题的答案不会成为另一个问题的隐藏上下文。你可以增加或删除问题,而不改变其他问题的结果。
置信度页面解释了 confidence 如何由 probabilities 推导而来,以及如何用它决定何时自动执行、何时上报给人工。
引用特定字段
被评估的内容,即状态,通常是一个包含多个部分的 JSON 对象:一段对话、一条记录、一份保单。当问题只针对其中某个部分时,在 instructions 中用带点和索引的路径指向它的键来指明该部分,并保留反引号。这样模型就知道要判断状态的哪个部分。
来看“状态”页面中的客服对话:
{
"ticket": {
"subject": "Duplicate charge",
"messages": [
{"from": "customer", "text": "I was charged twice for order A-104. Please refund the duplicate."},
{"from": "support", "text": "We are checking the charges."}
]
},
"order": {
"id": "A-104",
"charges": [
{"amount_usd": 49, "status": "captured"},
{"amount_usd": 49, "status": "captured"}
]
},
"refund_policy": "Duplicate charges are eligible for a refund."
}
下面两个问题通过路径指向客户的消息、保单和扣款记录:
questions = {
"refund_requested": {
"type": "noul",
"instructions": "Does `ticket.messages[0].text` request a refund?",
},
"policy_supports_refund": {
"type": "noul",
"instructions": (
"Does `refund_policy` support the refund requested "
"in `ticket.messages[0].text`, given `order.charges`?"
),
},
}
显式路径清楚地表明,结构化状态中的哪些部分应当参与每项判断。关于如何组织输入,参见状态。
一起提出多个问题
把使用同一个状态的所有问题放进一次请求发送。你可以自由混用问题类型。System One 模型会并行评估请求中的每个问题。增加问题几乎不会改变响应时间,只会消耗这些额外问题的 token,而这很便宜。问一个可能用不上的问题几乎是免费的。
下面这个请求一次性完成对客户消息的分类、紧急程度判断和沮丧程度评分:
{
"state": "Our API integration started returning 500 errors on every request about 20 minutes ago, and we can't process any customer orders until this is fixed.",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this",
"criteria": {
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions"
}
},
"is_urgent": {
"type": "noul",
"instructions": "The message conveys urgency or time-sensitivity"
},
"frustration": {
"type": "score",
"instructions": "How frustrated the customer appears",
"criteria": [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language"
]
}
}
}我们的客户端 SDK 提供类型化的问题和答案。在 Python 中,把由 Choice、Noul 和 Score 对象组成的 questions 字典传给 client.system_one(...)。下面的请求只发送一次工单和退款政策,就为每个问题拿到一个类型化答案:
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
state = {
"ticket_message": "My flight was cancelled. Can I get a refund?",
"refund_policy": "Cancelled flights are eligible for a full refund.",
}
with TypeSafeClient() as client:
response = client.system_one(
state=state,
questions={
"refund_requested": Noul(
instructions="Does `ticket_message` request a refund?",
),
"request_type": Choice(
instructions="What is the main request in `ticket_message`?",
criteria={
"refund": "The customer wants money returned.",
"rebooking": "The customer wants a replacement flight.",
"information": "The customer is asking for information only.",
},
),
"frustration": Score(
instructions="How frustrated does the customer appear in `ticket_message`?",
criteria=[
"Calm and neutral.",
"Concerned but civil.",
"Very angry or using strong language.",
],
),
},
)
print(response.answers["refund_requested"].noul)
print(response.answers["request_type"].choice)
print(response.answers["frustration"].score)
关于各语言的安装与用法,参见客户端 SDK。
提出投机性问题
把代码可能用到的每个问题都问出来,包括那些答案只对部分输入有意义的,再让代码决定使用哪些答案。如果工单最终并不是缺陷报告,就忽略严重程度那个答案。我们称之为投机性扇出模式。并行问题实战手册展示了把 13 个问题批量合并为一次调用,比 13 次单独调用便宜 11.5 倍、快 9.6 倍,而答案毫无变化。
编程代理比人更容易养成“每次调用只问一个问题”的习惯。TypeSafe agent skill 会告诉你的代理在每次调用中放入大量问题,包括那些只对部分输入有意义的问题。
把复杂判断拆分为多个问题
取决于多个因素的判断,最好拆成每个因素一个问题。在代码中组合答案,并按相对重要性给每个答案一个权重。权重由你决定。当组合结果与你团队的判断不符时,在代码里修改权重再跑一次。由于问题在同一个请求内并行运行,增加问题几乎不会改变响应时间。拆分只多花一点额外的问题 token。
例如,工单优先级可以由三个 Score 问题构成:缺陷有多严重、客户有多沮丧、报告给工程师提供了多少可用的信息。Score 页面在把复杂判断拆分为多个 Score中逐步讲解了这个请求以及对答案做归一化和加权的代码。这种技巧称为组合评分模式。
当一个问题依赖另一个问题时
同一请求中的问题是相互独立的:一个答案不会成为另一个问题的上下文。如果后面的判断依赖前面的答案,就在代码中发起第二次请求。只有当你的代码在拿到第一个答案之前无法构造第二个请求时,这种依赖才是真实存在的:比如需要用答案为状态获取更多数据、决定状态由什么构成,或选择下一个问题的选项。否则,就把问题放在一起提问,然后在代码中组合答案。
两次请求是例外而非常规。如果第二个请求的问题本来可以针对原始状态提问,就把它们放进第一个请求,让代码忽略用不上的答案。有三篇实战手册是出于真实原因发起第二次请求的。技能推荐在一次请求中对 182 个技能排序,然后取前三个的完整文本,用更好的证据再次评判它们。结构恢复先询问每个换行是否截断了一个句子,再根据这些答案把多行合并成块,然后对这些块进行分类——在第一次请求回答之前,这些块并不存在。层级分类用每个 Choice 答案来决定下一次请求提供哪些选项。
关于如何把工作流拆解为聚焦的判断,参见如何用 TypeSafe 构建。
下一步
想了解它们如何组合成系统架构,请前往模式。