返回博客

AI 入门指南

在 20 个案例上测试你的提示词:Python 中的精确匹配评分

构建一个 20 个案例的测试集,用简短的 Python 脚本通过精确匹配对提示词进行评分,并查看提示词更改是否真正有帮助。

作者:CheapTokens Team发布于 6 分钟阅读

封面:一个代码片段将模型答案与预期标签进行比较,标题为在 20 个案例上测试你的提示词
本页内容
  1. 构建测试集:20 条消息和你期望的答案
  2. 脚本:发送每个案例,比较,打印未命中的案例
  3. 改变一件事,再次运行
  4. 同一集合,另一个模型:它能保持分数吗?
  5. 精确匹配失效的地方

你的提示词在你试过的两条消息上有效。这是否意味着它有效?还不一定。用 20 条有已知答案的真实消息运行它,统计它答对了多少条,并阅读它答错的那几条。一个简短的 Python 脚本负责计数,当你修改提示词时,同一个脚本会告诉你这次修改是否有帮助,而不是凭感觉判断。

Anthropic 关于定义成功标准和构建评估的文档(2026 年 11 月 7 日查阅)用自己的话说明了这一点:让测试针对你的任务具体化,包含棘手的情况,并且宁愿用许多自动评分的题目,而不是少数手工评分的题目。下面是你可以今天运行的最小版本。

四个步骤:20 个案例交给模型,答案通过精确匹配进行比较,然后你得到一个准确率分数以及未命中的案例
整个方法:输入案例,输出一个数字和一份未命中列表

构建测试集:20 条消息和你期望的答案#

假设你将客户消息分为三类:正面、负面或混合。打开一个名为 cases.csv 的文件,写下 20 条你确实可能收到的消息,每条都附上你会给出的标签。在运行任何东西之前先写好标签。如果你把模型的答案复制为“正确”,测试就只能与模型一致。

csv
text,label
"Arrived a day early and works perfectly. Thank you!",positive
"The charger stopped working after two days.",negative
"Great screen, but the battery barely lasts until lunch.",mixed
"Support answered in five minutes and fixed it.",positive
"I waited three weeks and nobody replied.",negative
"Oh great, another update that deletes my settings.",negative
"Nice design. Pity about the price.",mixed
"Exactly what I ordered.",positive
"The box was crushed, but the headphones inside are fine and sound great.",mixed
"Refund took 10 days. Never again.",negative
"Love it!!",positive
"Cheaply made, but honestly it does the job for the price.",mixed
"Worst purchase of the year.",negative
"Five stars. Would buy again.",positive
"Fast delivery, wrong colour.",mixed
"It's fine. Not amazing, not terrible.",mixed
"Zero issues in six months of daily use.",positive
"The app crashes every time I open the camera.",negative
"Thanks for nothing.",negative
"Setup was a pain, but once it ran, it's been rock solid.",mixed

看看这个组合:6 条正面,7 条负面,7 条混合。讽刺的句子(“哦太好了,又一个更新……”、“多谢了,什么都没给。”)和模棱两可的句子(“还行。不算惊艳,也不算糟糕。”)是故意放进去的。一个只包含明显消息的集合会得 100% 分,什么也教不了你。从你自己的收件箱、工单或聊天记录中选取真实的消息;边缘情况就是那些你已经知道很烦人的情况。

为什么是 20?这是一个起始规模,不是文档中的数字(他们的例子使用了 1,000 条推文)。20 足够小,可以在半小时内手写完成;也足够大,一个幸运的答案不会决定分数。

脚本:发送每个案例,比较,打印未命中的案例#

计划有四个步骤,代码逐行遵循它。

排序

按顺序排列评估循环

  1. 从 CSV 加载 20 个案例
  2. 用你的提示词将每条消息发送给模型
  3. 将答案与你的标签进行比较
  4. 打印准确率和失败的案例

循环只有在第一次模型调用之前标签就存在时才有效,而失败案例放在最后,因为它们是你要阅读并采取行动的内容。

使用 pip install openai 安装 SDK,并将密钥保存在名为 CHEAPTOKENS_API_KEY 的环境变量中。任何 OpenAI 兼容的 API 都以相同方式工作;这里脚本指向 CheapTokens 和 GLM-5.3-Flash(设置详情见文档)。

python
import csv
import os
from openai import OpenAI

client = OpenAI(
    base_url='https://api.cheaptokens.dev/v1',
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'zai-org/GLM-5.3-Flash'

PROMPT = (
    'Classify the customer message as positive, negative or mixed. '
    'Answer with one word only.'
)


def ask(text):
    r = client.chat.completions.create(
        model=MODEL,
        temperature=0,
        max_tokens=100,
        messages=[
            {'role': 'system', 'content': PROMPT},
            {'role': 'user', 'content': text},
        ],
    )
    return r.choices[0].message.content or ''


with open('cases.csv', newline='', encoding='utf-8') as f:
    cases = list(csv.DictReader(f))

correct, failed = 0, []
for case in cases:
    got = ask(case['text'])
    ok = got.strip().strip('.').lower() == case['label']
    correct += ok
    if not ok:
        failed.append((case['text'], case['label'], got.strip()))

print(f'Accuracy: {correct}/{len(cases)} = {correct / len(cases):.0%}')
for text, want, got in failed:
    print(f'- {text!r}: expected {want}, got {got!r}')

有三个细节值得再看一眼。

  • temperature=0 尽可能保持答案稳定,这样分数的变化来自你的提示词,而不是运气。
  • max_tokens=100 远远超过一个单词标签所需。如果答案返回为空或被截断,请检查 finish_reason;finish_reason "length" 指南显示了需要提高什么。
  • 评分行是文档的配方,将 strip().lower() 与正确答案比较,外加一个补充:它还会去掉末尾的句号,因为“Negative.”和“negative”对你来说是同一个答案。

运行时你会看到什么?一行准确率,然后每个未命中一行。假设第一次运行打印如下:

text
Accuracy: 14/20 = 70%
- 'Oh great, another update that deletes my settings.': expected negative, got 'positive'
- "It's fine. Not amazing, not terrible.": expected mixed, got 'positive'
…

(这里的数字是假设的,用于展示输出的形状;你的会不同。)讽刺和模棱两可的措辞是简短提示词容易漏掉的那类消息。关键是,你不再猜测:你有了一个列表。

改变一件事,再次运行#

现在是有用的部分。阅读未命中的案例,找出一个原因,在一个地方修改提示词并重新运行。一次只改一处,否则你不会知道哪个修改改变了分数。

比较

提示词 v1 → v2:添加一条规则和两个示例

v1

text
Classify the customer message as positive, negative or mixed.
Answer with one word only.

v2

text
Classify the customer message as positive, negative or mixed.
Sarcasm counts as negative. Praise plus a complaint is mixed.
Answer with one word only.

Message: Lovely colour, but it arrived with a scratch.
Answer: mixed

Message: Sure, thanks a lot. Now my order is lost.
Answer: negative

这两个示例是虚构的,这是故意的。永远不要把 cases.csv 中的消息作为示例放入提示词:模型随后会根据你给它的答案进行评分,分数会被夸大。这是少样本技术;测试集告诉你它是否奏效。如果你的 v1 提示词仍然模糊,如何编写好的提示词是在测量之前开始的地方。

将 v2 粘贴到 PROMPT 中(三引号适用于多行)并再次运行脚本。假设它打印 Accuracy: 17/20 = 85%。又有三个案例正确,所以 v2 看起来更好。这有多可信?在 20 个案例的集合中,每个案例值 5 分,所以 70% → 85% 是三条消息。这是一个好迹象,不是证据。如果分数下降,你学到了同样有价值的东西,并避免发布一个更差的提示词。

同一集合,另一个模型:它能保持分数吗?#

同一个文件回答第二个问题:你是否为这个任务支付了超出需要的模型费用?改一行并再次运行:

python
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'

如果你的 20 个案例上的准确率匹配,你就有了这个任务的答案,来自你自己的数据而不是排行榜。如果下降,在做出任何决定之前,看看哪些消息出了问题。

这一切花费多少?假设每个案例约 150 个输入 token(指令加消息),一个单词标签约 5 个输出 token。那就是每次运行 20 个案例 3,000 个输入和 100 个输出 token,在 GLM-5.3-Flash 上合计为 $0.000064。十次重新运行该集合是 200 个请求;下表显示了每个模型的工作负载,旁边是 OpenRouter。在这个工作负载下,输入和输出合计,表中的每个模型成本都低于 OpenRouter 的参考价格。

模型 每次请求 200 次请求 OpenRouter,200 次请求
GLM-5.3-Flash $0.0000032 $0.00064 $0.0011
DeepSeek-V4-Flash $0.0000028 $0.00055 $0.0022
MiniMax-M2.7 $0.0000083 $0.0017 $0.0198

每次请求 150 个输入 token 和 5 个输出 token。每 100 万 token 的价格为当前价格,会随我们的资费一同变动;OpenRouter 列显示相同工作负载的参考价格。

其中两个用于本文:GLM-5.3-Flash 具有 390K 上下文和每次回答最多 8,192 个输出 token;DeepSeek-V4-Flash 具有 380K 上下文和最多 32,768 个输出 token,一天中任何时间一个价格,并且与其他所有模型相同的余额。当前价格在定价页面上,完整列表在模型中。

精确匹配失效的地方#

精确匹配有效是因为正确答案是固定列表中的一个词。对于开放式输出,如摘要、改写或解释,没有单一的字符串可以比较。文档描述了另一种途径,由模型评分,并建议使用与撰写答案不同的模型作为评分者。无论如何,从精确匹配开始:只要你能强制一个简短、固定的答案,它就是你所能运行的最便宜、最清晰的测试。文档的经验法则是,更多自动评分的题目胜过更少的手工评分题目。

还有一个习惯:当 v2 不再改进时,不要继续针对同样的 20 条消息打磨。添加 5–10 条新消息,最好是生产中的真实未命中案例,并再次运行。

快速检查

v2 得分为 17/20,v1 在相同案例上得分为 15/20。最安全的结论是什么?

显示答案

答案: v2 看起来更好,但 20 个案例中的两个案例差距很小:添加新案例并重新运行

每个案例 5 分,所以两个案例的差距是仔细查看的信号,而不是结论。v2 从未见过的新案例是下一步检查。