EP02. "Prompt 评测工作流:从测试数据集到自动打分"
🔒 登录后可标记已读- 只手动测几次就上线,看起来能用,其实是把「用户会怎么用」的判断权交给了自己的直觉——真实用户输入的花样,几乎一定超出你测过的范围
- Eval(评测)是一套系统化流程:准备测试数据集 → 让 Claude 跑一遍 → 用 grader 客观打分 → 照分数改 prompt → 再跑一遍比较,不是凭感觉说「这个 prompt 好像更好」
- Grader 分三种:code grader(程序检查格式/语法)、model grader(再呼叫一次 Claude 来评分)、human grader(人工复核)——各自适合检查不同的东西,混着用效果最好
- 测试数据集不一定要手写,可以让 Claude(用便宜快的模型如 Haiku)自动生成一批贴近真实场景的测试用例
- 前置知识:EP01 的
converse()/chat()基本用法、prefill + stop sequence 抽取干净 JSON 的技巧
重点内容
为什么"多测几次"不够
写完 prompt 后一般会走三条路:
| 做法 | 问题 |
|---|---|
| 测一次就上线 | 用户给出预期外的输入时,出问题的风险很高 |
| 手动多测几次、小改几下 | 还是不够,因为用户实际的输入方式往往超出你能想到的范围 |
| 建立评测 pipeline,照客观指标反复迭代 | 前期投入更多时间和成本,但更有把握 |
📌 工程师天然会倾向前两条路——测的都是「自己觉得理所当然」的输入,但真实用户跟 prompt 互动的方式永远比想象中花样多。系统化评测 + 数据驱动的迭代,才是解法。
一个典型 Eval 工作流的五步
- 写初版 prompt:先有个能跑的基础版本
prompt = f"""
Please answer the user's question:
{question}
"""
- 准备评测数据集:一批有代表性的测试输入,例如「2+2 等于多少」「怎么煮燕麦」「月球有多远」
- 喂给 Claude:把每条数据集问题套进 prompt 模板,发给 Claude,收集回答
- 喂给 grader 打分:拿「问题 + 回答」这一对,让 grader 评分(常用 1-10 分制:10 分=完美无可挑剔,4 分=明显有改进空间,1 分=差或答错)
- 改 prompt、重复流程:例如在 prompt 里加一句「Answer the question with ample detail」,重新跑一次评测
课程示例:v1 prompt 平均分 7.66,加了「回答要够详细」这句话后的 v2 涨到 8.7——分数变化就是「这次修改到底有没有用」的客观证据。
生成测试数据集
评测数据集就是一批会被喂进 prompt 的输入样本,两种做法:
- 手写测试用例
- 用 Claude 自动生成(生成多条用便宜快的模型,比如 Haiku,比较划算)
自动生成示例(AWS 场景:让用户描述任务,Claude 判断该写 Python / JSON / Regex 中的哪一种,只回传代码本身,不带任何说明文字):
def generate_dataset():
prompt = """
Generate 3 AWS-related tasks that require Python, JSON, or Regex solutions.
Focus on tasks that can be solved by writing a single Python function,
a single JSON object, or tasks that do not require writing much code.
Example output:
[
{
"task": "Description of task"
},
...additional
]
Please generate 3 objects.
"""
messages = []
add_user_message(messages, prompt)
add_assistant_message(messages, "```json")
text = chat(messages, stop_sequences=["```"])
return json.loads(text)
这里用的就是 EP01 讲过的 prefill + stop sequence 组合拳,确保拿到的是干净的 JSON。
生成完存下来,不用每次重新生成:
dataset = generate_dataset()
with open("dataset.json", "w") as f:
json.dump(dataset, f, indent=2)
💡 3 条测试用例只够跑通流程,真正上生产前的评测数据集需要更多条、覆盖更多样的场景。
跑评测 Pipeline:三层函数分工
run_prompt:把测试用例套进 prompt 模板,丢给 Claude:
def run_prompt(test_case):
"""Merges the prompt and test case input, then returns the result"""
prompt = f"""
Please solve the following task:
{test_case["task"]}
"""
messages = []
add_user_message(messages, prompt)
output = chat(messages)
return output
run_test_case:调用 run_prompt,再把结果送去打分(这一步先用假分数占位):
def run_test_case(test_case):
"""Calls run_prompt, then grades the result"""
output = run_prompt(test_case)
# TODO - Grading
score = 10
return {
"output": output,
"test_case": test_case,
"score": score
}
run_eval:遍历整个数据集,逐条跑 run_test_case:
def run_eval(dataset):
"""Loads the dataset and calls run_test_case with each case"""
results = []
for test_case in dataset:
result = run_test_case(test_case)
results.append(result)
return results
跑起来:
with open("dataset.json", "r") as f:
dataset = json.load(f)
results = run_eval(dataset)
📌 就算用 Haiku,跑完一整个数据集也可能要 30 秒以上,第一次跑不用觉得卡住了。
这一步搭好的只是「骨架」——固定分数占位,真正有意义的是接下来把打分逻辑换成真正的 grader。
三种打分方式:Code / Model / Human Grader
| Grader 类型 | 怎么打分 | 适合检查 |
|---|---|---|
| Code grader | 程序化检查:长度、关键词、JSON/Python/Regex 语法是否合法、可读性 | 格式对不对、语法能不能跑 |
| Model grader | 再呼叫一次 Claude,请它评估原始输出 | 有没有照指令做、答得完不完整、有没有帮到用户、安不安全 |
| Human grader | 人工复核 | 整体质量、深度、简洁度、相关性——最耗时但最贴近真实判断 |
以「生成代码」这类 prompt 为例,评分标准可以拆成:
- Format:是否只回传指定语言的代码,不带解释文字
- Valid Syntax:语法是否合法
- Task Following:内容有没有真正解决用户的任务
其中 format 和 syntax 交给 code grader 判断最准确、最省钱;task following 这种需要理解语义的,交给 model grader。
Model Grader 怎么写
def grade_by_model(test_case, output):
# Create evaluation prompt
eval_prompt = """
You are an expert code reviewer. Evaluate this AI-generated solution.
Task: {task}
Solution: {solution}
Provide your evaluation as a structured JSON object with:
- "strengths": An array of 1-3 key strengths
- "weaknesses": An array of 1-3 key areas for improvement
- "reasoning": A concise explanation of your assessment
- "score": A number between 1-10
"""
messages = []
add_user_message(messages, eval_prompt)
add_assistant_message(messages, "```json")
eval_text = chat(messages, stop_sequences=["```"])
return json.loads(eval_text)
💡 关键技巧:要求 grader 同时列出 strengths、weaknesses、reasoning,而不是直接吐一个分数——这样能防止模型偷懒,每次都给一个不上不下的中间分。
接进 run_test_case:
def run_test_case(test_case):
output = run_prompt(test_case)
# Get model evaluation
model_grade = grade_by_model(test_case, output)
score = model_grade["score"]
reasoning = model_grade["reasoning"]
return {
"output": output,
"test_case": test_case,
"score": score,
"reasoning": reasoning
}
算整体平均分,追踪 prompt 迭代有没有真正变好:
from statistics import mean
def run_eval(dataset):
results = []
for test_case in dataset:
result = run_test_case(test_case)
results.append(result)
average_score = mean([result["score"] for result in results])
print(f"Average score: {average_score}")
return results
Code Grader:检查格式和语法
Code grader 补的是 model grader 顾不到的一块:输出到底能不能被正常解析/编译。判断很直接——能 parse 就是满分 10 分,parse 失败就是 0 分:
def validate_json(text):
try:
json.loads(text.strip())
return 10
except json.JSONDecodeError:
return 0
def validate_python(text):
try:
ast.parse(text.strip())
return 10
except SyntaxError:
return 0
def validate_regex(text):
try:
re.compile(text.strip())
return 10
except re.error:
return 0
用 Python 内建能力做校验:json.loads() 验证 JSON、ast.parse() 生成 Python 抽象语法树(能生成就代表语法合法)、re.compile() 验证正则表达式。
测试数据集里每条要多带一个字段标注预期的输出格式:
{
"task": "Description of task",
"format": "python"
}
format 只会是 "json"、"python"、"regex" 三选一。
📌 想让 code grader 更准,prompt 本身也要更明确地要求:「只回复 Python / JSON / 纯 Regex,不要加任何注释或说明」,同样可以搭配 prefill + stop sequence 确保输出干净。
综合评分:把两种 Grader 分数合起来
model_grade = grade_by_model(test_case, output)
model_score = model_grade["score"]
syntax_score = grade_syntax(output, test_case)
score = (model_score + syntax_score) / 2
这样内容质量(model grader)跟技术正确性(code grader)各占一半权重——权重可以按实际场景调整,谁更重要就调高谁的比例。
💡 单独一次评测分数没什么意义,真正有用的是拿它当基准(baseline),每次改 prompt 后重新跑一次,比较分数有没有真的往上走。
Sources
官方文档:
- Claude with Amazon Bedrock(Claude Academy 课程)— https://academy.claude.com/courses/claude-with-amazon-bedrock