乐于分享
好东西不私藏

Claude Code 源码架构解密之工具调用自我纠错机制

Claude Code 源码架构解密之工具调用自我纠错机制

前言

上一章讲了如何实现一个最小版的 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 的一项核心工程能力。