大多数智能体的开发都以同样的方式开始。你写一个提示词,手动试几个问题,调整,再试一次。这在初期很有效,那时智能体的错误很明显,改进也容易看出来。但随着时间推移,越来越难判断你最近的改动是让智能体变好了还是变差了。

评估驱动开发(EDD)通过提供一套一致的指标来解决这个问题,让你可以衡量智能体的表现并指导决策。在 LangChain,我们在构建最先进的智能体时就使用了 EDD(可以看看我们关于IssueBench的博客),并将其归纳为三步流程:

  1. 定义你的智能体什么样才算好。
  2. 在构建之前先写评估。
  3. 做出改动并进行比较。

在这篇博客中,我们将逐步讲解这一流程,以便你的团队能将其应用到自己的智能体开发过程中。

什么是评估驱动开发?

评估驱动开发的名称和概念借鉴自测试驱动开发(TDD),即先写一个失败的测试,再写能通过它的代码。智能体与传统代码不同,因为它们是非确定性的:

  • 输出会变化。同一个问题每次运行可能得到不同的答案。
  • 不止一个答案可能是对的。两个自然语言结果即使措辞不同,也可以都被认为是正确的。
  • 智能体自己选择步骤。它可能调用了一个不需要的工具,或者跳过了一个本该使用的工具,却仍然得出正确答案。

因此,智能体需要一种不同类型的测试。评估(eval)通过在一组示例上对智能体的运行进行打分来处理这一点。评估比传统测试更灵活。你可以对智能体运行的任何方面打分,也可以使用其他 LLM 来打分(LLM 作为评判者)。

借助 EDD,与 TDD 类似,这些评估成为你对成功的定义,你朝着它们构建你的智能体。

第 1 步:定义你的智能体什么样才算好

EDD 的第一步是定义你的智能体应该做什么。然后,列出它为实现这一目标需要处理的行为。这些行为可以是诸如回答知识库中的问题、代表用户执行操作,或知道何时提出后续问题等任务。写下每个行为的需求。这些需求共同构成你的智能体的产品需求文档(PRD)。

EDD 一次只针对一个行为。这为你提供了一个狭窄的爬坡目标,避免一项改动干扰另一项改动的效果。你选择的行为通常来自你即将构建的功能,或你在智能体过往运行中发现的失败。在编写代码之前,定义一个简短的规格说明会很有帮助,涵盖:

  • 该行为应在何时发生
  • 智能体应该做什么
  • 智能体不应该做什么
  • 你的 PRD 中哪些行为必须保持正常工作

规格说明应足够明确,以便判定为通过或失败。

假设我们有一个内部文档智能体,员工会向它查询公司信息。它有两个工具:search_docs用于内部文档,以及web_search用于其他所有情况。我们计划添加一个create_ticket工具,以便它可以提交 IT 工单。我们的规格说明如下:

  • 何时:用户报告某物损坏,例如“我的笔记本电脑无法连接 VPN。”
  • 执行:调用create_ticket。
  • 不要:为操作类问题提交工单,例如“我如何连接 VPN?”
  • 保持完好:search_docs和web_search仍然处理它们今天处理的问题

我们将依据这些准则进行评估,即使智能体目前还不具备该能力。

Docs agent tool routing: search_docs, web_search, and the planned create_ticket tool, each with an example input

当前文档智能体的示例实现,以及我们计划添加的工单工具。每个工具都有一个可调用它的示例输入。

第 2 步:在构建之前编写评估

构建数据集。数据集是测试用例(称为“示例”)的集合,这些用例包含输入,以及可选的参考“黄金”输出。输入的最佳来源之一是真实的智能体运行记录。用户会以你无法预料的方式表达,他们的请求包含你可能想不到要自己编写的边缘情况。如果你的示例看起来像真实流量,那么评估中的好分数更有可能意味着生产环境中的良好行为。在LangSmith,你可以直接把真实的智能体运行记录加入数据集。如果还没有任何运行记录,常见的替代方案是从零开始构建示例。

对于我们的文档智能体来说,少量示例就足以起步。在数据集中,我们会包含工单请求,以及那些仍应转给search_docs和web_search的问题,这样现有行为也被纳入考虑。

Docs agent dataset: example input questions paired with the tool the agent should call

每个示例将一个输入问题与参考输出配对,即智能体应调用的工具。

你还可以扩展数据集,覆盖它尚未包含的情况。假设我们没有多少沮丧用户的示例。我们可以把现有示例交给 LLM,让它改写成更愤怒的消息,然后检查智能体是否仍调用正确的工具。

编写评估器。有了数据集后,你可以把 PRD 中对"好"的定义转化为测试或评估,为智能体在你的示例上的表现打分。编写评估器时,要选择最适合你的测试的评分方式。当检查是确定性的,使用代码评估;当检查不是确定性的,使用LLM 作为评判者。你想看智能体的最终答案、它为达成目标所采取的动作,还是完全不同的东西?对于我们的文档智能体,我们想查看它调用的工具,以验证它在每种情况下都使用了正确的工具。工具选择很容易用代码检查,所以我们可以用代码评估来检查每个示例是否调用了其参考输出中列出的工具。

Code evaluator that passes an example when the agent calls the tool in its reference output

如果智能体调用了其参考输出中的工具,则该示例通过。

我们的文档智能体是一个带有几个特定功能的简单聊天机器人。随着智能体变得越来越复杂,你的评估套件可能需要有所不同。如果你的智能体在环境中做出更改,比如沙箱中的编码智能体,仅检查几个工具调用和最终响应并不足以说明问题。你需要检查智能体在系统中留下的状态,而且每次运行都需要一个干净的环境,以免一次运行影响下一次。Harbor是一个开源框架,用于在此类任务上运行智能体,它也是Terminal-Bench等行业基准测试背后的运行框架。每个任务都在自己的沙箱中运行,并附带一个测试脚本,对最终状态进行评分。

针对 Terminal-Bench 这类基准测试做优化,遵循的是与 EDD 相同的循环。你先编写任务和测试,跑一个基线,然后在修改智能体的过程中对比实验。当我们构建LangSmith Engine时,我们用真实和合成的智能体运行记录自建了一个基准测试,并把它作为 Harbor 评测来运行。我们也用 Harbor 来对 Deep Agents 做基准测试。它能告诉我们,是否可以削减 harness 的某些部分(比如待办清单中间件或系统提示词的部分内容),而不会让智能体变差。

跑一个基线。无论你选择如何构建数据集或评估器,你都可以让智能体跑遍每个示例,并把结果作为实验记录到 LangSmith 中。一次实验就是让你的智能体在数据集上完整运行一遍,其中每个示例的输入、输出和评估器得分都保存在同一条记录里。拥有一个集中式系统,可以让你把不同智能体版本的结果相互对比。在对智能体做任何修改之前,先跑一个基线实验作为对比基准很重要。

在我们的文档智能体上,数据集里的工单示例会得到失败分数,因为该工具还不存在。其他示例则记录了已经正常工作的部分,这样你之后就能判断添加新工具是否会破坏其他东西。

第 3 步:做出修改并对比

现在,我们构建create_ticket工具并运行同样的评测。

请记住,EDD 涉及朝着评测“爬山”。爬山意味着重复改进智能体的过程(评估、对比和调整),从而建立一个有客观改进证据的明确循环。对智能体的每一次后续修改都会有自己的实验,每个实验都与上一个实验对比以验证进展。在 LangSmith 中,你可以通过同时选中两个实验来打开对比视图。它会并排显示每个示例的得分,让你看到哪些改进了、哪些退步了。

在比较实验时,重要的是不仅要关注平均分的变化,还要留意任何回归或对已有正常行为的覆盖。在我们的文档智能体中,工单示例现在通过了。但"How do I connect to the VPN?"现在会提交工单,而不是搜索文档。平均分上升了,但有一个用例坏了。

Experiment comparison: the ticketing tool raises the average score while the VPN how-to question regresses

加入工单工具提高了平均分,但"How do I connect to the VPN?"现在失败了。

查看 trace 是理解评估为何失败的最佳方式。trace 是对智能体一次运行的记录,包括每一次模型调用和工具调用。使用你选择的框架构建的任何智能体都可以在 LangSmith 中进行追踪。更多信息请查看我们的追踪快速入门。

当我们打开最新实验中失败的 trace 时,我们看到工具描述中写着要对"VPN 和笔记本电脑问题"使用create_ticket,因此任何提到 VPN 的问题都会匹配。这覆盖了基线中原本正常的行为。

Trace of the failing run showing a too-broad create_ticket tool description

trace 向我们显示工具描述过于宽泛,因此智能体对一个文档本可以回答的问题提交了工单。

解决方案是把工具描述收窄到需要 IT 采取行动的请求。实施这一改动后,我们再次运行评估。现在,每个示例都通过了。

Experiments over time: a tighter tool description gets every example passing

更精确的工具描述让每个示例都通过了。

在小数据集上拿到满分是个好迹象,但 100% 并不总是正确的目标。我们是在看着这些具体示例的情况下调整工具描述的。如果你不断调整智能体直到每个示例都通过,它可能开始对你的数据集过拟合,而不是你真正关心的行为。它能通过你的评估,却仍然会漏掉那些以你的示例未覆盖的方式表述的请求。要检查这一点,请保留一组你在迭代过程中不去查看的留出示例。如果你调优所用的示例上分数上升,但在留出集上持平,那你很可能过拟合了。

多步智能体可能在任意一步失败,因此 LangSmith 让你可以评估的不只是最终答案。你可以对单个步骤、完整的工具调用序列,或按线程分组的多轮对话进行评分。

在发布过程中持续运行 EDD

自动化你的评估

当你开始更快地部署变更时,很容易跳过运行评估或丢失对旧示例的追踪。通过将自动化评估加入你的工作流,你可以防止未经测试的变更触达用户,并在生产前捕获回归。

创建评估套件的最佳策略取决于你的具体设置。快速的代码检查可以阻止一次糟糕的合并,而带有 LLM 评判器的较慢评估可以在夜间或发布前运行。使用数据集拆分,在迭代时只运行你正在处理的行为的示例,然后在发布前运行完整集合。

LangSmith 可与pytest和Vitest集成,因此你的评估可以与代码放在一起,并在每次拉取请求时运行。每次运行都会记录为一个实验,因此你可以在与其他所有内容相同的地方查看结果。

在生产环境中评估

在部署你的智能体之前,你的数据集可能只覆盖了你在开发中想到要测试的用例。在生产环境中,你可以使用以下评估方法标记其余部分:

  • 在线评估器在实时追踪数据流入时对其进行评分。你可以将代码检查和LLM 评判器附加到追踪项目,并对一部分流量进行采样以管理成本。
  • 标注队列让你的团队手动审查被标记的运行。它们也是你在依赖 LLM 评判器之前,检查其是否与人类一致的方式。
  • 仪表盘可以随时间追踪评估分数、反馈、成本和延迟,因此你可以看到一次发布是让事情变好还是变坏。
  • 洞察将生产追踪数据分组为集群,以便你可以在数千次运行中发现新的模式。
  • LangSmith Engine更进一步,将问题集群转化为带有断言的数据集示例,供你审查。

我们还发布了我们用于 Deep Agents 的评估在其仓库中,因此你可以看到一个真实的生产评估套件。

常见错误

  • 将成功定义得过于宽松。在前期做繁重的工作,清晰地定义成功,以便评估可以轻松地评分通过或失败。“响应是有帮助的”不是一个定义良好的指标。
  • 在修复之后编写评估。一旦你看到了新的输出,就很容易编写一个按设计通过的评估。先编写它,并确认它在当前损坏的版本上失败。
  • 只检查平均分数。平均分的提升可能掩盖了出现退化的样本,或覆盖了良好行为的改动。在决定保留某项改动之前,请逐一审查每个样本。
  • 添加你并不需要的评估。每个评估都会把智能体推向某个方向。把每个评估与一项具体的改动绑定起来,比如你正在修复的某个失败,或你即将构建的某个功能。
  • 信任一个未经检验的评判器。先把你的 LLM 评判器与人工评分做对比。向它提范围很窄的问题,并使用通过或失败,而不是含糊的 1 到 10 分制。

关于构建智能体评估的更多信息,请查看这场关于以下主题的网络研讨会Agent Evals 101以及我们免费的LangChain Academy 上关于 LangSmith 的课程!

延伸阅读: