跳至主要内容

LangSmith 中调试与测试 LLM 的入门指南

了解 LangSmith 如何为 AI 应用优化 LLM 测试与调试。通过真实示例提升质量保障并简化开发流程。
· 12分钟 阅读

使用 AI 探索

我最近发现了一个 AI 平台,可以从任何类型的媒体中自动生成抽认卡。由于我喜欢用间隔重复来记忆概念,我立刻上传了我的XGBoost 教程,并让 AI 基于它生成 50 张抽认卡。

但结果让我很失望。大多数问题都太简单,而且几乎没有涉及代码语法。更离谱的是,有 10% 的问题是完全相同、脱离语境的——“法国的首都是哪里?”。

这类错误表明,该 AI 服务生成的输出缺乏恰当的评估与验证。也许他们并不知道 LangSmith。

LangSmith 是一个完善的平台,用于测试、调试和评估 LLM 应用。其中,也许最重要的功能就是对 LLM 输出进行评估与性能监控。在本教程中,我们将通过实际演示来了解这一框架,并学习如何在您自己的项目中应用相关技术。

让我们开始吧!

为什么选择 LangSmith?

如上所述,LangSmith 是一个对大型语言应用进行评估的综合平台。以下是它的部分功能以及能够带来的实际收益:

快速上手

开发者只需几分钟就能开始在 LangSmith 上做实验,而不是几个小时。这意味着一个小团队或独立开发者可以在一个周末完成 AI 应用的原型开发,并有望在周一上线付费服务。

质量保障

借助严格的评估工具,公司可以避免因问题百出的 AI 发布而招致的尴尬与成本(就像我在引言中举的例子)。LangSmith 允许您根据内置标准检查 LLM 的输出,如是否有帮助、是否连贯、是否存在厌女倾向,或者使用自然语言自定义评估,例如“输出内容是否陈词滥调?”又或者在抽认卡生成的场景中,“卡片是否包含一个与编码相关的问题?”。

实时监控与可视化

LangSmith 使用 trace 记录 LLM 运行的几乎所有方面。其中包括延迟、token 数、运行成本以及各类元数据。Web UI 允许您基于错误率、延迟、日期,甚至使用自然语言按文本内容对运行进行快速筛选。这意味着,如果一个 AI 导师在面向真实学生的答复中开始出现异常,您可以在几小时内推出修复。

与 LangChain 集成

LangChain 是 LangSmith 的母框架,专注于 LLM 的开发阶段。它提供模块化的软件设计来将多个 LLM(代理)串联起来,并与其他 API(如 YouTube、Google 搜索等)集成。LangSmith 则是点睛之笔,利用强大的评估与监控工具,确保基于 LangChain 构建的原型按预期运行。

查看我们的 LangChain 构建 LLM 应用教程,进一步了解 LangChain。

数据集

LangSmith 的另一大亮点功能是数据集。它们可用于在部署前,基于一组标准化示例来改进 LangChain 的链、代理或模型。比如,我们可能有一个包含两列的 CSV 文件——以特定格式编写的抽认卡问题与答案。

通过将此文件转换为参考数据集,我们可以让 LLM 使用前述的质量保障指标来评估自身输出。

接下来,我们将通过示例逐一了解这些功能。

LLM 应用开发工作流

在机器学习应用开发中,您会采集数据、训练、微调、测试并部署模型——步骤明确。而在 LLM 应用中,您通常从供应商提供的现成模型开始。微调?那可能很昂贵。因此,您会把大量精力放在打磨合适的提示上——关键在于向 LLM 应用提出正确的问题。就像训练一个优秀的 ML 模型需要大量数据一样,这里则需要大量提示来进行试验。

不过,提示处理的是进出系统的文本,而非数字。因此,传统的误差或准确率衡量方式,如 MSE 或交叉熵,并不适用。另外,要是靠人工阅读每条输入输出进行评估——当您有成千上万条提示要评估时,这会耗费很多天。

因此,您需要一个专注于高效创建和测试提示的工作流,以便在不被繁琐的人工检查所拖累的情况下评估 LLM 应用的表现。大致流程如下:

1. 开发

在这个阶段,您将使用 LangChain 等框架为应用搭建基石原型。以抽认卡生成器为例,您的原型可能会在所选供应商的 LLM 之上包含多个组件。例如,您可能需要将它串联到:

  • 检索器:搜索引擎 API、网页爬虫
  • 文档加载器:文件输入——PDF、文本、CSV、JSON、剪贴板、YouTube、搜索等
  • 聊天加载器
  • 向量存储
  • 嵌入模型
  • 回调

等等(可在 LangChain 文档此页面查看可添加的组件类型)。不过,您也可以通过使用 LangChain 提供的现成链来大幅缩短开发时间,以应对常见的高层任务。

2. 生产化

在这个阶段,您将尽可能多地对应用进行场景测试。这意味着要确保每个新增组件运行良好、串联合理,并能产生一致且高质量的输出。

鉴于 LLM 的非确定性(相同输入不一定产生相同输出)以及新增组件的复杂性,您会在这个阶段投入最多时间。而 LangSmith 的唯一目标就是尽可能缩短这段时间。我们将在整个教程中进一步讨论。

3. 部署

当您的应用可行后,您可以将其部署为 REST API。REST API 基本上会把现有的 LangChain 链或代理转换为他人可以通过 HTTPS 发送请求来与您的 AI 模型交互的链接。接下来,您将为您的服务构建用户界面,比如桌面应用,或者更常见的是网站。

目前,还没有非常简便的方式,但 LangChain 的开发者即将发布 LangServe,并把它集成进 FastAPI(这会非常酷)。可在文档此页抢先了解。

现在,终于可以开始动手使用 LangSmith 了。

LangSmith 平台概览

我们先来了解一下 Web UI。可通过 smith.langchain.com 访问。要使用平台,您需要注册并通过候补名单的审核,因为目前仍处于封闭测试阶段。

但一旦进入,落地页看起来会是这样:

image2.png

两个主要部分是项目与数据集及测试,这两个部分都可以通过 Python SDK 操作。平台还有部署与标注队列的标签页,但它们超出了本文的范围。

设置 LangSmith Python SDK

通过 Python SDK 管理 LangSmith 中的项目要容易得多,该 SDK 通过 API 密钥连接到平台。

要获取密钥,请在平台上点击钥匙图标并将其妥善保存。然后,在一个新目录中初始化新的虚拟环境,创建一个 .env 文件,并在其中粘贴以下两行:

LANGCHAIN_API_KEY="LangSmith-API-key"
OPENAI_API_KEY="Your-OPENAI-key"

接着,在终端中运行以下命令以安装 LangSmith 和用于读取环境变量的 python-dotenv:

python-dotenv to read environment variables:
pip install -U langsmith
pip install python-dotenv

现在,开始写代码:

import warnings

from dotenv import find_dotenv, load_dotenv

warnings.filterwarnings("ignore")

我们导入 find_dotenv 与 load_dotenv 函数来读取环境变量,并使用 os 进行设置:

import os

load_dotenv(find_dotenv())
os.environ["LANGCHAIN_API_KEY"] = str(os.getenv("LANGCHAIN_API_KEY"))
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"

将 LANGCHAIN_TRACING_V2 设为 True 可启用 tracing(日志记录),这对于调试 LLM 至关重要。随后,我们初始化一个客户端以与 LangSmith 平台通信:

from langsmith import Client

# Initialize a client
client = Client()

client 提供了在 LangSmith 中操作项目与资产的高级命令。我们要用的第一个命令是创建新项目:

import uuid

# Create id
uid = uuid.uuid4()
# Create a unique name
PROJECT_NAME = "flashcards-generator-" + str(uid)
# Create the project
session = client.create_project(
   project_name=PROJECT_NAME,
   description="A project that generates flashcards from user input",
)

当 create_project 成功运行后,您可以在 Web UI 的 Projects 部分看到该项目:

image12.gif

接着,我们需要通过另一个环境变量将新项目设为默认项目:

os.environ["LANGCHAIN_PROJECT"] = PROJECT_NAME

现在,我们的项目需要一个 LLM。我们将选择 GPT-3.5 turbo,因为它更便宜,但您也可以使用 langchain 支持的许多其他模型。OpenAI 模型通过 ChatOpenAI 类进行初始化。

from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI()  # Uses gpt-3.5-turbo by default

来进行我们的第一次调用:

llm.invoke("Hello, chatty, how you doin' today?")

AIMessage(content="Hello! I'm an AI language model, so I don't have feelings, but I'm here to help you. How can I assist you today?")

如果您快速在 UI 中查看该项目,会看到上述运行已被 trace(记录):

image7.png

在设置环境变量与客户端时,我们已自动启用日志记录。正如您所见,我们已经获得了不少关于该次运行的元数据。

image14.gif

再多运行几个提示:

message = llm.invoke("Do you know how to generate flashcards?")

message.content[:50]

'Yes, I can help you generate flashcards. There are'

我们已经可以构建一个基础的文本摘要器了。来对上一次运行的输出做个总结:

prompt = f"Summarize this text: {message.content}"

summary = llm.invoke(prompt)
summary.content

'The text explains different methods for creating flashcards. These methods include writing on small index cards, using online flashcard generators, utilizing flashcard apps on mobile devices, and using word processing software like Microsoft Word or Google Docs. It emphasizes the importance of consistency in format and organization for effective studying.'

很好,现在是时候创建我们的第一个数据集了。

在 LangSmith 中创建未标注数据集

正如我在“LLM 应用开发工作流”一节中提到的,您很可能需要创建或收集成千上万条提示来评估 LLM 模型、链或代理。因此,像上面那样逐条运行并非最佳实践。

为此,LangSmith 提供了三种类型的数据集:

  • 键值(kv)——默认:将输入定义为任意键值对。适用于评估需要多输入或返回多输出的链和代理。
  • LLM 数据集(llm):输入与输出以 LLM 的“补全样式”定义——输入字符串,输出字符串。
  • 聊天数据集(chat):由 LLM 聊天转换而来,使用结构化输入与序列化消息定义。

首先,我们来看看如何创建一个没有输出的键值数据集。我们将使用 client 的 create_dataset 函数:

create_dataset function of the client:
dataset_name = "deep_learning_fundamentals"

# Creating a blank dataset
dl_dataset = client.create_dataset(
   dataset_name=dataset_name,
   description="A deck containing flashcards on NNs and PyTorch",
   data_type="kv",  # default
)

现在,让我们添加三个输入,每个都要求 LLM 生成一张抽认卡:

# Storing only inputs into a dataset
example_inputs = [
   "Generate a single flashcard on backpropagation",
   "Generate a single flashcard on the use of torch.no_grad",
   "Generate a single flashcard on how Adam optimizer",
]

for ex in example_inputs:
   # Each example input must be unique
   # The output is optional
   client.create_example(
       inputs={"input": ex},
       outputs=None,
       dataset_id=dl_dataset.id,
   )

如果您打开 UI 的数据集标签页,会看到每条提示都列出,输出为 NULL:

image8.gif

现在,让我们用一行代码通过 run_on_dataset 函数运行所有提示:

from langchain.smith import run_on_dataset

results = run_on_dataset(
   client=client,
   dataset_name=dataset_name,
   llm_or_chain_factory=llm,
   project_name="unlabeled_test",
)

运行结束后,它会显示在数据集页面。如下所示:

image3.gif

我们刚刚在一个未标注数据集上进行了测试运行——这个数据集包含示例提示,但没有示例输出。测试只是对每条提示生成了响应,但没有进行任何评估。不过我们希望执行一些基本检查,比如“输出是否有帮助?”或“回应是短还是长?”

LangSmith 允许我们使用内置评估器来进行这些检查。

在 LangSmith 中评估 LLM

要使用我们数据集中的三条提示运行 conciseness(简洁)和 coherence(连贯)评估,我们可以使用 RunEvalConfig 类:

from langchain.smith import RunEvalConfig

# List the eval criteria
eval_config = RunEvalConfig(
   evaluators=[
       RunEvalConfig.Criteria("conciseness"),
       RunEvalConfig.Criteria("coherence"),
   ]
)

上面我们在一个名为 evaluators 的列表中定义了两个标准。将这些评估器传递给 run_on_dataset 函数的 evaluation 参数:

results = run_on_dataset(
   client=client,
   dataset_name=dataset_name,
   llm_or_chain_factory=llm,
   evaluation=eval_config,
   project_name="criteria_test",
)

run_on_dataset 是一个很实用的函数,它使用提供的 LLM 运行数据集中的所有提示,并可在运行时执行任意类型的评估。其结果会显示在每个数据集的专属页面上:

image9.gif

这一次,每条提示都有连贯与简洁的指标。在页面底部,您还会看到每个指标的平均分。

要查看所有内置标准的列表,请运行以下代码片段:

from langchain.evaluation import Criteria

list(Criteria)

[<Criteria.CONCISENESS: 'conciseness'>,
<Criteria.RELEVANCE: 'relevance'>,
<Criteria.CORRECTNESS: 'correctness'>,
<Criteria.COHERENCE: 'coherence'>,
<Criteria.HARMFULNESS: 'harmfulness'>,
<Criteria.MALICIOUSNESS: 'maliciousness'>,
<Criteria.HELPFULNESS: 'helpfulness'>,
<Criteria.CONTROVERSIALITY: 'controversiality'>,
<Criteria.MISOGYNY: 'misogyny'>,
<Criteria.CRIMINALITY: 'criminality'>,
<Criteria.INSENSITIVITY: 'insensitivity'>,
<Criteria.DEPTH: 'depth'>,
<Criteria.CREATIVITY: 'creativity'>,
<Criteria.DETAIL: 'detail'>]

为未标注数据集编写自定义评估器

当然,并非所有 LLM 用例都能用基础评估器检查。比如,目前没有评估器来判断一张抽认卡是否包含编码问题。因此,我们最好自己定义:

eval_config = RunEvalConfig(
   evaluators=[
       RunEvalConfig.Criteria(
           {"has_code": "Does the card contain a code syntax question?"}
       ),
       RunEvalConfig.Criteria(
           {
               "is_vague": "Is the front of the flashcard vague, meaning it hasn't enough context to answer?"
           }
       ),
   ]
)

要用自然语言传入自定义标准,我们只需将 {“criteria_name”: “待检查条件”} 传给 Criteria 类。上面我们创建了两个额外的评估器,因此 LangSmith 会在数据集提示产出的输出之上,再运行两条额外的提示:

# Run the evaluation
results = run_on_dataset(
   client,
   dataset_name,
   llm,
   evaluation=eval_config,
   project_name="custom_criteria_test",
)

如果您查看这次运行,会看到我们定义的自定义标准显示在每条提示之下。将鼠标悬停,还可以看到 LLM 给出的推理:

image6.png

image10.png

查看所有提示的评估结果后,您会发现我们的标准并未如预期那样评估。因此,需要注意的一点是——您还需要对评估标准本身进行提示工程,以确保它们检查的是正确的内容。

创建标注数据集

有时,您可能决定创建一个包含提示及期望输出的数据集,即标注数据集。您可以用多种格式创建标注数据集,但也许最常见的是 CSV 文件。比如,下图是我用 ChatGPT 生成的一个文件,包含了五个关于 PyTorch 语法的问题:

image4.png

要据此创建数据集,我们可以使用 upload_csv 函数:

dataset_name = "PyTorch code syntax"

csv_path = "data/pytorch_code_syntax_flashcards.csv"
input_keys = ["front"]
output_keys = ["back"]

csv_dataset = client.upload_csv(
   csv_file=csv_path,
   input_keys=input_keys,
   output_keys=output_keys,
   name=dataset_name,
   data_type="kv",
)

该函数有三个必需参数:CSV 路径,以及输入/输出列的名称。上传完成后,数据集会出现在 UI 中:

image11.png

我们也在这个数据集上运行上一节定义的自定义标准:

eval_config = RunEvalConfig(
   evaluators=[
       RunEvalConfig.Criteria(
           {"has_code": "Does the card contain a code syntax question?"}
       ),
       RunEvalConfig.Criteria(
           {
               "is_vague": "Is the front of the flashcard vague, meaning it hasn't enough context to answer?"
           }
       ),
   ]
)

# Run the evaluation
results = run_on_dataset(
   client,
   dataset_name,
   llm,
   evaluation=eval_config,
   project_name="custom_criteria_test_csv",
)

如果您前往数据集页面查看此次运行,我们可以看到每个自定义标准的平均分:

image5.png

评估标注数据集

基于自然语言编写的内置与自定义评估器,多用于未标注数据集。对于像我们上传的 CSV 这样的标注数据集,LangSmith 提供了更全面的评估器,用于衡量对提示的响应是否正确:

  • context_qa(上下文问答):在评估正确性时,使用样例输出作为上下文
  • qa(问答):指示 LLM 使用参考输出直接将响应判为“正确”或“错误”
  • cot_qa(思维链问答):与 context_qa 类似,但该评估器会在得出结论前强制 LLM 使用思维链推理。

我们来在示例上试试最后一个:

eval_config = RunEvalConfig(evaluators=[RunEvalConfig.CoTQA()])

results = run_on_dataset(
   client,
   dataset_name,
   llm,
   evaluation=eval_config,
   project_name="cotqa_test",
)

CoTQA 标准会返回一个名为 Contextual accuracy 的分数,如下方 GIF 所示(UI 中同样可见):

image1.gif

请访问 LangSmith 文档中的LangChain 评估器部分,进一步了解更多评估器信息。

结语

如果您读完本教程后的直观感受是:LangSmith 其实就是一个复杂的提示工程工具,那么您说对了!目前,LangSmith 是确保 LLM 的指令与输出符合您预期的最佳框架。

您也应该意识到,构建生产级的 LLM 应用需要投入大量精力。因此,在进行 LLM 项目时,请使用 LangSmith 以获得更顺畅的体验。

如果 LangSmith 中的一些概念让您感到困惑,可能是缺少一些 LangChain 的基础。以下是一些相关资源:

Bexruz (Bex) Tuychiev

作者

Bexruz (Bex) Tuychiev

我是一名数据科学内容创作者,拥有超过 2 年的经验,并在 Medium 上拥有较大的读者群。我喜欢用略带讽刺的笔调撰写有关 AI 和机器学习的深度文章——毕竟总得想点办法让这些内容不那么枯燥。我已发表 130 多篇文章,并制作了一门 DataCamp 课程,另有一门正在筹备中。我的内容已被超过 500 万人次阅读,其中有 2 万人在 Medium 和 LinkedIn 上成为关注者。 

主题