接手这个Agent项目的第一周,我对着需求文档发了半天呆。做过Web测试的人应该懂那种感觉:平时我们测的是按钮、接口、页面跳转,而这次摆在我面前的是一套会“自己思考”的系统——用户输入一句话,它能自己决定调哪个工具、按什么顺序、怎么组织回答。传统测试那套“输入固定、输出可断言”的玩法,在这里基本失效了。
更让人难受的是,开发那边一开始也不知道该怎么配合测试。需求描述只有“让Agent能帮用户完成报销审批”“支持查询销售数据”这种粒度,既没有明确的验收标准,也没有“什么算答对”的定义。我们团队的技术负责人半开玩笑地说了一句:“测试别着急,等Agent跑通了再介入也不迟。”但我知道,如果真的等开发完再介入,这个项目大概率会在上线前陷入无尽的“人肉回归”——因为大模型输出的不确定性,会让每一次改动都像推倒重来。
所以这篇笔记我想认真聊聊:在一个Agent项目里,测试到底是怎么一步步从边缘走向核心的。这篇不是讲理论,而是我在实际项目里踩过、填过的坑,希望给同样在做Agent测试、或者团队里还没想清楚测试该怎么介入的朋友一些参考。
1. 接手Agent项目的第一周:先别急着写用例
我刚接手项目时,最常见的冲动是“赶紧找几个用户问题跑一遍,看看Agent答得对不对”。但真跑起来就发现问题了:同一个问题问三遍,答案可能三遍都不一样;有时Agent会绕一大圈去调无关工具;有时干脆拒绝执行。如果这时候就开始写用例,写出来的全是一堆“结果不确定”的用例,根本无法支撑长期回归。
1.1 和开发对齐的第一件事:把“不可测”变成“可测”
我做的第一件事不是写测试用例,而是拉着开发和产品经理开了一个“可测性对齐会”。主题只有一个:Agent系统的哪些部分是代码逻辑,哪些部分是模型行为,哪些是外部依赖。快速梳理下来,一个典型的Agent请求链路可以拆成这样:
- 用户输入进入编排层,Agent决定调用哪个工具、传入什么参数;
- 编排层把工具结果返回给模型,模型基于结果生成最终回答;
- 整个过程涉及Prompt模板、工具定义、结果解析、异常处理、日志记录。
这里有个关键认知:Agent项目的测试,不能只盯着“模型的回答”这一个输出点。模型产生的是“会话回复”,而工具调用、参数生成、状态流转、权限判断这些环节,大部分是确定性逻辑,它们完全可以按传统测试的方式来断言。把能确定的东西先测住,剩下大模型那部分才能专心对付。
1.2 定义“正确”的样子:搭建评判体系
对齐完边界之后,紧接着要做的是定义“什么样的输出算正确”。这一步听起来虚,但实际上是整个测试工作能不能落地的分水岭。
我们当时把Agent的输出拆成了四个评价维度,并且跟产品经理逐条确认了每个维度的口径:
- 任务完成度:用户的目的有没有被满足,比如“查一下上月销售额”是否真的返回了对应数据;
- 工具使用正确性:调用的工具是否合理,参数是否正确、是否缺少必要字段;
- 回答规范性:格式、语气、是否包含必要的数字单位、来源标注;
- 安全与边界:是否拒绝越权操作、是否编造不存在的功能、是否泄露敏感信息。
这四个维度不是凭空定的,而是来自当时线上用户反馈里最常见的四类问题:任务没完成、工具调错、答案敷衍、以及“一本正经地胡说八道”。把用户痛点转成评判维度,后面所有测试用例的断言都有了依据。
1.3 任务分级:不同复杂度的任务采用不同测试策略
维度定好之后,我又和产品一起把所有计划支持的场景按复杂度分了个级,这个分级直接决定了后面测试投入的优先级:
| 级别 | 场景示例 | 测试策略 |
|---|---|---|
| L1 简单规则任务 | 查询天气、计算器、查快递 | 全量自动化回归,断言相对固定 |
| L2 业务查询任务 | 查销量、导报表、查审批进度 | 自动化回归 + 关键字段断言 + 少量人工抽检 |
| L3 多步操作任务 | 跨部门报表汇总、申请审批、异常流转 | 半自动化脚本 + 黄金数据集比对 + 上线前人工评审 |
| L4 高风险操作 | 删除数据、支付、外部下单 | 强制人工确认,自动化只做前置校验和结果回读 |
这个分级最大的价值在于:它让测试组知道哪些场景要用“确定性断言”去卡,哪些场景必须保留“人工判断”的入口。不是所有Agent回答都能用代码断言,硬用反而会让测试集变成形式主义。
2. 测试分层视角的重构:确定性逻辑和大模型行为分开测
在传统Web项目里,测试金字塔一般是UI、接口、单元逐层展开的。Agent项目表面看也有一套“界面”(聊天窗口),但实际上完全不是一个逻辑。如果拿测Web的思路去测Agent,很容易出现两种情况:要么天天在聊天框里人肉点点点,要么写一堆脆弱到一改Prompt就全红的UI自动化脚本。
2.1 传统测试三层模型在Agent项目里哪里失效了
传统UI层测的是页面元素的交互,而Agent的“UI”是自然语言对话。同样的问法在不同时间可能得到不同的表述,页面自动化断言文本很容易误报。接口层测的是请求和响应结构,但Agent的响应是模型生成的,字段结构虽然稳定,内容语义却千变万化。所以Agent项目不能沿用“UI、接口、单元”的三层包抄,而要改成按“确定性边界”来分层。
我自己的做法是把整个系统切成两半来测:
- 确定性逻辑层:Agent的编排、工具选择、参数解析、会话管理、鉴权、工具执行结果处理;
- 模型行为层:Prompt的阅读理解、任务规划、语气风格、面对模糊输入时的决策。
确定性的部分用pytest直接做自动化,模型行为的部分用“样本集 + 评分函数 + 人工抽检”的方式来管。两层分开之后,测试用例的稳定性一下子高了很多。
2.2 确定性部分:工具调用参数、状态流转、权限校验
这里我可以用一个我们项目的真实场景来说明。我们有个“销售数据查询”工具,用户说“查一下上个月华东区的销售额”,Agent需要调用一个报表服务接口。整个调用过程里面,Agent要生成类似这样的工具调用参数:
{ "tool": "sales_report", "params": { "region": "华东", "date_from": "2024-10-01", "date_to": "2024-10-31" } }对于这一层,我要验的是:
- 用户说“上个月”,Agent是否翻译成了正确的起止日期;
- “华东区”是否被正确映射到区域编码;
- 如果用户说“去年同期”,是否识别为去年同一时间段;
- 在Agent误生成缺失参数或错误时间格式时,编排层是否能拦截并触发追问。
这些全是确定性的断言,跟大模型的“口才”没有关系,可以用非常稳的自动化用例覆盖。只要工具调用参数正确、编排逻辑健壮,Agent哪怕回答得啰嗦一点,任务也是成功的。
2.3 非确定性部分:大模型输出的评价维度
而模型“怎么回答”的部分,我基本不写死文本断言。除非是产品硬性要求“必须以X开头”,否则任何针对完整句子的相等比较都会在两天后变红。我们改成评价式断言:
- 是否包含了关键数字(比如销售额“1234.5万”不能丢);
- 是否夹带了来源或单位,保证可追溯;
- 是否在知识不足时明确说“不知道”,而不是编造;
- 是否保持了礼貌口径,没有出现负面表述。
这类断言背后是“语义是否合格”的判断,而不是“字符串是否相等”的判断。为了方便落地,我把每一个评价维度都做成可勾选的检查项,由评分器跑完自动给出分数。人工只需要抽检低分样本,不用全量看。
3. Prompt测试落地:从“人肉看效果”到自动化回归
Prompt测试是整个Agent项目里最容易被忽视、但也最值得投入的部分。很多团队的做法是:开发调Prompt的时候,拿几个例子试一下,“看起来不错就上”。可一旦Prompt被反复调整,前两周看着很好的效果,可能只是一些样例过拟合的结果。换一批真实问题,立刻打回原形。
3.1 把Prompt测试当成数据驱动测试来做
我在这里最核心的经验是:Prompt测试不测Prompt本身,而是测“Prompt在不同输入下的行为”。也就是说,Prompt是逻辑,输入输出对是数据,测试就是把这些数据喂进去看输出是否满足评分规则。
我维护了一套“行为测试集”,以JSON文件存放,每个样本包含:
{ "id": "TC-SALES-034", "input": "查一下上个月华南区的销售额,只要总数", "expected_tools": ["sales_report"], "expected_params": { "region": "华南", "date_from": "2024-10-01", "date_to": "2024-10-31" }, "expected_answer_contains": ["销售额"], "forbidden_keywords": ["猜测", "可能"] }这套JSON本身就是测试用例库,跟代码一样纳入版本管理。每次开发改了Prompt,我把这套数据跑一遍,马上就能看出哪些行为变了、是变更好了还是变更差了。这套机制落地之后,我们从“靠感觉调Prompt”变成了“看回归报告调Prompt”,研发那边的反馈是:终于不用怕改Prompt改出隐蔽问题了。
3.2 用pytest快速搭起Prompt回归框架
自动化框架我直接选用了pytest,原因很简单:团队里其他项目的接口测试就在用它,复用成本最低,而且它对数据驱动支持得很好。核心结构不复杂,每个人都能上手:
import pytest import json CASES = json.load(open("agent_test_cases.json", encoding="utf-8")) def run_agent(input_text): # 调用被测Agent的入口,返回工具调用和最终答案 ... def assert_tool_called(trace, expected_tool): assert any(item["tool"] == expected_tool for item in trace) def assert_params(trace, expected_params): # 找到对应工具调用,校验关键参数 ... @pytest.mark.parametrize("case", CASES, ids=lambda c: c["id"]) def test_agent_behavior(case): trace, answer = run_agent(case["input"]) assert_tool_called(trace, case["expected_tools"]) assert_params(trace, case["expected_params"]) for keyword in case["expected_answer_contains"]: assert keyword in answer for keyword in case["forbidden_keywords"]: assert keyword not in answer这个框架跑起来之后,每天CI都会执行。跑挂了的用例会自动拉出“当时的输入、工具调用轨迹、最终答案”,开发一眼就能定位是编排问题还是Prompt问题。这里我想特别强调:框架本身不值钱,值钱的是那个不断生长的测试数据集。
3.3 评分函数才是Prompt测试的灵魂
但纯pytest的逻辑断言解决不了“语义对不对”的问题。比如Agent这次回答“上月华东区销售额为1234.5万,较前月增长5%”,如果我断言“销售额”三个字在不在答案里,能过;但会不会数字张冠李戴?会不会“增长5%”是编的?这些没法靠关键词断言查出来。
所以我们引入了一个“评分器”组件,跑完Agent后自动给回答打分。具体做法是:先让Agent跑出答案,再把这个答案连同原始问题、工具结果、参考预期一起发给一个评审模型,让它按我们预设的四维标准打分。分数低于阈值(比如0.7)的用例自动进入“待人工复看”列表,由测试人员每周集中处理一次。
这个方案体验下来有两个好处:一是把人工从“全量看聊天记录”里解放出来,只需要盯低分样本;二是积累的“低分原因”反过来会变成下一轮Prompt优化的方向,形成闭环。
4. 工具调用与编排链路的验证:全链路mock与失败注入
如果说Prompt测试管的是Agent“想得对不对”,那工具调用和编排链路管的就是Agent“做得稳不稳”。这也是Agent项目里翻车最频繁的地方。用户在聊天里一句话,背后可能牵涉两三个工具调用、多个外部服务,任何一个环节出了问题,最终答案都可能错。
4.1 工具注册表与参数契约检查
我们的Agent框架里有一个工具注册表,所有可被调用的工具都定义了名称、描述、入参结构。测试介入时,第一个基础动作就是把注册表里所有工具逐一过一遍,校验三个点:
- 工具描述是否清晰,模型能不能根据描述做出正确选择;
- 入参是否定义完整,有没有必填参数缺失;
- 返回结构是否稳定,编排层解析结果时会不会因为字段结构变动而崩溃。
这个环节我们写了自动化脚本,每次框架或工具版本变更后自动执行。这里有个很典型的坑:开发在新建工具时,把返回结构里的字段名改了个大小写,结果Agent解析失败,直接导致用户问题无法完成。如果没有契约检查,这种失误很难在开发自测阶段暴露。
4.2 外部服务mock的边界要划清楚
Agent所在的业务流程里,通常要调企业内部的订单系统、报表系统、审批流、IM通知等外部服务。测试环境很难完整搭建一套真实系统,所以mock是必须的。但mock的边界如果划不清晰,测试很可能变成“自己和自己玩”。
我的做法是:按照“对Agent行为的观察价值”来决定mock粒度。如果被测点是Agent的工具选择与参数生成,那外部服务全部mock掉,返回固定结构的数据即可。如果被测点是Agent针对工具返回结果的推理能力,那么mock数据要设计成包含各种边界情况的样本——空结果、多记录、异常数据、字段缺失。
举个例子,用户问“查一下库存”,我mock一个工具返回“库存为0”。这时候正确的Agent行为应该是说“该商品暂无库存”,而不是傻乎乎地报一个0或者胡编一个仓库位置。这些边界样本需要测试提前跟业务方确认口径,做成固定样例,每次版本迭代反复验证。
4.3 失败注入:让Agent学会“体面地失败”
外部服务一定会失败。网络超时、接口报错、数据格式异常,这些在传统接口测试里测的是“报错信息是否友好”,但在Agent项目里测的是“Agent能不能从失败里恢复”。
我们当时专门做了一轮失败注入专项:
- 让报表接口随机返回500,看Agent是否会尝试重试;
- 让工具返回超时异常,看Agent是否会放弃任务并且向用户说清楚原因;
- 让某一步工具返回空数据,看Agent是否会编造数字还是诚实说“未查询到”。
结果发现,Agent在遇到工具异常时经常有两种错误表现:一种是彻底卡住反复调用同一个工具,形成死循环;另一种是假装任务完成,在回答里写“查询成功”,实际上什么数据都没有。这两个问题都是通过失败注入暴露的,修复方式分别是增加最大重试次数和增加“未获取到数据”的路径提示。如果没有这一轮测试,这些问题不到线上根本浮不出来。
5. 并发、会话隔离与幂等:Agent上线前的稳定性功课
Agent项目在功能层面跑顺之后,紧接着就是稳定性。很多人以为Agent系统的并发问题跟普通Web服务一样——多上几台机器、挂个负载均衡就扛住了。实际上,Agent因为“有状态会话”和“多次工具调用”的存在,并发测试的复杂度比普通接口高一大截。
5.1 有状态会话造成的并发陷阱
我们的Agent是带记忆的,同一个会话里Agent能记住用户前面说过的话。这样设计用户体验好,但也带来了并发问题:如果用户在A设备开了会话,又在B设备问了另一个问题,Agent能否正确区分两个会话的上下文?如果同一会话里用户连续发了两个指令,系统会不会把两个任务的上下文混在一起?
这些场景我整理成专门的测试用例去覆盖,重点验证的是“会话隔离”:
- 两个不同会话拥有独立记忆,互不串扰;
- 同一会话的并发请求按顺序处理,不产生覆盖写;
- 会话上下文不会因为工具调用而丢失或错乱。
当时真的测出过一个问题:两个用户同时唤起查询任务时,A用户拿到的报表数据被B用户的查询条件覆盖了。根因是工具实例不小心被设计成了全局单例,本来以为是小概率事件,结果并发一压就现了原形。
5.2 幂等性验证:同一个任务执行两次会怎样
Agent工具调用涉及外部系统时,幂等性问题比传统接口更隐蔽。比如用户点了一次“提交报销单”,Agent提交成功了,但响应超时导致界面重试,这时如果Agent又调了一次报销提交接口,就会重复提交,产生两笔报销单。
所以我在做自动化集成测试时,专门设计了一个幂等用例组:让同一输入、同一会话ID的任务被执行两遍,观察外部系统是否产生两条数据。这里的核心是:Agent系统必须在框架层面为工具调用生成唯一的调用ID,外部服务根据这个ID做去重。测试的工作就是验证“重复请求不会造成副作用”。
5.3 超时与重试策略的测试点
Agent一次任务里可能包含多个串行工具调用,总耗时天然比普通接口长。测试超时和重试策略时,要特别关注几个点:
- 超时阈值设在哪里合适,既不能太短导致正常任务被误判失败,也不能太长导致用户等待过久;
- 重试次数是否有限制,失败后是否退避,避免死循环;
- 重试是否会在外部服务已经处理成功的情况下重复提交。
我在压测环境里做过一次模拟:外部报表接口在30%的概率下延迟3秒。Agent系统原来的超时设的是2秒,结果导致大量任务被误判失败,用户体感极差。后来把超时调到4秒,并辅以一次重试,整体成功率才回到95%以上。这些参数需要测试用数据说话,不能拍脑袋。
6. 回归数据集的建设:让测试越做越轻松
Agent项目上线之后,测试工作并没有变少,反而更多了。因为模型在更新、Prompt在频繁调优、工具在不断增加,每一次改动都可能引入回归。如果没有一套能持续生长的测试数据体系,测试组永远在“救火”。
6.1 线上采集真实输入,转化成回归样本
最能发现问题的永远是真实用户。我们上线后做的第一件事,就是把线上用户输入(脱敏后)全部沉淀下来,按语义聚合成高频场景,再挑出有代表性的问题补充到回归数据集里。这些样本混杂着各种口语化说法、错别字、不完整句子,比测试工程师自己拍脑袋写的用例真实得多。
举个具体的例子:用户会说“查下上个月卖的怎么样”,也可能说“给我看看上个月华东的销量数据”,还可能说“上个月业绩咋样”。这三句话在字面上完全不同,但期望工具调用是一样的。把这些变体放进回归集里,就能持续检验Agent对语义泛化的能力。
6.2 数据回流机制:用户反馈是最好的测试用例
我们还做了一个“用户反馈转用例”的流水线:用户会给回答打低分或投诉,这些带着明确信号的数据,每周由测试人员做一次梳理,筛选出属于Agent本身问题的,直接转成回归样本。这条链路跑顺之后,每次版本迭代前都会先跑一遍“近期反馈集”,确保之前用户踩过的坑不会改着改着又冒出来。
这里我特别想提醒:那一类“低质量但不算错”的回答,比如答案不够简洁、没有给出关键数字、绕了很多话,是很难被离线自动检测出来的,只能靠用户反馈来捕捉。所以数据回流机制不是锦上添花,而是Agent质量闭环里的核心环节。
6.3 测试门禁与发布流程的结合
最后一步是把上面所有工作固化成门禁。我们的发布流程里设了三个关卡:
- 第一关:工具调用链路自动回归全部通过,覆盖注册表校验、参数契约、失败注入用例;
- 第二关:Prompt行为测试集通过率达到95%以上,低分样本无人复看确认风险;
- 第三关:并发与幂等抽测无问题,关键路径在压测环境下达到预期指标。
任何一次Prompt改动或Agent框架升级,都必须过这三道门禁才能合并发布。这个机制落地之后,团队最大的变化是:大家终于敢频繁调Prompt了。以前调一次怕一次,现在有回归数据和门禁兜底,改动效率反而大大提升。
我在这个项目里最深的体会是:Agent的测试工作从来不应该是开发的“下游”,而应该跟需求分析同步启动。测试人员前期的“可测性对齐”“评判体系建立”“样本集沉淀”,这三件事做得越扎实,后面开发迭代就越顺畅。等到项目跑稳了,你会发现测试手里最值钱的资产不是测试用例本身,而是那套从线上反馈里不断生长的数据集。它让每一次Prompt优化都有据可依,也让每一个版本的发布都更有底气。