评估聊天机器人
本指南将介绍如何为聊天机器人设置评估。这使您能够衡量应用程序在固定数据集上的表现。能够快速可靠地获取此洞察将使您能够自信地进行迭代。
概括来说,在本教程中我们将
- 创建一个初始黄金数据集来衡量性能
- 定义用于衡量性能的指标
- 对几种不同的提示或模型运行评估
- 手动比较结果
- 跟踪随时间变化的结果
- 设置自动化测试以在 CI/CD 中运行
有关 LangSmith 支持的评估工作流的更多信息,请查看操作指南,或参阅 evaluate 及其异步对应项 aevaluate 的参考文档。
内容很多,我们开始吧!
设置
首先安装本教程所需的依赖项。我们碰巧使用了 OpenAI,但 LangSmith 可以与任何模型一起使用。
pip install -U langsmith openai
并设置环境变量以启用 LangSmith 追踪
export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY="<Your LangSmith API key>"
export OPENAI_API_KEY="<Your OpenAI API key>"
创建数据集
准备测试和评估应用程序的第一步是定义要评估的数据点。这里有几个方面需要考虑:
- 每个数据点的模式应该是什么?
- 我应该收集多少个数据点?
- 我应该如何收集这些数据点?
模式:每个数据点至少应包含应用程序的输入。如果可能,定义预期输出也非常有帮助——这些代表了您期望正常运行的应用程序会输出什么。通常您无法定义完美的输出——没关系!评估是一个迭代过程。有时您可能还需要为每个示例定义更多信息——例如在 RAG 中要获取的预期文档,或作为代理要执行的预期步骤。LangSmith 数据集非常灵活,允许您定义任意模式。
数量:对于应该收集多少,没有硬性规定。主要事情是确保您对可能需要防范的边缘情况有适当的覆盖。即使是 10-50 个示例也能提供很大的价值!不要担心一开始就获取大量数据——您始终可以(也应该)随着时间的推移添加数据!
如何获取:这可能是最棘手的部分。一旦您知道要收集数据集……您如何实际操作呢?对于大多数开始新项目的团队,我们通常看到他们通过手动收集前 10-20 个数据点来开始。在从这些数据点开始之后,这些数据集通常是活的构造,并且随着时间的推移而增长。它们通常在看到真实用户如何使用您的应用程序、看到存在的痛点,然后将其中一些数据点移动到此集合中后增长。还有像合成生成数据的方法可以用于扩充您的数据集。首先,我们建议不要担心这些,只需手动标注约 10-20 个示例。
一旦您拥有了数据集,有几种不同的方法可以将其上传到 LangSmith。对于本教程,我们将使用客户端,但您也可以通过 UI 上传(甚至在 UI 中创建它们)。
在本教程中,我们将创建 5 个数据点进行评估。我们将评估一个问答应用程序。输入将是一个问题,输出将是一个答案。由于这是一个问答应用程序,我们可以定义预期答案。让我们展示如何创建并将此数据集上传到 LangSmith!
from langsmith import Client
client = Client()
# Define dataset: these are your test cases
dataset_name = "QA Example Dataset"
dataset = client.create_dataset(dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=[
{
"inputs": {"question": "What is LangChain?"},
"outputs": {"answer": "A framework for building LLM applications"},
},
{
"inputs": {"question": "What is LangSmith?"},
"outputs": {"answer": "A platform for observing and evaluating LLM applications"},
},
{
"inputs": {"question": "What is OpenAI?"},
"outputs": {"answer": "A company that creates Large Language Models"},
},
{
"inputs": {"question": "What is Google?"},
"outputs": {"answer": "A technology company known for search"},
},
{
"inputs": {"question": "What is Mistral?"},
"outputs": {"answer": "A company that creates Large Language Models"},
}
]
)
现在,如果我们进入 LangSmith UI,在 Datasets & Testing 页面中查找 QA Example Dataset,点击进入后,我们应该会看到五个新示例。

定义指标
创建数据集后,我们现在可以定义一些指标来评估我们的响应。由于我们有预期答案,我们可以将其作为评估的一部分进行比较。但是,我们不期望我们的应用程序输出那些确切的答案,而是输出一些相似的内容。这使得我们的评估有点棘手。
除了评估正确性,我们还要确保答案简短而精炼。这将更容易一些——我们可以定义一个简单的 Python 函数来测量响应的长度。
我们继续定义这两个指标。
对于第一个,我们将使用 LLM 来判断输出是否正确(相对于预期输出)。这种LLM 作为法官的方法对于过于复杂而无法用简单函数衡量的案例来说是相对常见的。我们可以在这里定义自己的提示和要用于评估的 LLM
import openai
from langsmith import wrappers
openai_client = wrappers.wrap_openai(openai.OpenAI())
eval_instructions = "You are an expert professor specialized in grading students' answers to questions."
def correctness(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
user_content = f"""You are grading the following question:
{inputs['question']}
Here is the real answer:
{reference_outputs['answer']}
You are grading the following predicted answer:
{outputs['response']}
Respond with CORRECT or INCORRECT:
Grade:
"""
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
messages=[
{"role": "system", "content": eval_instructions},
{"role": "user", "content": user_content},
],
).choices[0].message.content
return response == "CORRECT"
评估响应长度就容易多了!我们只需定义一个简单的函数,检查实际输出是否小于预期结果长度的两倍。
def concision(outputs: dict, reference_outputs: dict) -> bool:
return int(len(outputs["response"]) < 2 * len(reference_outputs["answer"]))
运行评估
太好了!那么我们如何运行评估呢?现在我们有了数据集和评估器,我们只需要我们的应用程序!我们将构建一个简单的应用程序,它只有一个系统消息,包含如何响应的指令,然后将其传递给 LLM。我们将直接使用 OpenAI SDK 来构建它。
default_instructions = "Respond to the users question in a short, concise manner (one short sentence)."
def my_app(question: str, model: str = "gpt-4o-mini", instructions: str = default_instructions) -> str:
return openai_client.chat.completions.create(
model=model,
temperature=0,
messages=[
{"role": "system", "content": instructions},
{"role": "user", "content": question},
],
).choices[0].message.content
在通过 LangSmith 评估运行此应用程序之前,我们需要定义一个简单的包装器,将数据集中的输入键映射到我们想要调用的函数,然后还将函数的输出映射到我们期望的输出键。
def ls_target(inputs: str) -> dict:
return {"response": my_app(inputs["question"])}
太棒了!现在我们准备运行评估了。开始吧!
experiment_results = client.evaluate(
ls_target, # Your AI system
data=dataset_name, # The data to predict and grade over
evaluators=[concision, correctness], # The evaluators to score the results
experiment_prefix="openai-4o-mini", # A prefix for your experiment names to easily identify them
)
这将输出一个 URL。如果我们点击它,我们应该会看到评估结果!

如果我们回到数据集页面并选择 Experiments 选项卡,我们现在可以看到我们一次运行的摘要!

现在让我们尝试使用不同的模型!试试 gpt-4-turbo
def ls_target_v2(inputs: str) -> dict:
return {"response": my_app(inputs["question"], model="gpt-4-turbo")}
experiment_results = client.evaluate(
ls_target_v2,
data=dataset_name,
evaluators=[concision, correctness],
experiment_prefix="openai-4-turbo",
)
现在让我们使用 GPT-4,同时更新提示,使其在要求答案简短方面更严格。
instructions_v3 = "Respond to the users question in a short, concise manner (one short sentence). Do NOT use more than ten words."
def ls_target_v3(inputs: str) -> dict:
response = my_app(
inputs["question"],
model="gpt-4-turbo",
instructions=instructions_v3
)
return {"response": response}
experiment_results = client.evaluate(
ls_target_v3,
data=dataset_name,
evaluators=[concision, correctness],
experiment_prefix="strict-openai-4-turbo",
)
如果我们回到数据集页面的 Experiments 选项卡,我们应该会看到所有三次运行都显示出来了!

比较结果
太棒了,我们已经评估了三次不同的运行。但是我们如何比较结果呢?第一种方法是直接查看 Experiments 选项卡中的运行。如果我们这样做,我们可以看到每次运行的指标概览。

太棒了!所以我们可以知道 GPT-4 在了解公司方面比 GPT-3.5 更出色,而且我们也可以看到严格的提示对长度有很大帮助。但是如果我们想更详细地探索呢?
为此,我们可以选择所有要比较的运行(在本例中是所有三个),并在比较视图中打开它们。我们立即看到三个测试并排显示。一些单元格是颜色编码的——这显示了某个指标相对于某个基线的回归。我们自动选择基线和指标的默认值,但您可以自行更改。您还可以使用显示控件选择要查看的列和指标。您还可以通过点击顶部的图标自动过滤,只查看有改进/回归的运行。

如果想查看更多信息,我们也可以选择将鼠标悬停在一行上时出现的“展开”按钮,以打开一个包含更详细信息的侧面板。

设置自动化测试以在 CI/CD 中运行
现在我们已经以一次性方式运行了它,我们可以将其设置为自动化运行。我们可以很容易地通过将其作为一个 pytest 文件包含在 CI/CD 中来完成。作为此过程的一部分,我们可以只记录结果,或者设置一些标准来确定它是否通过。例如,如果我想确保至少 80% 的生成响应通过 length 检查,我们可以通过以下测试来设置:
def test_length_score() -> None:
"""Test that the length score is at least 80%."""
experiment_results = evaluate(
ls_target, # Your AI system
data=dataset_name, # The data to predict and grade over
evaluators=[concision, correctness], # The evaluators to score the results
)
# This will be cleaned up in the next release:
feedback = client.list_feedback(
run_ids=[r.id for r in client.list_runs(project_name=experiment_results.experiment_name)],
feedback_key="concision"
)
scores = [f.score for f in feedback]
assert sum(scores) / len(scores) >= 0.8, "Aggregate score should be at least .8"
跟踪随时间变化的结果
现在我们已经以自动化方式运行了这些实验,我们希望跟踪这些结果随时间的变化。我们可以在数据集页面的整体 Experiments 选项卡中完成此操作。默认情况下,我们显示随时间变化的评估指标(红色突出显示)。我们还会自动跟踪 Git 指标,以便轻松将其与代码分支关联(黄色突出显示)。

结论
本教程到此为止!
我们已经介绍了如何创建初始测试集、定义一些评估指标、运行实验、手动比较它们、设置 CI/CD 以及跟踪随时间变化的结果。希望这能帮助您自信地进行迭代。
这只是开始。如前所述,评估是一个持续进行的过程。例如,您希望评估的数据点可能会随着时间不断变化。您可能希望探索许多类型的评估器。有关此信息,请查看操作指南。
此外,除了这种“离线”方式(例如,您可以评估生产数据)之外,还有其他评估数据的方法。有关在线评估的更多信息,请查看本指南。
参考代码
点击查看合并后的代码片段
import openai
from langsmith import Client, wrappers
# Application code
openai_client = wrappers.wrap_openai(openai.OpenAI())
default_instructions = "Respond to the users question in a short, concise manner (one short sentence)."
def my_app(question: str, model: str = "gpt-4o-mini", instructions: str = default_instructions) -> str:
return openai_client.chat.completions.create(
model=model,
temperature=0,
messages=[
{"role": "system", "content": instructions},
{"role": "user", "content": question},
],
).choices[0].message.content
client = Client()
# Define dataset: these are your test cases
dataset_name = "QA Example Dataset"
dataset = client.create_dataset(dataset_name)
client.create_examples(
dataset_id=dataset.id,
examples=[
{
"inputs": {"question": "What is LangChain?"},
"outputs": {"answer": "A framework for building LLM applications"},
},
{
"inputs": {"question": "What is LangSmith?"},
"outputs": {"answer": "A platform for observing and evaluating LLM applications"},
},
{
"inputs": {"question": "What is OpenAI?"},
"outputs": {"answer": "A company that creates Large Language Models"},
},
{
"inputs": {"question": "What is Google?"},
"outputs": {"answer": "A technology company known for search"},
},
{
"inputs": {"question": "What is Mistral?"},
"outputs": {"answer": "A company that creates Large Language Models"},
}
]
)
# Define evaluators
eval_instructions = "You are an expert professor specialized in grading students' answers to questions."
def correctness(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
user_content = f"""You are grading the following question:
{inputs['question']}
Here is the real answer:
{reference_outputs['answer']}
You are grading the following predicted answer:
{outputs['response']}
Respond with CORRECT or INCORRECT:
Grade:
"""
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
messages=[
{"role": "system", "content": eval_instructions},
{"role": "user", "content": user_content},
],
).choices[0].message.content
return response == "CORRECT"
def concision(outputs: dict, reference_outputs: dict) -> bool:
return int(len(outputs["response"]) < 2 * len(reference_outputs["answer"]))
# Run evaluations
def ls_target(inputs: str) -> dict:
return {"response": my_app(inputs["question"])}
experiment_results_v1 = client.evaluate(
ls_target, # Your AI system
data=dataset_name, # The data to predict and grade over
evaluators=[concision, correctness], # The evaluators to score the results
experiment_prefix="openai-4o-mini", # A prefix for your experiment names to easily identify them
)
def ls_target_v2(inputs: str) -> dict:
return {"response": my_app(inputs["question"], model="gpt-4-turbo")}
experiment_results_v2 = client.evaluate(
ls_target_v2,
data=dataset_name,
evaluators=[concision, correctness],
experiment_prefix="openai-4-turbo",
)
instructions_v3 = "Respond to the users question in a short, concise manner (one short sentence). Do NOT use more than ten words."
def ls_target_v3(inputs: str) -> dict:
response = my_app(
inputs["question"],
model="gpt-4-turbo",
instructions=instructions_v3
)
return {"response": response}
experiment_results_v3 = client.evaluate(
ls_target_v3,
data=dataset_name,
evaluators=[concision, correctness],
experiment_prefix="strict-openai-4-turbo",
)