Skip to main content

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

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

evaluate() 评估流程相比,此方法适用于以下情况:

  • 每个示例都需要不同的评估逻辑
  • 您希望断言二元期望值,同时在 LangSmith 中跟踪这些断言,并在本地(例如在 CI 流水线中)抛出断言错误。
  • 您希望利用 Vitest/Jest 生态系统中的模拟(mocks)、监听模式(watch mode)、本地测试结果或其他功能。
安装

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

测试版

Vitest/Jest 集成目前处于测试阶段,后续版本中可能会发生变化。

适用于 Python

Python SDK 具有类似的 pytest 集成

设置

按以下方式配置集成。请注意,您可以在现有单元测试(作为标准的 *.test.ts 文件)中一并添加 LangSmith 评估,使用您当前的测试配置文件;但下方示例还将额外设置一个独立的测试配置文件及运行评估的命令。该示例假定您的测试文件以 .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 以在运行评估之前加载环境变量
注意

当前不支持 JSDom 环境。您应从配置中省略 "environment" 字段,或将其设置为 "node"

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

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

请注意,上述脚本禁用了 Vitest 默认的监听模式(watch mode)以运行评估任务,因为许多评估器可能包含耗时较长的大语言模型(LLM)调用。

Jest

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

yarn add -D jest dotenv

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

yarn add langsmith openai
信息

以下安装说明适用于基础的 JS 文件和 CommonJS(CJS)。如需添加对 TypeScript 和 ES 模块(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 语法将评估(evals)定义为测试,但需注意以下几点:

  • 您应从 langsmith/jestlangsmith/vitest 入口点导入 describetest
  • 您必须将测试用例包裹在 describe 代码块中
  • 声明测试时,函数签名略有不同——多了一个参数,用于包含示例输入和预期输出。

通过创建一个名为 sql.eval.ts 的文件(如果使用 Jest 且未搭配 TypeScript,则创建名为 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() 的名称相同的数据集(dataset)
  • 如果数据集中尚不存在匹配的示例,则为测试用例中传入的每个输入和期望输出创建一个示例。
  • 为每个测试用例创建一个新实验,并生成一个结果
  • 收集每个测试用例在 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 文件(如果使用 Jest 且未启用 TypeScript,则为 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()。 这样可使“大语言模型作为评判器”的调用与测试用例其余部分分开追踪,从而避免追踪信息混杂;同时还能方便地生成反馈——当被包装函数的返回值与 { key: string; score: number | boolean } 匹配时即触发反馈。 在此情况下,评估器的追踪信息不会显示在主测试用例执行结果中,而是会出现在与 correctness 反馈键关联的追踪记录中。

您可以通过点击用户界面中对应的反馈标签,在 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.ENVIRONMENTprocess.env.NODE_ENVprocess.env.LANGSMITH_ENVIRONMENT 中提取环境变量,并将其作为元数据设置在所创建的实验上。随后,您便可在 LangSmith 的用户界面中按元数据筛选实验。

请参阅 API 参考文档 以获取完整的配置选项列表。

试运行模式

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

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


这个页面对你有帮助吗?


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