串行调用 3 个 API 总翻车?这 3 种错误处理策略让 Skill 稳如泰山
🚀 本文收录于Github:AI-From-Zero 项目 —— 一个从零开始系统学习 AI 的知识库。如果觉得有帮助,欢迎 ⭐ Star 支持!
一个Skill需要串行调用三个外部API,如何正确处理局部失败?
一、最常见的错误:一刀切的错误处理
串行调用三个API,最容易犯的错是把三个await直接写在一起,然后用一个try/catch包住——这意味着任何一步失败都会中断后续步骤,也无法区分是哪一步出了问题,更无法决定是否要重试或跳过。
说人话就是: 想象你要做一顿饭,需要买菜、洗菜、炒菜三个步骤。如果你采用"一刀切"的做法,买菜时发现超市关门了,你就直接放弃整顿饭,连家里已有的食材都不用了。但实际上,你可能家里还有存货,或者可以去其他地方买,或者干脆做个简单的面条。
在OpenClaw的Skill上下文里,Skill的结果最终会被Brain读取并决定下一步行动,所以错误信息本身也是有价值的输出——不应该只抛异常,而应该把每一步的成功或失败状态都结构化地返回给Brain,让LLM来决定如何处理局部失败。

二、策略一:强依赖链,任意失败即中止
适用场景
步骤之间有严格的数据依赖,中间结果缺失无法继续。
典型例子
获取用户信息 → 查询订单 → 获取物流详情(每步都依赖前一步的结果)
// Skill: 获取用户信息 → 查询订单 → 获取物流详情
async function getOrderTrackingSkill(userId) {
let user, orders, tracking;
// 步骤一:获取用户
try {
user = await fetchUser(userId);
} catch (err) {
return {
success: false,
failedAt: 'fetchUser',
error: err.message
};
}
// 步骤二:查询订单(依赖 user.accountId)
try {
orders = await fetchOrders(user.accountId);
} catch (err) {
return {
success: false,
failedAt: 'fetchOrders',
error: err.message
};
}
// 步骤三:物流详情(依赖 orders[0].trackingId)
try {
tracking = await fetchTracking(orders[0]?.trackingId);
} catch (err) {
return {
success: false,
failedAt: 'fetchTracking',
error: err.message
};
}
return { success: true, user, orders, tracking };
}
关键优势
- 明确标注failedAt:Brain能知道具体哪步失败
- 精准错误提示:可以给用户更具体的反馈,而不是模糊的"操作失败"
- 保留部分数据:即使失败,也可能包含有用的中间结果
三、策略二:弱依赖,局部失败用降级值填充
适用场景
某些步骤失败不影响整体流程,可以用默认值或空值继续。
典型例子
生成早报(日历 + 天气 + 邮件摘要,各自独立)
// Skill: 生成早报(日历 + 天气 + 邮件摘要,各自独立)
async function morningBriefingSkill() {
// 三个步骤并发发起,但各自独立捕获错误
const [calendarResult, weatherResult, emailResult] = await Promise.allSettled([
fetchCalendarEvents(),
fetchWeather(),
fetchEmailSummary(),
]);
// 结构化每一步的结果
const briefing = {
calendar: calendarResult.status === 'fulfilled'
? calendarResult.value
: { error: calendarResult.reason?.message || 'Unknown error', data: [] },
weather: weatherResult.status === 'fulfilled'
? weatherResult.value
: { error: weatherResult.reason?.message || 'Unknown error', data: null },
email: emailResult.status === 'fulfilled'
? emailResult.value
: { error: emailResult.reason?.message || 'Unknown error', data: [] },
};
// 告诉 Brain 哪些部分有问题
const partialFailures = Object.entries(briefing)
.filter(([, v]) => v.error)
.map(([k, v]) => `${k}: ${v.error}`);
return {
success: partialFailures.length === 0,
partialFailures,
briefing
};
}
关键工具:Promise.allSettled
- 不会短路:一个Promise reject不会影响其他Promise
- 完整结果:等待所有Promise完成,每个都标记fulfilled或rejected
- 灵活处理:可以根据业务需求选择串行或并发执行
注意:虽然示例使用并发,但如果业务要求串行,换回逐步await即可,错误处理结构不变。
四、策略三:带重试的串行调用
适用场景
外部API不稳定,瞬时失败应该重试而不是立刻上报错误。
重试工具函数
async function withRetry(fn, { maxRetries = 3, baseDelay = 500 } = {}) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
lastError = err;
// 不重试客户端错误(4xx),只重试服务端/网络错误
if (err.status >= 400 && err.status < 500) throw err;
if (attempt < maxRetries - 1) {
const delay = baseDelay * Math.pow(2, attempt); // 指数退避
await sleep(delay);
}
}
}
throw lastError;
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
在Skill中的应用
async function robustSkill(input) {
const step1 = await withRetry(() => callApiA(input), { maxRetries: 3 });
const step2 = await withRetry(() => callApiB(step1.id), { maxRetries: 2 });
const step3 = await withRetry(() => callApiC(step2.data), { maxRetries: 3 });
return { step1, step2, step3 };
}
重试策略要点
- 指数退避:避免频繁重试加重服务器负担
- 区分错误类型:4xx错误(客户端错误)不重试,5xx错误(服务端错误)重试
- 限制重试次数:避免无限重试导致任务长时间阻塞

五、把错误结构化返回给Brain是关键
OpenClaw的Brain是ReAct循环——它读Skill的输出,然后决定下一步Thought和Action。如果Skill直接throw异常,Brain只能知道"Skill执行失败";如果Skill把失败信息结构化返回,Brain可以做更细粒度的决策。
Skill返回格式的最佳实践
// Skill 返回格式的约定(供 Brain 解析)
return {
success: false,
failedAt: 'fetchOrders', // Brain 可以据此决策
completedSteps: ['fetchUser'], // 已完成的步骤
partialData: { user }, // 部分数据仍可用
error: 'Orders API timeout', // 人类可读的错误描述
retryable: true, // 是否值得重试
suggestedAction: 'ask_user_for_alternative_account' // 建议的下一步行动
};
结构化错误的优势
| 信息类型 | 传统异常 | 结构化返回 | Brain能做什么 |
|---|---|---|---|
| 失败位置 | 调用栈 | failedAt字段 | 精准定位问题 |
| 已完成步骤 | 无 | completedSteps | 避免重复执行 |
| 部分数据 | 无 | partialData | 利用已有信息 |
| 重试建议 | 无 | retryable字段 | 自动重试或询问用户 |
| 下一步建议 | 无 | suggestedAction | 智能决策 |
六、设计哲学:Skill输出要对LLM友好
这个设计哲学和OpenClaw的Skill是Markdown自然语言说明书的理念一致——Skill不只是执行代码,它的输出要对LLM友好,让Brain能读懂发生了什么,而不是只看到一个二进制的成功/失败。
对比两种设计思路
- 传统API设计:成功返回数据,失败抛异常
- Agent-friendly设计:总是返回结构化结果,包含成功/失败的详细信息
实际效果差异
当用户问"我的订单到哪了?":
传统设计:
- Skill失败 → Brain收到异常 → 回复"抱歉,查询失败了"
结构化设计:
- Skill返回
{success: false, failedAt: 'fetchOrders', partialData: {user: {...}}, error: 'No orders found'} - Brain理解"用户存在但没有订单" → 回复"您还没有下单,需要帮您创建订单吗?"
七、选择策略的决策树
面对串行API调用,如何选择合适的错误处理策略?
三个API调用是否有数据依赖?
├─ 是 → 是否允许部分成功?
│ ├─ 否 → 使用策略一(强依赖链)
│ └─ 是 → 使用策略二(弱依赖+降级)
└─ 否 → API是否稳定?
├─ 否 → 使用策略三(带重试)
└─ 是 → 使用策略二(并发+独立错误处理)
实用建议
- 优先考虑业务语义:技术方案服务于业务需求
- 总是结构化返回:无论选择哪种策略,都要返回详细的状态信息
- 为Brain提供决策依据:错误信息要包含足够的上下文
- 测试各种失败场景:确保Skill在各种异常情况下都能优雅降级
在Agent时代,错误处理不再是简单的try/catch,而是为智能决策提供高质量输入的关键环节。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)