Agent 测试:先测确定性部件,再测真实轨迹
Agent 的测试不能只停留在“输出像不像对”。更有效的做法是分层验证:先测确定性逻辑,再测真实集成,最后补轨迹评估和回归门槛。
01.Agent 测试为什么比普通后端更难
普通软件里,我们常常可以直接断言:
- •输入是什么
- •输出应该是什么
- •中间步骤是否满足确定规则
但 Agent 系统往往不这么老实。它的问题在于:
- •模型输出有非确定性
- •工具调用会受外部服务影响
- •一次任务由多个步骤组成
- •同一个目标可能走出不同路径
这意味着,Agent 测试不能只靠“对最终文本做字符串比对”。更有效的办法,是把系统拆成不同层级,分别验证。
LangChain 当前官方测试文档给出的思路很实用:把测试分成 unit tests、integration tests 和 evals。这个分层基本就是 Agent 场景下最值得采用的默认做法。
02.一条更靠谱的测试主线
我更推荐把 Agent 测试看成三层:
- •单元测试:验证确定性部件
- •集成测试:验证真实模型、真实工具和真实网络
- •轨迹评估:验证整个 agent 执行过程是否符合预期
这三层不是互相替代,而是互相补位。
03.第一层:先把确定性部件测稳
Agent 虽然不确定,但它内部一定仍有很多确定性逻辑:
- •路由函数
- •参数校验
- •状态 reducer
- •工具封装
- •安全规则
这些部分最适合做单元测试,因为它们快、便宜,而且失败时定位清楚。
例如,一个简单的路由函数就应该直接测:
from typing import Literal
def route_by_intent(intent: str) -> Literal["search", "execute", "handoff"]:
if intent in {"faq", "docs"}:
return "search"
if intent in {"run", "apply"}:
return "execute"
return "handoff"
def test_route_by_intent():
assert route_by_intent("faq") == "search"
assert route_by_intent("run") == "execute"
assert route_by_intent("unknown") == "handoff"这种测试看起来不起眼,但它能稳定托住系统最关键的骨架。
工具也值得单独测
很多 Agent 问题不是模型出错,而是工具封装本身不稳。
例如:
- •参数 schema 不严
- •错误处理不一致
- •返回结构不稳定
这类问题完全应该在单元测试里先拦住,而不是等到线上 trace 里再看。
04.第二层:用集成测试验证真实链路
LangChain 官方测试文档强调过一个很重要的现实:Agent 应用往往更依赖 integration tests,因为它们天然要串联多个组件,还要面对 LLM 的非确定性。
这层测试的目标不是做精确字符串断言,而是验证:
- •模型和工具是否真的能协同工作
- •真实凭证、schema、网络是否可用
- •延迟和成本是否还在可接受范围
一个更贴近工程的断言方式通常是:
- •是否调用了正确工具
- •参数是否符合预期
- •最终结果是否满足结构约束
- •是否在步数上限内完成
而不是要求输出句子逐字完全一致。
05.第三层:Agent 真正要补的是轨迹评估
这是普通软件测试和 Agent 测试差异最大的地方。
Agent 的很多质量问题,不会体现在“最后一句话对不对”,而会体现在轨迹里:
- •工具选错
- •参数传错
- •步数过多
- •本可直接回答却绕远路
- •不该调用高风险工具时却调用了
所以 LangChain 官方测试文档把 evals 也放进测试主线里,是很合理的。
对 Agent 来说,真正有价值的评估对象通常包括:
- •工具选择是否正确
- •参数格式是否正确
- •轨迹是否满足约束
- •最终回答是否可用
06.一个实用的测试组合方式
如果只保留一套最小但够用的组合,我会建议:
1. 单元测试测确定规则
- •路由
- •工具封装
- •guardrails
- •状态合并
2. 集成测试测真实 happy path
- •一两个最关键任务
- •真实工具调用
- •真实模型
- •真实凭证
3. 评估测代表性轨迹
- •常见任务
- •高风险任务
- •边界任务
- •回归任务
这个组合通常比“为所有地方都写大而全的 LLM 测试”更稳。
07.LangGraph 场景下,值得测节点和部分执行
如果你的系统已经进入 LangGraph,自定义图结构会越来越多。这时除了测整图,还应该测:
- •单个节点
- •条件边
- •中间状态
- •部分执行
LangGraph 官方测试文档里就强调了一个很实用的模式:每个测试里重新创建图,并为测试编译新的 checkpointer。这样能减少状态污染,也更容易验证图的不同分支。
一个简化示例如下:
from langgraph.checkpoint.memory import InMemorySaver
def build_graph():
...
def test_graph_happy_path():
graph = build_graph().compile(checkpointer=InMemorySaver())
result = graph.invoke(
{"messages": [{"role": "user", "content": "帮我查北京天气"}]},
{"configurable": {"thread_id": "test-1"}},
)
assert result["messages"]这个例子看上去简单,但它表达了两个重点:
- •图测试应该隔离状态
- •测试不仅关注最终输出,也关注中间 state 是否合理
08.不要把“自动生成测试用例”误当成测试体系
AI 当然可以帮助你生成测试代码,但这和“系统已经可验证”是两回事。
自动生成测试最常见的问题是:
- •只会补 happy path
- •断言很弱
- •不理解真实业务风险
- •生成很多噪音测试
所以更稳的用法是:
- •让 AI 辅助你补测试样板
- •让人定义风险边界和验收规则
- •把测试门槛放在关键行为上,而不是测试数量上
09.把评估接进回归流程,才算真正形成闭环
LangSmith 的 evaluation concepts 文档很明确地区分了 testing 和 evaluation:
- •testing 更像硬性门槛,不通过就不能发
- •evaluation 更像质量度量,用于比较和持续改进
这两个概念最好配合使用。
例如一个实用的回归门槛可能是:
- •单元测试必须全绿
- •核心集成测试必须全绿
- •关键数据集上的离线评估不能低于当前基线
这比只看“模型回答感觉还行”要稳得多。
10.一套更接近工程落地的测试清单
如果你在做一个真实 Agent 项目,我建议优先保证下面这些测试存在:
- •工具 schema 和错误处理的单元测试
- •关键路由和 guardrails 的单元测试
- •至少 1 到 3 条真实任务集成测试
- •一组小而精的离线评估数据集
- •针对历史事故和坏例子的回归集
先把这些建起来,再谈更复杂的自动化生成和大规模评估,会更符合投入产出比。
11.总结
Agent 测试最容易走偏的地方,是一上来就试图“验证整个智能系统”,结果什么都测不稳。更靠谱的顺序是:
- •先测确定性部件
- •再测真实集成
- •再测整条轨迹和回归质量
这样做的好处是,问题一旦出现,你更容易判断到底是规则错了、工具错了,还是模型决策本身退化了。