当答案是一段可以分档描述的连续区间上的位置时,使用 Score。例如,缺陷有多严重、客户有多满意、候选人的 Python 经验有多少。如果答案是固定选项集合中的一项、且选项之间没有顺序,请使用 Choice。如果是非题,请使用 Noul。选择问题类型对三者做了比较。
Score 的答案是 score 中沿你的级别的一个位置,它可以落在两个级别之间。模型还会在 probabilities 中返回每个级别的概率,并为该答案返回一个 confidence 值。
The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.
How the score and confidence are calculated
Multiply each level number by its probability, then add the results:
TypeSafe computes this from how the probability is spread across the levels. All of it on one level gives 1.0; the more evenly it spreads, the lower the confidence.
每个台阶前方的数字是位置,具体解释见级别。
请求结构
发往 TypeSafe API 的 POST 请求体与其他问题类型一样,有三个顶层字段:state,要评估的内容;model;以及 questions。每个 Score 问题包含以下字段:
type:始终为"score"。instructions:模型要回答的问题,即它在给什么评级。criteria:级别描述的有序数组,从量表低端到高端。至少要有两个级别;API 最多接受 10 个。
下面这个请求的状态是一份缺陷报告,问题是这个缺陷有多严重:
{
"state": "The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.",
"questions": {
"bug_severity": {
"type": "score",
"instructions": "How severe is the reported issue?",
"criteria": [
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists"
]
}
}
}问题 ID 由你选定,本例中是 bug_severity。该 ID 不会发送给模型。答案会以同样的 ID 返回。
级别
criteria 中的每一项是一个级别:用文字描述的、可能答案所构成区间上的一个点。级别的编号是它在 criteria 数组中的位置,从 0 开始,因此上面的三项分别是级别 0、1 和 2。数组的顺序就是编号的顺序。
模型拿到的只有描述,别无其他;每个级别都独立地与状态比对判断。
响应中的 score 是级别区间上的一个位置。对三级量表而言,它的取值范围是 0 到 2,并且可以落在两个级别之间。
我们的客户端 SDK 提供类型化的问题。在 Python 中,同一个问题就是一个 Score:
from typesafe_sdk import Score, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state="The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.",
questions={
"bug_severity": Score(
instructions="How severe is the reported issue?",
criteria=[
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists",
],
),
},
)
print(response.answers["bug_severity"].score)
使用 system_one 方法或 https://api.typesafe.ai/v1/systemone 端点来调用 System One 模型。model 字段选择由哪个模型处理请求。如何用 TypeSafe 构建介绍了应在代码中的哪个位置调用它。
使用我们的客户端 SDK 之一,或直接调用 TypeSafe API。如果是编程代理在替你编写集成代码,请先安装 TypeSafe agent skill,让它了解请求和响应的结构。
响应结构
响应的 answers 中,每个问题各有一条记录,键为请求中的 ID。以下是对上面示例请求的响应:
{
"model": "jev-1.13.0",
"answers": {
"bug_severity": {
"type": "score",
"score": 1.43,
"confidence": 0.35,
"legend": {
"0": "Cosmetic; no impact to functionality",
"1": "Broken or degraded feature, but workaround exists",
"2": "Blocking issue; no workaround exists"
},
"probabilities": {
"0": 0.0,
"1": 0.57,
"2": 0.43
}
}
},
"usage": {
"input_tokens": 332,
"output_tokens": 18
}
}
每个 Score 答案包含五个值:
type:TypeSafe 问题的类型。probabilities:每个级别的概率,以字符串形式的级别编号为键。所有值之和为 1。score:级别数轴上的位置,取值从 0 到最高级别编号(这里是 2)。它等于每个级别编号乘以其概率后求和:0 x 0.0 + 1 x 0.57 + 2 x 0.43 = 1.43。legend:每个级别编号到其描述的映射。confidence:一个 0 到 1 之间的数,由probabilities的分散程度计算得出。单个级别上的尖峰意味着高置信度;概率分散在多个级别上则意味着低置信度。
分数 1.43 表示模型在级别 1 和 2 之间摇摆,略微偏向级别 1。这与报告相符:导出功能坏了,对大多数客户来说改用 Chrome 是一个变通办法,但对只用 Safari 的客户则不然。模型给“存在变通办法”0.57,给“没有变通办法”0.43,置信度因为是分散的而为 0.35。
在使用 Python SDK 时,ScoreAnswer 将 score、confidence、probabilities 和 legend 作为类型化字段。SDK 用整数级别的键来索引 probabilities 和 legend,而不是字符串。
解读 Score
我们来看看分数如何随输入变化。例如,沿用上面请求中的问题及其级别:
"How severe is the reported issue?"
→ 0: Cosmetic; no impact to functionality
→ 1: Broken or degraded feature, but workaround exists
→ 2: Blocking issue; no workaround exists
我们可以看到不同的缺陷报告如何改变分数:
probabilities | |||||
|---|---|---|---|---|---|
| State | score | confidence | Level 0 | Level 1 | Level 2 |
| The export button is misaligned by a few pixels on the settings page. | 0.0 | 1.0 | 1.0 | 0.0 | 0.0 |
| The PDF export button does nothing when clicked. I can still export to CSV and convert it myself, but that takes ages. | 1.0 | 1.0 | 0.0 | 1.0 | 0.0 |
| Export to PDF fails with a spinner that never finishes. Some of our team say CSV export still works for them, others say it fails too. | 1.11 | 0.84 | 0.0 | 0.89 | 0.11 |
| The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari. | 1.43 | 0.35 | 0.0 | 0.57 | 0.43 |
| Nobody on our team can log in since this morning. We get a 500 error on every attempt. | 2.0 | 1.0 | 0.0 | 0.0 | 1.0 |
在这些示例中,置信度 1.0 表示返回的分布把全部概率放在了一个级别上。这描述的是模型的答案,并不保证答案是正确的。
分数是级别编号的概率加权平均值。在第三和第四个示例中,概率分散在级别 1 和 2 之间。级别 2 上的权重越大,分数越高。它衡量的并不是没有变通办法的客户比例。
不同的分布可能产生相同的分数。分数 1.0 可能意味着全部概率都在级别 1 上,也可能意味着级别 0 和 2 各占一半。请把 probabilities 和 confidence 与分数放在一起读,以区分这些情况。
小数分数是一个位置。你可以用它按严重程度对报告排序,或在代码需要一个确定结果时将其四舍五入到最近的级别。我们的实体对齐实战手册展示了一个四舍五入到最近级别来做决策的例子。
Score 的低置信度通常意味着三种情况之一:级别在这个状态上存在重叠、问题在衡量不止一件事,或状态没有提供足够的信息来定位。我们的置信度文档介绍了如何在代码中使用它。
写出好的级别
描述情形,而不是程度。“功能损坏或退化,但存在变通办法”给了模型可与状态比对的东西,“中等严重”则没有。具体的描述能帮助模型区分级别。请用已知的示例核对答案;单凭更高的置信度并不能说明某条描述更好。
每个级别都是单独评估的。模型看不到级别的编号或相邻级别,因此“比上一级更糟”对它毫无意义,描述或 instructions 中的数字也帮不上忙。下面是只使用数字作为级别时,在上表“按钮错位”报告上的结果:
instructions: "Rate severity from 0 to 2, where 2 is worst"
criteria: ["0", "1", "2"]
→ score 0.55, confidence 0.33, probabilities 0: 0.45, 1: 0.55, 2: 0.0
同一份报告在使用三个描述性级别时得分为 0.0、置信度 1.0。而只有数字时,模型没有任何可比对的东西,于是把概率分散到 0 和 1 之间。
能用多少级别就用多少,上限 10 个,前提是每个级别都能描述得彼此分明。三个级别就很好。不要添加你无法清晰区分的级别。
让每个 Score 问题只衡量一个维度。如果一条描述写着“守时、聪明、有经验”,那这个问题就在衡量三件事,一项输入在某个维度高、另一个维度低时将无法定位。置信度下降,分数的意义也随之减弱。把它拆成每个维度一个 Score 问题,再在代码中组合,如下一节所示。
如果量表的顶端存在一种需要区别对待的罕见极端情形,就为它单设一个级别。一个以“非常愤怒”收尾的情绪量表可以再加一级“辱骂或威胁”。没有这一级,两类消息都可能得到接近顶端的分数,仅凭分数可能无法区分它们。
如果根本不存在中间地带,答案是少数几个离散类别之一,请改用 Choice,或把问题拆成多个 Noul 问题。用你自己的数据测试级别很重要。同一个量表的两种措辞在你的数据上可能表现不同。
把复杂判断拆分为多个 Score 问题
复杂判断——即取决于多个因素的判断——最好拆成每个因素一个 Score 问题,然后在代码中组合 TypeSafe 返回的各个 Score 来完成判断。有些 Score 问题可能比其他的更重要,因此要按相对重要性给每个 Score 问题一个权重。权重由你决定。当组合结果与你团队的判断不符时,在代码里修改权重再跑一次。把这些 Score 问题放进一次请求发送。它们是并行评估的。增加问题几乎不会改变响应时间,只多消耗一点额外的问题 token;参见一起提出多个问题。
下面的请求是上表中“转圈”工单的扩充版本,补充了更多上下文。它提出三个 Score 问题:缺陷有多严重、客户有多沮丧、报告给工程师提供了多少可用的信息。
{
"state": "Export to PDF fails with a spinner that never finishes. Some of our team say CSV export still works for them, others say it fails too. This is the third time I'm writing in and honestly I'm done. Steps: open any report, click Export, choose PDF. Chrome 128 on macOS.",
"questions": {
"severity": {
"type": "score",
"instructions": "How severe is the reported issue?",
"criteria": [
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists"
]
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language or threatening to leave"
]
},
"report_quality": {
"type": "score",
"instructions": "How much does the report give an engineer to work with?",
"criteria": [
"No detail; just says something is broken",
"Names the feature but no steps or environment",
"Steps to reproduce or environment, but not both",
"Steps to reproduce and environment"
]
}
}
}TypeSafe 的响应:
{
"model": "jev-1.13.0",
"answers": {
"severity": {
"type": "score",
"score": 1.24,
"confidence": 0.64,
"legend": {
"0": "Cosmetic; no impact to functionality",
"1": "Broken or degraded feature, but workaround exists",
"2": "Blocking issue; no workaround exists"
},
"probabilities": {
"0": 0.0,
"1": 0.76,
"2": 0.24
}
},
"frustration": {
"type": "score",
"score": 1.28,
"confidence": 0.58,
"legend": {
"0": "Calm, just stating facts",
"1": "Frustrated but civil",
"2": "Very angry, strong language or threatening to leave"
},
"probabilities": {
"0": 0.0,
"1": 0.72,
"2": 0.28
}
},
"report_quality": {
"type": "score",
"score": 3.0,
"confidence": 1.0,
"legend": {
"0": "No detail; just says something is broken",
"1": "Names the feature but no steps or environment",
"2": "Steps to reproduce or environment, but not both",
"3": "Steps to reproduce and environment"
},
"probabilities": {
"0": 0.0,
"1": 0.0,
"2": 0.0,
"3": 1.0
}
}
},
"usage": {
"input_tokens": 468,
"output_tokens": 43
}
}
每个问题都独立地针对工单作答并得到一个分数:
severity为 1.24,置信度 0.64。与开头示例的解读相同:导出功能坏了,有些人有变通办法。frustration为 1.28,置信度 0.58。措辞还算克制,但“第三次”和“我受够了”把一部分分数推向了最高级别,于是模型在“沮丧但礼貌”和“非常愤怒”之间按 0.72 和 0.28 分配。对这张工单而言这两个级别存在重叠,所以置信度只是中等。report_quality为 3.0,置信度 1.0。复现步骤和浏览器版本都写明了。
这三个量表的长度不同,因此在组合之前要对每个分数做归一化。四级量表返回 0 到 3,三级量表返回 0 到 2,因此一个上的最高分比另一个上的最高分更大。把每个分数除以其最高级别编号 len(criteria) - 1,就可以把所有分数都放到 0 到 1 的区间。这样权重的含义就名副其实:severity 取 0.6、frustration 取 0.3,意味着 severity 的重要性是 frustration 的两倍。
下面的 TypeSafe Python SDK 代码提出这三个问题,对每个分数做归一化,然后以一个示例优先级计算将它们组合起来:
from typesafe_sdk import Score, TypeSafeClient
TRIAGE_QUESTIONS = {
"severity": Score(
instructions="How severe is the reported issue?",
criteria=[
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists",
],
),
"frustration": Score(
instructions="How frustrated is the customer?",
criteria=[
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language or threatening to leave",
],
),
"report_quality": Score(
instructions="How much does the report give an engineer to work with?",
criteria=[
"No detail; just says something is broken",
"Names the feature but no steps or environment",
"Steps to reproduce or environment, but not both",
"Steps to reproduce and environment",
],
),
}
def normalized(answers, question_id: str) -> float:
"""Put a score on 0 to 1 by dividing by its top level number."""
top_level = len(TRIAGE_QUESTIONS[question_id].criteria) - 1
return answers[question_id].score / top_level
def priority(ticket: str) -> float:
with TypeSafeClient() as client:
response = client.system_one(
state=ticket,
questions=TRIAGE_QUESTIONS,
)
answers = response.answers
severity = normalized(answers, "severity")
frustration = normalized(answers, "frustration")
report_quality = normalized(answers, "report_quality")
# A detailed report helps an engineer investigate, so it raises priority a little.
return 0.6 * severity + 0.3 * frustration + 0.1 * report_quality
对上面的示例响应,归一化后的分数为:severity 0.62、frustration 0.64、report quality 1.0。优先级为 0.6 × 0.62 + 0.3 × 0.64 + 0.1 × 1.0 = 0.664,四舍五入后是 0.66。
权重就在你的代码里,因此你可以清楚地看到这个数字是如何算出来的,并在排序与你们团队的做法不符时修改它。如果之后需要更多 Score 问题,直接加进 TRIAGE_QUESTIONS。请求数保持为一次。这种把复杂判断拆成多个独立的 Score、再用代码中的权重组合的技巧,称为组合评分模式。
结构化级别描述
先为每个级别写一段基本的文字描述。当你认为某输入很明确、模型却总在两个相邻级别之间打分时,就把每个级别从字符串改为对象,用一个字段说明该级别涵盖什么,再用一个字段给出几个示例情形。每个级别使用相同的字段名,让模型能够对等比较。
下面的请求还是之前用过的“转圈”工单,但每个级别都带上了示例:
{
"state": "Export to PDF fails with a spinner that never finishes. Some of our team say CSV export still works for them, others say it fails too.",
"questions": {
"bug_severity": {
"type": "score",
"instructions": "How severe is the reported issue?",
"criteria": [
{
"what": "Cosmetic; no impact to functionality",
"examples": [
"typo in a label",
"misaligned icon"
]
},
{
"what": "Broken or degraded feature, but workaround exists",
"examples": [
"export fails in one browser but works in another"
]
},
{
"what": "Blocking issue; no workaround exists",
"examples": [
"cannot log in",
"data loss"
]
}
]
}
}
}响应:
{
"model": "jev-1.13.0",
"answers": {
"bug_severity": {
"type": "score",
"score": 1.09,
"confidence": 0.87,
"legend": {
"0": {
"what": "Cosmetic; no impact to functionality",
"examples": [
"typo in a label",
"misaligned icon"
]
},
"1": {
"what": "Broken or degraded feature, but workaround exists",
"examples": [
"export fails in one browser but works in another"
]
},
"2": {
"what": "Blocking issue; no workaround exists",
"examples": [
"cannot log in",
"data loss"
]
}
},
"probabilities": {
"0": 0.0,
"1": 0.91,
"2": 0.09
}
}
},
"usage": {
"input_tokens": 379,
"output_tokens": 18
}
}
使用纯字符串时,这张工单得分 1.11、置信度 0.84。加上示例后得分为 1.09、置信度 0.87,变化很小,因为纯字符串本来就能很好地定位它。当纯字符串让模型摇摆不定时,效果会更明显,如下表所示。
示例会引导模型,而只有当它们看起来像你的真实输入时才有帮助。下表是开头的 Safari 报告配以三组不同的级别对象:
| 级别描述 | score | confidence |
|---|---|---|
| 纯字符串:没有带示例的对象 | 1.43 | 0.35 |
| 添加了 examples 数组,包含有用的示例:“export fails in one browser but works in another” | 1.03 | 0.96 |
| 添加了 examples 数组,但示例与浏览器无关:“search fails, but browsing categories still works” | 1.43 | 0.35 |
在这组对比中,匹配的示例把几乎所有概率集中到了一个级别上。无关的示例则返回与纯字符串相同的结果。更高的置信度并不能确立哪个答案是正确的。请选择已知预期级别的示例,然后在保留修改后的描述之前,用另外的输入对其测试。