Multi-Agent 系统怎么测试?Mock LLM + 图执行回放,这套方案一次讲透
你有没有遇到过这种情况:Agent 逻辑写完了,每次手跑一遍感觉没问题,结果上线后某个边界 case 一触发,整个 Graph 就卡住了。或者修了一个节点的逻辑,另一个节点莫名其妙输出异常,排查半天发现是状态流转写错了。
你有没有遇到过这种情况:Agent 逻辑写完了,每次手跑一遍感觉没问题,结果上线后某个边界 case 一触发,整个 Graph 就卡住了。或者修了一个节点的逻辑,另一个节点莫名其妙输出异常,排查半天发现是状态流转写错了。
这些问题的根源,都是 Multi-Agent 系统缺少系统性测试。LLM 调用是随机的,每次结果不一样,传统的单元测试根本用不上。但如果完全依赖集成测试,每跑一次就真实调 API,速度慢、成本高,而且结果不可复现。
这篇我们从头拆解 Multi-Agent 的测试体系:Mock LLM 怎么写、Graph 执行回放是什么、分层测试策略怎么设计,以及最容易踩的几个坑。
01 为什么 Multi-Agent 测试这么难:三个本质障碍
Multi-Agent 系统的测试难点,和普通业务逻辑不一样,有三个本质障碍:
障碍一:LLM 输出随机性。 同一个 prompt,每次调用 GPT-4 返回的内容都不一样。你根本无法写出「期望输出 = 固定字符串」这种断言。传统单元测试的基础假设——函数幂等性——直接失效。
障碍二:跨节点状态传播。 一个 Multi-Agent Graph 里有 Supervisor、ResearchAgent、WriterAgent 三个节点。ResearchAgent 的输出进入 State,WriterAgent 再从 State 读取。如果你想测试 WriterAgent 的行为,必须先有一个合法的 State——但这个 State 从哪来?
障碍三:工具调用的副作用。 Agent 会调用真实工具:搜索 API、数据库写入、邮件发送。测试时如果不隔离这些副作用,每跑一次测试都可能产生真实数据污染。
三个障碍的解法分别是:Mock LLM(让输出可预测)、固定 State 构造(独立测每个节点)、工具 Stub(隔离副作用)。这三件事搞定了,Multi-Agent 才有测试基础。
02 FakeListChatModel:让 LLM 输出变成你说了算
LangChain 提供了两个 Mock LLM 工具。你可以把它们理解成「剧本阅读机」——提前写好 AI 对话剧本,按顺序逐句输出,接口和真实 LLM 完全兼容,但输出 100% 可预测。
importFakeListChatModelFakeChatModelfrom"@langchain/core/utils/testing"importAIMessagefrom"@langchain/core/messages"// 方案A:文本响应(Supervisor 路由 + ResearchAgent + WriterAgent)constnewFakeListChatModelresponsesJSONstringifynext"ResearchAgent""研究完成:RAG 优化方案包括 Rerank、混合检索、分块策略三块...""Final Answer: 基于研究结果,最终建议采用混合检索方案。"// 方案B:支持工具调用(Agent 需要调用 Tool 时)constnewFakeChatModelresponsesnewAIMessagecontent""tool_callsname"searchWeb"argsquery"RAG optimization"id"call_001"type"tool_call"newAIMessagecontent"根据搜索,答案是 RAG + Rerank。"// 两种方案使用方式相同,直接替换真实 LLMconstcreateReactAgentllmtoolsconstawaitinvokemessagesrole"user"content"如何优化 RAG?"expectmessagesat1contenttoContain"Rerank"
怎么选?
| 场景 | 推荐工具 |
|---|---|
| 只测文本输出的 Agent | FakeListChatModel(代码更简洁) |
| 需要测试工具调用路径 | FakeChatModel + 手写 AIMessage |
| 需要模拟 LLM 报错 | 子类化,重写 _generate 抛异常 |
| 需要无限响应(防列表耗尽) | 子类化,重写 _generate 返回固定值 |
03 MemorySaver + 图执行回放:多轮测试与 Bug 重放
MemorySaver(多轮测试):Multi-Agent 系统往往需要多轮交互,MemorySaver 把 Checkpoint 存在进程内存里,测试结束自动销毁,零外部依赖。
图执行回放(Bug 重现):一个 5 节点的 Graph 在第 4 个节点出了 Bug,修好后不用从头重跑——直接从第 3 个节点的 Checkpoint 恢复,只重跑第 4、5 节点。
importMemorySaverfrom"@langchain/langgraph"importfrom"../src/supervisor-graph"// ===== 多轮对话测试 =====constnewMemorySaverconstcreateSupervisorGraphllmbuildFakeLLMconstconfigurablethread_id`test-${Date.now()}`awaitinvokemessagesrole"user"content"研究 RAG 优化"constawaitgetStateexpectvaluesnextAgenttoBe"ResearchAgent"// 第一轮路由正确constawaitinvokemessagesexpectmessagesat1contenttoContain"研究完成"// ===== 图执行回放:从出错节点重跑 =====constawaitgetStateHistoryconfigurablethread_id"prod-crashed-abc"constfindh =>nextincludes"writerAgent"constawaitinvokenullconfigurablethread_id"replay-debug-001"checkpoint_idconfigconfigurablecheckpoint_idexpectmessagesat1contentnottoContain"Error"
04 工具 Stub + 节点单元测试:最小粒度的隔离验证
工具 Stub 的核心是:用一个和真实工具同名但行为可预测的假对象替换掉真实工具,同时记录调用次数用于断言。节点单元测试 则是不启动 Graph,直接调用单个节点函数,给固定 State 输入,验证输出 State 的变化。
importfrom"@langchain/core/tools"importfrom"zod"importfrom"../src/nodes/research-agent"// ===== 工具 Stub:可预测 + 带调用计数 =====let0consttoolasyncreturn`关于 "${query}" 的固定结果:RAG 优化有三种策略...`name"searchWeb"// ⚠️ 名字必须和真实工具完全一致!description"搜索互联网"schemaobjectquerystring// ===== 节点单元测试:直接测节点函数 =====describe"ResearchAgent 节点"() =>it"应将搜索结果写入 state.research,并路由到 WriterAgent"asyncconstawaitresearchAgentNodemessagestask"research RAG"researchnullnextAgent"ResearchAgent"llmtoolsexpectresearchtoContain"RAG"expectnextAgenttoBe"WriterAgent"expecttoBe1// 确认工具只被调了一次it"工具失败时,应记录错误并回 Supervisor,不崩溃"asyncconsttoolasyncthrownewError"API 超时"name"searchWeb"description"..."schemaobjectquerystringconstawaitresearchAgentNodemessagestask"..."researchnullnextAgent"ResearchAgent"llmtoolsexpecterrorstoContain"API 超时"expectnextAgenttoBe"Supervisor"
05 三层测试金字塔:从单元到 E2E 的完整策略
把以上工具组合起来,Multi-Agent 系统的测试分三层,各有侧重:
三层策略概览(从底到顶):节点单元测试(100+ 个,每次提交跑,< 100ms)→ 图集成测试(20-30 个,每次 PR 跑,测路由逻辑)→ E2E 端到端测试(3-5 个,每天跑一次,真实 LLM + 真实工具)。
// 第二层:图集成测试——专门测路由逻辑it"任务是搜索时,应路由到 ResearchAgent 而非 WriterAgent"asyncconstnewFakeListChatModelresponsesJSONstringifynext"ResearchAgent"constconfigurablethread_id`routing-${Date.now()}`awaitinvokemessagesrole"user"content"帮我搜索 LangGraph 文档"constawaitgetStateexpectvalueslastExecutedAgenttoBe"ResearchAgent"expectvalueswriterDrafttoBeUndefined// WriterAgent 不应被触发
06 常见坑
坑 1:响应列表耗尽。FakeListChatModel 的 responses 用完就报错。Multi-Agent 里 LLM 调用次数取决于路由逻辑,比你以为的多。解决方案:先打印图的执行轨迹,数清楚调用次数再填 responses。
坑 2:thread_id 测试间污染。多个测试共用同一个 thread_id,上一个测试的 State 会泄漏。每次测试必须生成唯一 thread_id:test-${Date.now()}-${Math.random()}。
坑 3:Stub 工具名字对不上。工具 name 字段必须和真实工具完全一致,否则 Agent 报「找不到工具」而不是「Mock 不对」,排查方向跑偏。
坑 4:只断言最终输出,不验证中间状态。用 graph.getState() 同时检查关键中间状态,出问题能秒定位是哪个节点。
// ❌ 只看最终输出,不知哪个节点坏了expectmessagesat1contenttoContain"完成"// ✅ 同时验证中间状态,精确定位constawaitgetStateexpectvaluesresearchResulttoBeDefinedexpectvalueswriterDrafttoContain"完成"expectvaluessupervisorDecisiontoBe"FINISH"
坑 5:只测 Happy Path。工具超时、LLM 格式错误、节点抛异常——这些才是生产最常出问题的地方,必须有对应测试覆盖。
总结
Mock LLM 是 Multi-Agent 测试的核心武器:用 FakeListChatModel / FakeChatModel 替换真实 LLM,把随机输出变成可预测的剧本,测试才能稳定跑起来。
图执行回放让生产 Bug 的复现成本降为零:保存出错时的 Checkpoint,测试里直接从出错节点重放,不需要重建完整上下文。
三层金字塔各有分工:节点单元测试保底(快)、图集成测试测路由(中等)、E2E 做冒烟(慢),不要把所有压力堆到 E2E。
graph.getState() 是最被低估的测试能力:在任意节点后读取完整 State,精确断言中间状态,出问题秒定位是哪个节点。
唯一 thread_id + 工具名精确匹配:这两个细节是最常踩的坑,提前规避能节省大量排查时间。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

更多推荐


所有评论(0)