如何使用 Vitest/Jest 运行评估(beta)
LangSmith 提供了与 Vitest 和 Jest 的集成,允许 JavaScript 和 TypeScript 开发者使用熟悉的语法定义数据集并进行评估。

与 evaluate() 评估流程相比,这在以下情况下非常有用:
- 每个示例都需要不同的评估逻辑
- 您希望断言二元期望,并在 LangSmith 中跟踪这些断言,同时在本地(例如在 CI 管道中)抛出断言错误
- 您想利用 Vitest/Jest 生态系统的模拟功能、监视模式、本地结果或其他特性
需要 JS/TS SDK 版本 langsmith>=0.3.1。
Vitest/Jest 集成处于测试阶段,在后续版本中可能会发生变化。
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负责如上所示将您的输出格式化得美观setupFilesrunsdotenvto load environment variables before running your evals
JSDom 环境目前尚不支持。您应该从配置中省略 "environment" 字段,或将其设置为 "node"。
最后,将以下内容添加到您的scripts字段的package.json中,以使用您刚刚创建的配置运行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负责如上所示将您的输出格式化得美观setupFilesrunsdotenvto load environment variables before running your evals
JSDom 环境目前尚不支持。您应该从配置中省略 "testEnvironment" 字段,或将其设置为 "node"。
最后,将以下内容添加到您的 scripts 字段的 package.json 中,以使用您刚才创建的配置运行 Jest:
{
"name": "YOUR_PROJECT_NAME",
"scripts": {
"eval": "jest --config ls.jest.config.cjs"
},
"dependencies": {
...
},
"devDependencies": {
...
}
}
定义和运行评估
您现在可以使用熟悉的 Vitest/Jest 语法将评估(evals)定义为测试,但需注意以下几点:
- 您应该从
describe或test入口点导入langsmith/jest和langsmith/vitest - 您必须将测试用例包裹在
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 将执行以下操作:
- 如果不存在,则创建一个与传递给
ls.describe()的名称相同的名称的 数据集,该名称在 LangSmith 中 - 如果测试用例中传入的输入和预期输出在数据集中不存在匹配项,则为每个输入和预期输出在数据集中创建一个示例
- 创建一个新的实验,为每个测试用例生成一个结果
- 收集每个测试用例在
pass反馈键下的通过/失败率
当您运行此测试时,它将基于测试用例的通过/失败状态拥有一个默认的 pass 布尔反馈键。 它还将跟踪您使用 ls.logOutputs() 记录的任何输出,或从测试函数返回的来自您应用的实验“实际”结果值。
如果您还没有,请使用您的 .env 和 LangSmith 凭据创建一个 OPENAI_API_KEY 文件:
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,
});
}
);
});
注意在 ls.wrapEvaluator() 函数周围使用了 myEvaluator。 这使得 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() 传递额外参数来配置测试套件(例如元数据或自定义客户端),以针对整个套件;或者向 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。