1. 为什么传统 TDD 在大模型项目里越来越不够用
如果你最近一年在带团队做大模型相关的功能开发,大概率会有一种共同的体感:测试用例写了一堆,覆盖率报告看着挺漂亮,但上线之后该翻车还是翻车。问题不在于测试写得少,而在于测试的粒度、时机和表达方式,跟大模型应用的真实失效模式对不上。
传统 TDD 的经典循环是 Red-Green-Refactor:先写一个失败的单元测试,再写最小实现让它通过,最后重构。这套方法论在确定性系统里非常有效,因为输入输出是稳定的、可枚举的。但大模型应用的核心特征恰恰是输出不确定、边界模糊、语义正确性难以用断言表达。你没法用assertEquals("你好", response)去验证一个对话系统的回答,因为同一个问题问十遍,可能得到十种措辞不同但都合理的答案。
我在实际项目里踩过最典型的一个坑:给一个知识抽取模块写了 200 多个单元测试,全部通过,结果上线后用户反馈"抽取结果里把公司名识别成了人名"。回头一查,测试用例里覆盖的公司名都是训练集里出现过的,而线上遇到的是新领域的实体。这不是测试写得不够多,而是测试的驱动方式错了——我们是在用"实现视角"写测试,而不是用"行为视角"描述期望。
这就是 Behavior-Driven AI-TDD 要解决的核心问题。它不是把 TDD 换个名字,而是把驱动测试的源头从"函数签名和返回值"换成"业务行为和可观测的语义契约"。关键词里的Behavior-Driven AI-TDD、AI-TDD、软件研发闭环,本质上指向的是同一件事:在大模型时代,我们需要一套新的研发节奏,让"行为定义"成为整个闭环的起点,而不是事后补的验收文档。
这篇文章适合三类人看:一是正在做大模型应用开发、被测试和回归问题折磨的工程师;二是负责研发效能、想重构工作流的 Tech Lead;三是对 AI 工程实践感兴趣、想理解"闭环"到底怎么落地的人。我会从工作流重构的角度,把 Behavior-Driven AI-TDD 的完整链路拆开讲,包括它和传统 TDD 的本质差异、行为契约怎么写、评估集怎么建、CI 里怎么跑、以及我在实际项目中踩过的那些坑。
2. Behavior-Driven AI-TDD 到底改变了什么
2.1 从"断言输出"到"约束行为"
传统 TDD 的核心动作是断言:给定输入 X,期望输出 Y。Behavior-Driven AI-TDD 的核心动作是约束行为:给定场景 S,期望系统表现出某种可观测的行为特征 B。注意这里的措辞差异——不是"输出等于什么",而是"行为满足什么约束"。
举个具体例子。假设你在做一个客服问答机器人,传统测试可能是:
def test_answer(): assert answer("退货政策是什么") == "7天无理由退货"这种测试在大模型场景下几乎必然失败,因为模型可能回答"我们支持7天内无理由退货哦"或者"根据平台规则,您可以在7天内申请无理由退货"。Behavior-Driven AI-TDD 的写法会变成:
def test_refund_policy_behavior(): response = agent.ask("退货政策是什么") assert contains_key_facts(response, ["7天", "无理由"]) assert not contains_hallucination(response, forbidden_facts) assert response.tone == "friendly"这里约束的是关键事实必须出现、禁止事实不能出现、语气符合预期。这三条约束合起来,构成了一个"行为契约"。它不要求逐字匹配,但要求语义上不能跑偏。
2.2 行为契约的三个层次
我在实践中把行为契约分成三层,这个分层对后续的评估集设计和 CI 策略影响很大:
| 层次 | 约束对象 | 示例 | 稳定性 |
|---|---|---|---|
| L1 事实层 | 关键实体、数值、结论 | 必须包含"7天" | 高,可硬断言 |
| L2 语义层 | 意图、逻辑一致性 | 不能自相矛盾 | 中,需模型评估 |
| L3 风格层 | 语气、格式、长度 | 友好、不超过200字 | 低,可容忍波动 |
L1 层可以用规则或小模型做硬校验,成本低、速度快,适合放在每次提交的 CI 里。L2 层需要引入评估模型或者人工标注的参考集,成本高一些,适合放在每日构建或发布前。L3 层波动最大,通常只做抽样检查,不阻塞流程。
这个分层的关键价值在于:它让"测试失败"变得可分级。L1 失败是阻断性的,必须修;L2 失败要人工确认;L3 失败记录但不阻断。没有这个分层,你要么被大量误报淹没,要么放过真正的严重问题。
2.3 为什么"行为驱动"比"测试驱动"更适合大模型
根本原因在于大模型应用的失效模式是语义的,不是逻辑的。传统软件 bug 通常是逻辑分支写错、边界没处理,这类问题用单元测试能精准捕获。但大模型应用的 bug 往往是:幻觉、事实漂移、指令遵循失败、上下文遗忘、格式不匹配。这些问题用传统断言几乎测不出来,但用行为约束可以。
另一个原因是迭代速度。大模型应用经常要换模型、调 prompt、改检索策略,每次改动都可能影响行为。如果测试是绑定实现的,每次改动都要重写测试,成本极高。而行为契约是绑定业务期望的,实现怎么变,契约不变。这就是为什么 Behavior-Driven 的"驱动"二字很关键——它驱动的是研发节奏,不是某个函数。
3. 把行为契约写进研发闭环的完整链路
3.1 闭环的五个环节
一个完整的 Behavior-Driven AI-TDD 闭环,我把它拆成五个环节,每个环节都有明确的输入输出:
- 行为定义:从需求或用户故事出发,写出行为契约(场景 + 约束)
- 契约固化:把契约转成可执行的评估用例,进入版本管理
- 开发驱动:开发过程中以契约为目标,先让 L1 通过,再优化 L2/L3
- 持续评估:CI/CD 中分级执行,L1 阻断、L2 告警、L3 记录
- 反馈回流:线上 badcase 反哺契约库,形成闭环
这五个环节里,最容易做错的是第 1 步和第 5 步。很多团队直接从第 2 步开始,拿现成的测试集当契约,结果契约和业务期望脱节。也有团队做完第 4 步就停了,线上问题没有回流机制,契约库永远不更新。
3.2 行为定义阶段:怎么把需求翻译成契约
这一步是整个闭环的地基。我的做法是用"场景-约束"模板来写,每个契约包含四要素:
- 场景:什么条件下触发(用户输入、上下文状态)
- 期望行为:系统应该表现出什么(关键事实、意图、格式)
- 禁止行为:系统绝对不能出现什么(幻觉、越权、敏感内容)
- 容忍度:哪些波动可以接受(措辞、长度、语气)
举个例子,一个"订单查询"功能的行为契约可能是:
场景:用户询问订单状态,提供了有效订单号 期望行为:返回订单当前状态、预计送达时间,状态描述与后台一致 禁止行为:编造不存在的物流节点、泄露其他用户信息 容忍度:措辞可不同,但状态关键词必须一致
这个模板的好处是,它逼着你在写代码之前就想清楚"什么算对、什么算错"。我见过太多团队,开发完了才讨论"这个回答算不算合格",那时候已经晚了。
3.3 契约固化阶段:评估用例的工程化
契约写完不能只躺在文档里,必须变成可执行的用例。这里有个工程上的关键决策:用什么形式存储契约。我试过三种方案:
- 纯代码:契约写成 pytest 用例,灵活但难维护,非技术人员看不懂
- YAML/JSON 配置:契约写成结构化配置,易读但表达能力有限
- 混合方案:L1 用配置,L2/L3 用代码
实测下来混合方案最稳。L1 的事实约束用 YAML 描述,比如:
- id: refund_policy_001 scenario: "用户询问退货政策" must_contain: ["7天", "无理由"] must_not_contain: ["30天", "收费"] level: L1L2 的语义约束用代码写,因为需要调用评估模型:
def test_refund_semantic_consistency(): response = agent.ask("退货政策是什么") score = eval_model.judge(response, reference="7天无理由退货") assert score >= 0.8这样分层存储,既保证了 L1 的可维护性,又保留了 L2/L3 的表达能力。
3.4 开发驱动阶段:先让 L1 绿,再谈优化
开发阶段的节奏很重要。我的建议是先把 L1 全部跑绿,再动 L2/L3。原因很简单:L1 是硬约束,如果关键事实都错了,优化语气和格式没有意义。
具体操作上,我会在本地跑一个快速评估脚本,只跑 L1,几秒钟出结果。开发过程中反复跑,确保每次改动不破坏事实层。等 L1 稳定了,再跑 L2 看语义一致性,最后抽样看 L3。
这里有个反直觉的经验:不要追求 L2/L3 全绿。大模型输出天然有波动,强行让所有语义评估都过,要么是评估模型太松,要么是你把 prompt 调得过拟合。我一般把 L2 的通过率目标定在 85%-90%,留出合理波动空间。
3.5 持续评估阶段:CI 里的分级策略
CI 集成是闭环能不能跑起来的关键。我的配置策略是:
| 触发时机 | 执行层级 | 阻断策略 | 耗时预算 |
|---|---|---|---|
| 每次 push | L1 | 失败即阻断 | < 2分钟 |
| 每日构建 | L1 + L2 | L1 阻断,L2 告警 | < 30分钟 |
| 发布前 | L1 + L2 + L3 抽样 | 人工确认 | < 2小时 |
这个策略的核心是把成本花在刀刃上。L1 快,每次提交都跑;L2 慢,每天跑一次;L3 最慢,发布前跑。这样既保证了快速反馈,又不会让 CI 变成瓶颈。
3.6 反馈回流阶段:线上 badcase 怎么变成契约
这是最容易被忽略但价值最高的一环。我的做法是建一个badcase 收集管道:线上每次用户反馈"回答不对",就自动记录输入、输出、上下文,进入待处理队列。每周 review 一次,把确认的问题转成新的行为契约。
这个机制的价值在于,它让契约库随业务演进。没有这个回流,契约库会逐渐和真实场景脱节,最后变成一堆过时的测试。我见过一个团队,契约库半年没更新,结果新上线的功能完全没有对应的行为约束,等于裸奔。
4. 评估集建设:Behavior-Driven AI-TDD 最容易被低估的工程
4.1 评估集不是测试集
很多人把评估集和测试集混为一谈,这是个大误区。测试集是验证实现正确性的,评估集是衡量行为质量的。前者关注"对不对",后者关注"好不好"。
在大模型场景下,评估集的建设难度远高于测试集。因为你需要:
- 覆盖真实场景的分布(不是随机采样)
- 包含边界和对抗样本
- 有可靠的参考答案或评分标准
- 能持续更新
我一般把评估集分成三类:核心集(必须全过,对应 L1)、回归集(防止能力退化,对应 L2)、探索集(发现新问题,对应 L3)。
4.2 核心集怎么建:从真实流量里挖
核心集的建设原则是从真实流量里挖,而不是自己编。我的做法是:
- 从线上日志里采样 1000 条真实请求
- 人工标注每条的关键事实和期望行为
- 筛选出 200 条覆盖主要场景的,作为核心集
- 每季度更新一次,替换过时场景
这个过程的成本主要在人工标注,但值得。我试过用模型自动生成评估集,结果生成的样本分布严重偏斜,覆盖不到真实的长尾场景。
4.3 评估模型的选择与校准
L2 层需要评估模型来判断语义一致性。这里有个关键决策:用大模型评还是小模型评。
大模型评估准确率高,但成本高、速度慢;小模型快,但准确率不稳定。我的方案是分层用:核心集用大模型评,回归集用小模型评,并且定期用大模型校准小模型。
校准的方法很简单:每周抽 100 条,同时用大模型和小模型评,对比结果。如果小模型和大模型的一致率低于 90%,就说明小模型需要重新微调或换掉。
4.4 评估集的版本管理
评估集必须版本化,和代码一样管理。我见过团队评估集改了没记录,结果两次评估结果没法对比,完全失去意义。
我的做法是评估集放在独立仓库,每次修改走 PR,记录修改原因和影响范围。评估结果也存档,形成时间序列,这样能看到行为质量的变化趋势。
5. 实操中踩过的坑与应对策略
5.1 坑一:契约写得太细,变成过拟合
最开始我写契约的时候,恨不得把每个可能的回答都列出来。结果契约库膨胀到几千条,维护成本极高,而且模型稍微换个措辞就失败。
后来我调整了策略:契约只约束关键事实和禁止行为,措辞和格式交给容忍度。比如"7天无理由退货"这个事实必须出现,但"7天"还是"七天"、"无理由"还是"无理由退货",都算通过。这样契约数量降了一个数量级,通过率反而更稳定。
5.2 坑二:评估模型本身有偏差
有段时间我发现 L2 通过率异常高,但线上用户反馈还是不好。排查后发现是评估模型太宽松,把很多不合格的回答判成了合格。
解决办法是定期用人工标注校准评估模型。我每月抽 200 条,人工标注后和模型评估对比,计算混淆矩阵。如果发现模型对某类问题系统性偏松,就针对性补充训练样本或调整阈值。
5.3 坑三:CI 跑太慢,开发不愿意等
L2 评估如果每条都调大模型,几百条用例跑下来要半小时以上。开发提交一次等半小时,很快就没人愿意跑了。
我的优化方案是并行化 + 缓存。评估用例并行跑,同时缓存相同输入的评估结果。另外把 L2 拆成"快速子集"(50 条核心用例,5 分钟跑完)和"完整集"(每日构建跑)。这样开发提交时跑快速子集,日常构建跑完整集。
5.4 坑四:契约和实现耦合
有段时间我们的契约直接引用了实现里的函数名和参数,结果重构一次就要改一堆契约。这是典型的耦合问题。
解决原则是契约只依赖外部可观测的行为,不依赖内部实现。契约里出现的应该是用户输入、系统输出、业务概念,而不是函数名、类名、内部状态。这条原则说起来简单,做起来需要纪律,但长期收益巨大。
5.5 坑五:没有回滚机制
大模型应用有个特点:换个模型或调个 prompt,行为可能整体漂移。如果没有回滚机制,一旦新版本行为退化,只能紧急修,压力很大。
我的做法是每次发布都保留上一版的行为评估报告,新版本发布后对比关键指标。如果 L1 通过率下降超过 5%,自动触发回滚。这个机制救过我好几次。
6. 工具链选型与工程化落地建议
6.1 评估框架的选择
市面上评估框架不少,我的选型原则是看它能不能支持分级评估和自定义评估器。因为 Behavior-Driven AI-TDD 的核心就是分级,如果框架只支持单一评估,就不合适。
我实际用过的方案里,比较顺手的是基于 pytest 自建 + 评估模型 API 的组合。pytest 负责用例组织和分级执行,评估模型负责语义判断。这个组合灵活度高,缺点是前期要自己搭一些基础设施。
6.2 契约管理工具
契约管理我建议用 Git + YAML,不要上太重的平台。YAML 易读易 diff,Git 天然支持版本管理和 review。我试过用专门的测试管理平台,结果发现平台和代码库割裂,维护成本反而更高。
6.3 与现有 CI/CD 的集成
集成原则是不改变现有流程,只增加评估环节。开发还是照常提交代码,CI 里增加一个评估 job,分级执行。这样对现有流程侵入最小,团队接受度高。
具体配置上,我会在 CI 里加三个阶段:lint→unit-test→behavior-eval。behavior-eval 里再分 L1/L2/L3,按前面说的策略执行。
6.4 团队协作与角色分工
Behavior-Driven AI-TDD 落地不是一个人的事。我的经验是需要三个角色配合:
- 产品/业务:定义行为契约的业务部分(什么算对)
- 开发:把契约转成可执行用例,维护评估基础设施
- 测试/QA:维护评估集,校准评估模型,分析失败案例
这三个角色缺一不可。我见过只有开发在推的团队,结果契约和业务脱节;也见过只有测试在推的,结果工程化程度不够,跑不起来。
7. 一个完整的落地案例拆解
7.1 背景与初始状态
去年我参与了一个企业知识问答系统的重构。系统基于大模型 + 检索增强,上线三个月,用户满意度一直上不去。排查发现主要问题是:回答里经常出现幻觉、关键信息遗漏、格式不统一。
初始状态下,团队只有传统的单元测试,覆盖的是检索模块和格式化模块,大模型生成部分完全没有测试。每次改 prompt 或换模型,全靠人工抽检,效率极低。
7.2 行为契约的建立过程
我们花了三周时间建行为契约库。第一周从线上日志采样 2000 条真实问答,人工标注关键事实和期望行为。第二周把标注结果转成契约,分成 L1/L2/L3。第三周搭建评估基础设施,接入 CI。
最终契约库包含 180 条 L1、120 条 L2、80 条 L3。L1 覆盖核心事实,L2 覆盖语义一致性,L3 覆盖格式和语气。
7.3 闭环跑起来之后的变化
闭环跑起来后,最明显的变化是迭代速度。以前改 prompt 要人工抽检半天,现在提交后 2 分钟出 L1 结果,30 分钟出 L2 结果。开发敢改了,迭代频率从每周一次提升到每天多次。
第二个变化是问题发现提前。以前是上线后用户反馈才发现问题,现在大部分问题在 CI 阶段就被 L1/L2 拦住了。上线后的 badcase 数量下降了约 70%。
第三个变化是团队协作方式。产品开始参与契约定义,测试开始维护评估集,开发专注实现。角色分工清晰后,整体效率提升明显。
7.4 遇到的阻力和解决
最大的阻力来自开发团队,觉得"写契约太麻烦"。我们的应对是先做减法:不要求一次写全,先覆盖核心场景,跑起来再逐步补充。同时把 L1 的编写做成模板化,降低门槛。
第二个阻力是评估模型成本。我们的应对是分层用模型,核心集用大模型,回归集用小模型,成本控制在可接受范围。
8. 关于 Behavior-Driven AI-TDD 的几个常见误解
8.1 误解一:这是测试方法论
不是。Behavior-Driven AI-TDD 本质是研发工作流的重构,测试只是其中一环。它的核心价值在于把"行为定义"前置到研发起点,驱动整个开发节奏。如果只把它当测试方法用,价值会大打折扣。
8.2 误解二:只适合大团队
恰恰相反,小团队更需要。小团队资源有限,经不起反复返工。Behavior-Driven AI-TDD 通过前置行为定义,减少了后期返工,对小团队性价比更高。我见过几个人的小团队用这套方法,效果很好。
8.3 误解三:要等工具成熟
工具永远在演进,等不来完美工具。我的建议是先用最小可行方案跑起来:pytest + YAML + 一个评估模型 API,就能跑通闭环。跑起来之后再逐步优化工具链。
8.4 误解四:契约越多越好
契约数量不是目标,覆盖率才是。我见过契约库几千条但覆盖场景有限的团队,也见过几百条就覆盖核心场景的。关键是契约要覆盖真实场景的分布,而不是追求数量。
9. 我个人在实际操作中的几点体会
做了几个大模型项目之后,我对 Behavior-Driven AI-TDD 最大的体会是:它解决的不是技术问题,是协作问题。大模型应用开发最难的不是写代码,是"什么算对"这件事没有共识。行为契约的价值就在于,它把"什么算对"变成了可讨论、可版本化、可执行的东西。
第二个体会是不要追求完美闭环。我一开始想一步到位,结果卡在评估模型选型上两周没进展。后来改成先用简单方案跑起来,边跑边优化,反而推进得快。闭环的价值在于转起来,不在于每个环节都完美。
第三个体会是契约库要当产品运营。它不是写完就完了,需要持续更新、定期 review、根据线上反馈迭代。我现在的做法是每周花半天时间 review badcase,把确认的问题转成新契约。这半天投入的回报率极高。
最后一个体会是关于心态。大模型应用的行为波动是常态,不要指望通过测试消除所有波动。Behavior-Driven AI-TDD 的目标是把波动控制在可接受范围,而不是消除波动。接受这一点之后,很多决策会变得清晰:哪些波动要拦,哪些可以放,哪些要人工确认。
如果你正在做大模型应用,被测试和回归问题困扰,我的建议是先从一个小场景开始,写 10 条行为契约,跑通 L1 评估,感受一下闭环的节奏。跑通之后再逐步扩展。不要一上来就搞大而全的体系,那样大概率会卡住。