如何运行成对评估
LangSmith 支持以比较的方式评估现有实验。这允许您对多个实验的输出相互评分,而不是局限于一次评估一个输出。想想 LMSYS Chatbot Arena - 这是相同的概念!为此,请使用带有两个现有实验的 evaluate() 函数。
如果您尚未创建要比较的实验,请查看我们的快速入门或我们的指南以开始评估。
evaluate()
比较参数
本指南需要 langsmith
Python 版本 >=0.2.0
或 JS 版本 >=0.2.9
。
最简单来说,evaluate
/ aevaluate
函数接受以下参数
参数 | 描述 |
---|---|
目标 | 您想要相互评估的两个现有实验的列表。这些可以是 uuids 或实验名称。 |
评估器 | 您想要附加到此评估的成对评估器的列表。请参阅下面的部分,了解如何定义这些评估器。 |
除此之外,您还可以传入以下可选参数
参数 | 描述 |
---|---|
randomize_order / randomizeOrder | 一个可选的布尔值,指示是否应为每次评估随机化输出的顺序。这是一种最大限度地减少 prompt 中位置偏差的策略:通常,LLM 会根据顺序偏向其中一个响应。这主要应通过 prompt 工程来解决,但这又是另一种可选的缓解措施。默认为 False。 |
experiment_prefix / experimentPrefix | 要附加到成对实验名称开头的 prefix。默认为 None。 |
描述 | 成对实验的描述。默认为 None。 |
max_concurrency / maxConcurrency | 要运行的并发评估的最大数量。默认为 5。 |
客户端 | 要使用的 LangSmith 客户端。默认为 None。 |
元数据 | 要附加到您的成对实验的元数据。默认为 None。 |
load_nested / loadNested | 是否加载实验的所有子 runs。为 False 时,只有根 trace 将传递给您的评估器。默认为 False。 |
定义成对评估器
成对评估器只是具有预期签名的函数。
评估器参数
自定义评估器函数必须具有特定的参数名称。它们可以采用以下参数的任何子集
inputs: dict
: 与数据集中的单个示例对应的输入的字典。outputs: list[dict]
: 由每个实验在给定输入上产生的 dict 输出的两项列表。reference_outputs
/referenceOutputs: dict
: 与示例关联的参考输出的字典(如果可用)。runs: list[Run]
: 由两个实验在给定示例上生成的完整 Run 对象的两项列表。如果您需要访问中间步骤或关于每个 run 的元数据,请使用此项。example: Example
: 完整数据集 Example,包括示例输入、输出(如果可用)和元数据(如果可用)。
对于大多数用例,您只需要 inputs、outputs 和 reference_outputs / referenceOutputs。run 和 example 仅在您需要应用程序的实际输入和输出之外的一些额外 trace 或示例元数据时才有用。
评估器输出
预计自定义评估器会返回以下类型之一
Python 和 JS/TS
dict
: 带有键的字典key
,表示将要记录的反馈键scores
,是从 run ID 到该 run 的分数的映射。comment
,是一个字符串。最常用于模型推理。
目前仅限 Python
list[int | float | bool]
: 包含两项分数的列表。该列表假定与 runs / outputs 评估器参数的顺序相同。评估器函数名称用作反馈键。
请注意,您应选择一个与 run 上的标准反馈不同的反馈键。我们建议以 pairwise_
或 ranked_
作为成对反馈键的前缀。
运行成对评估
以下示例使用一个 prompt,该 prompt 要求 LLM 决定两个 AI 助手响应中哪个更好。它使用结构化输出解析 AI 的响应:0、1 或 2。
在下面的 Python 示例中,我们正在从 此结构化 prompt 从 LangChain Hub 中提取此结构化 prompt,并将其与 LangChain 聊天模型包装器一起使用。
LangChain 的使用是完全可选的。 为了说明这一点,TypeScript 示例直接使用了 OpenAI SDK。
- Python
- TypeScript
需要 langsmith>=0.2.0
from langchain import hub
from langchain.chat_models import init_chat_model
from langsmith import evaluate
# See the prompt: https://smith.langchain.com/hub/langchain-ai/pairwise-evaluation-2
prompt = hub.pull("langchain-ai/pairwise-evaluation-2")
model = init_chat_model("gpt-4o")
chain = prompt | model
def ranked_preference(inputs: dict, outputs: list[dict]) -> list:
# Assumes example inputs have a 'question' key and experiment
# outputs have an 'answer' key.
response = chain.invoke({
"question": inputs["question"],
"answer_a": outputs[0].get("answer", "N/A"),
"answer_b": outputs[1].get("answer", "N/A"),
})
if response["Preference"] == 1:
scores = [1, 0]
elif response["Preference"] == 2:
scores = [0, 1]
else:
scores = [0, 0]
return scores
evaluate(
("experiment-1", "experiment-2"), # Replace with the names/IDs of your experiments
evaluators=[ranked_preference],
randomize_order=True,
max_concurrency=4,
)
需要 langsmith>=0.2.9
import { evaluate} from "langsmith/evaluation";
import { Run } from "langsmith/schemas";
import { wrapOpenAI } from "langsmith/wrappers";
import OpenAI from "openai";
import { z } from "zod";
const openai = wrapOpenAI(new OpenAI());
async function rankedPreference({
inputs,
runs,
}: {
inputs: Record<string, any>;
runs: Run[];
}) {
const scores: Record<string, number> = {};
const [runA, runB] = runs;
if (!runA || !runB) throw new Error("Expected at least two runs");
const payload = {
question: inputs.question,
answer_a: runA?.outputs?.output ?? "N/A",
answer_b: runB?.outputs?.output ?? "N/A",
};
const output = await openai.chat.completions.create({
model: "gpt-4-turbo",
messages: [
{
role: "system",
content: [
"Please act as an impartial judge and evaluate the quality of the responses provided by two AI assistants to the user question displayed below.",
"You should choose the assistant that follows the user's instructions and answers the user's question better.",
"Your evaluation should consider factors such as the helpfulness, relevance, accuracy, depth, creativity, and level of detail of their responses.",
"Begin your evaluation by comparing the two responses and provide a short explanation.",
"Avoid any position biases and ensure that the order in which the responses were presented does not influence your decision.",
"Do not allow the length of the responses to influence your evaluation. Do not favor certain names of the assistants. Be as objective as possible.",
].join(" "),
},
{
role: "user",
content: [
`[User Question] ${payload.question}`,
`[The Start of Assistant A's Answer] ${payload.answer_a} [The End of Assistant A's Answer]`,
`The Start of Assistant B's Answer] ${payload.answer_b} [The End of Assistant B's Answer]`,
].join("\n\n"),
},
],
tool_choice: {
type: "function",
function: { name: "Score" },
},
tools: [
{
type: "function",
function: {
name: "Score",
description: [
`After providing your explanation, output your final verdict by strictly following this format:`,
`Output "1" if Assistant A answer is better based upon the factors above.`,
`Output "2" if Assistant B answer is better based upon the factors above.`,
`Output "0" if it is a tie.`,
].join(" "),
parameters: {
type: "object",
properties: {
Preference: {
type: "integer",
description: "Which assistant answer is preferred?",
},
},
},
},
},
],
});
const { Preference } = z
.object({ Preference: z.number() })
.parse(
JSON.parse(output.choices[0].message.tool_calls[0].function.arguments)
);
if (Preference === 1) {
scores[runA.id] = 1;
scores[runB.id] = 0;
} else if (Preference === 2) {
scores[runA.id] = 0;
scores[runB.id] = 1;
} else {
scores[runA.id] = 0;
scores[runB.id] = 0;
}
return { key: "ranked_preference", scores };
}
await evaluate(["earnest-name-40", "reflecting-pump-91"], {
evaluators: [rankedPreference],
});
查看成对实验
从数据集页面导航到“成对实验”选项卡
单击您想要检查的成对实验,您将被带到比较视图
您可以通过单击表格标题中的“赞/踩”按钮来筛选到第一个实验更好或反之亦然的 runs。