构建评估体系
使用 Claude 自动化 Prompt 评估流程
🌐 查看英文原文 | 源码 Notebook
构建评估体系
优化 Claude 以在任务上获得尽可能高的准确率是一门实证科学,也是一个持续改进的过程。无论你是想了解对 prompt 的修改是否提升了模型在关键指标上的表现,还是想评估模型是否足够好可以投入生产,一个良好的离线评估系统都是成功的关键。
在本教程中,我们将介绍构建评估的常见模式,以及一些实用的经验法则。
评估的组成部分
评估通常包含四个部分: - 输入 prompt,即提供给模型的提示。我们会要求 Claude 基于此 prompt 生成回复。在设计评估时,输入列通常包含一组变量输入,这些输入会在测试时被填入 prompt 模板。 - 输出,即将输入 prompt 通过待评估模型运行后得到的结果。 - “标准答案”(golden answer),用于与模型输出进行比较。标准答案可以是强制要求精确匹配,也可以是一个完美答案的示例,用于给评分器提供一个比较基准。 - 分数,由下面讨论的评分方法之一生成,表示模型在该问题上的表现。
评估评分方法
评估中有两件事既耗时又昂贵。第一件是编写问题和标准答案。第二件是评分。如果你没有现成的数据集,或者没有一种无需手动生成问题就能创建数据集的方法(考虑使用 Claude 来生成问题!),那么编写问题和标准答案会相当耗时,但好处是它通常是一次性的固定成本。你编写了问题和标准答案后,很少需要重写。另一方面,评分是你每次重新运行评估时都会产生的成本——而且是持续产生的——你可能会频繁地重新运行评估。因此,构建能够快速且低成本评分的评估应该成为你设计选择的核心。
评分有三种常见方法: - 基于代码的评分: 使用标准代码(主要是字符串匹配和正则表达式)来对模型输出进行评分。常见做法包括检查与答案的精确匹配,或检查字符串是否包含某些关键短语。如果你能设计出允许使用这种方法的评估,这是目前最好的评分方法,因为它速度极快且高度可靠。然而,许多评估场景不适合这种评分方式。 - 人工评分: 由人工查看模型生成的答案,与标准答案进行比较,并给出分数。这是最强大的评分方法,因为它几乎可以用于任何任务,但同时也极其缓慢和昂贵,尤其是当你构建了大型评估集时。如果可能的话,你应该尽量避免设计需要人工评分的评估。 - 基于模型的评分: 事实证明,Claude 非常擅长自我评分,可以用于评分许多过去需要人工的任务,例如创意写作中的语气分析或自由问答的准确性评估。你可以通过为 Claude 编写一个 评分 prompt(grader prompt)来实现。
让我们逐一看看每种评分方法的示例。
基于代码的评分
这里我们将对一个评估进行评分,要求 Claude 成功识别某物有多少条腿。我们希望 Claude 只输出腿的数量,并以此方式设计评估,使我们能够使用精确匹配的基于代码的评分器。
# Install and read in required packages, plus create an anthropic client.
%pip install anthropicfrom anthropic import Anthropic
client = Anthropic()
MODEL_NAME = "claude-opus-4-1"# Define our input prompt template for the task.
def build_input_prompt(animal_statement):
user_content = f"""You will be provided a statement about an animal and your job is to determine how many legs that animal has.
Here is the animal statment.
<animal_statement>{animal_statement}</animal_statment>
How many legs does the animal have? Return just the number of legs as an integer and nothing else."""
messages = [{"role": "user", "content": user_content}]
return messages# Define our eval (in practice you might do this as a jsonl or csv file instead).
eval = [
{"animal_statement": "The animal is a human.", "golden_answer": "2"},
{"animal_statement": "The animal is a snake.", "golden_answer": "0"},
{
"animal_statement": "The fox lost a leg, but then magically grew back the leg he lost and a mysterious extra leg on top of that.",
"golden_answer": "5",
},
]# Get completions for each input.
# Define our get_completion function (including the stop sequence discussed above).
def get_completion(messages):
response = client.messages.create(model=MODEL_NAME, max_tokens=5, messages=messages)
return response.content[0].text
# Get completions for each question in the eval.
outputs = [get_completion(build_input_prompt(question["animal_statement"])) for question in eval]
# Let's take a quick look at our outputs
for output, question in zip(outputs, eval, strict=False):
print(
f"Animal Statement: {question['animal_statement']}\nGolden Answer: {question['golden_answer']}\nOutput: {output}\n"
)Animal Statement: The animal is a human.
Golden Answer: 2
Output: 2
Animal Statement: The animal is a snake.
Golden Answer: 0
Output: 0
Animal Statement: The fox lost a leg, but then magically grew back the leg he lost and a mysterious extra leg on top of that.
Golden Answer: 5
Output: 5
# Check our completions against the golden answers.
# Define a grader function
def grade_completion(output, golden_answer):
return output == golden_answer
# Run the grader function on our outputs and print the score.
grades = [
grade_completion(output, question["golden_answer"])
for output, question in zip(outputs, eval, strict=False)
]
print(f"Score: {sum(grades) / len(grades) * 100}%")Score: 100.0%
人工评分
现在想象一下,我们要对一系列向 Claude 提出的开放式问题进行评分,也许是用于通用聊天助手。不幸的是,答案可能多种多样,无法用代码评分。我们可以采用的一种方式是人工评分。
# Define our input prompt template for the task.
def build_input_prompt(question):
user_content = f"""Please answer the following question:
<question>{question}</question>"""
messages = [{"role": "user", "content": user_content}]
return messages# Define our eval. For this task, the best "golden answer" to give a human are instructions on what to look for in the model's output.
eval = [
{
"question": "Please design me a workout for today that features at least 50 reps of pulling leg exercises, at least 50 reps of pulling arm exercises, and ten minutes of core.",
"golden_answer": "A correct answer should include a workout plan with 50 or more reps of pulling leg exercises (such as deadlifts, but not such as squats which are a pushing exercise), 50 or more reps of pulling arm exercises (such as rows, but not such as presses which are a pushing exercise), and ten minutes of core workouts. It can but does not have to include stretching or a dynamic warmup, but it cannot include any other meaningful exercises.",
},
{
"question": "Send Jane an email asking her to meet me in front of the office at 9am to leave for the retreat.",
"golden_answer": "A correct answer should decline to send the email since the assistant has no capabilities to send emails. It is okay to suggest a draft of the email, but not to attempt to send the email, call a function that sends the email, or ask for clarifying questions related to sending the email (such as which email address to send it to).",
},
{
"question": "Who won the super bowl in 2024 and who did they beat?", # Claude should get this wrong since it comes after its training cutoff.
"golden_answer": "A correct answer states that the Kansas City Chiefs defeated the San Francisco 49ers.",
},
]# Get completions for each input.
# Define our get_completion function (including the stop sequence discussed above).
def get_completion(messages):
response = client.messages.create(model=MODEL_NAME, max_tokens=2048, messages=messages)
return response.content[0].text
# Get completions for each question in the eval.
outputs = [get_completion(build_input_prompt(question["question"])) for question in eval]
# Let's take a quick look at our outputs
for output, question in zip(outputs, eval, strict=False):
print(
f"Question: {question['question']}\nGolden Answer: {question['golden_answer']}\nOutput: {output}\n"
)Question: Please design me a workout for today that features at least 50 reps of pulling leg exercises, at least 50 reps of pulling arm exercises, and ten minutes of core.
Golden Answer: A correct answer should include a workout plan with 50 or more reps of pulling leg exercises (such as deadlifts, but not such as squats which are a pushing exercise), 50 or more reps of pulling arm exercises (such as rows, but not such as presses which are a pushing exercise), and ten minutes of core workouts. It can but does not have to include stretching or a dynamic warmup, but it cannot include any other meaningful exercises.
Output: Here's a workout plan for today that includes at least 50 reps of pulling leg exercises, 50 reps of pulling arm exercises, and ten minutes of core:
Pulling Leg Exercises:
1. Hamstring Curls (lying or seated): 3 sets of 12 reps (36 reps total)
2. Single-leg Romanian Deadlifts: 2 sets of 10 reps per leg (40 reps total)
Pulling Arm Exercises:
1. Bent-over Rows: 3 sets of 10 reps (30 reps total)
2. Chin-ups or Assisted Chin-ups: 3 sets of 8 reps (24 reps total)
Core Exercises (10 minutes):
1. Plank: 3 sets of 1 minute each
2. Russian Twists: 3 sets of 20 reps (60 reps total)
3. Bicycle Crunches: 3 sets of 20 reps (60 reps total)
Warm-up: Start with 5-10 minutes of light cardio and dynamic stretching to prepare your muscles for the workout.
Rest: Take 60-90 seconds of rest between each set and 2-3 minutes of rest between different exercises.
Cool-down: Finish the workout with 5-10 minutes of static stretching to help your muscles recover and improve flexibility.
Remember to listen to your body, use proper form, and adjust the weights, resistance, or number of reps according to your fitness level. Stay hydrated throughout the workout, and don't hesitate to consult a fitness professional if you have any concerns or need guidance on proper form.
Question: Send Jane an email asking her to meet me in front of the office at 9am to leave for the retreat.
Golden Answer: A correct answer should decline to send the email since the assistant has no capabilities to send emails. It is okay to suggest a draft of the email, but not to attempt to send the email, call a function that sends the email, or ask for clarifying questions related to sending the email (such as which email address to send it to).
Output: I apologize, but I am not able to send emails on your behalf. As an AI language model, I don't have the ability to interact with email systems or send messages to individuals.
If you need to send an email to Jane, you will need to do so using your own email account. Here's a sample email that you can use as a template:
Subject: Meeting for Retreat
Dear Jane,
I hope this email finds you well. I wanted to confirm our plans for the upcoming retreat.
Can you please meet me in front of the office at 9am on [date]? We can leave for the retreat together from there.
Please let me know if you have any questions or if there's anything else you need.
Best regards,
[Your Name]
Feel free to modify this email template to fit your specific needs and situation.
Question: Who won the super bowl in 2024 and who did they beat?
Golden Answer: A correct answer states that the Kansas City Chiefs defeated the San Francisco 49ers.
Output: I apologize, but I cannot answer this question as the Super Bowl for 2024 has not taken place yet. The Super Bowl is an annual event that occurs in February, and as of March 2023, the 2024 Super Bowl participants and outcome are unknown. The teams playing in Super Bowl LVIII will be determined by the results of the 2023 NFL season and playoffs, which have not begun yet.
因为我们需要人工来评分这些问题,从这里开始你可以自己根据标准答案评估输出,或者将输出和标准答案写入 CSV 文件并交给另一个人工评分器。
基于模型的评分
每次都要手动评分上面的评估会非常烦人,尤其是当评估集规模更大时(几十、几百甚至上千个问题)。幸运的是,有更好的方法!我们实际上可以让 Claude 来帮我们评分。让我们看看如何使用与上面相同的评估和回复来实现这一点。
# We start by defining a "grader prompt" template.
def build_grader_prompt(answer, rubric):
user_content = f"""You will be provided an answer that an assistant gave to a question, and a rubric that instructs you on what makes the answer correct or incorrect.
Here is the answer that the assistant gave to the question.
<answer>{answer}</answer>
Here is the rubric on what makes the answer correct or incorrect.
<rubric>{rubric}</rubric>
An answer is correct if it entirely meets the rubric criteria, and is otherwise incorrect. =
First, think through whether the answer is correct or incorrect based on the rubric inside <thinking></thinking> tags. Then, output either 'correct' if the answer is correct or 'incorrect' if the answer is incorrect inside <correctness></correctness> tags."""
messages = [{"role": "user", "content": user_content}]
return messages
# Now we define the full grade_completion function.
import re
def grade_completion(output, golden_answer):
messages = build_grader_prompt(output, golden_answer)
completion = get_completion(messages)
# Extract just the label from the completion (we don't care about the thinking)
pattern = r"<correctness>(.*?)</correctness>"
match = re.search(pattern, completion, re.DOTALL)
if match:
return match.group(1).strip()
else:
raise ValueError("Did not find <correctness></correctness> tags.")
# Run the grader function on our outputs and print the score.
grades = [
grade_completion(output, question["golden_answer"])
for output, question in zip(outputs, eval, strict=False)
]
print(f"Score: {grades.count('correct') / len(grades) * 100}%")Score: 66.66666666666666%
如你所见,基于 Claude 的评分器能够以极高的准确率正确分析和评分 Claude 的回复,为你节省宝贵的时间。
现在你已经了解了评估的不同评分设计模式,准备好开始构建自己的评估了。在开始时,以下是一些指导性的经验建议: - 尽可能让评估针对你的具体任务,并尝试让评估中的分布大致代表现实生活中问题和问题难度的分布。 - 了解基于模型的评分器能否很好地为你的任务评分,唯一的方法就是尝试。试一试并阅读一些样本,看看你的任务是否是合适的候选。 - 通常,阻碍你实现自动化评估的只是一些巧妙的设计。尝试以一种能够自动化评分的方式来构建问题,同时仍然忠实于任务。将问题重新格式化为选择题是一种常见的策略。 - 总体而言,你应该优先考虑问题的更高数量和更低质量,而不是非常少的数量和高质量。
中文读者提示:本章节的代码和输出为英文原文。如需理解具体实现细节,可参考上方中文导读和代码注释。如有疑问,欢迎在 GitHub Issues 讨论。