OpenAI Evals 入门指南
技术背景
在开发基于 LLM 的应用时,模型升级或提示调整可能引入回归,缺乏系统评估将难以追踪性能变化。OpenAI Evals 提供标准化框架,通过定义评估任务、运行测试并分析结果,帮助开发者量化模型在特定用例上的表现。它支持两种评分方式:确定性代码检查(如字符串匹配、JSON 解析)和模型评分(利用 GPT-4 等模型判断答案质量)。适用于问答、代码生成、分类等场景,但模型评分存在误差,需人工验证。
核心原理
Evals 核心由数据集和评估类组成,数据集为 JSONL 格式,每条记录包含输入消息(system/user 角色)和理想答案。评估类在 YAML 文件中定义,指定指标(如 accuracy)和评分逻辑。运行时,框架将输入发送给模型,获取补全,再根据指标计算得分。模型评分采用两阶段:先让模型生成答案,再让评分模型(如 GPT-4)判断答案是否正确,通常使用 CoT 提示提升准确性。
架构分析
OpenAI Evals 架构分为三层:注册表(registry)存储评估定义(YAML)和数据集(JSONL);执行引擎(elsuite)提供评估类,如 ModelBasedClassify;CLI 工具(oaieval)负责调度。评估流程:CLI 读取 YAML 配置,加载数据集,调用模型 API 生成补全,应用评分逻辑,输出报告并记录日志。设计上支持扩展,开发者可自定义评估类和指标。
完整实战教程
OpenAI Evals 入门指南
OpenAI Evals 是一个用于评估 AI 模型(如 GPT)性能的开源框架。它允许你定义评估任务,运行测试,并查看结果。本指南将带你快速入门。
安装
首先,确保你已安装 Python 3.7 或更高版本。然后,使用 pip 安装 Evals:
pip install evals
配置 API 密钥
Evals 需要 OpenAI API 密钥。你可以通过环境变量设置:
export OPENAI_API_KEY="your-api-key"
或者,在代码中设置:
import openai
openai.api_key = "your-api-key"
编写第一个评估
Evals 使用 YAML 文件定义评估。创建一个名为 my-eval.yaml 的文件,内容如下:
# my-eval.yaml
description: "测试模型对简单问题的回答"
metrics:
- accuracy
prompts:
- "什么是机器学习?"
- "解释一下神经网络。"
运行评估
使用命令行运行评估:
evals eval my-eval.yaml
或者,在 Python 中运行:
from evals import run_eval
run_eval("my-eval.yaml")
查看结果
评估完成后,结果会打印在终端,并保存为 JSON 文件。你可以查看每个提示的响应和得分。
自定义评估
你可以定义更复杂的评估,包括自定义指标、多轮对话等。参考 官方文档 获取更多信息。
集成到项目
你可以将 Evals 集成到 CI/CD 流程中,确保模型更新不会降低性能。
总结
本指南介绍了 Evals 的基本用法。现在你可以开始评估自己的模型了。
**注意:OpenAI 现在有一个托管的 evals 产品,并提供 API!我们建议您使用它。
请参阅 Evals**
OpenAI Evals 框架由以下部分组成:
- 一个用于评估 LLM(大型语言模型)或基于 LLM 构建的系统的框架。
- 一个开源的、具有挑战性的 evals 注册表。
本笔记本将涵盖:
* 评估和 OpenAI Evals 库的介绍
* 构建一个 Eval
* 运行一个 Eval
什么是评估/evals?
评估是验证和测试您的 LLM 应用程序产生的输出的过程。拥有强大的评估("evals")意味着更稳定、更可靠的应用程序,能够抵御代码和模型变化的影响。Eval 是用于衡量 LLM 或 LLM 系统输出质量的任务。给定一个输入提示,生成一个输出。我们使用一组理想答案来评估这个输出,并找出 LLM 系统的质量。
评估的重要性
如果您正在使用像 GPT-4 这样的基础模型进行构建,创建高质量的 evals 是您可以做的最有影响力的事情之一。开发 AI 解决方案涉及迭代设计过程。没有 evals,很难且耗时地理解不同的模型版本和提示如何影响您的用例。
借助 OpenAI 的持续模型升级,evals 允许您以标准化的方式高效地测试模型在您的用例中的性能。开发一套针对您目标定制的 evals 将帮助您快速有效地了解新模型在您的用例中的表现。您还可以将 evals 作为 CI/CD 管道的一部分,以确保在部署前达到所需的准确性。
Evals 的类型
我们有两种主要方式来评估/评分补全:在代码中编写一些验证逻辑,或使用模型本身来检查答案。我们将通过一些示例来介绍每种方式。
编写答案检查逻辑
最简单、最常见的 eval 类型具有输入和理想响应或答案。例如,我们可以有一个 eval 样本,其中输入是“奥巴马第一次当选总统是哪一年?”,理想答案是“2008”。我们将输入提供给模型并获得补全。如果模型说“2008”,则被评分为正确。我们可以编写一个字符串匹配来检查补全是否包含短语“2008”。如果包含,我们认为它是正确的。
考虑另一个 eval,其中输入是生成有效的 JSON:我们可以编写一些代码,尝试将补全解析为 JSON,如果可解析,则认为补全正确。
模型评分:一个两阶段过程,首先模型回答问题,然后我们要求模型检查响应是否正确。
考虑一个输入,要求模型写一个有趣的笑话。然后模型生成一个补全。然后我们创建一个新的输入给模型,要求回答:“下面这个笑话有趣吗?首先逐步推理,然后回答是或否”,其中包含补全。最后,如果新的模型补全以“是”结尾,我们认为原始补全正确。
模型评分在使用最新、最强大的模型(如 GPT-4)并赋予其在做出判断前进行推理的能力时效果最佳。模型评分会有一定的错误率,因此在大规模运行评估之前,通过人工评估来验证其性能非常重要。为了获得最佳结果,使用与生成答案不同的模型进行评分是有意义的,例如使用 GPT-4 来评分 GPT-3.5 的答案。
OpenAI 评估模板
在使用评估的过程中,我们发现了几种能够适应许多不同基准的“模板”。我们已在 OpenAI Evals 库中实现了这些模板,以简化新评估的开发。例如,我们定义了两种可以直接使用的评估模板类型:
* 基础评估模板:这些模板包含确定性函数,用于将输出与理想答案进行比较。在期望的模型响应变化很小的情况下,例如回答多项选择题或答案直接了当的简单问题,我们发现以下模板非常有用。
* 模型评分模板:这些模板包含函数,其中 LLM 将输出与理想答案进行比较,并尝试判断准确性。在期望的模型响应可能包含显著变化的情况下,例如回答开放式问题,我们发现使用模型自我评分是一种可行的自动评估策略。
环境设置
首先,前往 github.com/openai/evals,使用 git clone git@github.com:openai/evals.git 克隆仓库,并按照设置说明进行操作。
要在本笔记本中运行评估,你需要设置并指定你的 OpenAI API 密钥。获取 API 密钥后,使用 OPENAI_API_KEY 环境变量进行指定。
请注意,运行评估时使用 API 会产生相关费用。
from openai import OpenAI
import pandas as pd
client = OpenAI()
为 OpenAI Evals 框架构建评估
核心上,一个评估由数据集和定义在 YAML 文件中的评估类组成。要开始创建评估,我们需要
- 以
jsonl格式的测试数据集。 - 要使用的评估模板。
创建评估数据集
让我们为一个用例创建数据集,该用例评估模型生成语法正确的 SQL 的能力。在这个用例中,我们有一系列与汽车制造相关的表。
首先,我们需要创建要评估的系统提示。我们将向模型传递指令以及表结构的概述:
"TASK: Answer the following question with syntactically correct SQLite SQL. The SQL should be correct and be in context of the previous question-answer pairs.\nTable car_makers, columns = [*,Id,Maker,FullName,Country]\nTable car_names, columns = [*,MakeId,Model,Make]\nTable cars_data, columns = [*,Id,MPG,Cylinders,Edispl,Horsepower,Weight,Accelerate,Year]\nTable continents, columns = [*,ContId,Continent]\nTable countries, columns = [*,CountryId,CountryName,Continent]\nTable model_list, columns = [*,ModelId,Maker,Model]\nForeign_keys = [countries.Continent = continents.ContId,car_makers.Country = countries.CountryId,model_list.Maker = car_makers.Id,car_names.Model = model_list.Model,cars_data.Id = car_names.MakeId]"
对于这个提示,我们可以提出一个具体的问题:
"Q: how many car makers are their in germany?"
而且我们有一个预期的答案:
"A: SELECT count ( * ) FROM CAR_MAKERS AS T1 JOIN COUNTRIES AS T2 ON T1.Country = T2.CountryId WHERE T2.CountryName = 'germany'"
数据集需要采用以下格式:
"input": [{"role": "system", "content": "<input prompt>"}, {"role": "user", "content": <user input>}, "ideal": "correct answer"]
综上所述,我们得到:
{"input": [{"role": "system", "content": "TASK: Answer the following question with syntactically correct SQLite SQL. The SQL should be correct and be in context of the previous question-answer pairs.\nTable car_makers, columns = [*,Id,Maker,FullName,Country]\nTable car_names, columns = [*,MakeId,Model,Make]\nTable cars_data, columns = [*,Id,MPG,Cylinders,Edispl,Horsepower,Weight,Accelerate,Year]\nTable continents, columns = [*,ContId,Continent]\nTable countries, columns = [*,CountryId,CountryName,Continent]\nTable model_list, columns = [*,ModelId,Maker,Model]\nForeign_keys = [countries.Continent = continents.ContId,car_makers.Country = countries.CountryId,model_list.Maker = car_makers.Id,car_names.Model = model_list.Model,cars_data.Id = car_names.MakeId]\n"}, {"role": "system", "content": "Q: how many car makers are their in germany"}, "ideal": ["A: SELECT count ( * ) FROM CAR_MAKERS AS T1 JOIN COUNTRIES AS T2 ON T1.Country = T2.CountryId WHERE T2.CountryName = 'germany'"]}
加速构建评估数据集的一种方法是使用 GPT-4 生成合成数据。
## Use GPT-4 to generate synthetic data
# Define the system prompt and user input (these should be filled as per the specific use case)
system_prompt = """You are a helpful assistant that can ask questions about a database table and write SQL queries to answer the question.
A user will pass in a table schema and your job is to return a question answer pairing. The question should relevant to the schema of the table,
and you can speculate on its contents. You will then have to generate a SQL query to answer the question. Below are some examples of what this should look like.
Example 1
User input: Table museum, columns = [*,Museum_ID,Name,Num_of_Staff,Open_Year]\nTable visit, columns = [*,Museum_ID,visitor_ID,Num_of_Ticket,Total_spent]\nTable visitor, columns = [*,ID,Name,Level_of_membership,Age]\nForeign_keys = [visit.visitor_ID = visitor.ID,visit.Museum_ID = museum.Museum_ID]\n
Assistant Response:
Q: How many visitors have visited the museum with the most staff?
A: SELECT count ( * ) FROM VISIT AS T1 JOIN MUSEUM AS T2 ON T1.Museum_ID = T2.Museum_ID WHERE T2.Num_of_Staff = ( SELECT max ( Num_of_Staff ) FROM MUSEUM )
Example 2
User input: Table museum, columns = [*,Museum_ID,Name,Num_of_Staff,Open_Year]\nTable visit, columns = [*,Museum_ID,visitor_ID,Num_of_Ticket,Total_spent]\nTable visitor, columns = [*,ID,Name,Level_of_membership,Age]\nForeign_keys = [visit.visitor_ID = visitor.ID,visit.Museum_ID = museum.Museum_ID]\n
Assistant Response:
Q: What are the names who have a membership level higher than 4?
A: SELECT Name FROM VISITOR AS T1 WHERE T1.Level_of_membership > 4
Example 3
User input: Table museum, columns = [*,Museum_ID,Name,Num_of_Staff,Open_Year]\nTable visit, columns = [*,Museum_ID,visitor_ID,Num_of_Ticket,Total_spent]\nTable visitor, columns = [*,ID,Name,Level_of_membership,Age]\nForeign_keys = [visit.visitor_ID = visitor.ID,visit.Museum_ID = museum.Museum_ID]\n
Assistant Response:
Q: How many tickets of customer id 5?
A: SELECT count ( * ) FROM VISIT AS T1 JOIN VISITOR AS T2 ON T1.visitor_ID = T2.ID WHERE T2.ID = 5
"""
user_input = "Table car_makers, columns = [*,Id,Maker,FullName,Country]\nTable car_names, columns = [*,MakeId,Model,Make]\nTable cars_data, columns = [*,Id,MPG,Cylinders,Edispl,Horsepower,Weight,Accelerate,Year]\nTable continents, columns = [*,ContId,Continent]\nTable countries, columns = [*,CountryId,CountryName,Continent]\nTable model_list, columns = [*,ModelId,Maker,Model]\nForeign_keys = [countries.Continent = continents.ContId,car_makers.Country = countries.CountryId,model_list.Maker = car_makers.Id,car_names.Model = model_list.Model,cars_data.Id = car_names.MakeId]"
messages = [{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": user_input
}
]
completion = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=messages,
temperature=0.7,
n=5
)
for choice in completion.choices:
print(choice.message.content + "\n")
一旦我们有了合成数据,就需要将其转换为与评估数据集格式相匹配的格式。
eval_data = []
input_prompt = "TASK: Answer the following question with syntactically correct SQLite SQL. The SQL should be correct and be in context of the previous question-answer pairs.\nTable car_makers, columns = [*,Id,Maker,FullName,Country]\nTable car_names, columns = [*,MakeId,Model,Make]\nTable cars_data, columns = [*,Id,MPG,Cylinders,Edispl,Horsepower,Weight,Accelerate,Year]\nTable continents, columns = [*,ContId,Continent]\nTable countries, columns = [*,CountryId,CountryName,Continent]\nTable model_list, columns = [*,ModelId,Maker,Model]\nForeign_keys = [countries.Continent = continents.ContId,car_makers.Country = countries.CountryId,model_list.Maker = car_makers.Id,car_names.Model = model_list.Model,cars_data.Id = car_names.MakeId]"
for choice in completion.choices:
question = choice.message.content.split("Q: ")[1].split("\n")[0] # Extracting the question
answer = choice.message.content.split("\nA: ")[1].split("\n")[0] # Extracting the answer
eval_data.append({
"input": [
{"role": "system", "content": input_prompt},
{"role": "user", "content": question},
],
"ideal": answer
})
for item in eval_data:
print(item)
接下来,我们需要创建评估注册表,以便在框架中运行它。
评估框架需要一个 .yaml 文件,其结构包含以下属性:
* id - 评估的标识符
* description - 评估的简短描述
* disclaimer - 关于评估的附加说明
* metrics - 我们可以选择三种评估指标:match、includes、fuzzyMatch
对于我们的评估,我们将配置以下内容:
"""
spider-sql:
id: spider-sql.dev.v0
metrics: [accuracy]
description: Eval that scores SQL code from 194 examples in the Spider Text-to-SQL test dataset. The problems are selected by taking the first 10 problems for each database that appears in the test set.
Yu, Tao, et al. \"Spider; A Large-Scale Human-Labeled Dataset for Complex and Cross-Domain Semantic Parsing and Text-to-SQL Task.\" Proceedings of the 2018 Conference on Empirical Methods in Natural Language Processing, 2018, https://doi.org/10.18653/v1/d18-1425.
disclaimer: Problems are solved zero-shot with no prompting other than the schema; performance may improve with training examples, fine tuning, or a different schema format. Evaluation is currently done through model-grading, where SQL code is not actually executed; the model may judge correct SQL to be incorrect, or vice-versa.
spider-sql.dev.v0:
class: evals.elsuite.modelgraded.classify:ModelBasedClassify
args:
samples_jsonl: sql/spider_sql.jsonl
eval_type: cot_classify
modelgraded_spec: sql
"""""
运行评估
我们可以使用 oaieval 命令行工具来运行这个评估。首先,进行设置:安装库 pip install .(如果你在本地运行 OpenAI Evals 库)或 pip install oaieval(如果你运行现有的评估)。
然后,使用命令行工具运行评估:oaieval gpt-3.5-turbo spider-sql
该命令需要一个模型名称和一个评估集名称。请注意,我们提供了两个命令行界面(CLI):oaieval 用于运行单个评估,oaievalset 用于运行一组评估。有效的评估名称在 evals/registry/evals 下的 YAML 文件中指定,其对应的实现可以在 evals/elsuite 中找到。
!pip install evals --quiet
oaieval 命令行接口可以接受各种标志来修改默认行为。你可以运行 oaieval --help 查看完整的命令行选项列表。
运行该命令后,你会在控制台看到最终准确率报告,以及一个包含完整报告的临时文件的路径。
oaieval 将按照上面单元格 4 中指定的格式,在 evals/registry/evals 目录中查找 spider-sql 评估 YAML 文件。评估数据集路径在评估 YAML 文件的 args 参数下指定为 samples_jsonl: sql/spider_sql.jsonl,文件内容为 JSONL 格式(如上面步骤 3 中生成的那样)。
运行该命令后,您将在控制台看到最终准确率报告,以及包含完整报告的临时文件的文件路径。
!oaieval gpt-3.5-turbo spider-sql --max_samples 25
oaievalset 需要一个模型名称和一个评估集名称,有效的选项在 evals/registry/eval_sets 下的 YAML 文件中指定。
查看评估日志
评估日志位于 /tmp/evallogs 目录下,每次评估运行都会生成不同的日志文件。
log_name = '240327024443FACXGMKA_gpt-3.5-turbo_spider-sql.jsonl' # "EDIT THIS" - copy from above
events = f"/tmp/evallogs/{log_name}"
display(pd.read_json(events, lines=True).head(5))
# processing the log events generated by oaieval
with open(events, "r") as f:
events_df = pd.read_json(f, lines=True)
该文件将包含评估的结构化日志。第一条目提供了评估的详细规范,包括完成函数、评估名称、运行配置、创建者姓名、运行ID和创建时间戳。
display(events_df.iloc[0].spec)
我们再来看一下提供评估最终报告的条目。
display(events_df.dropna(subset=['final_report']).iloc[0]['final_report'])
我们还可以查看单个评估事件,这些事件提供了具体的样本(sample_id)、结果、事件类型和元数据。
pd.set_option('display.max_colwidth', None) # None means no truncation
display(events_df.iloc[2][['run_id', 'event_id', 'sample_id', 'type', 'data', 'created_at']])
# Inspect samples
for i, row in events_df[events_df['type'] == 'sampling'].head(5).iterrows():
data = pd.json_normalize(row['data'])
print(f"Prompt: {data['prompt'].iloc[0]}")
print(f"Sampled: {data['sampled'].iloc[0]}")
print("-" * 10)
让我们回顾一下失败之处,以了解哪些测试没有成功。
def pretty_print_text(prompt):
# Define markers for the sections
markers = {
"question": "[Question]:",
"expert": "[Expert]:",
"submission": "[Submission]:",
"end": "[END DATA]"
}
# Function to extract text between markers
def extract_text(start_marker, end_marker):
start = prompt.find(start_marker) + len(start_marker)
end = prompt.find(end_marker)
text = prompt[start:end].strip()
if start_marker == markers["question"]:
text = text.split("\n\nQuestion:")[-1].strip() if "\n\nQuestion:" in text else text
elif start_marker == markers["submission"]:
text = text.replace("```sql", "").replace("```", "").strip()
return text
# Extracting text for each section
question_text = extract_text(markers["question"], markers["expert"])
expert_text = extract_text(markers["expert"], markers["submission"])
submission_text = extract_text(markers["submission"], markers["end"])
# HTML color codes and formatting
colors = {
"question": '<span style="color: #0000FF;">QUESTION:<br>',
"expert": '<span style="color: #008000;">EXPECTED:<br>',
"submission": '<span style="color: #FFA500;">SUBMISSION:<br>'
}
color_end = '</span>'
# Display each section with color
from IPython.display import display, HTML
display(HTML(f"{colors['question']}{question_text}{color_end}"))
display(HTML(f"{colors['expert']}{expert_text}{color_end}"))
display(HTML(f"{colors['submission']}{submission_text}{color_end}"))
# Inspect metrics where choice is made and print only the prompt, result, and expected result if the choice is incorrect
for i, row in events_df[events_df['type'] == 'metrics'].iterrows():
if row['data']['choice'] == 'Incorrect':
# Get the previous row's data, which contains the prompt and the expected result
prev_row = events_df.iloc[i-1]
prompt = prev_row['data']['prompt'][0]['content'] if 'prompt' in prev_row['data'] and len(prev_row['data']['prompt']) > 0 else "Prompt not available"
expected_result = prev_row['data'].get('ideal', 'Expected result not provided')
# Current row's data will be the actual result
result = row['data'].get('result', 'Actual result not provided')
pretty_print_text(prompt)
print("-" * 40)
回顾一些失败案例,我们观察到以下情况:
* 第二个错误答案中,与 'Templates' 表进行了不必要的连接。我们的评估能够准确识别这一点并将其标记为错误。
* 少数其他答案存在细微的语法差异,导致答案被标记。
* 在这种情况下,值得探讨是否应该继续迭代提示词以确保特定的风格选择,或者是否应该修改评估套件以涵盖这种变体。
* 这种类型的失败暗示了可能需要使用模型评分的评估来确保评分结果的准确性。
结论
构建有效的评估是基于LLM应用开发周期中的核心部分。OpenAI Evals框架提供了开箱即用的构建评估的核心结构,并允许您快速为各种用例启动新测试。在本指南中,我们逐步演示了如何创建评估、运行评估并分析结果。
本指南中展示的示例代表了评估的一个简单用例。随着您继续探索此框架,我们建议您探索为实际生产用例创建更复杂的模型分级评估。祝您评估愉快!
常见问题
常见错误:1) API 密钥未设置,导致认证失败;2) 数据集格式错误,如缺少 'ideal' 字段;3) YAML 配置错误,如类路径不正确;4) 模型评分误判,需人工抽查。排查方法:检查环境变量、验证 JSONL 格式、查看日志文件(/tmp/evallogs)中的错误信息。生产优化:使用缓存减少 API 调用,并行化评估,设置超时和重试,监控成本。
企业应用扩展
可扩展方向:1) 自定义评估类,实现复杂逻辑(如多轮对话、工具调用);2) 集成 CI/CD,在模型更新时自动运行评估;3) 使用合成数据生成器扩充数据集;4) 结合人工评估流程,对模型评分结果进行抽样验证;5) 探索更多指标(如 F1、BLEU)和评分模板(如分类、生成)。
总结
本教程面向有编程基础的开发者,深入讲解 OpenAI Evals 框架,用于评估 LLM 性能。内容涵盖安装配置、编写评估 YAML、运行评估、自定义评估及集成 CI/CD。通过 SQL 生成案例,演示如何构建数据集、使用 GPT-4 生成合成数据、配置评估注册表,并运行 oaieval 命令。同时分析评估日志,识别失败案例,探讨模型评分与代码检查的权衡。最后提供常见错误排查与生产环境优化建议,帮助开发者构建稳定可靠的 LLM 应用。
- --
内容来源与验证
- 内容等级:PREMIUM_A
- 质量等级:A
- 难度:中级
- 验证等级:V1
- 原文作者:royziv11, shyamal-anadkat
- 原文日期:2024-03-21
- 来源许可:MIT
- 来源版本:
18091d97f1fb0a4d51da751856e58e17dcb3f5d9 - 查看原文
站内还有更多完整实战教程
加入会员,解锁全部教程、完整代码与持续更新内容。
加入会员