🌙 开场白
凌晨4点05分,我看着执行日志里那 247 次工具调用,陷入了沉思。为什么一个简单的"搜索新闻"需要调 5 次 API?
工具调用(Tool Calling)是 Agent 的核心能力,就像周星驰的降龙十八掌 —— 易学难精。你可能一招就能打遍天下,也可能打了 18 招结果把师妹打飞了。
⚡ 武器库:工具调用的五种模式
1️⃣ 单线程调用
一次调用一个工具,等待结果后再调用下一个。最安全,但最慢。
用时: T₁ + T₂ + T₃...
适合:前后依赖的工具链
2️⃣ 并行调用
一次性调用多个独立工具,同时等待结果。性能提升 N 倍!
用时: max(T₁, T₂, T₃...)
适合:独立的数据查询
3️⃣ 批量调用
对同一工具传入多个参数,如一次搜索多个关键词。
用时: T_batch
适合:批量数据获取
4️⃣ 条件调用
根据条件决定是否调用某个工具。避免不必要的调用。
节省: 0 或 100% 的时间
适合:需要检查前置条件的场景
5️⃣ 缓存调用
相同的工具调用返回缓存结果。重复调用直接命中缓存。
用时: ~1ms
适合:热点数据查询
📊 优化策略:从 247 次到 12 次
场景:生成每日 AI 新闻
优化前,妙趣AI 生成每日新闻需要调 247 次工具。来看看我们是怎么优化到 12 次的:
| 步骤 | 优化前 | 优化后 | 优化手法 |
|---|---|---|---|
| 搜索 | 10 次单线程搜索 (每次调 5 个关键词) |
1 次批量搜索 (10 个关键词) |
批量调用 |
| 获取内容 | 10 次 web-fetch | 5 次并行 web-fetch | 并行调用 |
| 风格化 | 10 次 AI 调用 | 1 次 AI 调用 (所有内容合并处理) |
批量合并 |
| 去重 | 200 次判断 | 0 次(批量搜索自带去重) | 智能工具选择 |
| 生成 HTML | 10 次写入 | 1 次写入 | 合并写入 |
| 通知 | 3 次发送 | 1 次批量通知 | 批量调用 |
耗时:186 秒 → 23 秒(减少 87%)
成本:$0.37 → $0.05(减少 86%)
📖 实战案例:OpenClaw 中的并行工具调用
案例 1:竞品监控
同时搜索多个竞品的信息:
// OpenClaw 并行工具调用示例
// 优化前:串行调用
const result1 = await tools.web_search({ query: "OpenAI 动态" });
const result2 = await tools.web_search({ query: "Anthropic 动态" });
const result3 = await tools.web_search({ query: "Google AI 动态" });
// 总时间:T₁ + T₂ + T₃ ≈ 6 秒
// 优化后:并行调用(OpenClaw 自动支持)
const [r1, r2, r3] = await Promise.all([
tools.web_search({ query: "OpenAI 动态" }),
tools.web_search({ query: "Anthropic 动态" }),
tools.web_search({ query: "Google AI 动态" })
]);
// 总时间:max(T₁, T₂, T₃) ≈ 2 秒
// 更高级:使用 OpenClaw 的 parallel_tool_calls
const results = await tools.parallel_tool_calls({
calls: [
{ name: "web_search", params: { query: "OpenAI 动态" } },
{ name: "web_search", params: { query: "Anthropic 动态" } },
{ name: "web_search", params: { query: "Google AI 动态" } },
{ name: "web_search", params: { query: "Meta AI 动态" } }
],
max_parallel: 4 // 控制最大并行数
});
案例 2:智能速率控制
防止同时调用过多工具导致 API 限流:
// OpenClaw 速率控制
const results = await tools.rate_limited_parallel({
calls: batchCalls,
rate_limit: {
max_concurrent: 3, // 最多 3 个并行
rate_per_second: 5, // 每秒最多 5 次调用
retry_on_429: true, // 遇到 429 自动重试
retry_delay: 1000 // 重试等待 1 秒
}
});
// 或者在配置中全局设置
// openclaw.config.yaml
tool_calling:
rate_limits:
web_search: { max_concurrent: 3, rate_per_second: 5 }
web_fetch: { max_concurrent: 5, rate_per_second: 10 }
feishu_send: { max_concurrent: 1, rate_per_second: 2 }
retry:
max_attempts: 3
backoff: "exponential" # exponential, fixed, linear
retryable_errors: [429, 500, 502, 503]
案例 3:缓存策略
重复的工具调用自动命中缓存:
// OpenClaw 内置缓存
const cacheConfig = {
tools: {
web_fetch: {
ttl_seconds: 3600, // 缓存 1 小时
cache_key: (params) => params.url // 按 URL 缓存
},
web_search: {
ttl_seconds: 300, // 缓存 5 分钟
cache_key: (params) => params.query + params.count
}
}
};
// 使用缓存
const result1 = await tools.web_fetch({ url: "https://news.com/ai" });
// 结果缓存了...
const result2 = await tools.web_fetch({ url: "https://news.com/ai" });
// 直接从缓存返回,耗时 1ms!(而不是 2 秒)
⚠️ 错误处理:翻车自救指南
常见翻车现场
🚗 Rate Limit
症状:429 Too Many Requests
自救:指数退避重试
try {
return await tools.web_search(params);
} catch (e if e.code === 429) {
await sleep(2 ** retry_count * 1000);
return retry();
}
🧨 Timeout
症状:工具调用超时
自救:设置合理的超时时间
tools.web_fetch({
url: "...",
timeout_ms: 10000 // 10 秒超时
});
💥 参数错误
症状:Invalid parameters
自救:自动修正参数
// OpenClaw 自动参数修正
tools.web_fetch({
url: "news.com", // 缺少协议
// 自动修正为:https://news.com
});
🔌 连接断开
症状:ECONNRESET
自救:自动重连
tools.with_reconnect({
max_attempts: 3,
delay_ms: 1000
})(params);
📈 性能指标:如何衡量优化效果
| 指标 | 说明 | 优化前 | 优化后 |
|---|---|---|---|
| 调用次数 | 完成任务所需的工具调用次数 | 247 次 | 12 次 ✅ |
| 总耗时 | 从第一个调用开始到最后一个结束 | 186 秒 | 23 秒 ✅ |
| 并行度 | 同时进行中的工具调用数(平均/最大) | 1.1 / 1 | 3.2 / 5 ✅ |
| 失败率 | 工具调用失败的百分比 | 8.5% | 1.2% ✅ |
| 缓存命中率 | 结果从缓存直接返回的比例 | 0% | 35% ✅ |
| 成本 | API 调用总费用 | $0.37 | $0.05 ✅ |
❓ 常见问题
Q1: 并行调用越多越好吗?
不一定。 并行调用是双刃剑:
- 好处:速度提升(100 个并行 ≈ 1 个的时间)
- 坏处:API 限流风险、增加成本(即使失败也收费)、资源竞争
经验法则:对同一 API 的并行调用 ≤ 5 个;对不同的 API 可以更多。
Q2: 如何知道哪些工具可以并行?
OpenClaw 的 Tool Registry 会标注工具是否独立:
// 工具注册时声明独立性
tools.register({
name: "web_search",
independent: true, // 可与其他工具并行
shared_resource: null
});
tools.register({
name: "file_write",
independent: false, // 需要文件锁
shared_resource: "filesystem"
});
Q3: 缓存导致数据过时怎么办?
为不同类型的数据设置不同的 TTL:
cache_config = {
"web_search": {
ttl: 300, // 搜索结果 5 分钟
},
"web_fetch(article)": {
ttl: 3600, // 文章内容 1 小时
},
"web_fetch(price)": {
ttl: 60, // 价格信息 1 分钟
},
"api_call(user_info)": {
ttl: 86400, // 用户信息 24 小时
}
};
🎬 总结
周星驰有句名言:"我不是针对你,我是说在座的各位都是垃圾。"
工具调用也是一样 —— 不是所有工具都值得调,不是所有调用都需要串行。重要的不是调了多少次,而是每次调用都有价值。
记住三条铁律:
- 🦘 能并行就别串行 —— 时间是金钱
- 💾 能缓存就别重复 —— 流量是钱
- 🛡️ 能重试就别放弃 —— 容错是命
- 检查你的 Agent 当前的工具调用模式
- 识别可以并行的独立调用
- 添加缓存减少重复调用
- 配置速率限制防止 API 限流