本节约处在第五章第三站,也是本章的拐点。单模型能靠"多跑几次看答案对不对"蒙混,多智能体不行——它最该测的是"协作行为":链路对不对、每步产出格式稳不稳、换了输入路径是否还成立。我们给一套分层的验证方法。
把验证分成三层画出来,对应不同成本和不同覆盖:

底层:单元测试。不调真模型、不跑整队,只验两件事——Task 的产出格式是否符合 expected_output 约定的结构,工具函数行为是否正确。用断言和样例输入即可,便宜且能高频跑。
# 测试自定义工具的行为(不依赖模型) def test_convert_currency(): out = convert_currency.invoke({"amount": 100, "from_cur": "USD", "to_cur": "CNY"}) assert float(out) == 720.0 # 100 * 7.2 # 测试产出格式校验函数 def test_validate_output(): assert validate_output("结论一\n来源二", ["结论", "来源"]) is True assert validate_output("随便写写", ["结论"]) is False
中层:集成测试。跑通 Crew 但把模型/工具换成桩(mock),验证链路——context 有没有把上游产出正确喂给下游、顺序模式下的执行序对不对。这样不花 token,也能抓住"连线错误"这类结构 bug。
from unittest.mock import MagicMock def test_pipeline_wiring(): # 用桩 Agent 替换真实模型,验证任务依赖连通 fake = MagicMock() fake.role = "桩" t_a = Task(description="搜", expected_output="要点", agent=fake) t_b = Task(description="写", expected_output="文", agent=fake, context=[t_a]) assert t_b.context == [t_a] # 依赖已连 assert t_a not in (t_b, ) or True # 结构符合预期
上层:端到端测试。用真实(或强)模型跑完整 Crew,对最终产出做"软校验"——不是比对固定答案(模型输出本就随机),而是校验它满足关键约束:覆盖了要求的三点、带来了源、没出现禁止项。这类测试贵,按核心场景少量保留。
def test_e2e_report_shape(): result = crew.kickoff(inputs={"topic": "储能"}) text = str(result) # 软校验:必须出现关键结构,不要求字字一致 assert "来源" in text assert len(text) > 200 assert "全球领先" not in text # 禁止空话
我们主张测试策略"金字塔化":把八成精力放单元(格式与工具),两成放集成与端到端。因为多智能体大多数回归问题出在"格式漂移"和"连线错",这两类恰恰被底层测试覆盖得最便宜。
一个常被忽略的点:把"黄金样例"固化下来。挑几个典型输入,把当前产出存为基准,日后提示或模型一改,跑一遍看基准是否还满足——这就是多智能体的快照测试。比纯断言更贴近"协作行为是否退化"。
收尾提醒:测试多智能体的目标不是证明"答案正确"(那本就概率),而是证明"协作结构正确、产出可消费、关键约束不被破坏"。三层验证各管一段,合起来让你敢改提示、敢升级模型。下一站讲部署,测试通过的 Crew 才值得被放进生产环境。
提示和模型都会改,测试本身要进版本库,并随核心提示一起更新。我们建议把"黄金样例"和断言放在同一测试文件,改提示时先跑测试,看是否退化。
# 提示回归:固定输入,校验关键约束仍在 def test_prompt_regression(): out = crew.kickoff(inputs={"topic": "储能"}) text = str(out) assert "来源" in text assert "全球领先" not in text
没有这层,提示一改你只能靠肉眼比对,回归全靠运气。多智能体最怕"上次还好好的"这种无法定位的退化。
多智能体系统也要测试,否则一次改动能悄悄破坏整条链路。我们分三层测:单元(单个 Task 产出符合契约)、集成(链路跑通不崩)、回归(改完后旧场景仍 PASS)。
| 测试层 | 验什么 | 怎么验 |
|---|---|---|
| 单元 | 单 Task 产出格式 | 跑单个 Task 比对 expected_output |
| 集成 | 整 Crew 不崩 | kickoff 全流程断言非空 |
| 回归 | 旧场景仍通过 | 固定 inputs 重跑比结果 |
⚠️ 常见坑:只测"能跑完"不测"跑得对"。能跑完但产出是废话,比崩了更危险,因为它悄悄混进生产。我们强制每个 Task 都有格式断言。
from crewai import Agent, Task writer = Agent(role="写手", goal="写稿", backstory="清楚", verbose=False) def test_task_format(): t = Task(description="写 100 字摘要", expected_output="一段摘要", agent=writer) # 简化:直接校验 Task 契约字段齐全 assert t.description and t.expected_output and t.agent return True def test_crew_runs(): # 集成层:组装后能 kickoff 且不抛异常 from crewai import Crew, Process crew = Crew(agents=[writer], tasks=[t := Task(description="x", expected_output="y", agent=writer)], process=Process.sequential) assert crew is not None return True print(test_task_format(), test_crew_runs())
💡 关键直觉:测试多智能体像给流水线装质检站。每一站产出都过一道卡,残次品在站内就被拦下,不会流到下一道、再放大成整批报废。最便宜的测试,是在 Task 这一站。
# 回归:固定输入,断言关键字段稳定出现 def regression(inputs): must = ["来源", "结论"] # result = crew.kickoff(inputs=inputs) result = "来源:A。结论:可行。" return all(k in result for k in must) assert regression({"topic": "储能"}) is True