技术 · 熟练
MCP 一慢,整场会话就被掐断
起任务 · 存句柄 · 下次取结果
agent 跑不完的原因里,有一类跟模型能力无关:它调用的那个工具太慢,而它是同步等的。同步等意味着这一步的整段时间都被占住 —— 网关有响应超时、平台有会话上限,两者都会在工具还没回来时把这次会话掐掉,于是你看到的是「没有输出」而不是「工具失败」。这一页给的是一条结构性改法,只动接口形状、不动模型:把慢工具拆成「起任务」与「查状态」两个工具 —— agent 先拿到一个句柄(job id),存进上下文,下一次调用再取结果。配套还有两条同样便宜的约束:给每个工具设调用次数上限(工具返回没有明确结论时,agent 会一直重试同一个调用),以及要求工具给出明确的响应契约(什么算成功、什么算失败、什么都没有时分几种情况)。同批实践者的原话是:改用异步模式的会话跑完了,而同样的事在同步模式下从来没返回过。
方法 · 一图看懂
1
先分清哪一类工具会拖慢 — 远程 MCP server、外部 API、网关后面的查询;本地读文件这类不在此列
2
把慢工具拆成两个:起任务 + 查状态 — 起任务立刻回一个句柄,真正的工作在后台跑;改的是接口形状,⛔ 不改模型提示
3
句柄要能活过这一步 — 句柄存进会话状态并在后续调用里取回;⛔ 存在你脑子里或日志里不算,agent 下一次调用看不到它
4
给每个工具设调用次数上限 — 工具没有明确结论时 agent 会一直重试;上限同时压住 token 增长与账单
5
要求工具给出响应契约 — 成功 / 失败 / 空结果三种要能分开,否则 agent 只能靠猜,猜不动就再试一遍
原理 · 为什么有效
同步等待为什么是结构性问题而不是调优问题:agent 的一次工具调用是串行的 —— 调用没返回,这一步就没有下一步。同步模式下,整条链路的可用时长由最慢的那个工具决定,而网关的响应超时与会话上限是别人定好的、不会因为你的事还没做完就延长。所以「多等一会儿」不是解法;把等待从「阻塞这一步」变成「留给下一次调用」才是。异步句柄的做法就是把时间轴拆开:起任务(立即返回)→ 保存句柄 → 下次调用取结果 —— 等待发生在两次调用之间,而这段时间里 agent 可以去做别的、或者干脆结束这一轮。另一半是重试:工具返回没有明确结论时,agent 的默认动作是再试一次,而它不会自己知道「再试也没用」 —— 所以上限要写在工具侧(每个工具允许几次),而不是指望提示词里写一句「不要重复调用」。⚠️ 一条要把话说清的边界:异步化会带走「顺序保证」 —— 一旦多个任务并发起,结果回来的顺序与发起顺序无关,凡是有前后依赖的步骤必须显式写成「先取 A 的结果,再起 B」。
适用场景
这份做法适合三类人:① 自己写或维护 MCP server / 工具接口的人 —— 这是接口形状的事,越早改越省;② 搭过 agent 但遇到「没有输出」「跑一半断掉」的人 —— 先排查是不是同步等待;③ 用上了远程 MCP 或云端工具链的团队 —— 远程这一层天然比本地慢。不适用:① 单一本地工具、单轮问答式的简单用法 —— 直连函数调用更省事,接 MCP 反而是多余的中间层;② 所有工具都在毫秒级返回的场景 —— 异步化带来的顺序问题比它解决的问题更贵。⚠️ 另有一条前置:如果你的 agent 连「工具返回什么算成功」都没定义清楚,先补这一条,异步化会把它放大成更难看的样子。
自测 · 5 问
我能说出链路里最慢的那个工具是哪一个、它通常在多少秒内返回吗
慢工具是不是已经拆成「起任务」与「查状态」两个,而不是同步等一个返回
起任务返回的句柄存在会话状态里吗(下一次调用能不能取到它)
每个工具设了调用次数上限吗(没有上限时 agent 会一直重试同一个调用)
工具有没有写清「成功 / 失败 / 空结果」三种响应,还是只有一种「返回了什么」
怎么上手 · 验证步骤
不要一次改所有工具 —— 先挑一个:把你链路里返回最慢的那个工具,改写成两个接口(一个起任务、一个查状态),并在调用它的地方加一行「保存句柄」。改完只验一件事:跑一次原本会超时的那个任务,看它这一次能不能走完。这一步走通之后再加第二条:给这个工具设调用次数上限(先设 3 次),观察一次「工具返回空结果」时 agent 会不会在第三次停下来。⚠️ 顺序上先做异步、再做上限 —— 反过来会让你以为上限解决了慢的问题。
改完之后,给自己设两条只读一遍日志的判据:① 最近一次失败的运行里,失败发生在「工具还没返回」还是「工具返回了但结果不可用」 —— 前者说明还有同步等待没拆掉;② 有没有任何一个工具在同一轮里被调用了三次以上 —— 有,说明它的响应契约还不清楚(agent 分不出「空结果」与「还没好」)。另一条自检:随机挑一个已经异步化的工具,问自己「如果它的结果晚十步才回来,我这套流程还对不对」——不对,说明顺序依赖没有显式写出来,异步化把这处隐患暴露了出来而不是制造了它。