LangSmith 提供与以下工具的集成 Vitest 和 Jest 允许 JavaScript 和 TypeScript 开发人员定义他们的 数据集 并使用熟悉的语法进行评估。
与 evaluate() 评估流程相比,Vitest 或 Jest 测试框架在以下情况下很有用:
- * **每个示例需要不同的评估逻辑**:标准评估流程假设所有数据集示例的应用程序和评估器执行保持一致。对于更复杂的系统或全面评估,特定系统子集可能需要使用特定输入类型和指标进行评估。这些异构评估作为独立的测试用例套件编写会更简单,它们可以一起跟踪。
- * **你想要断言二元期望**:在 LangSmith 中跟踪断言并在本地抛出断言错误(例如在 CI 管道中)。当同时评估系统输出并对其基本属性进行断言时,测试工具会很有帮助。
- * **You want to take advantage of mocks, watch mode, local results, or other features of the Vitest/Jest ecosystems**.
设置
按如下方式设置集成。请注意,虽然你可以将 LangSmith 评估与其他单元测试一起添加(作为标准 *.test.ts 文件)使用你现有的测试配置文件,但以下示例还将设置一个单独的测试配置文件和命令来运行你的评估。它假设你将测试文件以 .eval.ts.
结尾。这确保自定义测试报告器和其他 LangSmith 接触点不会修改你现有的测试输出。
Vitest
如果尚未安装,请安装所需的开发依赖项:
yarn add -D vitest dotenv
npm install -D vitest dotenv
pnpm add -D vitest dotenv
以下示例还需要 openai (和 langsmith)作为依赖项:
yarn add langsmith openai
npm install langsmith openai
pnpm add langsmith openai
然后,创建一个单独的 ls.vitest.config.ts 文件,其中包含以下基础配置:
test: {
include: ["**/*.eval.?(c|m)[jt]s"],
reporters: ["langsmith/vitest/reporter"],
setupFiles: ["dotenv/config"],
testTimeout: 30000,
},
});
- *
include确保只有以某种变体的eval.ts结尾的文件在你的项目中运行 - *
reporters负责如上所示很好地格式化你的输出 - *
setupFiles运行dotenv在运行你的评估之前加载环境变量 - *
testTimeout为每个测试设置全局默认超时。因为 LLM 调用可能很慢,我们从 Vitest 默认值增加了这个值
最后,将以下内容添加到 scripts 字段在你的 package.json 中以使用你刚创建的配置运行 Vitest:
{
"name": "YOUR_PROJECT_NAME",
"scripts": {
"eval": "vitest run --config ls.vitest.config.ts"
},
"dependencies": {
...
},
"devDependencies": {
...
}
}
请注意,此脚本禁用了 Vitest 的默认监视模式来运行评估,因为许多评估器可能包含更长的 LLM 调用。
Jest
如果尚未安装,请安装所需的开发依赖项:
yarn add -D jest dotenv
npm install -D jest dotenv
pnpm add -D jest dotenv
以下示例还需要 openai (和 langsmith)作为依赖项:
yarn add langsmith openai
npm install langsmith openai
pnpm add langsmith openai
然后,创建一个名为的独立配置文件 ls.jest.config.cjs:
module.exports = {
testMatch: ["**/*.eval.?(c|m)[jt]s"],
reporters: ["langsmith/jest/reporter"],
setupFiles: ["dotenv/config"],
testTimeout: 30000,
};
- *
testMatch确保只有以某种变体结尾的文件eval.js在你的项目中运行 - *
reporters负责将你的输出格式化为如上所示的精美形式 - *
setupFiles运行dotenv在运行评估之前加载环境变量 - *
testTimeout为每个测试设置全局默认超时。因为LLM调用可能很慢,我们将其从Jest默认值增加
最后,将以下内容添加到 scripts 字段在你的 package.json 用你刚创建的配置运行Jest
{
"name": "YOUR_PROJECT_NAME",
"scripts": {
"eval": "jest --config ls.jest.config.cjs"
},
"dependencies": {
...
},
"devDependencies": {
...
}
}
定义和运行评估
You can now define evals as tests using familiar Vitest/Jest syntax, with a few caveats:
- * 你应该导入
describe和test从langsmith/jestorlangsmith/vitestentrypoint. - * 你必须将测试用例包装在
describeblock. - * 声明测试时,签名略有不同——有一个包含示例输入和预期输出的额外参数。
通过创建一个名为的文件来尝试 sql.eval.ts (or sql.eval.js 如果你使用不带TypeScript的Jest)并将这些代码粘贴到其中
// import * as ls from "langsmith/jest";
// import { expect } from "@jest/globals";
// 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-5.4-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()的名称相同(如果不存在) - * 在数据集中创建一个 示例 如果匹配的示例不存在,则为每个输入和预期输出创建一个测试用例
- * 创建新的 实验 每个测试用例对应一个结果
- * Collects the pass/fail rate under the
pass每个测试用例的反馈密钥
当你运行此测试时,它会有一个默认的 pass boolean feedback key based on the test case passing / failing. It will also track any outputs that you log with ls.logOutputs() 或从测试函数中返回作为实验的"实际"结果值
使用你的 .env 和LangSmith凭证创建一个 OPENAI_API_KEY 文件(如果你还没有的话)
OPENAI_API_KEY="YOUR_KEY_HERE"
LANGSMITH_API_KEY="YOUR_LANGSMITH_KEY"
LANGSMITH_TRACING="true"
现在使用我们在上一步设置的 eval 脚本来运行测试
yarn run eval
npm run eval
pnpm run eval
你声明的测试应该运行了
完成后,如果你设置了LangSmith环境变量,你应该会看到一个链接,指向在LangSmith中创建的实验以及测试结果
以下是该测试套件对应的实验示例
!实验
追踪反馈
By default LangSmith collects the pass/fail rate under the pass 每个测试用例的反馈键。您可以使用 ls.logFeedback() 或 ls.wrapEvaluator()。为此,请尝试将以下内容作为您的 sql.eval.ts 文件(或 sql.eval.js 如果您在没有 TypeScript 的情况下使用 Jest):
// import * as ls from "langsmith/jest";
// 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-5.4-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-5.4-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: "what's 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 反馈键关联的跟踪中。
您可以在 LangSmith 中通过点击 UI 中相应的反馈标签来查看评估器运行情况。
针对测试用例运行多个示例
您可以使用 ls.test.each() 对多个示例运行相同的测试用例,并参数化您的测试。当您想以相同方式针对不同输入评估您的应用时,这很有用:
// import * as ls from "langsmith/jest";
const DATASET = [
{
inputs: { userQuery: "what's 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 中创建的示例。
使用现有数据集(仅限 Vitest)
而不是定义 示例 内联,您可以针对 LangSmith 中的现有数据集运行测试:
- - 使用
client.listExamples()从 LangSmith 中已存在的数据集获取示例。 - - 将示例收集到一个数组中(例如
testExamples),通过遍历异步生成器。 - - 将数组传递给
ls.test.each()来针对数据集中的每个示例运行您的测试逻辑。
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" }
);
// Fetch examples from an existing dataset
const client = new Client();
const examples = client.listExamples({
datasetName: "generate sql demo",
});
const testExamples: Example[] = [];
for await (const example of examples) {
testExamples.push(example);
}
ls.describe(
"generate sql demo",
() => {
ls.test.each(testExamples)(
"generates valid sql",
async ({ inputs, referenceOutputs }) => {
const sql = await generateSql(inputs.userQuery);
ls.logOutputs({ sql });
expect(sql).toEqual(referenceOutputs?.sql);
}
);
}
);
记录输出
每次运行测试时,我们都会将其同步到数据集示例并将其作为运行进行跟踪。要跟踪运行的最终输出,您可以使用 ls.logOutputs(),如下所示:
// 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/jest";
ls.describe("generate sql demo", () => {
ls.test(
"offtopic input",
{
inputs: { userQuery: "..." },
referenceOutputs: { sql: "..." }
},
async ({ inputs, referenceOutputs }) => {
return { sql: "SELECT * FROM users;" }
},
);
});
但请记住,如果您这样做,由于断言失败或其他错误导致测试无法完成,您的输出将不会显示。
跟踪中间调用
LangSmith 将自动跟踪测试用例执行过程中发生的任何可跟踪中间调用。
聚焦或跳过测试
You can chain the Vitest/Jest .skip 和 .only 上的方法 ls.test() 和 ls.describe():
// 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.ENVIRONMENT, process.env.NODE_ENV 和 process.env.LANGSMITH_ENVIRONMENT 并将其设置为已创建实验的元数据。然后您可以在 LangSmith 的 UI 中按元数据筛选实验。
参见 API 参考文档 获取完整的配置选项列表。
试运行模式
如果您想在不将结果同步到 LangSmith 的情况下运行测试,可以省略 LangSmith 追踪环境变量或设置 LANGSMITH_TEST_TRACKING=false 在您的环境中。
测试将正常运行,但实验日志不会发送到 LangSmith。