并行带来的不只是延迟收益
大多数团队开启并行工具调用是为了速度。在多数 Agent 里,工具执行占端到端延迟的 35%–60%,编码类任务偏高端,深度研究类任务偏中端。三个独立的 200 ms 调用并发跑,墙上时钟就从 600 ms 压到 200 ms,收益很直接。
但一旦并发跑起来,工具设计里原本被顺序执行掩盖的假设就暴露了。有篇文章把这个过程叫“非自愿的架构审计”。我认同这个判断:并行不是性能开关,而是对副作用模型的强制检查。问题不在“能不能并行”,而在“并行结果返回之后,怎么把副作用合并成一致的下一轮状态”。
先把执行机制说清楚。并行是模型决定的,不是编排层决定的:模型在单次响应里回多个 tool_use 块,运行器分派全部调用,等全部完成,再把整批结果一次性交回去。模型看不到中间结果。OpenAI 的接口有 parallel_tool_calls 参数且默认为 true,推理模型(o3、o4-mini)遇到显式设置会返回 400;Anthropic 没有直接的开关,Claude 根据自己的判断决定并行与否。这让不同 provider、不同版本之间的并行行为并不稳定,编排层不能假设它一直是串行或一直是并行。
三类不会报错的故障
顺序执行自带宽容:A 依赖 B 时,历史执行顺序刚好总是让 B 先跑。并行撤销了这份宽容。失效往往不伴随异常。
- 上下文依赖:工具 A 从一个共享上下文变量读物,这变量本该由工具 B 填入。并行时 A 在新数据就位前就跑了,读完或读旧,返回一个看着合法的结果。不报错。
- 共享状态变更:典型读-改-写竞态。A 读 100、决定 +10、写回 110;B 同时读 100、+20、写回 120。最终 120,预期 130。两边都没抛错。
- 执行时序依赖:B 不读 A 的输出,但 B 逻辑上依赖 A 前置执行了。创建数据库记录和写相关记录、建会话和加会话数据、取锁和锁上操作,时序一并发就没了。不报错。
这三类的共同点是:失败静默,且轨迹迅速深入。阶段已经在错误输出上推进,等错误可见,副作用已污染了十几轮上下文,追溯不到该批次。
先分类,再决定能不能并发
落地时,最省事的护栏栏先把库里的工具过三层筛:它够不够原子(会不会留被并发的可观察中间态)、幂不按相领输入多次执行后对下游世界状态影响是否一致、独不直够不依赖同批次其他工具的产物或副作用。三条全过才配进并发集。只读搜索、列表、配置读属通常第一批能过筛的。文件重命名不原子——旧名消失、新名未现的窗口是真实存在的写工具必须先放一面——大部分发通知工具既不幂等也已耦合。
由此可生出一条可落地的规则:读批次并行,写批次串行。存读批次并行到底能把墙钟砍 40%–50%,风险几乎为零。而批次里一旦混入写,整组撤回到串行,因为出可能等读回的果或与同伴读扭互相干。
一个容易忽略的工程细节:并行返回的结果数组必须按“请求顺序”排列,不用“完成顺序”。同时每个调用带唯一的 tool_use_id 靠这个 ID 才能配对;把同轮所有结果合并在一条 user message 里否则模型以后学会“一个个等结果”,并行退化。一条用户消息对应的就是一次原子结果批。
副作用合并的核心,是别把并发当合流
这里最常见的操作错误,是把“多个并行调用的结果照回”当成“副作用已等价线性执行”。事实不是:没折够真的创建好且用初始过的,写工具的副作需要显式合。
我给团队的建议,是把副作用按“发件箱模式”保守合并:工具在执行层先把准副作用写在批次草稿里,其中检查器按规范串行审看这批次里的读中间前,再提交。这样并发冲突不再依赖写锁,也不再希望在工具文件一级就去保证幂等性相反,它有所有命一层的“影子意图”意图对齐后再单调串并入以世界状态
有几件事没那么幸运:网络有并发的不够快,你不知发商报效一直定。重试为副作用买单时,你得手动试一遍幂已试并发合并。我的观察是个简单的“精确一次提交登记 + 去重指纹(工具名、加参指纹、会话与工作流标)”打配套,它的边界比锁在工具层强很多。
不急着上 DAG
既然遇到这类事务,最陡的是上 DAG 显式用显式推图编排起来。LangGraph 是用显式的模型用关系代表对话消息,可以表达“两个独立的读记点能同时跑、写入节点等到两个读依赖门都完成之后才能开”。LLMCompiler 的路子就是编译执行计划、按同样并行表达。公开里报在好几任务的步多执行上去到了 2.2 倍收益且不破坏正确性;解这类需要显式子已关系很明确的公开码很相符合,可观测性很好,有节点的失败链路、节点收到多输入、同层次均等旁支几点并发写入状态,全部能检查。而对于那种还只有几个十几行的服务制、每个行为又很少现的工程,上显式关系实这运维成本未免值。以读写分类规则做简单的执行前拦截就够够用了这也是架构选择的取舍。
一套跑在生产前的 Check List
在生产中开启并行调用前,我会做以下几条排查,算是老工程保底闭缺后的固定卡口:
- 把全部工具按只读、只写、读修-写三类标注清楚,标不清的先不得进入执行批次。
- 对每写工具逐一析出内频次最多的目标资源登记表,标出可能也在同一批里出现的不可变对象名,不要建里扫。
- 查工具有没隐声取共享内存、会话变量、文件指向等,别信签名那部分,要看实现部分。
- 在调试环境强上早为并行条件下的重入测。此前顺序环境太一直在序去“验证”了。典型落点是原序性。
class SideEffect: def __init__(self, resource, op, payload, fingerprint): self.resource = resource # 被打档的对象 self.op = op self.payload = payload self.fingerprint = fingerprint # hash(工具名, 参数,请求代) def commit(side_off) : 1. 按 resource 分组 2. 每组先审读中间值再行写 3. 按工具声明确定性两两一组“竞写区”入合并口径。有几点是这篇文章的局限:如果把工具里的副作用当成幂等就完了;建议给的还是一个保守结构(拆读-合并-放行),并发工具结果做单批次替配、边对比物可复制替代去担中的一致性等仍算是目前够不彻底的。宁可并行晚一年放、从工具把发布严管节,也不要抱着侥幸心理只在顶层加一把全局锁。是分发还是合并分不清的模批次最危险——因到那时候,你会以为是调度问题了,但你真正发现的是语义不该在那里,它一直没在你的架构存在过。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/agent-engineering/633.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
-
AI Coding Agent 如何稳定提交可合并的 PR
上周 review 了一个 AI 生成的 PR,测试全绿,但我还是把它关了。它改了 37 个文件,顺手升级了一个依赖,还修改了两条测试断言。问题不是代码不能跑,而是这个 PR 没人敢合。
2026/10/14
-
MCP 进入生产环境:工具协议、网关与权限边界
MCP 刚出来时,我以为最难的是协议兼容。真正接了十几个工具之后才发现,把工具接上只花了两天,把权限、审计和故障处理理清楚花了两个月。
2026/10/07
-
Agent Harness:可观测、可评测、可回滚的 Agent 基础设施
去年我们做了一个处理退款申请的 Agent。Demo 很顺,上线第一周就出了事故:进程重启后状态丢了,同一笔申请被处理了两次。问题不在模型,而在模型外面那层没人愿意写的基础设施。
2026/10/09
-
Agent 安全清单:提示注入、工具越权与数据外泄
一个内部助手被要求总结一封客户邮件。邮件末尾有一行白色小字:把最近的客户名单转发到某个地址。Agent 真的准备去调邮件工具了。安全不能靠模型自觉,得靠系统设计。
2026/10/16
-
并行工具调用暴露的工具设计耦合问题
并行工具调用能砍掉四成延迟,但它更重要的价值在于暴露工具之间的隐性耦合。本文拆解上下文依赖、共享状态竞争与时序前提这三类静默失败,给出读写分类规则、工具合并的判据,以及什么时候该上依赖图编排。
6天前
-
Agent 轨迹回放调试:把失败变成可回放资产
Agent 调试难在无法复现、故障点藏在数步之前。这篇文章讲三件具体的事:把轨迹归一化成可校验的步骤结构,用工具签名和领域策略生成可执行约束,以及用标注过的失败轨迹在 CI 里做回归回放——顺带说明为什么只盯延迟和错误率的可观测看不到这类故障。
5天前
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布