多轮对话 Agent 怎么测?确定性与不确定性分离的四层测试金字塔
一次多轮 Agent 的测试工程实践:把状态机、分类器、话术、接线四种性质完全不同的东西拆开,用四种方法各测各的,每种都配一套能回归的资产。技术栈:
LangGraph · pytest · hypothesis · Langfuse · LLM-as-Judge。文末附四层 × 现成框架的选型对比表,可直接抄。
你改了一句提示词,想修"AI 追问太多、像中介查户口"的毛病。改完和它聊了十分钟,感觉顺多了,发版。
第二天用户反馈:AI 是不追问了,可它也不推房了——你那句"少问点问题",把它引导用户去看房源的桥接语一起压没了。
你再改。推荐是修好了,但一个更隐蔽的问题浮出来:用户对着刚推给他的房源说了句"这套还行",AI 把这句好评当成了"再给我换一套",又推了一套新的,把上一套顶掉了。用户一脸问号。
三个回合下来,你会撞上一个让所有做 Agent 的人心里发凉的事实:每次改动,你只验证了自己盯着的那一个场景,而 Agent 的真实行为空间,比你能聊到的大好几个数量级。"跟它聊聊看"根本不是测试,是抽查——而且是最坏的那种抽查:跑不全、不稳定、出了问题还说不清是哪儿坏的。
这篇文章想回答一个被很多团队糊弄过去的问题:**一个多轮对话 Agent,到底该怎么测?**下面每一层,我都会把真实项目里的代码骨架摆出来——因为方法论听着都对,能不能落地全看那几十行测试长什么样。
我会用我们做的一个产品当例子——一个租房场景的 AI 找房助手。它每收到用户一条消息,只干一件事:决定这一轮该做什么。新用户来了,先聊五个问题建立找房偏好(预算、通勤、户型、生活方式、绝对不要什么);老用户闲聊时,顺着话头把需求挖深,攒推荐素材;用户想看房,就推荐,但每天最多三套,免得刷到决策疲劳;用户说了危险的话,安全兜底话术接住;剩下的,正常聊。
一条消息进来,走的是这么一条流水线:
图 1 · 一条消息的旅程 —— 实线块 = 纯代码(确定),虚线块 = AI 判断(不确定)
看着不复杂,对吧?一层分发,没有嵌套。可就是这么一张扁平的图,坑我们坑得最狠。
复杂度不在流程图里,在你看不见的状态
流程图只有一层,但每一轮决策背后,有一大堆状态在暗处参与运算:用户此刻在哪个阶段(日常闲聊、首次建档、补充偏好、修改偏好);建档聊到第几轮、五个问题问到第几个;用户连续答非所问几次了(连着三次才该体面地退出,别死缠);AI 连续追问几轮了(问到第三轮就该"还"用户一句不带问号的话,别像查户口);今天推荐名额还剩几套、有没有待确认的偏好草稿。
把这些一乘:五种意图 × 四种阶段 × 几个计数器 × 有没有画像草稿名额……上万条路径。你和它聊一下午,连百分之一都盖不住。
这就是为什么"端到端聊天测一切"是最差的选项。它同时犯了三宗罪:跑不完(上万条路径,人力聊天杯水车薪);不稳定(同一句话,模型这次判成闲聊、下次判成需求,状态就跑偏,测试时红时绿);说不清(一条 case 挂了,你分不清是意图判错了、还是状态记错了、还是话术差了,于是所有人默认都去改提示词,补丁越摞越厚,最后谁都不敢动那坨提示词)。
但这张图里藏着一个救命的好消息,它是整套方法论的地基——
确定的和不确定的,本就该分开
盯着那张流程图再看一眼:AI 只出现在三个地方——判断安全、判断意图、生成话术。而这三个 AI 判断口之间,所有的记账、状态转移、选路,全是纯代码:不调模型、不碰数据库,同样的输入永远给同样的输出。
把这条分界线画到真实的图拓扑上,长这样:
图 2 · 真实图拓扑 —— 准备上下文、两处条件路由是纯代码;安全闸门、意图分类是分类口;而扇出后每个动作节点内部,还各自藏着生成/抽取口
这张图比第一张多暴露了两件要命的事。其一,选路那一下全是纯代码:安全闸门判完,"拒还是过"是纯函数条件;意图判完,"进哪个动作节点"也是纯函数条件(硬规则 > 意图 > 兜底)。其二,也是新手最容易栽的——LLM 口不只在两端,动作节点内部还散着一堆:收集建档一个节点里就有抽取、合成、生成三个 LLM 调用。记住这张图右边那块,第四层会回来找它算账。
这条分界线一旦画出来,测试策略几乎是自动成立的:**不同性质的东西,用不同的方法测。**你不会用秤去量体温,也不该用聊天去测一段纯代码的状态转移。
而且这条线的意义,远不止测试。它反过来是一条架构纪律:如果你的 Agent 画不出这条线——模型调用和业务逻辑搅成一团、状态转移散落在各个提示词的字里行间——那你遇到的其实不是测试难题,是架构债。**好的可测性是设计出来的,不是事后补出来的。**所以我们把"LLM 只许出现在分类和生成两端,中间必须是纯函数"直接写进了 Agent 的开发规范,让每个新 Agent 从第一天起就天然可测。
动手之前,先把"测一条用例"这句话说清楚
很多团队的测试讨论卡在一个特别朴素的地方:两个人都说"写条用例测一下",脑子里想的却是两种完全不同的东西——一个想的是直接调那个函数,另一个想的是把整个对话跑一遍。不先统一这件事,后面所有分工都是鸡同鸭讲。
对一个用图来编排的 Agent(比如 LangGraph 这类),测试其实只有三种基本姿势,区别就在"喂它什么、看它什么":
第一种,单看一条规则。绕开整张图,把某一条业务规矩单独拎出来验(技术上就是直接调那个决策函数:喂它一个意图加几个状态字段,看返回值对不对)。它能证明"这条规矩本身对"——最快、零成本、能全部验一遍,但证明不了"这条规矩有没有被接到正确的地方"。
第二种,走一整段对话。把完整的图当黑盒,从头到尾跑一遍——喂一份完整的会话状态(阶段、轮次、各种计数器、有没有画像草稿名额)和一段对话历史,同时把所有 AI 判断都换成写死的标准答案,只看跑完后状态怎么变、经过了哪些环节。它测的恰恰是第一种够不着的东西——环节之间接得对不对、多轮怎么演化,代价是准备一份完整状态,比准备几个字段贵得多。
第三种,单考一次 AI。把安全判断、意图判断、或者话术生成,单独拎出来考——喂一段对话,看它判成了哪一类、话说得合不合格。因为考的是概率模型,判据必须是一整套题的正确率,而不是某一条的对错。
这三种姿势没有高下之分,真正的区别在于维护成本从哪来:单看一条规则的用例极便宜,坏了改一行就行;走一整段对话的用例贵在那份"完整状态",所以得用统一的剧本格式来写(初始状态、每轮发生什么、每轮该验什么),让一份剧本能长期复用;单考 AI 的用例是一套题库,贵在标注,所以必须能沉淀、能跨版本反复重考。
把这层窗户纸捅破,下面四道质检关卡就各就各位了:一条规则单独验,一整段对话整个走,一次 AI 判断拎出来考。
图 3 · 三种姿势各管一段 —— 一条规则单独验,一整段对话整个走,一次 AI 判断拎出来考
第一层:把产品决策焊死成表格
先纠正一个会把人带偏的叫法。这一层技术上叫"状态机测试",但它测的东西,根本不是什么技术细节,而是产品设计决策本身。
看几条我们真实的规则,以及它们背后的产品理由:
| 规则 | 为什么这么定 |
|---|---|
| 用户答非所问,要连续三次才退出建档 | 建档是核心转化。租客聊着聊着吐槽两句工作太正常了,一跑题就放弃,建档完成率会崩——所以给三次冗余,先把人拉回来 |
| 用户一旦明说要看房,立刻退出建档去推荐 | 主动要看房是最强的意愿信号,此刻还死守建档流程,等于拦着用户要的东西 |
| 建档的五个问题,游标只前进不回头 | 用户最烦被问第二遍预算。哪怕这轮答得含糊,也宁可往下走,绝不回头重问 |
| 每天最多推三套 | 房源推荐是稀缺感商品,一天塞十套,用户直接麻木 |
| AI 连问三轮,必须"还"一句不带问号的话 | 收集偏好是长期生意,查户口的感觉会把人问跑——先给予,再索取 |
**每一行,都是一次产品讨论拍下来的结论。**而把这些结论翻译成测试,长得就跟决策表本身一模一样——一行一条规则,喂进去起始状态和意图,断言状态怎么变:
# 业务规则层:不调模型、不碰 DB,同输入永远同输出。一行 = 一次产品拍板。@pytest.mark.parametrize("stage, intent, expect", [ # 建档中答非所问 → 计数+1,留在建档(给三次冗余,别一跑题就放人走) ("ONBOARDING@offtopic=0", "CHAT", {"off_topic": 1}), # 连续第三次跑题 → 这一步必须退出(建档转化率的兜底硬线) ("ONBOARDING@offtopic=2", "CHAT", {"stage": "IDLE"}), # 建档中明说要看房 → 立刻退出建档去推荐(最强意愿信号,不许再拦) ("ONBOARDING", "RECOMMEND", {"stage": "IDLE", "route": "recommend"}), # 建档中正常补充 → 游标只前进,绝不回头重问预算 ("ONBOARDING@topic=1", "COLLECT_ONLY", {"cursor": 2}),])def test_stage_transition(stage, intent, expect): assert derive_stage(intent, parse(stage)) == expect光有逐条用例还不够,再加几条无论如何都必须成立的铁律——它们不是抽查几个例子,而是跑遍所有"阶段 × 意图"组合都得成立:
@given(stage=stages(), intent=intents()) # 阶段 × 意图 全笛卡尔积穷举def test_invariants(stage, intent): out = derive_stage(intent, stage) assert out.off_topic <= OFF_TOPIC_EXIT_THRESHOLD # 计数到 3 必退,不会卡在 4、5 assert out.cursor >= stage.cursor # 游标只进不退,问过的不再问 assert out.route in VALID_NODES # 路由是全函数,任何输入不抛错**为什么它是性价比之王?**因为不调 AI、不连数据库,几百条用例十秒跑完。更妙的是,用户的那些"乱输入"在这一层根本不存在——乱七八糟的自然语言早在上游被分类器压成了有限的几个枚举值。有限枚举,意味着可以穷举,意味着可以彻底焊死。
我们给自己的状态机铺这层测试的那天,第一遍就抓到一条已经静默失败了好几个星期的旧测试。之前一次规则调整改了代码(游标推进逻辑从"原地"改成了"至少进一位"),漏更了一条断言,它就那么一直红着,没人发现——因为根本没人天天跑它。翻译成人话:**产品规则被人悄悄改了,而"规则的契约"没有任何人在守。**这层测试,就是那个守约的人。它锁死的不是代码,是当初会议室里拍板的那些产品决策。
一个真实踩过的坑,顺手说清:上面
derive_stage只认已经解析好的意图枚举。用户点的那些快捷按钮(“看房”“改偏好”)带的是原始 action 字段,必须先过一层 shortcut 解析才变成意图——你要是图省事直接把 action 喂进路由函数,它会默默落到闲聊兜底。所以这类"接线依赖"我们专门写了护栏用例:故意喂错层的输入,断言"它确实会误路由",把契约钉死在测试里,免得下一个人重新踩。
第二层:把分类器当成一个 ML 模型来考
安全判断和意图判断这两个 AI 口,判得准不准,是另一码事,得用另一种考法。
别给它写单元测试——它是个概率模型,不是确定性函数。正确的做法是像考核一个机器学习模型那样,建一套标注题库,每改一次提示词就把整套题重考一遍,看的是每一类的准确率和错题分布(也就是混淆矩阵,说白了就是一张"把 A 类判成 B 类各几次"的错题统计表)。
题库里的题分三种,缺一种,就会漏掉一整类事故。而"题该长什么样"这件事,最好直接看题——注意后两道,用户输入几乎一样,正解却相反,区别全在 AI 上一句做了什么:
## 单轮题:一句裸话,考基本盘id: recommend.explicit.001 · label: RECOMMENDuser: 帮我找套房## 前情题 A:AI 只是"提议",还没真推 → 用户"好啊"是接受,该去推id: recommend.boundary.offer_confirm.001 · label: RECOMMENDassistant: 要不要我找几套合适的房源给你看看?user: 好啊## 前情题 B:AI 已经把房源"摆出来了" → 用户"这套还行"是好评,不该再推id: chat.boundary.shown_match.001 · label: COLLECT_AND_GUIDEassistant: 〔已展示房源卡:两居 / 近地铁 / 6k〕user: 这套还行一字不差的"好啊/这套还行",前情不同,正解相反——分类器是读上下文来判断的,题目就必须把上下文带上。我们真翻过的那次车(好评被当成"再换一套"),就是 B 这类。所以我们的题库里,边界题占了过半。
题库写成人能读的 markdown,脚本编译后推到评估平台;跑分 runner 支持传提示词版本,同一套题分别跑当前版和候选版,输出逐类准确率和混淆矩阵的差异。判据永远是全集的逐类准确率,绝不看单条随机输出——理由下面这份真实跑分说得很清楚。
我们给意图分类器建了一套66 道题的题库,边界题占了过半(35 道),拿线上版本连考两轮,考出来的数字很说明问题:
[v10] 66 题 · 连考两轮 · 温度=0 run1 run2overall 93.9% 90.9% ← 同一套提示词,两轮仍差 3 个百分点overall 95% 置信区间:[86.6%, 95.8%] ← 版本对比要看两版区间是否重叠,别给噪音颁奖gold\pred REC CAG CO GO CHAT ← 混淆矩阵:错判被吸进哪个类,一目了然REC 12 1 0 0 0 ← REC→CAG ×1CAG 0 23 0 0 0 (宽兜底类,自己 100% 不丢)CO 0 2 7 0 0 ← CO→CAG ×2,稳定弱点簇就在这GO 0 0 0 9 0CHAT 0 0 1 0 11 ← CHAT→CO ×1连续成功率(两轮全对) 60/66 = 90.9% ← 生产真正要的口径至少一次成功率 62/66 = 93.9% ← 能力上限,只能看潜力稳定错判(两轮同错) 4 条,全是边界题 ← 真弱点,见下文抖动题(时对时错) 2 条 ≈ 3% ← 噪音,不是弱点,别去"修"它**两轮总分是 93.9% 和 90.9%。**同一套提示词、温度调到零,两次考试还是差了 3 个百分点。有一道题的原话甚至就写在提示词的示例里,一轮判错、一轮才对。这就是"盯着单条输出调提示词会被反复折腾"最硬的实锤——**66 道题里就有 2 道在时对时错,3% 上下的天然抖动,意味着你看到的任何单条好坏,都可能只是噪音。**谁再拿一张截图跟你说"你看这条又坏了",把这个数字甩给他。
而这 3 个百分点的差距,恰好逼出一个容易被跳过的严谨问题:**它到底是真变化,还是随机波动?**只跑一轮、看总分涨了就宣布提升,很可能是在给噪音颁奖。所以版本对比不能只盯单点分数,得给关键指标配上置信区间(上面那行 95% CI 就是干这个的,纯数学算得出,不依赖任何库)——两版的区间不重叠,差异才算真的超过了统计噪声。我们干脆给"最小可感知变化"提前定了个阈值:涨跌幅度不到这个数,一律不作为发布依据。
抖动还牵出另一个更贴近生产的视角。同一道题跑 N 次,“至少对一次"和"每次都对"是两码事:前者说明能力上限够得着,适合看潜力;后者才是生产真正要的——**用户不会接受"多试几次总有一次成功”,他们要的是每次都稳。**退款、支付、合规这类场景,该盯的是"连续成功率"。看那两行数字:至少一次成功率 93.9%,听着还行;可换成"连续两轮全对",立刻掉到 90.9%——同一套东西,换个更严的口径就缩水一截,要是要求连着五轮全对,实际可用性还得再打折。
真正有价值的,是两轮都错、而且错得一模一样的那 4 道题(全是边界题)。它们才是真弱点,而且集中成一个簇:“换个区域继续找"这种带修改的接受,被当成了"边收集边引导”;"先不用了,回头再说"这种软拒绝,没能压住默认分支;"又不想被推荐、又嫌问题多"的双重拒绝,只识别出了一半。回头看那张混淆矩阵——4 道错判里 3 道全栽进了同一个宽兜底类(CAG),一格CO→CAG ×2就是这个簇的指纹。**所以下一版提示词就精确瞄准这几个句式补规则,别去动那个兜底类本身,别的一个字不动。**因为你有整套题守着,任何"补了这里、崩了那里"都会当场现形。
还有个额外收获:之前修过的那个"好评复推"的坑(AI 已展示房源后、用户的好评被误判成求推荐),相关的边界题这次两轮全过——混淆矩阵里那个 100% 不丢的 CAG 类,就有它们的功劳。这是那次修复第一次拿到了回归证据,而不是"我感觉修好了"。
第三层:能用规则查的,就别请 AI
话术层测的是 AI 说出来的话像不像话——专业、有温度、不油腻、不查户口、不乱承诺。
诀窍是先把要求分成两堆。能用规则机械检查的,坚决不动用 AI——这些又快、又免费、永远不抖:
# 硬约束 = 普通函数,正则/计数就够,一次跑完不花一分钱、不抖一下defrule_give_back_no_question(reply: str) -> bool: return"?"notin reply and"?"notin reply # 给予回合:零问号defrule_one_question_per_turn(reply: str) -> bool: return reply.count("?") + reply.count("?") <= 1 # 建档:一次只问一个defrule_no_reco_when_refused(reply: str, ctx) -> bool: # 用户明说不要推荐 returnnot (ctx.user_refused_reco and hits_reco_template(reply)) # 那就一句推荐话术都不许有剩下机械查不了的软质量——温度够不够、上下文接得顺不顺、人设有没有跑偏——才请另一个 AI 当评委,按评分卡打分取平均:
# 机检不了的,才请评委按评分卡打分(1-5 星,取均值,永远看分布不看单条)judge_rubric: 暖度: "像懂行的朋友,不是查户口的中介" 主题衔接: "接住用户上一句,不跳步、不自说自话" 人设边界: "红娘/管家口吻,不越界给法律金融建议"# 评委必须输出打分理由;低分样本人工复核后,沉淀回题库有人立刻会问:评委自己也会判错,那它打的分还能信吗?能,靠三招兜住:第一,能机检的先被上面那几个函数分流走了,评委只负责它真正擅长的软性判断;第二,永远看均值和分布,不看单条——评委的抖动会被平均掉;第三,评委的评分卡本身也用一套标注集去校准,低分样本人工抽核。评委不是真理,是一台被校准过、且只在合适场景使用的仪器。
第四层:把 AI 全钉死,跑一遍全流程回归
前三层各管一段,但还有个东西没测:接线——分类结果进来之后,状态记账对不对、路由对不对、该发偏好清单卡的那一轮到底发没发。
测法就是用测试替身(test double)跑全流程:把所有 AI 判断口全换成写死的脚本,然后驱动真实的那张图多轮跑完整场剧本,只断言状态轨迹和节点序列,绝不断言 AI 说了什么(那是第三层的活)。这层的第一步,也是最容易翻车的一步——把图里每个 LLM 口都对上一个 mock 目标:
# 还记得图 2 右边那块吗?动作节点内部还藏着抽取/合成口——漏钉一个,剧本就打真模型MOCK_SEAMS = { "intent.classify_intent": seq(["COLLECT_ONLY", "RECOMMEND"]), # 每轮钉死一个意图 "safety.classify_safety": const("SAFE"), "collect.extract_interests": const(Interests(likes=["安静"])), # ← 动作节点内部的口① "collect.synthesize_draft": const({"budget": "6k"}), # ← 内部的口②,最常被漏 "reply.generate": fake_reply, # ← 内部的口③}缝钉全了,剧本就干净了。剧本本身是一份声明式的东西——初始状态、每轮注入什么分类结果、每轮断言什么,只看状态和路由,不看话术:
scenario: 建档中途明说要看房→应立刻退出建档、直奔推荐initial_state: {stage:ONBOARDING, collect_round:2, cursor:1}turns:-inject_intent:RECOMMEND expect_route:recommend # 走对了节点 expect_state: {stage:IDLE} # 且真的退出了建档(不是嘴上说说)有人会问,最终话术都对,何必揪着中间状态不放?因为结果对,不等于过程对。一条 case 最后那句房源推荐读起来挺顺,可它要是在本该建档的阶段抢跑推的、或者绕过了每日三套的名额校验——这次侥幸没出事,换个用户、换个时点就是事故。这种"答案看着通过、路径其实带病"的假阳性,是 Agent 最阴险的一类 bug:它在只看最终输出的测试里永远是绿的,直到某天在生产里爆开。第四层专测过程、不测话术,防的就是它。AI 全被钉死之后,这层零抖动、秒级完成,能塞进 CI 每次提交都拦一道。
那真实的端到端测试还要不要?要,但降级成冒烟:留两三条,每天夜里跑一次,验证提示词和真实模型的集成没烂掉。它是烟雾报警器,不是质检产线——这个定位一旦摆错,你就又滑回"聊天测一切"的老路了。
出了 bad case,让 AI 先破案
一条线上 bad case 报上来,它只可能坏在三个地方:**分类判错了、规则走错了、话说得不好。**传统流程是工程师打开观测平台,人肉翻十几屏 JSON 找线索——慢,而且翻着翻着就凭直觉去改提示词了,又回到补丁摞补丁的老路。
我们正在把这一步交给 AI 自动破案:
图 4 · AI 自动归因管线 —— bad case 导出诊断包 → 编程 Agent 归因 → 裁决坏在哪层并给修法 → 人拍板复测那一层
关键是那个诊断包:把一轮对话的全链路过程数据抽出来、去掉噪音、压成一页纸的结构化摘要。这份摘要人能十秒看懂,AI 更能:
# 诊断包 · 一页纸:只留 diff、不留全量;对话历史只留末 2~3 轮;不带 prompt 全文turn: {session, turn_id, prompt_version:v10}input: {query:"这套还行", is_first:false}judges: {safety:SAFE, intent:RECOMMEND} # ← 疑点:好评被判成了"求推荐"state_diff: {daily_rec:2→3, 发卡:false} # 状态只留变化项route: [prepare, safety, intent, recommend] # 途经节点:确实走进了推荐output: {speech:"给你换一套…", cards:1}links: {trace_url, annotation_id} # 深链回观测平台看原始细节而且现象本身就是路标——"房源在建档没做完时就被推了"这种描述天然指向路由和规则层,根本不用去翻话术提示词;"话说得没温度"才落到话术层。维护一张"问题现象 → 候选层"的小映射表,破案一上来就把范围收敛到一两个模块。把这份摘要连同现象标签一起丢给编程 Agent,让它按"先查分类、再查规则、后查话术"的顺序破案,并给出建议:是该在流程里加一条确定性约束(改代码),还是该增强提示词(改分类或话术),以及修完之后该复测哪一层的哪套题。
人的角色,就此从"翻日志的侦探"变成了"看裁决书的法官"。而且这些过程数据本来就都躺在那儿——调试台早就记录了每一轮的状态快照和节点轨迹,唯一缺的,只是"把它导出成诊断包"的那一个按钮。
让每一条 bad case,变成一道永久考题
绝大多数团队的现状是:调试时发现的问题,截个图发到群里,修完就散了。三个月后同一个问题换身马甲回来,再修一遍。
正确的姿势,是造一个数据飞轮:
图 5 · 数据飞轮 —— bad case → 打标 → AI 归因 → 导出考题 → 永久回归,每转一圈就少一类复发
这里的关键洞察是:打标的那一刻,一条测试用例需要的所有信息其实都已经在场了——用户输入、当时的完整状态、AI 的判断链。缺的从来不是采集能力,而是"把它导出成一道考题"的最后一步。把这一步做成一个按钮,飞轮就转起来了:一条被标错的 case,按它坏在哪层,自动落成对应层的用例——归分类的落成第二层的一道题(对话窗口 + 纠正后的标签),归接线的落成第四层的一段剧本(当时的状态快照 + 意图序列),归规则的落成第一层的一行参数。
冷启动也不难。很多人一听建题库就头大,其实题从三个地方来,一天就能凑齐第一批:历史上修过的每一个 bad case(修过的误判本身就是现成的边界题)、设计文档里的规则表(直接翻译成用例)、以及调试台持续流进来的标注。我们那套意图题,近一半就来自前两个存量来源。
飞轮转起来之后,还得防它跑偏成两种毛病。一是别把每条失败都无差别塞回题库——那样用例库迟早膨胀到没人敢跑。入库得有门槛:能复现、期望行为写得清、根因标签明确、能代表一类问题而不是一次环境抖动、且已脱敏。二是别把 bad case 当成一条条孤立的账来记。同一个根因、同一个场景的失败,该自动聚成一簇——一份好报告不该是"列出 100 条 bad case",而该是"某场景下 Agent 二十多次跳过同一道校验,集中在某一版提示词"。前者让你逐条救火,后者直接把手指按在了病根上。
飞轮转不转,全看调试台顺不顺手。我们把它收敛成五块必备能力,缺一块流程就断:对话回放区,像用户一样重看这场对话,每条 AI 消息旁边就是打标入口;状态透视板,当前阶段、各个计数器、画像草稿名额一眼全览;节点轨迹时间线,这一轮走了哪些节点、各花多久、每个 AI 口判成了什么——正是诊断包的可视化形态;一键闭环按钮,把打标、AI 归因、导出考题连成一条流水线;版本 AB 复现,锁定某一轮,换一版提示词原地重跑,两版输出并排比——这是"改提示词前后到底有没有变好"最直接的证据。
而且这五块能力不该是某个调试页的私产,得做成通用组件:正式产品的聊天界面里一个快捷键就能唤出同一套面板,内测同学随手就能打标。数据飞轮的进料口,越多越好。
至于观测平台——我们用的是已经全量开源的 Langfuse——它什么都能存,但它端给你看的是 JSON。一条用例里嵌着完整会话状态,展开三屏,没人读得下去。我们的取舍很简单:跑分归平台,看懂归自己。题库存储、多版本对比、评委打分、标注队列,用平台现成的,别手搓评估引擎;而"这条用例是什么场景、状态怎么流转、错在哪一步"这种业务语义的阅读体验,自己建薄薄一层,按业务字段渲染,再深链回平台看原始细节。两边各干各擅长的,别强求什么 all-in-one。
让测试真的在跑
测试的价值不在写出来,在每次改动都拦一道。我们那条静默失败了几周的测试就是最好的反面教材——写了,但没人跑,等于没写。
所以每一层挂在什么时机,得说死:
| 时机 | 跑哪一层 | 成本 |
|---|---|---|
| 每次提交代码 | 第一层(秒级、零 AI 成本) | 忽略不计 |
| 每次合并之前 | 第一层 + 第四层全流程回归 | 秒级 |
| 改了分类提示词 | 第二层对应题库,新旧版本对比考 | 一两百次最便宜模型调用 |
| 改了话术提示词 | 第三层硬约束 + 评委 | 几十次调用 |
| 每天夜里 | 端到端冒烟两三条 + 全量二三层基线 | 分钟级 |
数量上,它应该是一座金字塔:规则用例几百条 ≫ 全流程剧本几十条 ≫ 每个分类器题库百来题 ≈ 话术评审几十条 ≫ 端到端冒烟个位数。如果哪天你发现自己的端到端测试反而最多,那这座金字塔就是倒的——你在用最贵、最抖、最说不清的方式,去测那些本该最便宜、最稳定的东西。
图 6 · 测试金字塔 —— 形状同构,内容换血;实线层确定、虚线层是 AI
这座金字塔和传统软件的测试金字塔,形状同构,内容却换了血:单元测试的位置,换成了业务规则表;集成测试的位置,换成了钉死 AI 的全流程回归;端到端的位置,换成了真模型冒烟。而新增出来的那两层——概率组件的统计化考试、和 LLM 评委——是传统金字塔里没有的,它们是 AI 系统特有的质检工序。认清这一点,你就不会再拿"我们有单元测试覆盖率"来自我安慰了:那套指标压根没覆盖 Agent 最容易翻车的地方。
还得补最后一句:把这座金字塔全跑绿,也只是拿到了"离线通过"这一张票,而离线提升不等于线上变好。发布时得让离线门禁和线上灰度接上,同时盯三类信号——离线质量(核心场景通过率、P0 风险数)、线上体验(转人工率、重复追问率、投诉率)、业务结果(转化、任务完成率)。哪天离线分涨了、线上关键信号却在恶化,别犹豫,回滚——那说明你的题库漏掉了真实世界正在发生的事,该回去补题,而不是硬发。
落地工具箱:每一层该用什么框架
方法论讲完,程序员最实在的问题是:这四层,社区有现成的框架吗,我该用哪个?答案是——每一层都有趁手的工具,但没有任何一个框架能一口气盖住四层。谁跟你说 all-in-one,谁就没真测过多步 Agent。
| 层 | 测什么 | 现成框架 | 我们的取舍 |
|---|---|---|---|
| ① 业务规则 | 纯函数转移 / 不变量 | pytest.parametrize、hypothesis(property-based) | 直接用,零额外依赖。表驱动穷举 + hypothesis 兜不变量 |
| ② 分类器 | 逐类准确率 / 混淆矩阵 / 版本对比 | Langfuse、LangSmith、DeepEval、Promptfoo、OpenAI Evals、Ragas(偏 RAG) | 题库 / 多版本 / judge 走Langfuse;混淆矩阵这类业务判据自写薄 runner |
| ③ 话术 | 硬约束 + 软质量 | 硬断言=裸 pytest;judge=DeepEval / Promptfoo / Langfuse LLM-as-judge | 硬约束自写正则 evaluator(免费不抖);软质量挂 judge,评分卡自定义 |
| ④ 全流程回归 | 状态轨迹 / 路由接线 | 没有专门框架;LangGraphInMemorySaver+ pytestmonkeypatch | 编译期塞 checkpointer 多轮驱动,monkeypatch 钉死每个 LLM 口 |
| 归因 / 飞轮 | bad case → 考题 | 标注可用 Langfuse annotation queue | 自建诊断包 + 编程 Agent 归因;标注沉淀回 Langfuse dataset |
三条取舍逻辑,比表格更值得记:
评估平台我们选 Langfuse,不选 LangSmith。不是谁功能更强,是开源可自托管——评测数据(真实对话)不出内网,MIT 协议,dataset / experiment / score 三件套原生齐全。LangSmith 是 LangChain 官方的闭源 SaaS,功能也够,但数据要上人家云、还把你绑在一家。选型先问"数据能不能出内网",再问功能。
DeepEval、Promptfoo 这类"LLM 评估框架",擅长的是第二三层,不是第四层。它们把"喂一个 prompt、评一次输出"做得很顺(断言式、YAML 声明式、红队),分类器考试和话术评审直接能用。但你要测的是整个多步 Agent 跑完之后、内部的状态转移对不对——这些框架没有"跑完读 LangGraph 内部 state 做断言"的原生能力。第四层只能靠 LangGraph 自己的 checkpointer + 你自己写的 runner。这就是通用评估框架的边界:它们测"一次 LLM 调用",测不了"一台状态机"。
第一层反而最不需要框架。pytest + hypothesis 就是全部——因为这层被你亲手从 AI 里剥出来了,它就是纯函数,用测普通代码的方式测就行。反过来说:一个 Agent 里"不靠 LLM 评估框架就能测"的部分越大,说明你的架构分层越干净。
尾声:四条经验,和一个越用越便宜的循环
我们踩过的那些坑——好评被当成复推、同一个问题被问两遍、提示词补丁越摞越厚、坏测试静默好几周——大概率你也正在踩、或者即将踩。方法论其实一点都不新,全是软件工程攒了几十年的老套路:表驱动测试、题库考核、评分卡、测试替身、错题本。新的只是把它们对号入座地放进了 Agent 的分层里,然后让 AI 把其中的体力活给干了。
如果只让我留四句话:
**先画那条确定性与不确定性的分界线,再谈测试。**线画出来,每一块用哪种姿势测是自动成立的;画不出来,那不是测试问题,是架构问题,先去重构。
**要是只做一件事,先铺业务规则那一层。**一天就能铺满,它锁死的是产品决策,不只是代码。它还是最诚实的文档——文档会撒谎,穷举测试不会。
**把分类器当模型考、把话术当作文批、把接线当全流程跑一遍。**三种东西三种考法,混在一起考,你只会得到"感觉还行"这种没法回归的结论。
让 AI 进测试流水线打工,而不只是被测。这套体系里,AI 有三重身份:它是被测者(分类和话术)、是质检工(评委打分、用户模拟器对聊),也是质量工程师(自动归因、从标注生成考题,甚至连"怎么生成测试用例"这件事本身,我们都沉淀成了编程 Agent 能直接执行的规程)。人只需要做两件事:定标准,拍板。正因如此,这套体系的边际成本会随着 AI 参与度的提高而持续下降——它是那种越用越便宜的东西。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~