AB
AiBoss
チュートリアル

LangChain

チュートリアル

用 LangChain 与 LangSmith 落地评估驱动开发(EDD)

评估驱动开发(EDD)把「改完到底变好还是变坏」从肉眼判断变成可复现的对比:先为一个具体行为写出可打分的样例,改动前后跑同一份数据集,逐例比较而不是只看平均分。本文讲清 EDD 的三段流程、代码评估器与 LLM-as-judge 的分工、基线留存与 held-out 样例的做法,并给出小团队从零开始的最小落地步骤与 LangSmith 的保持期、配额等成本前提。

LLM 智能体改了一版之后,到底是变好了还是变坏了?样例只有几条时还能靠肉眼扫一遍,样例一多、输出又每次不同,肉眼判断就失效了。评估驱动开发(Eval-Driven Development,简称 EDD)针对的正是这个问题:先把「期望的行为」写成可打分的样例,再在改动前后用同一批样例测量,逐例比较差异,而不是只看一个平均分。本文面向已经开始维护智能体、但改动质量只能靠感觉判断的团队,讲清 EDD 的流程、评估器的选择、基线留存与成本前提,并给出小团队可以照着做的最小落地路径。文中涉及 LangSmith 的套餐、配额与保持期等易变信息,请以官网当前信息为准。若你还需要对照站内的工具条目,可先看 LangChain 这一页。

准备工作

EDD 的流程本身不依赖某个具体产品,但要把「逐例比较」这件事做得省力,通常需要一个能存数据集、跑实验、展示差异的观测与评估平台。下面按「先能跑起来」的顺序列前置条件。

概念与术语

先把几个词对齐,后面所有步骤都建立在这套词汇上:

  • dataset(数据集):一组样例的集合,每条样例包含输入和参照输出(也就是期望的答案)。
  • experiment(实验):让智能体的某一个版本,在一份数据集上完整跑一遍所得到的结果记录。
  • evaluator(评估器):给输出打分的机制。分两类,一类用代码写,一类让 LLM 来打分(LLM-as-a-judge)。
  • online evaluation(在线评估):在生产环境的执行记录(trace)中自动抽取一部分来打分。
  • annotation queue(标注队列):把需要人工确认的执行记录堆在一起,等人来看。
  • feedback(反馈记录):一条打分结果,通常带一个名字,例如 pass。

账号与套餐前提

如果使用 LangSmith 承载数据集与实验,需要先确认套餐与配额。以下为某一时点的公开信息,实际以官网当前信息为准:

项目内容
Developer 套餐每席位每月 0 美元,1 个席位,含每月 5,000 条 base 执行记录,超出部分按量计费
Plus 套餐每席位每月 39 美元,席位数量无上限,含每月 10,000 条 base 执行记录
base 保持期14 天
extended 保持期180 天,费用高于 base
实验执行记录默认使用 extended
新建在线评估器默认把目标执行记录提升为 extended,可主动关闭
数据集不会被删除

另外,无付费的 Developer 套餐在执行记录发送量上有每小时上限(例如 500MB 一类的限制),并且当月超过 5,000 条执行记录后,可能返回 HTTP 429(请求过多)而被拒绝。这个上限针对的是全部执行记录,不是评估专用额度,所以评估跑得勤也会占用它。

本地依赖

若希望把「代码能判定的检查」接进 CI,需要准备一个测试运行器。Python 侧用 pytest,JavaScript 侧用 Vitest 都可以。使用 LangSmith 的 pytest 集成时,需要 langsmith 包 0.3.4 及以上版本。版本号请以实际安装与官方发布信息为准。

pip install -U "langsmith>=0.3.4" pytest

还需要配置访问凭据,让测试运行器能把样例与结果同步到平台。具体环境变量名与取值方式请以平台当前的接入说明为准,不要凭记忆填写。

操作步骤

第一步:一次只锁定一个行为

EDD 的粒度是「一个行为」,也就是「某一类请求下智能体应当如何表现」这一个单位。不要一上来就给整个智能体写一份大而全的评估,那样写出来的条件往往含糊到无法判定。

把选定的行为用四句话写清楚:

  1. 什么时候触发:什么样的请求属于这个行为。
  2. 要做什么:必须完成哪些动作,例如必须调用某个工具、必须给出某个字段。
  3. 不要做什么:明确禁止的动作,例如「只是询问操作步骤时不得创建工单」。
  4. 不能破坏的既有行为:这次改动不允许影响到的其他能力。

这四句话写不出来,说明「好」还没有被定义,此时写评估器只会把模糊变成更贵的模糊。一个反例是把成功条件写成「helpful」——它不是指标,无法判定通过与否。

第二步:造出第一批样例

起步不需要很多。按重要部件分别准备 5 到 10 条「好」的样例是常见起点,把质量做高的样例集以 10 到 20 条作为推荐起始规模;对话类应用的教程里也提到,10 到 50 条就已经能产生价值。样例要包含输入与参照输出两部分,参照输出是后面做逐例比较的依据。

样例的来源应当是真实观察到的失败,而不是想象出来的失败。还没见过的失败类型,先不要急着为它写评估器。

第三步:先写一个代码评估器

能用代码判定的东西优先用代码判定,因为代码判定稳定、便宜、可进 CI。典型可代码化的检查包括:期望调用了哪些工具、调用顺序是否满足约束、输出是否符合某种格式、是否包含禁止出现的内容。

在 pytest 里,这类检查就是普通的 assert。下面是一个结构示意,函数名与参数请按你实际使用的集成方式调整:

import pytest

@pytest.mark.langsmith
def test_no_ticket_for_howto_question():
    result = run_agent("VPN 怎么配置?")
    # 只问操作步骤时,不应创建工单
    assert not any(call.name == "create_ticket" for call in result.tool_calls)

@pytest.mark.langsmith
def test_creates_ticket_for_outage():
    result = run_agent("整个办公室断网了,帮我报修")
    assert any(call.name == "create_ticket" for call in result.tool_calls)

使用 LangSmith 的 pytest 集成时,测试的输入会作为数据集的样例同步上去,测试的通过与否会以名为 pass 的 feedback 记录同步上去。这样本地跑测试和平台上的数据集就保持了一致。

如果某个检查的通过条件暂时写不成断言,可以不在测试里调用 assert,只把分数记录到平台。但要注意:能让 CI 拦住改动的只有测试运行器的失败,所以真正想卡住合并的检查,必须写成 assert。

第四步:改动之前先取基线

这是最容易被跳过、也最不能跳过的一步。在动手修改之前,先用当前版本在同一份数据集上跑一次实验,把结果留下来作为基线。没有基线,改动之后就没有可比的参照物,「变好了」只能靠感觉。

基线实验要保留,不要覆盖。后续每次改动都跑同一份数据集,与基线逐例对比。

第五步:改动之后逐例比较,而不是只看平均分

改动完成后,用同一份数据集再跑一次实验,然后做两件事:

  • 把变差的样例和变好的样例分开列出来,逐条看。
  • 确认平均分的变化不是由少数样例的剧烈波动撑起来的。

只看平均分是常见的失败做法:平均分持平甚至略升,也可能掩盖了某几类请求的明显退化。反过来,平均分小幅下降也可能只是噪声。判断依据应当是逐例的差异,而不是一个汇总数字。

第六步:留出 held-out 样例

用来调参、改提示词的那批样例,不能同时用来证明「改好了」。在小数据集上把全部样例都调到通过,本身就是过拟合的信号。因此要另外留一批不参与调整的 held-out 样例,只在最后用来验证。

需要提醒的是,仅仅把数据分成两份,并不足以保证「没见过的输入也能工作」。有研究指出,当训练数据与 held-out 问题存在语义上的重叠时,held-out 上的准确率最多可以被抬高 22 个百分点。所以 held-out 的样例要尽量与调整用的样例在表述和场景上真正区分开。

第七步:把生产环境的失败变成下一条样例

离线样例有参照输出,而生产环境的执行记录通常没有参照输出。因此需要人工确认的检查,要先把失败案例收回数据集,再在离线环境里做对照。

流程是:在线评估器自动给一部分执行记录打分,失败的记录进入标注队列,由人确认后补进数据集。这样数据集会随着真实失败逐步长大,而不是靠想象扩充。

第八步:谨慎引入 LLM-as-a-judge

当检查对象是「一段没有唯一正确答案的文字质量」时,代码评估器无能为力,这时才用 LLM 打分。使用前必须做一件事:校准,也就是拿 LLM 的判定结果与人的通过/不通过结论对照,看两者是否一致。没有校准过的 judge 不能信。

另外,打分方式建议用通过/不通过两档,而不是 1 到 10 分的量表。两档更容易判定,也更容易与人的结论对齐。

两类评估器的分工可以概括为:

评估方式适合检查的内容
代码评估器期望的工具调用、输出格式等可以写成性质的东西
校准过的 LLM-as-a-judge没有唯一正确答案的文字质量

一个完整示例

下面用一个文档检索智能体的改动,把上面的步骤串起来。这个例子用于说明流程,其中的数值不代表任何实测结论。

背景

智能体原本有两个工具:文档检索和网页检索。现在要给它加第三个工具——创建工单。

1. 写行为规格

  • 什么时候触发:用户报告故障、要求报修、要求跟进时。
  • 要做什么:调用创建工单工具,并在回复中说明已创建。
  • 不要做什么:用户只是询问操作步骤(例如「VPN 怎么配置」)时,不得创建工单。
  • 不能破坏的既有行为:文档检索与网页检索的调用不受影响。

2. 准备样例

围绕这个行为准备约 10 条样例,覆盖三类:应当创建工单的、不应当创建工单的、以及需要先检索再决定是否创建的。每条样例都写上参照输出。

3. 写代码评估器

import pytest

@pytest.mark.langsmith
def test_howto_question_does_not_create_ticket():
    result = run_agent("VPN 怎么配置?")
    assert not any(c.name == "create_ticket" for c in result.tool_calls)

@pytest.mark.langsmith
def test_outage_report_creates_ticket():
    result = run_agent("三楼会议室网络断了,帮我报修")
    assert any(c.name == "create_ticket" for c in result.tool_calls)

@pytest.mark.langsmith
def test_retrieval_still_works():
    result = run_agent("报销流程是什么?")
    assert any(c.name == "search_docs" for c in result.tool_calls)

4. 取基线

在加入创建工单工具之前,用当前版本跑一遍数据集,保留这次实验作为基线。

5. 改动并对比

加入工具后重跑同一份数据集。假设发现一个回归:用户只是询问 VPN 配置步骤,智能体也创建了工单。这就是「不要做什么」被违反。

处理方式是收窄创建工单工具的描述文字,让它只在明确的报修或跟进请求下才被选中,然后再次重跑。此时应当逐例确认:之前失败的那几条现在通过了,同时原本通过的样例没有因此变差。

6. 用 held-out 验证

调整过程中用到的样例已经「被看过」了,不能作为最终证据。用另外一批未参与调整的样例再跑一次,确认改动不是只对调过的那几条有效。

7. 上线后接在线评估

上线后让在线评估器对一部分生产执行记录打分,失败的进入标注队列,人工确认后补进数据集。下一轮改动就有了新的样例。

注意事项

EDD 与测试驱动开发的区别

测试驱动开发是先写一个会失败的测试,再写代码让它通过。EDD 同样把评估放在前面,但 LLM 的输出不保证同一输入得到同一输出:正确答案可能有多个,工具调用顺序不同也可能同样达成目的。因此 EDD 的通过与否不能靠字符串完全匹配,而要拆成两类判定——能用代码写成性质的用代码,没有唯一答案的用校准过的 LLM 打分。

也正因如此,强制某个固定的工具调用顺序是脆弱的判定方式。

「评估先写」有适用边界

「先把评估写出来」这条建议,只在成功条件已经能用文字写清楚的行为上成立。LLM 的失败类型无法事先穷举,所以更稳妥的顺序是:先读实际输出、把错误分类(error analysis),再针对已经发现的错误写评估器。例外是那些一开始就能写成硬约束的条件,例如「不得提及竞争对手」。

同理,在看过真实输出之前就把评分标准固定下来,容易漏掉预料之外的问题。

小幅提升不要急着采纳

智能体基准的分数本身会随执行次数波动。有研究报告,在同一个智能体基准上,单次尝试的正确率(pass@1)在不同执行之间会浮动 2.2 到 6.0 个百分点。这意味着 2 到 3 个百分点的提升,不足以凭一次实验就决定采纳。

另一个相关的算术直觉:如果单次尝试的成功率是 75%,那么连续三次全部成功的概率约为 0.75 的三次方,也就是大约 42%。输出每次都变,一次成功不能说明稳定成功。

常见失败做法

  • 成功条件写得含糊,例如用「helpful」当指标。
  • 先看修改结果,再回头补写评估。
  • 只看平均分,不看逐例差异。
  • 信任没有校准过的 judge。
  • 用 1 到 10 分的量表打分,而不是通过/不通过。

成本与保持期

在 LangSmith 上跑这套循环,会带来数据保持与费用上的条件:实验的执行记录默认使用 extended 保持期(180 天),费用高于 base(14 天);新建的在线评估器默认会把目标执行记录提升为 extended,虽然可以关闭,但保持期与费用都会随之增加。数据集本身不会被删除。

如果执行记录量带来的保持成本不划算,可以收窄在线评估器的适用范围并降低采样率——公开示例中使用的采样率是 0.1,即只评估 10% 的执行记录。套餐价格、包含额度与各项上限请以官网当前信息为准。

什么时候不必上平台

如果不需要逐例对比的可视化界面,本地的测试运行器(例如 pytest)就够用。只有当确实需要「按样例看差异」「把测试结果同步成数据集」「给生产执行记录打分」这几件事时,引入平台才有额外价值。

小团队的最小落地清单

把上面的内容压缩成一份可以照着执行的顺序:

  1. 选一个接下来要改的行为,用「什么时候触发 / 要做什么 / 不要做什么 / 不能破坏什么」四句话写下来。
  2. 为这个行为准备约 10 条样例,并至少写一个能用代码判定的检查。
  3. 在改动之前跑一次,把结果留作基线。
  4. 改完之后,把失败的样例和通过的样例各看一遍,不要只凭平均分决定去留。
  5. 另外留一批不参与调整的 held-out 样例。
  6. LLM-as-a-judge 只用于代码判不了的文字质量,并且先与人的结论对照过再使用。
  7. 只把生产环境真实出现过的失败补进样例集,不要为还没见过的失败凭想象写评估器。

这套流程的价值不在于某个数字,而在于把「改完到底变好还是变坏」从主观判断变成可复现的对比。至于平台侧的价格、配额与保持期,请以官网当前信息为准,并在开始之前就把这部分成本算进去。