多Agent串行编排,每段的超时预算怎么分
一句话结论:多个 Agent 接力干一件事,别给每个 Agent 单独拍一个固定超时,要给整条链路设一个总的 deadline,剩多少时间往下传,谁也别超。我没这么干之前,一条三段的链路理论上能等到 30 秒还不返回,用户早跑了。
问题长啥样
我有条调研链路:检索 Agent → 分析 Agent → 汇总 Agent,串行跑。一开始我图省事,每段都写死timeout=10s。看着挺合理,真出问题才发现:检索那段网络抖了一下用掉 9 秒,分析又满满当当跑了 10 秒,到汇总用户已经等了快 20 秒,前端那边 15 秒早就超时断开了。三段各自都"没超时",合一块儿就崩了。
毛病在于:单段超时只管自己,没人管全局还剩多少时间。
正确姿势:传 deadline,不传 timeout
把"还能跑多久"做成一个随链路往下传的 deadline。每个 Agent 进来先看:离总截止还剩多少?拿这个剩余时间当自己的超时上限,绝不许超。
import time, asyncio TOTAL_BUDGET = 12.0 # 整条链路总预算(秒),比前端超时留点余量 async def run_pipeline(query): deadline = time.monotonic() + TOTAL_BUDGET ctx = await run_stage(retrieve_agent, query, deadline) ctx = await run_stage(analyze_agent, ctx, deadline) return await run_stage(summarize_agent, ctx, deadline) async def run_stage(agent, payload, deadline): remaining = deadline - time.monotonic() if remaining <= 0.5: # 剩的时间不够干活了,直接降级 return fallback(agent, payload) try: # 这一段最多只能用掉"还剩的时间" return await asyncio.wait_for(agent(payload), timeout=remaining) except asyncio.TimeoutError: return fallback(agent, payload) # 超了就走兜底,别拖死后面我后来补的几个细节
总预算要比上游超时留余量。前端如果是 15 秒断,我总预算给 12 秒,留 3 秒给网络和序列化这些杂活,不然你"卡点返回"的那一下还是会被上游判超时。
越靠前的 Agent,越要懂得"见好就收"。检索那段我加了限制:召回够用就别贪多,把时间留给后面分析和汇总。不然前面把预算吃光,后面只能降级,得不偿失。
降级要分得清"没数据"和"超时了"。汇总 Agent 拿到的若是上一段降级的半成品,得在结果里标个
partial=true,前端好提示"部分结果",别假装一切正常。
一个我纠结过的取舍
按比例预分配(比如 4s/4s/4s)还是动态剩余?我试过预分配,简单,但僵——检索快了用不完那 4 秒也匀不给后面。动态剩余(上面这种)灵活但有个隐患:如果前段普遍偏慢,后段会被频繁压到降级。我最后的折中是动态剩余 + 给关键的汇总段留一个"保底时间"(比如至少留 2 秒),别让它永远在降级。
说点不好的
deadline 这套对纯串行很顺,一旦链路里有并行分支或者循环节点,预算怎么分就开始烧脑了——并行的几支是按最长那支算还是各自算,得case by case。还有time.monotonic()这种墙钟时间,在跨进程、跨机器的 Agent 调用里传递得自己序列化好,别传了个本地时间戳到另一台机器上,时钟一偏就全乱。
模型推理我直接用讯飞星辰MaaS 的现成 API,各段 Agent 的模型调用延迟相对稳,省了我自己维护算力、还得估单机延迟的麻烦,预算才好估。
你们多 Agent 链路是怎么控总超时的?固定分配还是 deadline 传递?评论区交流下。