文档目录

Score(打分题)

Score(打分题)是 System One 的一种题型,用于按有序的描述性档位对内容进行评分。答案包括一个分数(score)、每个档位的概率(probability),以及置信度(confidence)。

非官方中文翻译 · 内容以 英文原文 ↗ 为准 · 同步于 2026-10-15

当答案是一个可以用若干步骤描述的谱系上的位置时,可以使用 Score(打分题)。例如,一个 bug 的严重程度、一个客户有多满意,或者一位候选人有多少 Python 经验。如果答案是一组固定选项且选项之间没有顺序,请使用 Choice(选择题)。如果只是「是」或「否」,请使用 Noul(是非题)。选择题型 一节对三者进行了比较。

Score 的答案是一个沿着档位排列的位置,写在 score 中,可以落在两个档位之间。模型还会返回每个档位在 probabilities 中的概率,以及答案的 confidence(置信度)值。

The numbers in front of each step are positions, explained under 档位(level)。

请求结构

发往 TypeSafe API 的 POST 请求体与其他题型一样,包含三个顶层字段:state(要评估的内容)、model(模型)、questions(问题)。每个 Score 问题包含以下字段:

  • type:始终为 "score"。
  • instructions(指令):模型回答的问题,即它在评什么。
  • criteria(判定标准):按档位描述组成的有序数组,从刻度的低端到高端。至少需要两个档位;API 最多接受 10 个。

下面的请求中,state 是一份 bug 报告,问题就是这个 bug 严重程度如何:

json
{
  "model": "jev-latest",
  "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 下。

档位(level)

criteria 中的每一项就是一个档位(level):答案谱系上的一个点,用文字描述。档位的编号就是它在 criteria 数组中的位置,从 0 开始,所以上面三个条目分别是档位 0、1 和 2。数组的顺序就是编号。

模型只会拿到这些描述,没有其他内容,每个档位都独立地与 state 进行比较。

响应中的 score 是档位谱系上的一个位置。对于三档刻度,它的范围是 0 到 2,并且可以落在两个档位之间。

我们的客户端 SDK 提供了带类型的问题。在 Python 中,同样的问题写成一个 Score:

python
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。如果由编程智能体(coding agent)代你完成集成,请先安装 TypeSafe 智能体技能,让它了解请求与响应的结构。

响应结构

响应中 answers 的每个问题对应一个条目,键为请求中的 id。下面是上述示例请求的响应:

json
{
  "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 × 0.0 + 1 × 0.57 + 2 × 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

我们来看看不同输入下分数如何变化。例如,使用上面请求中的问题和档位:

text
"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

我们可以看到不同的 bug 报告如何改变分数:

text
  <th colspan="3" style="text-align: left;"><code>probabilities</code></th>
</tr>

<tr>
  <th style="width: 44%;">State</th>
  <th style="width: 12%; white-space: nowrap;"><code>score</code></th>
  <th style="width: 16%; white-space: nowrap;"><code>confidence</code></th>
  <th style="width: 9%; white-space: nowrap;">档位 0</th>
  <th style="width: 9%; white-space: nowrap;">档位 1</th>
  <th style="width: 10%; white-space: nowrap;">档位 2</th>
</tr>
text
<tr>
  <td>点击 PDF 导出按钮没有任何反应。我仍然可以导出为 CSV 再自己转换,但要花很长时间。</td>
  <td>1.0</td><td>1.0</td><td>0.0</td><td>1.0</td><td>0.0</td>
</tr>

<tr>
  <td>导出为 PDF 失败,旋转图标一直不消失。我们团队里有人说 CSV 导出对他们仍然有效,也有人说也失败了。</td>
  <td>1.11</td><td>0.84</td><td>0.0</td><td>0.89</td><td>0.11</td>
</tr>

<tr>
  <td>导出按钮在 Safari 中会导致设置页面崩溃。它在 Chrome 中可以正常使用,但我们有一些客户只使用 Safari。</td>
  <td>1.43</td><td>0.35</td><td>0.0</td><td>0.57</td><td>0.43</td>
</tr>

<tr>
  <td>从今天早上起,我们团队里没有人能登录。每次尝试都会出现 500 错误。</td>
  <td>2.0</td><td>1.0</td><td>0.0</td><td>0.0</td><td>1.0</td>
</tr>
导出按钮在设置页面上错位了几个像素。 0.01.01.00.00.0

在这些示例中,置信度 1.0 表示返回的概率分布将所有概率都放在某一个档位上。这描述的是模型的答案,并不保证答案一定正确。

分数是档位编号的概率加权平均值。在第三个和第四个示例中,概率分散在档位 1 和档位 2 之间。档位 2 上的权重越大,分数就越高。它并不衡量没有变通方案的用户比例。

不同的概率分布可以产生相同的分数。分数 1.0 可以表示所有概率都落在档位 1 上,也可以表示一半落在档位 0、另一半落在档位 2。要区分这些情况,请同时查看 probabilities 和 confidence。

小数形式的分数是一个位置。你可以用它来按严重程度给报告排序,或者在你需要单一结果的代码中将其四舍五入到最近的档位。我们的实体对齐实战示例 展示了一个四舍五入到最近档位以做决策的示例。

Score 上置信度低通常意味着以下三种情况之一:档位之间存在重叠、问题同时衡量了多个维度,或者 state 提供的信息不足以定位它。我们的置信度 文档介绍了如何在代码中使用它。

写出好的档位

描述情境,而不是程度。"功能损坏或能力下降,但存在临时方案"能让模型有具体的东西可以对照上下文状态。"中等严重"做不到。具体的描述能帮助模型区分不同档位。把答案与已知样本对照检查;仅仅置信度更高,并不能说明描述更好。

每个档位都会被独立评估。模型看不到档位的编号,也看不到相邻的档位,所以"比上一档更糟"对它毫无意义,描述或指令里写数字也没有帮助。下面是上表中"按钮错位"那条工单在档位只用数字时的结果:

text
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 题放在同一次请求里提交,它们会并行评估。多加几道题几乎不会影响响应时间,只多花几个 question token;参见 一次提交多道问题。

下面这条请求对应上表中"导出转圈"那条工单,并补充了一些上下文。它同时问了三个 Score 题:Bug 有多严重、客户有多不满、工单给工程师的线索有多充分。

json
{
  "model": "jev-latest",
  "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 的响应:

json
{
  "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 代码同时问三道题,对每个分数做归一化,并用一个示例的优先级算法把它们合并:

python
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、再用代码按权重合并的技巧,叫做 组合打分(composite scoring) 设计模式。

结构化档位描述

为每个档位先写一个基础的文本描述。当模型在你认为清晰的输入上持续在相邻两个档位之间摇摆时,把每个档位从字符串换成对象:一个字段描述档位覆盖什么,一个字段列出几个示例场景。每个档位使用相同的字段名,这样模型才能同类对比。

下面的请求是之前用过的那个 spinner 工单,不过每个档位都加了示例:

json
{
  "model": "jev-latest",
  "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"
          ]
        }
      ]
    }
  }
}

响应:

json
{
  "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

在这个对比中,匹配的示例把几乎所有概率都集中到了一个档位上。不相关的示例与普通字符串的结果一致。置信度更高并不能说明哪个答案就是正确的。挑选预期档位已知的示例,然后在另一批输入上测试改过的描述,再决定是否保留。

本文档基于 Mintlify 构建和托管,它是一个开发者文档平台。