AgentTool Orchestration并发一致性可靠性
工具调用冲突调度:串行、并行与仲裁器怎么选(Agent Orchestration)
当 Agent 同时调用多个工具时,真正的难题不是“能不能并行”,而是“并行后怎么保证一致性与可恢复”。本文把工具冲突分成资源冲突、数据依赖、副作用竞态与配额冲突四类,给出从串行到 DAG 并发的调度策略,并提供可落地的仲裁器(arbiter)设计与实现清单。
2026年3月4日
Synthly 团队
预计阅读 15 分钟

📷 Photo by Jonathan Borba via Pexels
你以为的问题是“并行”,实际的问题是“一致性”
把工具调用想成数据库事务会更接近现实:
- 读请求(Read):可重试、可缓存
- 写请求(Write):有副作用,需要幂等与补偿
当你把两类请求混着并行,冲突就出现了。
本文默认你已经具备结构化工具调用的基本功(schema、回执、容错)。如果还没有,建议先看:
一、先分类:工具冲突到底有哪些?
把冲突分清,调度策略才不会变成玄学。
1)资源冲突(Resource Contention)
- 同一个账号的速率限制(API rate limit)
- 同一份文件的写锁
- 同一条会话的“唯一进行中任务”
2)数据依赖(Data Dependency)
- B 的输入来自 A 的输出(显式依赖)
- B 的决策需要 A 的回执字段(隐式依赖)
3)副作用竞态(Side-effect Race)
- 并行发两封重复邮件
- 并行创建两张重复工单
- 并行修改同一条记录,后写覆盖前写
4)配额/预算冲突(Budget Conflict)
- token/费用预算耗尽
- 工具调用次数超限
- 端到端时延超限(p95 目标)
结论:并发不是“快”,而是“需要治理”。
二、三种基础调度模型:串行、受控并行、DAG 并行
1)串行(Serial):默认方案,先把正确性做稳
适用:
- 高副作用任务(写操作多)
- 工具不稳定、失败率高
- 没有补偿机制
串行的关键不是“顺序执行”,而是:
- 每步有回执校验
- 写操作幂等(见下文)
- 失败可在检查点重入
2)受控并行(Controlled Parallel):并行读、串行写
很多业务的最优解是:
- 读请求尽量并行(查资料、拉配置、读数据库)
- 写请求严格串行或按资源加锁(发信、下单、写库)
这能拿到大部分性能收益,同时控制风险面。
3)DAG 并行(Task Graph):用依赖图明确可并行边界
当任务能被拆成依赖明确的子任务时,DAG 是最清晰的表达。
一个简化示意(Mermaid):
flowchart LR
A[解析用户意图] --> B[拉取联系人列表]
A --> C[生成邮件草稿]
B --> D[校验联系人权限]
C --> E[合规模板检查]
D --> F[发送邮件(写)]
E --> F
注意:DAG 并行的前提是“节点契约清晰”。否则你只是在把不确定性扩散到更多节点。
三、仲裁器(Arbiter):把“并发决策权”从模型收回来
一个可上线的系统,建议把这些决策做成规则/策略,而不是让模型即兴决定:
- 是否可并行
- 是否需要锁
- 重试次数与退避
- 超时预算
- 风险动作是否需要人工确认(HITL)
你可以把仲裁器看成“运行时安全壳”。
1)仲裁器的最小职责
- 资源锁:按
resourceKey(例如user:123、mailbox:abc)加锁 - 幂等键:为所有写操作生成
idempotencyKey - 预算管理:token/tool/time 三类预算
- 策略分级:对不同工具/不同风险等级应用不同重试/降级
2)一个最小的接口形态(TypeScript 伪代码)
type ToolRisk = 'READ' | 'WRITE_LOW' | 'WRITE_HIGH';
type ToolRequest = {
tool: string;
risk: ToolRisk;
resourceKey?: string;
idempotencyKey?: string;
timeoutMs: number;
maxRetries: number;
};
type ArbiterDecision =
| { action: 'ALLOW' }
| { action: 'QUEUE'; reason: string }
| { action: 'DENY'; reason: string }
| { action: 'REQUIRE_APPROVAL'; reason: string };
interface Arbiter {
decide(req: ToolRequest): Promise<ArbiterDecision>;
onResult(req: ToolRequest, result: unknown): Promise<void>;
}
这不是“工作流引擎”,但已经能解决多数稳定性问题。
四、一致性与可恢复:并发系统的两条生命线
1)幂等:并发与重试的基础设施
强烈建议:
- 所有写操作都带
idempotencyKey - 工具侧尽可能支持“幂等创建”(server-side idempotency)
- 不支持时,在你的系统侧做去重(先查再写/写前锁)
2)补偿(Compensation):不要迷信回滚
很多外部系统不支持真正回滚(发出去的邮件收不回)。
补偿思路:
- 创建后立刻能“撤销/关闭/标记作废”
- 发送前改为“生成草稿 + 人工确认”
- 写操作拆成两段:预提交(prepare)→ 提交(commit)
3)乐观并发 vs 悲观锁
- 悲观锁:简单可靠,但吞吐受限
- 乐观并发:吞吐更高,但需要冲突检测与补偿
对于 Agent 系统,建议:
- 默认悲观锁(尤其是写操作)
- 对“读多写少”的路径,逐步引入乐观并发
五、如何选:一张工程决策表
| 场景 | 推荐调度 | 原因 |
|---|---|---|
| 写操作多、不可逆、工具不稳定 | 串行 + 锁 + 审批 | 风险面大,先稳 |
| 读操作多、写操作少且可幂等 | 受控并行(并行读、串行写) | 性能收益大、风险可控 |
| 子任务依赖清晰、节点契约稳定 | DAG 并行 + 仲裁器 | 可并行边界明确 |
| 工具经常 429/超时 | 队列化 + 退避 + 预算 | 避免重试风暴 |
六、上线 Checklist(把“并行”变成可运营能力)
- 冲突分类:资源/依赖/副作用/预算四类都有处理策略
- 仲裁器:锁、幂等键、预算、重试策略集中管理
- 事件日志:每次工具调用可追溯(耗时、错误类型、决策)
- 并行边界:并行读、串行写是默认;DAG 并行需节点契约
- 补偿方案:高风险写操作有撤销/作废/草稿机制
- 保护阈值:单任务调用次数上限、全链路超时预算
常见问题
我能不能让模型自己决定哪些步骤并行?
可以做探索,但不建议作为生产默认。并行决策涉及风险与资源治理,更适合用仲裁器的规则来控制。模型可以“提议并行”,但最终执行应由系统裁决。
并行后怎么向用户展示过程?
建议用事件流(Event Stream)而不是“聊天拼接”。每个节点有 status(queued/running/succeeded/failed)与可点击的回执摘要。前端实践可以参考: