如何使用 Vitest/Jest 运行评估(Beta 版)
LangSmith 提供了与 Vitest 和 Jest 的集成,允许 JavaScript 和 TypeScript 开发者使用熟悉的语法定义其数据集并进行评估。
与 evaluate()
评估流程相比,这在以下情况下很有用:
- 每个示例需要不同的评估逻辑
- 您希望断言二元预期,并且在 LangSmith 中跟踪这些断言,同时在本地(例如在 CI 流水线中)引发断言错误
- 您希望利用 Vitest/Jest 生态系统中的模拟、监视模式、本地结果或其他功能
需要 JS/TS SDK 版本 langsmith>=0.3.1
。
Vitest/Jest 集成处于 Beta 阶段,在即将发布的版本中可能会发生变化。
Python SDK 有一个类似的 pytest 集成。
设置
按如下方式设置集成。请注意,虽然您可以使用现有的测试配置文件将 LangSmith 评估与其他单元测试(作为标准 *.test.ts
文件)一起添加,但以下示例也将设置单独的测试配置文件和命令来运行您的评估。它将假定您的测试文件以 .eval.ts
结尾。
这确保了自定义测试报告器和其他 LangSmith 接触点不会修改您现有的测试输出。
Vitest
如果尚未安装,请安装所需的开发依赖项
- yarn
- npm
- pnpm
yarn add -D vitest dotenv
npm install -D vitest dotenv
pnpm add -D vitest dotenv
以下示例还需要 openai
(当然还有 langsmith
!)作为依赖项
- yarn
- npm
- pnpm
yarn add langsmith openai
npm install langsmith openai
pnpm 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
以加载环境变量
目前不支持 JSDom 环境。您应该从配置中省略 "environment"
字段,或者将其设置为 "node"
。
最后,将以下内容添加到您的 package.json
文件中的 scripts
字段,以使用您刚刚创建的配置运行 Vitest
{
"name": "YOUR_PROJECT_NAME",
"scripts": {
"eval": "vitest run --config ls.vitest.config.ts"
},
"dependencies": {
...
},
"devDependencies": {
...
}
}
请注意,上述脚本禁用了 Vitest 默认的监视模式,以运行评估,因为许多评估器可能包含耗时较长的 LLM 调用。
Jest
如果尚未安装,请安装所需的开发依赖项
- yarn
- npm
- pnpm
yarn add -D jest dotenv
npm install -D jest dotenv
pnpm add -D jest dotenv
以下示例还需要 openai
(当然还有 langsmith
!)作为依赖项
- yarn
- npm
- pnpm
yarn add langsmith openai
npm install langsmith openai
pnpm 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
以加载环境变量
目前不支持 JSDom 环境。您应该从配置中省略 "testEnvironment"
字段,或者将其设置为 "node"
。
最后,将以下内容添加到您的 package.json
文件中的 scripts
字段,以使用您刚刚创建的配置运行 Jest
{
"name": "YOUR_PROJECT_NAME",
"scripts": {
"eval": "jest --config ls.jest.config.cjs"
},
"dependencies": {
...
},
"devDependencies": {
...
}
}
定义并运行评估
现在您可以使用熟悉的 Vitest/Jest 语法将评估定义为测试,但有几点需要注意
- 您应该从
langsmith/jest
或langsmith/vitest
入口点导入describe
和test
- 您必须将测试用例包装在
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()
记录的任何输出或从测试函数返回的“实际”结果值,作为您应用程序在实验中的结果。
如果您还没有,请创建一个包含您的 OPENAI_API_KEY
和 LangSmith 凭据的 .env
文件
OPENAI_API_KEY="YOUR_KEY_HERE"
LANGSMITH_API_KEY="YOUR_LANGSMITH_KEY"
LANGSMITH_TRACING_V2="true"
现在使用我们在上一步中设置的 eval
脚本来运行测试
- yarn
- npm
- pnpm
yarn run eval
npm run eval
pnpm run eval
您声明的测试应该会运行!
一旦完成,如果您已设置 LangSmith 环境变量,您应该会在测试结果旁边看到一个链接,引导您前往在 LangSmith 中创建的实验。
以下是针对该测试套件的实验示例
追踪反馈
默认情况下,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-a-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()
传递一个额外参数来配置整个测试套件的元数据或自定义客户端,或者通过在 ls.test()
中为单个测试传递一个 config
字段来配置
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.ENVIRONMENT
、process.env.NODE_ENV
和 process.env.LANGSMITH_ENVIRONMENT
中提取环境变量,并将其设置为创建的实验的元数据。然后,您可以在 LangSmith 的 UI 中按元数据过滤实验。
有关完整的配置选项列表,请参阅API 参考。
空跑模式
如果您想在不将结果同步到 LangSmith 的情况下运行测试,您可以省略 LangSmith 追踪环境变量,或者在您的环境中设置 LANGSMITH_TEST_TRACKING=false
。
测试将正常运行,但实验日志不会发送到 LangSmith。