前言
上一章讲了如何实现一个最小版的 Agent,也就是 实现 Tool + Agent Loop 就能实现 Agent 的最小闭环。
这个闭环已经能跑起来,但它还不够稳定。因为 Tool 的调用经常会有各种原因的失败,比如文件可能不存在,路径可能写错,参数可能不符合 schema,命令也可能因为环境差异、权限、超时等失败。
所以为了失败后让程序能够进行下去,就要让 Agent 提供一套工具调用的自我纠错机制,这也是 Agent Harness 里非常关键的一层工程能力。
工具调用为什么一定会失败
LLM 生成 tool_use,是在根据上下文和工具提供的描述进行预测:
应该调用哪个工具?应该传什么参数?大概会拿到什么结果?问题在于,这些判断都只是模型基于上下文包括工具的描述做出的预测,由于大模型存在幻觉并且真实的环境是动态的,那工具调用就可能失败。比较典型的错误包括:
1. 选错工具:模型调用了当前不存在的 tool。2. 传错参数:Read 需要 file_path,模型传了 path。3. 执行失败:文件不存在、命令 exit 1、权限不足、超时。4. 输出太长:工具成功了,但结果太大,模型无法有效处理。所以需要 Tool 的自我纠错机制。
如何实现
解决思路也很简单:
当 tool 调用失败时,Agent 会把失败原因包装成 tool_result,并作为上下文传回给 LLM。下一轮 LLM 看到这个失败原因后,就可以重新规划下一次 tool_use:可能是改参数、换工具、缩小范围,或者换一种执行策略。也就是:让失败原因变成 LLM 下一轮行动的依据核心流程:

也就是:
-> 调用失败 -> 模型能继续往下做的反馈 -> 下一轮模型重新规划如果把它放回上一章的 Agent Loop 流程,可以理解成多了一层“错误观察”。
举个栗子
用户提出需求:
读取 src/config.ts用 messages 作为上下文,流程是这样的:
一开始,messages 只有用户请求:
let messages = [ { role: "user", content: "读取 src/config.ts" }]模型基于当前上下文,预测下一步应该调用 Read:
messages = [ { role: "user", content: "读取 src/config.ts" }, { role: "assistant", content: [ { type: "tool_use", id: "toolu_001", name: "Read", input: { file_path: "src/config.ts" } } ] }]系统执行 Read,发现文件不存在,于是 Agent 把失败包装成 tool_result,继续追加到 messages:
messages = [ ..., { role: "user", content: [ { type: "tool_result", tool_use_id: "toolu_001", is_error: true, content: "File not found: src/config.ts.\n\nSuggestion: Check the path. Use Glob or Grep to locate the file before reading it." } ] }]可以看到,工具调用失败后,失败原因和建议会作为一条 user message 进入上下文。
下一轮模型看到的是完整上下文:
用户要读取 src/config.ts。我刚才调用 Read 失败了。失败原因是文件不存在。建议是先用 Glob 或 Grep 定位文件。于是它可能不再继续盲目读取同一路径,而是改用 Glob 获取正确路径:
messages = [ { role: "user", content: "读取 src/config.ts" }, { role: "assistant", content: [ { type: "tool_use", id: "toolu_001", name: "Read", input: { file_path: "src/config.ts" } } ] }, { role: "user", content: [ { type: "tool_result", tool_use_id: "toolu_001", is_error: true, content: "File not found: src/config.ts.\n\nSuggestion: Check the path. Use Glob or Grep to locate the file before reading it." } ] }, { role: "assistant", content: [ { type: "tool_use", id: "toolu_002", name: "Glob", input: { pattern: "**/config.ts" } } ] }]这就是在 Agent中让 Tool 调用自我纠错机制的原理:
模型读到工具调用的错误后,再生成新的 tool_use。代码实现思路
Agent中要做 Tool 自我纠错,至少要做到两点:
1. 所有工具失败都返回 tool_result,不要让异常逃出主循环。2. 错误内容要带行动建议,让模型知道下一步怎么改。errorResult 统一错误结果结构
先定义一个统一的错误结果的函数。
关键点是:错误也必须是 tool_result,并且要带上原始 tool_use_id。
这样模型下一轮才知道:这条错误反馈对应的是自己刚才哪一次工具调用。
function errorResult(toolUseId: string, reason: string, suggestion?: string) { return { type: "tool_result", tool_use_id: toolUseId, is_error: true, content: suggestion ? `${reason}\n\nSuggestion: ${suggestion}` : reason, }}runToolUse 工具执行函数
然后把工具执行过程收敛到一个 runToolUse 函数里。
不管错误发生在找工具、校验参数、权限检查,还是具体执行阶段,最后都返回同一种 tool_result 结构。
async function runToolUse(toolUse, tools) { const tool = tools.find(t => t.name === toolUse.name) // 处理找不到 tool 的场景:模型调用了一个当前不可用的工具 if (!tool) { return errorResult( toolUse.id, `No such tool available: ${toolUse.name}`, `Use one of the available tools instead.` ) } // 处理参数结构错误:还没进入 tool.call,先用 schema 拦住 const parsed = tool.inputSchema.safeParse(toolUse.input) if (!parsed.success) { return errorResult( toolUse.id, // 注意:formatSchemaError 负责将原始错误翻译成对 LLM 友好的自然语言建议 `Invalid input for ${tool.name}: ${formatSchemaError(parsed.error)}`, `Retry with arguments that match the tool schema.` ) } // 处理业务校验错误:参数形状对,但业务上不能执行 const validation = await tool.validateInput?.(parsed.data) if (validation?.result === false) { return errorResult(toolUse.id, validation.message) } try { // 真正进入 tool.call:开始执行具体工具逻辑 const output = await tool.call(parsed.data) return { type: "tool_result", tool_use_id: toolUse.id, content: output, } } catch (error) { // 处理执行过程中的错误:文件不存在、命令失败、超时等 return errorResult( toolUse.id, `Tool execution failed: ${error.message}`, `Use the error details to adjust the next tool call.` ) }}总结
Agent 中需要实现 Tool 的自我纠错机制,主要的实现思路是 Agent 要把 Tool 调用失败的原因和建议包装成 tool_result,再放回上下文 messages,让 LLM 在下一轮根据这些反馈重新规划工具调用。这套实现机制也是 Agent Harness 的一项核心工程能力。
夜雨聆风