智能体测试不能只做单元验证
智能体测试不能只做单元验证

智能体的风险常出现在多个工具串联之后:它是否用了正确的身份、是否重复调用、遇到超时会不会停止。单元测试依然必要,但还应在隔离环境中检查调用轨迹、权限拒绝和异常降级,避免把自然语言回答看成唯一结果。
在传统的软件工程中,写好单元测试(Unit Test)往往能覆盖 80% 以上的代码缺陷。但在 AI Agent 和多模态交互系统的开发里,仅仅依靠单元测试去校验单个 Tool 函数或者单个 Prompt 模板,上线后依然会频繁崩溃。Agent 的核心特征在于状态机推演的自发性与多轮 Tool Calling 的涌现行为,这些问题只有在端到端(E2E)的轨迹测试与沙箱评估中才能暴露出来。
1. 为什么单元测试对 Agent 系统往往会失效
单元测试的核心前提是确定性断言:给定输入 $A$,输出必须严格等于 $B$。
然而在 Agent 系统中,即使 Tool Calling 接口本身通过了 100% 的单元测试覆盖率,当模型在第 3 轮思考中因为网络抖动收到的工具返回格式稍有偏差时,它可能会选择调用另一个完全意想不到的工具,甚至陷入死循环。
[常规单元测试]
输入 Mock 工具参数 ➔ 执行 Tool Function ➔ 断言 Return Value (可以通过,但无法验证 Agent 决策逻辑)
[Agent 轨迹测试]
用户目标 ➔ Agent 多轮思考 ➔ 工具 A ➔ 工具 B ➔ 条件分支 C ➔ 断言完整执行 Trajectory 与最终状态
单点函数的正确,无法保证 Agent 在多轮交互中不偏离最初的业务目标。
2. 多模态交互中的视觉文本对齐测试困境
在多模态 Agent 场景下(如根据 UI 截图自动点击或解析工单),测试复杂度会呈现指数级上升。
测试人员面临的典型困局是:
- 图像渲染微小差异导致的定位失效:前端 UI 按钮位置偏移了 5 个像素,多模态模型识别出的 bounding box 坐标就会改变,导致原本固定的坐标点击脚本失效。
- 多轮对话中图像历史的积累污染:模型在第 1 轮识别了图 A,第 2 轮识别了图 B,在第 3 轮回答时却把图 A 的特征误应用到了图 B 上。
这类视觉-文本跨模态的上下文污染,只有通过模拟多轮状态演进的集成测试才能捕捉到。
3. 构建 Agent 模拟沙箱与轨迹断言(Trajectory Assertion)
为了全面测试 Agent,我们需要建立一套能够 Mock 外部工具、记录 Agent 思考 Trajectory 并进行状态断言的测试框架。
以下是用 Python 实现的 Agent 轨迹测试器示例:
import json
from typing import List, Dict, Any, Callable
class TrajectoryAssertionError(Exception):
pass
class MockToolRegistry:
def __init__(self):
self.tools: Dict[str, Callable] = {}
self.call_logs: List[Dict[str, Any]] = []
def register(self, name: str, func: Callable):
self.tools[name] = func
def execute(self, tool_name: str, tool_args: Dict[str, Any]) -> Any:
self.call_logs.append({"tool": tool_name, "args": tool_args})
if tool_name not in self.tools:
raise KeyError(f"Mock 工具不存在: {tool_name}")
return self.tools[tool_name](**tool_args)
class AgentTrajectoryEvaluator:
def __init__(self, agent_under_test, mock_registry: MockToolRegistry):
self.agent = agent_under_test
self.mock_registry = mock_registry
def assert_trajectory(
self,
user_prompt: str,
expected_tool_sequence: List[str],
max_allowed_turns: int = 5
):
"""
断言 Agent 在执行特定任务时,调用的工具顺序与状态转移符合预期
"""
# 1. 运行 Agent
result = self.agent.run_task(user_prompt, max_turns=max_allowed_turns)
actual_sequence = [log["tool"] for log in self.mock_registry.call_logs]
# 2. 轨迹序列强校验
if actual_sequence != expected_tool_sequence:
raise TrajectoryAssertionError(
f"Agent 调用的工具轨迹不匹配!\n"
f"期望序列: {expected_tool_sequence}\n"
f"实际序列: {actual_sequence}\n"
f"实际执行日志: {json.dumps(self.mock_registry.call_logs, indent=2)}"
)
# 3. 步数上限断言,防止潜在死循环
if result["total_turns"] > max_allowed_turns:
raise TrajectoryAssertionError(
f"Agent 运行轮次 ({result['total_turns']}) 超过最大限制 ({max_allowed_turns})"
)
print(f"✅ Agent 轨迹测试通过!共消耗 {result['total_turns']} 轮,工具轨迹: {actual_sequence}")
# 测试用例示范
def test_order_cancellation_agent_flow():
registry = MockToolRegistry()
# 注册 Mock 业务 API
registry.register("search_order", lambda order_id: {"status": "PAID", "amount": 100})
registry.register("cancel_order", lambda order_id: {"success": True})
# 假设 MockAgent 为带测试的 Agent 实例
class DummyAgent:
def run_task(self, prompt, max_turns):
# 模拟 Agent 正确执行了 search -> cancel
registry.execute("search_order", {"order_id": "ORD_123"})
registry.execute("cancel_order", {"order_id": "ORD_123"})
return {"total_turns": 2}
evaluator = AgentTrajectoryEvaluator(DummyAgent(), registry)
evaluator.assert_trajectory(
user_prompt="帮我取消订单 ORD_123",
expected_tool_sequence=["search_order", "cancel_order"],
max_allowed_turns=3
)
if __name__ == "__main__":
test_order_cancellation_agent_flow()
代码的关键点在于 assert_trajectory 函数。它不再关心模型单次吐出的自然语言标点符号是否完美,而是强行拦截并记录 Agent 调用的工具序列 actual_sequence。如果 Agent 跳过了身份校验工具直接去调用取消订单 API,测试会立刻抛出异常。
4. 混沌工程:在网络抖动与异常工具返回下测试 Agent 容错
在真实生产环境中,工具 API 可能会遇到超时、返回 500 错误或者吐出非法 JSON。Agent 在面对这些异常时能否实现优雅降级,是考验系统工程质量的试金石。
推荐在测试沙箱中加入混沌注入机制:
- 工具延迟与超时注入:随机让 10% 的 Mock API 延迟 3 秒返回,观察 Agent 是否会触发无休止的重试。
- 工具返回坏数据注入:让 API 随机吐出空对象
{}或格式错误的 HTML,测试 Agent 是否会自动修正参数重试,还是直接抛出未捕获的 KeyError。 - Token 预算耗尽注入:在第 2 轮强制截断 Context Window,验证 Agent 框架能否正确触发工程保底逻辑,把错误平滑传给前端。
5. Agent 测试体系的三层金字塔构建
构建稳健的 Agent 测试架构,应当形成分层的测试金字塔:
| 测试层级 | 测试对象 | 断言焦点 | 执行频率 |
|---|---|---|---|
| 底层 Tool 单元测试 | 本地 Python / Go 工具函数 | 边界参数、SQL 注入防御、数据格式正确性 | 每次 Git Commit 自动触发 |
| 中层 轨迹沙箱测试 | Agent 状态机与 Tool Calling 循环 | 工具调用顺序(Trajectory)、死循环熔断、异常降级 | 每日 Nightly Build 自动运行 |
| 顶层 真实基准评估 | 端到端大模型 + 真实多模态输入 | 任务完成率(Task Success Rate)、平均 Token 消耗、P95 延时 | 版本发布前全量回归 |
把测试重心从简单的“字符串匹配”转移到“工具调用轨迹与状态机断言”上,才是保障 AI Agent 系统在复杂生产环境中稳妥运行的根本保障。
更多推荐



所有评论(0)