跳到主要内容

如何使用 Vitest/Jest 运行评估 (beta)

LangSmith 提供了与 Vitest 和 Jest 的集成,允许 JavaScript 和 TypeScript 开发人员使用熟悉的语法定义他们的数据集和进行评估。

evaluate() 评估流程相比,当以下情况时,此方法很有用:

  • 每个示例需要不同的评估逻辑
  • 您想要断言二元期望,并在 LangSmith 中跟踪这些断言,并在本地引发断言错误(例如,在 CI 管道中)
  • 您想要利用 mocks、watch 模式、本地结果或 Vitest/Jest 生态系统的其他功能
安装

需要 JS/TS SDK 版本 langsmith>=0.3.1

Beta

Vitest/Jest 集成处于 beta 阶段,并且在即将发布的版本中可能会发生更改。

对于 Python

Python SDK 具有类似的 pytest 集成

设置

按如下方式设置集成。请注意,虽然您可以将 LangSmith 评估与您的其他单元测试(作为标准 *.test.ts 文件)一起添加到您现有的测试配置文件中,但以下示例还将设置单独的测试配置文件和命令来运行您的评估。它将假定您的测试文件以 .eval.ts 结尾。

这确保了自定义测试报告器和其他 LangSmith 接触点不会修改您现有的测试输出。

Vitest

如果您尚未安装,请安装所需的开发依赖项

yarn add -D vitest dotenv

以下示例还需要 openai(当然还有 langsmith!)作为依赖项

yarn add langsmith openai

然后创建一个单独的 ls.vitest.config.ts 文件,其中包含以下基本配置

import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
include: ["**/*.eval.?(c|m)[jt]s"],
reporters: ["langsmith/vitest/reporter"],
setupFiles: ["dotenv/config"],
},
});
  • include 确保只运行项目中以 eval.ts 的某些变体结尾的文件
  • reporters 负责很好地格式化您的输出,如上所示
  • setupFiles 运行 dotenv 以在运行评估之前加载环境变量

最后,将以下内容添加到 package.json 中的 scripts 字段,以使用您刚刚创建的配置运行 Vitest

{
"name": "YOUR_PROJECT_NAME",
"scripts": {
"eval": "vitest run --config ls.vitest.config.ts"
},
"dependencies": {
...
},
"devDependencies": {
...
}
}

请注意,由于许多评估器可能包含运行时间较长的 LLM 调用,因此上述脚本禁用了 Vitest 的默认 watch 模式以运行评估。

Jest

如果您尚未安装,请安装所需的开发依赖项

yarn add -D jest dotenv

以下示例还需要 openai(当然还有 langsmith!)作为依赖项

yarn add langsmith openai
信息

以下设置说明适用于基本的 JS 文件和 CJS。要添加对 TypeScript 和 ESM 的支持,请参阅 Jest 的官方文档或使用 Vitest

然后创建一个单独的配置文件,命名为 ls.jest.config.cjs

module.exports = {
testMatch: ["**/*.eval.?(c|m)[jt]s"],
reporters: ["langsmith/jest/reporter"],
setupFiles: ["dotenv/config"],
};
  • testMatch 确保只运行项目中以 eval.js 的某些变体结尾的文件
  • reporters 负责很好地格式化您的输出,如上所示
  • setupFiles 运行 dotenv 以在运行评估之前加载环境变量

最后,将以下内容添加到 package.json 中的 scripts 字段,以使用您刚刚创建的配置运行 Jest

{
"name": "YOUR_PROJECT_NAME",
"scripts": {
"eval": "jest --config ls.jest.config.cjs"
},
"dependencies": {
...
},
"devDependencies": {
...
}
}

定义和运行评估

您现在可以使用熟悉的 Vitest/Jest 语法将评估定义为测试,但有一些注意事项

  • 您应该从 langsmith/jestlangsmith/vitest 入口点导入 describetest
  • 您必须将测试用例包装在 describe 块中
  • 声明测试时,签名略有不同 - 有一个额外的参数,其中包含示例输入和预期输出

尝试创建一个名为 sql.eval.ts 的文件(如果您使用不带 TypeScript 的 Jest,则为 sql.eval.js),并将以下内容粘贴到其中

import * as ls from "langsmith/vitest";
import { expect } from "vitest";
// import * as ls from "langsmith/jest";
// import { expect } from "@jest/globals";

import OpenAI from "openai";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers/openai";

// Add "openai" as a dependency and set OPENAI_API_KEY as an environment variable
const tracedClient = wrapOpenAI(new OpenAI());

const generateSql = traceable(
async (userQuery: string) => {
const result = await tracedClient.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
"Convert the user query to a SQL query. Do not wrap in any markdown tags.",
},
{
role: "user",
content: userQuery,
},
],
});
return result.choices[0].message.content;
},
{ name: "generate_sql" }
);

ls.describe("generate sql demo", () => {
ls.test(
"generates select all",
{
inputs: { userQuery: "Get all users from the customers table" },
referenceOutputs: { sql: "SELECT * FROM customers;" },
},
async ({ inputs, referenceOutputs }) => {
const sql = await generateSql(inputs.userQuery);
ls.logOutputs({ sql }); // <-- Log run outputs, optional
expect(sql).toEqual(referenceOutputs?.sql); // <-- Assertion result logged under 'pass' feedback key
}
);
});

您可以将每个 ls.test() 用例视为对应于一个数据集示例,并将 ls.describe() 视为定义一个 LangSmith 数据集。如果您在运行测试套件时设置了 LangSmith 跟踪环境变量,则 SDK 将执行以下操作

  • 如果 LangSmith 中不存在同名的 数据集(名称与传递给 ls.describe() 的名称相同),则创建该数据集
  • 如果不存在匹配的示例,则为传递到测试用例中的每个输入和预期输出在数据集中创建一个示例
  • 为每个测试用例创建一个新的 实验,其中包含一个结果
  • 收集每个测试用例在 pass 反馈键下的通过/失败率

当您运行此测试时,它将具有基于测试用例通过/失败的默认 pass 布尔反馈键。它还将跟踪您使用 ls.logOutputs() 记录或从测试函数返回的任何输出,作为实验中应用程序的“实际”结果值。

如果您还没有 .env 文件,请创建一个包含您的 OPENAI_API_KEY 和 LangSmith 凭据的文件

OPENAI_API_KEY="YOUR_KEY_HERE"

LANGSMITH_API_KEY="YOUR_LANGSMITH_KEY"
LANGSMITH_TRACING_V2="true"

现在使用我们在上一步中设置的 eval 脚本来运行测试

yarn run eval

您声明的测试应该会运行!

完成后,如果您已设置 LangSmith 环境变量,您应该会看到一个链接,将您定向到 LangSmith 中创建的实验以及测试结果。

以下是针对该测试套件的实验示例

Experiment

跟踪反馈

默认情况下,LangSmith 收集每个测试用例在 pass 反馈键下的通过/失败率。您可以使用 ls.logFeedback()wrapEvaluator() 添加其他反馈。为此,请尝试以下内容作为您的 sql.eval.ts 文件(如果您使用不带 TypeScript 的 Jest,则为 sql.eval.js

import * as ls from "langsmith/vitest";
// import * as ls from "langsmith/jest";

import OpenAI from "openai";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers/openai";

// Add "openai" as a dependency and set OPENAI_API_KEY as an environment variable
const tracedClient = wrapOpenAI(new OpenAI());

const generateSql = traceable(
async (userQuery: string) => {
const result = await tracedClient.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
"Convert the user query to a SQL query. Do not wrap in any markdown tags.",
},
{
role: "user",
content: userQuery,
},
],
});
return result.choices[0].message.content ?? "";
},
{ name: "generate_sql" }
);

const myEvaluator = async (params: {
outputs: { sql: string };
referenceOutputs: { sql: string };
}) => {
const { outputs, referenceOutputs } = params;
const instructions = [
"Return 1 if the ACTUAL and EXPECTED answers are semantically equivalent, ",
"otherwise return 0. Return only 0 or 1 and nothing else.",
].join("\n");
const grade = await tracedClient.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: instructions,
},
{
role: "user",
content: `ACTUAL: ${outputs.sql}\nEXPECTED: ${referenceOutputs?.sql}`,
},
],
});
const score = parseInt(grade.choices[0].message.content ?? "");
return { key: "correctness", score };
};

ls.describe("generate sql demo", () => {
ls.test(
"generates select all",
{
inputs: { userQuery: "Get all users from the customers table" },
referenceOutputs: { sql: "SELECT * FROM customers;" },
},
async ({ inputs, referenceOutputs }) => {
const sql = await generateSql(inputs.userQuery);
ls.logOutputs({ sql });
const wrappedEvaluator = ls.wrapEvaluator(myEvaluator);
// Will automatically log "correctness" as feedback
await wrappedEvaluator({
outputs: { sql },
referenceOutputs,
});
// You can also manually log feedback with `ls.logFeedback()`
ls.logFeedback({
key: "harmfulness",
score: 0.2,
});
}
);
ls.test(
"offtopic input",
{
inputs: { userQuery: "whats up" },
referenceOutputs: { sql: "sorry that is not a valid query" },
},
async ({ inputs, referenceOutputs }) => {
const sql = await generateSql(inputs.userQuery);
ls.logOutputs({ sql });
const wrappedEvaluator = ls.wrapEvaluator(myEvaluator);
// Will automatically log "correctness" as feedback
await wrappedEvaluator({
outputs: { sql },
referenceOutputs,
});
// You can also manually log feedback with `ls.logFeedback()`
ls.logFeedback({
key: "harmfulness",
score: 0.2,
});
}
);
});

请注意在 myEvaluator 函数周围使用 ls.wrapEvaluator()。这使得 LLM-as-judge 调用与测试用例的其余部分分开跟踪,以避免混乱,并且如果包装函数的返回值与 { key: string; score: number | boolean } 匹配,则方便地创建反馈。在这种情况下,评估器跟踪不会显示在主测试用例运行中,而是会显示在与 correctness 反馈键关联的跟踪中。

您可以通过单击 UI 中相应的反馈芯片在 LangSmith 中查看评估器运行。

针对测试用例运行多个示例

您可以使用 ls.test.each() 对多个示例运行相同的测试用例并参数化您的测试。当您想要以相同的方式针对不同的输入评估您的应用程序时,这很有用

import * as ls from "langsmith/vitest";
// import * as ls from "langsmith/jest";

const DATASET = [{
inputs: { userQuery: "whats up" },
referenceOutputs: { sql: "sorry that is not a valid query" }
}, {
inputs: { userQuery: "what color is the sky?" },
referenceOutputs: { sql: "sorry that is not a valid query" }
}, {
inputs: { userQuery: "how are you today?" },
referenceOutputs: { sql: "sorry that is not a valid query" }
}];

ls.describe("generate sql demo", () => {
ls.test.each(DATASET)(
"offtopic inputs",
async ({ inputs, referenceOutputs }) => {
...
},
)
});

如果您启用了跟踪,则本地数据集中的每个示例都将同步到 LangSmith 中创建的示例。

记录输出

每次我们运行测试时,我们都会将其同步到数据集示例并将其作为运行进行跟踪。要跟踪运行的最终输出,您可以像这样使用 ls.logOutputs()

import * as ls from "langsmith/vitest";
// import * as ls from "langsmith/jest";

ls.describe("generate sql demo", () => {
ls.test(
"offtopic input",
{
inputs: { userQuery: "..." },
referenceOutputs: { sql: "..." }
},
async ({ inputs, referenceOutputs }) => {
ls.logOutputs({ sql: "SELECT * FROM users;" })
},
)
});

记录的输出将显示在您的报告器摘要和 LangSmith 中。

您也可以直接从测试函数返回值

import * as ls from "langsmith/vitest";
// import * as ls from "langsmith/jest";

ls.describe("generate sql demo", () => {
ls.test(
"offtopic input",
{
inputs: { userQuery: "..." },
referenceOutputs: { sql: "..." }
},
async ({ inputs, referenceOutputs }) => {
return { sql: "SELECT * FROM users;" }
},
);
});

但是请记住,如果您这样做,如果您的测试由于断言失败或其他错误而未能完成,则您的输出将不会显示。

跟踪中间调用

LangSmith 将自动跟踪在测试用例执行过程中发生的任何可跟踪的中间调用。

聚焦或跳过测试

您可以在 ls.test()ls.describe() 上链接 Vitest/Jest 的 .skip.only 方法

import * as ls from "langsmith/vitest";
// import * as ls from "langsmith/jest";

ls.describe("generate sql demo", () => {
ls.test.skip(
"offtopic input",
{
inputs: { userQuery: "..." },
referenceOutputs: { sql: "..." }
},
async ({ inputs, referenceOutputs }) => {
return { sql: "SELECT * FROM users;" }
},
);
ls.test.only(
"other",
{
inputs: { userQuery: "..." },
referenceOutputs: { sql: "..." }
},
async ({ inputs, referenceOutputs }) => {
return { sql: "SELECT * FROM users;" }
},
);
});

配置测试套件

您可以通过将额外的参数传递给完整套件的 ls.describe() 或将 config 字段传递到单个测试的 ls.test(),使用元数据或自定义客户端等值来配置测试套件

ls.describe("test suite name", () => {
ls.test(
"test name",
{
inputs: { ... },
referenceOutputs: { ... },
// Extra config for the test run
config: { tags: [...], metadata: { ... } }
},
{
name: "test name",
tags: ["tag1", "tag2"],
skip: true,
only: true,
}
);
}, {
testSuiteName: "overridden value",
metadata: { ... },
// Custom client
client: new Client(),
});

测试套件还将自动从 process.env.ENVIRONMENTprocess.env.NODE_ENVprocess.env.LANGSMITH_ENVIRONMENT 中提取环境变量,并将它们设置为已创建实验的元数据。然后,您可以在 LangSmith 的 UI 中按元数据筛选实验。

有关配置选项的完整列表,请参阅 API 参考

Dry-run 模式

如果您想在不将结果同步到 LangSmith 的情况下运行测试,您可以省略您的 LangSmith 跟踪环境变量,或在您的环境中设置 LANGSMITH_TEST_TRACKING=false

测试将照常运行,但实验日志不会发送到 LangSmith。


此页面是否对您有帮助?


您可以留下详细的反馈 在 GitHub 上.