Introducing Pytest and Vitest integrations for LangSmith Evaluations

LangSmith Evaluations Pytest 和 Vitest 集成介绍

隆重推出使用 LangSmith 的 Pytest 和 Vitest/Jest 集成运行评估的新方法。

6 分钟阅读

评估 (evals) 是构建可靠和高质量 LLM 应用程序的重要组成部分。它们帮助您评估应用程序的性能,确保在您进行更新时质量保持一致。如果您来自软件工程背景,您可能熟悉为此目的使用测试。为了扩展这种熟悉的界面,我们很高兴推出一种使用 LangSmith 的 PytestVitest/Jest 集成运行评估的新方法。

这些新的集成现已在 LangSmith PythonTypescript SDK 的 v0.3.0 beta 版本中提供。

为什么为 LLM 评估使用测试框架

如果您已经在使用 PytestVitest/Jest 测试您的应用程序,新的 LangSmith 集成为您提供 Pytest/Vitest 的灵活性、熟悉性和运行时行为,以及 LangSmith 的可观察性和共享功能。这些集成使用您习惯的完全相同的开发者体验 (DX),并具有以下优点

在 LangSmith 中调试您的测试

由于 LLM 应用程序的非确定性特性,在调试时会增加额外的复杂性。LangSmith 保存来自您的测试用例的输入/输出和堆栈跟踪,以帮助您查明问题的根本原因。

在 LangSmith 中记录指标(超出通过/失败),并跟踪随时间的进展

通常,测试框架仅关注通过/失败结果,但测试 LLM 应用程序通常需要更细致的方法。您可能没有硬性的通过/失败标准;相反,您希望记录结果并查看您的应用程序如何随时间改进。借助 LangSmith,您可以记录反馈并比较随时间推移的结果,以防止回归并确保您始终部署应用程序的最佳版本。

与您的团队分享结果

使用 LLM 构建通常是一项团队工作。我们经常看到主题 matter 专家参与提示创建过程或创建评估时。LangSmith 允许您在团队中共享实验结果,从而使协作更加容易。

内置评估函数

如果您正在使用 Python,LangSmith 提供了一些内置评估函数,以帮助检查您的 LLM 的输出。例如,expect.edit_distance() 用于计算您的测试输出与提供的参考输出之间的字符串距离。有关内置评估函数的更多详细信息,请访问我们的 API 参考

开始使用

这是一个简单的测试用例,演示如何评估生成 SQL 查询的应用程序。此测试检查应用程序是否正确识别了离题的用户输入,并将结果记录到 LangSmith。当您运行测试套件时,将在 LangSmith 中创建/更新数据集,并创建一个新的实验。

Pytest 入门

要在 LangSmith 中跟踪测试,请添加 @pytest.mark.langsmith 装饰器。

# tests/test_sql.py
import openai
import pytest
from langsmith import wrappers
from langsmith import testing as t

oai_client = wrappers.wrap_openai(openai.OpenAI())

# Define your app logic elsewhere:
# @traceable
# def generate_sql(user_query: str) -> str: ...

@pytest.mark.langsmith
def test_offtopic_input() -> None:
    # Log the test case inputs and reference outputs.
    user_query = "whats up"
    t.log_inputs({"user_query": user_query})
    
    expected = "Sorry that is not a valid question."
    t.log_reference_outputs({"response": expected})
    
    actual = generate_sql(user_query)  
    t.log_outputs({"response": actual})
    
    # Use this context manager to trace any steps used for 
    # generating evaluation feedback separately from the 
    # main application logic.
    with t.trace_feedback():
        instructions = (
            "Return 1 if the ACTUAL and EXPECTED answers are semantically equivalent, "
            "otherwise return 0. Return only 0 or 1 and nothing else."
        )        
        grade = oai_client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": instructions},
                {"role": "user", "content": f"ACTUAL: {actual}\nEXPECTED: {expected}"},
            ],
        )
        score = float(grade.choices[0].message.content)
        t.log_feedback(key="correctness", score=score)

    assert actual
    assert score

像往常一样启动测试

pytest tests

这将像任何其他 pytest 测试运行一样运行,并将所有测试用例结果、应用程序跟踪和反馈跟踪记录到 LangSmith。

访问我们的 Pytest 实践指南 以获取完整示例。

Vitest 入门

要在 LangSmith 中跟踪测试,请将您的测试用例包装在 ls.describe() 块中。

import * as ls from "langsmith/vitest";

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

//Set OPENAI_API_KEY as an environment variable
const tracedClient = wrapOpenAI(new OpenAI());

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(
    "offtopic input",
    {
      inputs: { userQuery: "whats up" },
      referenceOutputs: { sql: "sorry that is not a valid query" },
    },
    async ({ inputs, referenceOutputs }) => {
      const sql = await generateSql(inputs.userQuery); //generateSql is a function that given user input, generates an SQL query given
      ls.logOutputs({ sql });
      const wrappedEvaluator = ls.wrapEvaluator(myEvaluator);
      // Will automatically log "correctness" as feedback
      await wrappedEvaluator({
        outputs: { sql },
        referenceOutputs,
      });
    }
  );
});

访问我们的 Vitest/Jest 实践指南 以获取完整示例。

测试框架 vs. evaluate()

大多数流行的评估库,例如 OpenAI EvalsHugging Face 的 EvaluateLangSmith 的 evaluate() 工作方式类似 – 您首先预先创建一个数据集,然后定义一个生成函数和一组评估器以在数据集上运行。这种方法通常适用于您需要在数据集上运行同一组评估的用例,例如,如果您正在对代理的输入和输出进行黑盒测试。但是,我们发现它在某些情况下会不足。

我们在我们构建的许多应用程序中集成了新的 Pytest 和 Vitest/Jest 集成(视频即将推出!),并发现它具有三个主要优点:

每个测试用例的特定评估逻辑。

如果您想评估应用程序的特定部分,则将示例和评估器定义为测试用例比使用 evaluate() 更灵活和直观。例如,当测试可以访问多个工具的代理时,您需要测试模型调用每个工具的能力。但是,评估两个工具的方式可能完全不同,这使得定义全局评估器函数非常繁琐。借助新的测试集成,您可以在每个测试用例中拥有具有自定义评估逻辑的单独测试用例。要查看实际效果,请查看我们的 教程

实时本地反馈。

测试框架提供有关测试状态的实时反馈,这使得在进行过程中更容易发现和修复问题。当您在本地迭代应用程序(包括模拟应用程序的各个部分)并需要快速测试评估时,这种快速反馈循环非常有用。

CI 管道集成。

在 CI 管道中运行评估有助于尽早发现回归。测试框架自然支持在 CI 工作流程中定义通过/失败标准和引发断言错误。

在接下来的几周内,我们将发布一个 Github Action,以使配置特别容易。

试用一下!

我们很高兴分享我们使用 Pytest 和 Vitest/Jest 集成运行评估的新方法!访问我们的开发者教程和实践指南 (Python, TypeScript) 以开始使用,并查看我们的视频演练 (Python, TypeScript)。

如果您有反馈或功能请求,请通过 LangChain Slack 社区 或在 GitHub 上打开问题,让我们知道您的想法。如果您还不是 Slack 社区的一员,请在此处注册 here