Score
当答案是一条可以分档描述的刻度上的位置时,用 Score。例如:bug 有多严重、顾客有多满意、候选人某项技能有多熟练。
Score 的答案是 score:它在你的档位刻度上的位置,可以落在两档之间。同时返回每一档的概率 probabilities、档位对照表 legend 和一个 confidence。
几个典型问题(前面的数字就是档位编号):
"这个 bug 有多严重"
→ 0 表面问题 / 1 功能受损但有变通办法 / 2 功能不可用且无变通办法
"这条评价的情绪如何"
→ 0 很差 / 1 偏负面 / 2 中性 / 3 偏正面 / 4 很好
"候选人的 SQL 水平"
→ 0 没提到 / 1 了解 / 2 工作中日常使用 / 3 能做性能调优和建模请求结构
和其他问题类型一样,请求体顶层是 state、model(可省略)和 questions。每个 Score 问题包含:
type:固定为"score";instructions:要评估的是什么;criteria:有序的档位描述数组,从低到高。至少 2 档,最多 10 档。
下面的 state 是一份内部 bug 报告:
{
"state": "财务系统的「导出 Excel」按钮在 Safari 上点了没反应,Chrome 正常。但财务部统一用公司配的 Mac,很多人只装了 Safari。",
"model": "xiangxin-latest",
"questions": {
"bug_severity": {
"type": "score",
"instructions": "这个 bug 有多严重?",
"criteria": [
"表面问题:样式错位、错别字,不影响功能",
"功能受损,但存在变通办法",
"功能完全不可用,且没有变通办法"
]
}
}
}档位
criteria 里的每一项是一档:刻度上的一个点,用文字描述。档位的编号就是它在数组中的下标,从 0 开始,所以上面三项分别是 0、1、2 档。数组的顺序就是编号。
模型拿到的只有这些描述本身,并且每一档都是独立地与 state 对照评估的。
用 Python SDK 写成 Score:
from xiangxin import Score, XiangxinClient
with XiangxinClient() as client:
resp = client.system_one(
state=(
"财务系统的「导出 Excel」按钮在 Safari 上点了没反应,Chrome 正常。"
"但财务部统一用公司配的 Mac,很多人只装了 Safari。"
),
questions={
"bug_severity": Score(
instructions="这个 bug 有多严重?",
criteria=[
"表面问题:样式错位、错别字,不影响功能",
"功能受损,但存在变通办法",
"功能完全不可用,且没有变通办法",
],
),
},
)
sev = resp.answers["bug_severity"]
print(sev.score, sev.confidence)响应结构
上面请求的响应:
{
"model": "xiangxin-1.0.0",
"answers": {
"bug_severity": {
"type": "score",
"score": 1.26,
"confidence": 0.7,
"legend": {
"0": "表面问题:样式错位、错别字,不影响功能",
"1": "功能受损,但存在变通办法",
"2": "功能完全不可用,且没有变通办法"
},
"probabilities": {"0": 0.02, "1": 0.7, "2": 0.28}
}
},
"usage": {"input_tokens": 102, "output_tokens": 272}
}Score 答案包含:
type:"score";probabilities:每一档的概率,键是字符串形式的档位编号,总和为 1;score:刻度上的位置,范围 0 到最高档编号。它等于"档位编号 × 该档概率"之和:0 × 0.02 + 1 × 0.7 + 2 × 0.28 = 1.26;legend:档位编号 → 描述的对照表;confidence:等于最高那一档的概率。本例为 0.7。
1.26 表示模型主要把它放在 1 档,但也给了 2 档不小的概率(0.28)。这和报告内容吻合:换 Chrome 是一种变通办法,但对只装了 Safari 的财务同事来说又不太现实。
解读 Score
同一个问题、同一组档位,换几份不同的报告:
| bug 报告 | 各档概率(0 / 1 / 2) | score | confidence |
|---|---|---|---|
| "登录页的按钮文字「登陆」写成了错别字" | 0.79 / 0.16 / 0.05 | 0.26 | 0.79 |
| "批量导入偶尔超时,重试一次就好了" | 0.03 / 0.94 / 0.03 | 1.00 | 0.94 |
| 上文的 Safari 导出报告 | 0.02 / 0.70 / 0.28 | 1.26 | 0.70 |
| "所有用户都无法下单,支付页白屏" | 0.01 / 0.08 / 0.91 | 1.90 | 0.91 |
几点需要注意:
- confidence 高(如支付页白屏的 0.91)表示分布把绝大部分概率放在一档上。它描述的是答案的形状,不保证答案正确。
- score 是档位编号的概率加权平均。第三行里 2 档的权重越大,score 越高;但 1.26 不是"26% 的用户没有变通办法"之类的量。
- 不同的分布可能得到同一个 score:score = 1.0 既可以像第二行那样几乎全部概率在 1 档,也可以是 0 档和 2 档各一半。要区分这两种情况,请同时看
probabilities和confidence。 - 小数 score 是一个位置:可以用来排序(把 bug 按严重程度排队),也可以四舍五入到最近的档位,让代码得到一个离散结论。
Score 的置信度低,通常意味着三种情况之一:档位之间有重叠;这个问题其实在衡量不止一件事;state 里没有足够的信息来定位。怎么在代码里利用置信度,见 置信度。
在 score 上设阈值
最常见的用法是给 score 设一个或几个阈值,映射到代码分支:
sev = resp.answers["bug_severity"]
if sev.confidence < 0.5:
queue = "triage_human" # 模型自己拿不准,交给人判断
elif sev.score >= 1.5:
queue = "oncall_now" # 接近"不可用",立刻呼叫值班
elif sev.score >= 0.5:
queue = "sprint_backlog"
else:
queue = "polish_later"不要用 score 反推精确数值
如果档位是"0 元 / 100 元 / 1000 元"这样的数值,不要指望用 score 在两档之间插值,推算出"大约 430 元"。score 适合做"是否越过某条线"的比较,但档位之间的数值刻度并没有被精确校准。需要精确数值时,在代码里从文本中解析出来,或者让模型在候选值中做 Choice。参见 已知短板。
写好档位
描述情形,而不是描述程度。 "功能受损,但存在变通办法"给了模型可以对照的具体情形;"中等严重"则没有。写完后,用一批你知道正确答案的样例去检验:置信度变高本身不能说明写得更好。
每一档都是单独评估的。 模型看不到档位的编号,也看不到相邻的档位,所以"比上一档更严重"这种描述对它没有意义;在描述或 instructions 里写数字也帮不上忙。如果档位只写 ["1", "2", "3"],模型无从对照,概率往往会摊在几档之间。
能清楚区分几档就写几档,最多 10 档。 三档完全够用。写不出明显差异的档位,不要硬凑。
一个 Score 只衡量一个维度。 如果某一档写的是"准时、聪明、经验丰富",这就是在同时衡量三件事:一个准时但经验不足的人无处安放,置信度下降,score 的意义也变模糊。把它拆成三个 Score。
罕见的极端情况单独成档。 如果刻度顶端有一种需要特殊处理的极端情形,给它一个独立档位。例如情绪刻度到"非常愤怒"为止,那么"辱骂或威胁"最好单独加一档,否则两者都会落在最高档附近,score 分不出来。
没有中间地带就别用 Score。 如果答案其实是几个离散类别,用 Choice,或者拆成几个 Noul。
同一个刻度的两种措辞,在你的数据上可能表现不同。一定要用自己的数据测试。
把复杂判断拆成多个 Score
一个依赖多种因素的判断,最好一个因素一个 Score,再在代码里合成。有的因素更重要,就给它更大的权重。
下面仍是那张 Safari 工单,补充了一些上下文,一次问三个 Score:bug 有多严重、提交人有多不满、这份报告给工程师的信息够不够:
from xiangxin import Score, XiangxinClient
TRIAGE_QUESTIONS = {
"severity": Score(
instructions="这个 bug 有多严重?",
criteria=[
"表面问题:样式错位、错别字,不影响功能",
"功能受损,但存在变通办法",
"功能完全不可用,且没有变通办法",
],
),
"frustration": Score(
instructions="提交人有多不满?",
criteria=[
"平静,只是陈述事实",
"有些不满,但语气克制",
"非常不满,措辞激烈",
],
),
"report_quality": Score(
instructions="这份报告给工程师提供了多少可用信息?",
criteria=[
"几乎没有信息,无法着手",
"描述了现象,但没有复现步骤",
"有复现步骤,但缺少环境信息",
"复现步骤和环境信息(浏览器、版本等)都齐全",
],
),
}
WEIGHTS = {"severity": 0.6, "frustration": 0.3, "report_quality": 0.1}
report = (
"这已经是第三次反馈了:财务系统「导出 Excel」在 Safari 17.4 上点了没反应。"
"复现:登录 → 报表 → 月度汇总 → 导出。Chrome 正常。"
"月底要交报表,再不修我们只能手抄了。"
)
with XiangxinClient() as client:
resp = client.system_one(state=report, questions=TRIAGE_QUESTIONS)
priority = 0.0
for qid in TRIAGE_QUESTIONS:
answer = resp.answers[qid]
top_level = len(answer.legend) - 1 # 3 档 → 2,4 档 → 3
normalized = answer.score / top_level # 归一化到 0–1
priority += WEIGHTS[qid] * normalized
print(round(priority, 2))响应中的三个答案:
| 问题 | 各档概率 | score | confidence | 归一化 |
|---|---|---|---|---|
severity | 0.02 / 0.55 / 0.43 | 1.41 | 0.55 | 1.41 / 2 = 0.705 |
frustration | 0.31 / 0.49 / 0.20 | 0.89 | 0.49 | 0.89 / 2 = 0.445 |
report_quality | 0.03 / 0.01 / 0.06 / 0.90 | 2.83 | 0.90 | 2.83 / 3 ≈ 0.943 |
三个刻度长度不同(3 档返回 0–2,4 档返回 0–3),所以合成前要先归一化:用 score 除以最高档编号,即档位数减一(代码里用 len(answer.legend) - 1)。
优先级 = 0.6 × 0.705 + 0.3 × 0.445 + 0.1 × 0.943 ≈ 0.651,打印出来是 0.65。
权重写在代码里,数字怎么来的一目了然;排序结果和团队的判断不一致时,直接改权重。以后要加新的维度,往 TRIAGE_QUESTIONS 里加一个 Score 就行,请求数仍然是一次。这种做法就是 组合评分 模式。
结构化档位描述
先用一句话描述每一档。如果模型总是在两个相邻档位之间摇摆,而你觉得这些输入其实很明确,可以把每一档改成对象:一个字段写这一档覆盖的情形,一个字段写几个例子。各档使用相同的字段名。
Score(
instructions="这个 bug 有多严重?",
criteria=[
{"定义": "表面问题,不影响功能", "例子": ["错别字", "图标错位 2 像素"]},
{"定义": "功能受损,但存在变通办法", "例子": ["换个浏览器能用", "重试一次就成功"]},
{"定义": "功能完全不可用,且没有变通办法", "例子": ["支付页白屏", "所有人都无法登录"]},
],
)例子会引导模型,只有当它们长得像你的真实输入时才有帮助。和你的数据毫不相干的例子,效果通常与纯文本描述无异。挑选你确知正确档位的样例来写例子,然后用另一批样例检验修改后的描述。更多写法见 进阶:结构化。

