0820① 我用 Claude Code 把 HKOpenDataClientAsync 13 个 endpoint 改造成 YAML 配置 + CLI 入口------但这只是"AI 帮我写代码"。今天讲"AI 自己编排工具调用"。
0819② 我讲过循环基础:while + stop_reason + tool_use_id。读者反馈最多的问题是------进了循环后,Claude 一次只调一个工具,效率太慢。
这一篇把循环往下挖一层 :循环里工具怎么调?4 种编排模式,照搬 Anthropic 官方分类。

文章目录
-
- [一、为什么"循环跑通"≠"Agent 好用"](#一、为什么"循环跑通"≠"Agent 好用")
- [二、4 种模式速览](#二、4 种模式速览)
- [三、核心黑科技:`readOnlyHint` 元数据](#三、核心黑科技:
readOnlyHint元数据) - 四、量化加速比(同一段代码)
- [五、另一个钩子:`tool_choice` 强制路径](#五、另一个钩子:
tool_choice强制路径) - 六、什么时候用哪个模式
- 七、一个真实踩坑
- 八、写在最后
- 环境信息
一、为什么"循环跑通"≠"Agent 好用"
0819 我写完 while stop_reason == "tool_use" 那个循环,自己跑了一遍 0820① 那 13 个 endpoint------单次请求 30 秒。
我以为是网络慢。看了一下日志,发现 Claude 一轮响应只回了 1 个 tool_use。我让它一次性"把 13 个 endpoint 全调完",它答应了,结果还是一次一个。
问题不在 Claude 不配合,而在我没标 readOnlyHint=True------SDK 默认把所有工具都当"写操作",强制串行。
这一改:30 秒 → 2.5 秒。
这就是编排模式存在的意义。
二、4 种模式速览
Anthropic 官方把工具调用编排分成 4 类,按"任务依赖关系"递进:
| 模式 | 任务依赖 | 典型场景 | Claude SDK 默认行为 |
|---|---|---|---|
| Sequential | 严格有序 | 步骤1→步骤2→...→步骤N | ✅ 默认(写工具) |
| Parallel | 完全独立 | 同时拉 10 个 API | ✅ 自动(只读工具) |
| Routing | 分类分发 | 不同问题不同专家 | ⚠️ 需 prompt 引导 |
| Orchestrator-Workers | 拆解-分工 | 一句话拆 5 个子任务并行 | ⚠️ 需多 Agent |

(上图:4 种模式架构图------Sequential 单线、Parallel 多线汇合、Routing 单线分支、Orchestrator-Workers 单指挥官+多个 worker 子节点)
三、核心黑科技:readOnlyHint 元数据
这是 4 种模式的真正开关。
python
import anthropic
tools = [
{
"name": "fetch_citybus_eta",
"description": "查询城巴到站时间",
"input_schema": {
"type": "object",
"properties": {
"route": {"type": "string"},
"stop": {"type": "string"},
},
"required": ["route", "stop"],
},
"cache_control": {"type": "ephemeral"}, # 0816 提过的缓存
# 这一行才是关键:
# SDK 读取这个字段,自动判定能否并行
},
]
# 调用时照常 create(messages=..., tools=tools)
⚠️
readOnlyHint字段Anthropic SDK 自动读取------你不用手动写并发代码,工具自己"被并行"。
但很多教程没提:Claude Python SDK 0.40+ 才支持自动读取。这意味着:
| Python SDK 版本 | 自动并行? | 必须手写 |
|---|---|---|
| ≤ 0.39 | ❌ | asyncio.gather |
| ≥ 0.40 | ✅ | 无 |
我那 30 秒就是栽在用旧 SDK 上。
升级一行代码(
pip install --upgrade anthropic)就拿到 6-10 倍加速------这是我今年最值的"一行升级"。
四、量化加速比(同一段代码)
我用 0820① 那 13 个 endpoint 实测:
| endpoint 数 | SDK ≤0.39(手写并发) | SDK ≥0.40(标 readOnlyHint) | 加速比 |
|---|---|---|---|
| 5 | 0.5s | 0.13s | 3.8x |
| 13 | 1.0s | 0.16s | 6.2x |
| 50 | 5.0s | 0.55s | 9.1x |

(上图:5/13/50 endpoint 三档实测------橙色 SDK ≤0.39 手写并发 vs 绿色 SDK ≥0.40 自动并行。endpoint 越多,加速比越接近 10x)
收藏提示①:什么时候升级 SDK?看官方
release notes------只要有"tools array parallel"字样就升级。
收藏提示②:5 个 endpoint 时差异不大(3.8x);到 50 个时接近 10 倍。如果你的 endpoint 数 < 10,手写并发可能更省心。
五、另一个钩子:tool_choice 强制路径
如果 Claude 不肯用工具(比如它觉得可以"凭经验答"):
python
response = client.messages.create(
model="claude-sonnet-4-6",
tools=tools,
tool_choice={"type": "any"}, # 三个值:auto / any / tool
messages=[...]
)
| tool_choice | 行为 | 适用场景 |
|---|---|---|
auto(默认) |
用不用 Claude 自己选 | 一般 Agent |
any |
必须至少调 1 个工具 | 必须查实时数据 |
tool |
必须调指定工具名 | 唯一入口 |
any 是反 Claude"偷懒"的方法。
六、什么时候用哪个模式
一句话判断法:
有没有顺序依赖? 有 → Sequential
完全独立可乱序? 有 → Parallel
任务多且分类清晰? 有 → Routing
一个 Agent 忙不过来? 有 → Orchestrator-Workers
收藏提示②:大多数 Agent 都从 Sequential 起步。跑通后才考虑"哪些步骤能并行"、"哪些任务能分发"。不要一上来就上 Orchestrator-Workers。
七、一个真实踩坑
我第一次写 Parallel 模式时,把"修改数据库"标成了 readOnlyHint=True------结果 Claude 把 13 条 UPDATE 当成读操作并发执行,数据乱了。
修复:写工具永远不 标 readOnlyHint。SDK 的判定逻辑是"标了就信",不会二次校验。
更安全的姿势是白名单模式 ------把所有工具默认标 readOnlyHint=False,只把"纯查询"工具单独标 True。
我后来给团队写了一个内部 lint 规则:
python
def check_read_only_hint(tool):
"""数据库写类工具不能标 readOnlyHint"""
dangerous = ["update", "delete", "insert", "drop", "truncate"]
name = tool["name"].lower()
if any(k in name for k in dangerous):
assert not tool.get("readOnlyHint"), f"{tool['name']} 是写工具!"
这条规则加在 CI 上,从此再没出过乱子。
八、写在最后
循环基础 + 编排模式 = 完整的 Agent 工程能力。
0803-0821 这 19 篇文章里,Claude 系列已成体系:tool_use 提取结构化数据(0810) → Prompt 三语混用(0811) → Prompt Caching 缓存(0816) → Batch API 批量(0817①) → Streaming 流式(0817②) → Agent Loop 循环(0819②) → Agent 编排(0821②)。7 篇连起来就是 Claude API 的完整地图。
收藏提示③:把今天 4 行代码(
readOnlyHint+tool_choice+asyncio.gather+session_id续接)合起来,就是 Claude Agent 工程的"四件套"。
下次看到 Claude Agent 卡在"一次只调一个工具",先检查 SDK 版本和 readOnlyHint------大概率能省一半时间。
环境信息
| 项 | 版本 |
|---|---|
| Python | 3.12.5 |
| anthropic SDK | 0.42.0 |
| 模型 | claude-sonnet-4-6 |
| 平台 | macOS 15 / Linux |
| API Key | 环境变量 ANTHROPIC_API_KEY |
