你已经试过单条链路的检索问答,现在想拆成多个智能体分工:一个改写问题、一个决定检索哪些库、一个核对证据、一个写答案。问题出在串起来之后——谁负责什么、消息怎么在节点间传、某一步卡住要不要重试、跑飞了怎么止损。这篇按搭建顺序给一套可执行的工程做法,不涉及协议兼容和客户端配置。
单条链路的检索问答跑通之后,很自然会想拆成多个智能体分工:一个改写问题、一个决定检索哪个库、一个核对证据、一个写答案。拆开本身不难,难的是串起来之后——谁负责什么、消息怎么在节点之间传、某一步卡住要不要重试、整条链路跑飞了怎么止损。这篇按搭建顺序讲一套能落地的工程做法,不涉及协议兼容和客户端配置。
先明确一点:多智能体不是目标,是手段。如果你的问题只有一类、检索源只有一个,单链路反而更稳。只有当任务里出现了明显不同的子职责(改写、路由、核对、生成),而且它们的提示词互相打架时,拆角色才有收益。下面按状态、编排、限制、日志四块往下走。
先把状态和角色划清,别急着写节点
很多人第一步就去写节点函数,结果写着写着发现状态字段不够用,回头改一遍全图。正确顺序是先定状态结构,再定角色,最后才写节点。状态是节点之间唯一的共享内存,定得好,节点之间就不用互相知道对方的存在。
- 列出所有需要在节点间传递的字段:原始问题、改写后的问题、命中的片段与来源、当前轮次、是否已足够作答、错误标记、累计调用次数与已耗时;
- 给字段标上归属:哪个节点负责写、哪些节点只读,避免两个节点同时往同一个字段里塞东西导致互相覆盖;
- 按职责单一拆角色,常见起点是问题改写、检索决策、证据核对、生成作答这四类,任务简单时两三个就够,硬凑五个只是把一次调用拆成多次;
- 把终止条件写进状态:什么情况下不再检索、什么情况下直接返回资料不足,这个判断交给状态字段而不是让模型自由发挥;
- 想清楚哪些字段是给下游节点用的、哪些只是给日志用的,日志字段不参与判断,避免污染决策逻辑。
判断标准很简单:如果某个字段只有日志在看,就别让它影响任何分支条件。反之,凡是参与条件边的字段,都要在状态定义里写清默认值,否则第一次跑就可能因为字段缺失走到意料之外的路径。
节点与边的编排:循环、分支与兜底路径
检索和证据核对天然是循环结构:检索一轮、看证据够不够、不够就回跳改写。LangGraph 里用条件边控制回跳很直接,但一定要给循环设最大轮次上限,跑到上限强制走生成节点。不要指望模型自己判断该停了,实际跑起来它经常觉得还能再查一次。
每条分支都要写清失败出口。检索返回空结果时,是回跳改写重试,还是直接降级到不依赖检索的作答?这两个都得提前定,否则线上遇到空结果就是空转到超时。建议把分支画成一张表,一眼能看出每个节点的正常出口和异常出口分别指向哪里。
| 节点 | 正常出口 | 异常出口 |
|---|---|---|
| 问题改写 | 进入检索决策 | 改写失败沿用原问题继续 |
| 检索决策 | 按库路由并发检索 | 路由为空则降级到默认库 |
| 证据核对 | 证据充分进入生成 | 多轮仍不足则标记资料不足 |
| 生成作答 | 输出并结束 | 模型报错返回兜底话术 |
还有一点容易忽略:节点之间不是纯函数调用,一次请求里可能多次打模型接口。建议在状态里带上累计调用次数和已耗时,作为后面降级判断的依据。没有这两个数,降级就只能凭感觉。
超时、并发与降级:让链路跑不飞也跑得动
超时要按节点单独设,而不是给整条链路设一个总超时。检索节点的响应量级和生成节点差很多,混在一起设,结果是快的节点在等慢的节点,慢的节点又把整条链路拖垮。每个节点超时后返回什么、是否算失败,也要在编排里写清楚。
批量跑任务时,并发控制放在图外面:单次请求内部尽量串行,多个请求之间排队分批,遇到限流按指数退避重投。能开多少并发取决于你走的是哪条模型通道,各家的口径不一样,到站内 /inference 和 /official-api 核对当时的限制说明,别照搬别人的配置。
- 先定降级档位换更快的模型 → 缩减检索条数 → 跳过证据核对 → 返回兜底话术,四档写成配置,不要散在代码里。
- 每档设触发条件给每档绑定明确的信号,比如累计耗时或累计调用次数超过阈值,而不是等报错才降。
- 验证降级路径手动把某一档打开跑一遍,确认最终能正常返回,而不是降级后直接抛异常。
- 记录降级发生次数把降级档位写进日志字段,复盘时能看出是偶发还是通道整体变慢。
判断标准:如果降级只能靠改代码触发,那它就不是降级,是紧急修 bug。真正的降级应该改配置就生效,出问题时不用重新发版。
日志与评测:让每次跑崩都能复盘
多智能体链路最怕的不是报错,是悄无声息地给出一个看起来对、其实没检到证据的答案。所以日志要记到能还原整条路径的程度,缺了哪一项,复盘时就只能靠猜。
- 请求标识:把一次运行的所有节点日志串起来;
- 节点轨迹:走了哪些节点、顺序是什么、有没有触发回跳;
- 每节点耗时与调用次数:定位慢在哪一步;
- 检索命中数:判断是检索的问题还是生成的问题;
- 最终出口:正常作答、兜底话术还是降级收尾;
- 降级档位:本次是否触发了降级、触发在第几档。
自己攒一组小而固定的用例集,覆盖正常问答、无命中、资料冲突、需要多轮检索这几类。每次改提示词或换模型后重跑一遍做对比,不要靠单次手感判断好坏。用例集不用大,但要稳定,变来变去的用例集没有可比性。
上线之后,把接口可用性和答案质量分开看:前者出问题多半是通道或限流的事,后者掉了通常是编排逻辑或提示词变了。站内 /tools/quality-monitor 这类工具能持续盯质量波动,具体指标和当时的数据到页面上看,别把两类问题混在一起排查。
常见问题
LangGraph 和直接写 Python 循环有什么区别,非要用它吗
不是必须。LangGraph 的价值在于把状态流转和分支条件显式画出来,多智能体来回跳的时候好调试、好加检查点。如果你的流程是线性的、最多一层 if,直接写函数更省事。判断标准是:节点数超过四个、存在循环回跳、或者你需要在某一步中断恢复,再上编排框架。
多个智能体轮流调模型,成本和延迟会不会翻好几倍
会明显上升,一次用户提问可能触发三到五次模型调用。控制办法是让简单判断走小模型,只把改写和最终生成交给强模型,同时在状态里设调用次数上限。具体单价和用量测算可以到 /tools/model-cost-calculator 按自己的调用次数套一遍,再决定要不要砍环节。
检索结果不好,是编排的问题还是检索的问题
先把两者拆开验:把编排固定成一条直连链路,只改检索部分,看召回质量有没有变化;如果不变,问题在检索侧,跟智能体分工没关系。常见情况是分块策略和嵌入模型不匹配,跟多智能体编排无关,别一上来就加节点。