(OpenClaw前置知识)请解释 Tool Calling(工具调用)的完整链路:工具是怎么定义的、LLM 怎么调用它、结果怎么回传

魏远标 Lv7

参考答案

Tool Calling 的核心链路就四步:定义工具 → LLM 决策 → 系统执行 → 结果回传。

打个比方:LLM 就像一个只会动嘴的指挥官,它不能亲自去查数据库、读文件,但它可以”下命令”让外部系统去执行,然后看执行报告决定下一步。

Tool Calling 就是这个”下命令再拿报告”的标准化流程。

先说工具定义。每个工具本质上就是一段 JSON Schema(一种描述数据结构的标准格式),里面包含工具的名字、一段自然语言描述、以及参数的类型约束。LLM 不会直接执行任何代码,它只认这段 Schema 文本。你在系统侧注册好工具,然后把这些 Schema 塞进 system prompt 或者 tools 字段传给 LLM:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


json

复制代码

{
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称" }
},
"required": ["city"]
}
}

然后是 LLM 决策调用。LLM 收到用户消息和工具列表后,如果判断当前问题得调用工具,它不会直接回答用户,而是返回一个特殊的 tool_use 消息,里面带着工具名和填好的参数 JSON。

备注:不同厂商命名不同,OpenAI 用 tool_calls,Anthropic 用 tool_use,本文以 Anthropic 命名为例。

注意,LLM 只是”说”它想调什么工具、传什么参数,它自己压根不会去执行。这跟你在聊天里说”帮我查一下天气”一样,说的人不会真的去查,执行的是系统侧。

系统侧拿到 tool_use 消息后,解析出工具名,找到本地注册的对应函数,把参数传进去跑。执行完拿到结果,包装成 tool_result 消息追加到对话历史里,再整个发回给 LLM。

LLM 看到 tool_result 后有两种选择:

  • 如果信息够了就直接生成最终回答
  • 如果还需要更多信息,它会再发一个 tool_use,形成一个循环,直到它认为可以回复用户为止。

整个链路:用户发消息 → LLM 分析消息和工具列表 → LLM 返回 tool_use 含工具名和参数 → 系统执行工具函数 → 系统构造 tool_result → 发回 LLM → LLM 决定继续调用或输出最终回复

扩展知识

从 Function Calling 到 Tool Calling 的演进

OpenAI 最早在 2023 年 6 月推出的叫 Function Calling,当时一次只能调一个函数。到了 2023 年 11 月,升级成了 Tool Calling,最大的变化是支持 parallel tool calls,LLM 一次可以返回多个 tool_use,系统可以并行执行完再统一回传结果。

举个场景:用户问”北京和上海今天天气怎么样”,用 Function Calling 的话 LLM 得先调一次 get_weather(“北京”),等结果回来再调 get_weather(“上海”),两轮网络往返。换成 Tool Calling,LLM 直接返回两个 tool_use,系统并发执行,一轮就搞定了。

Anthropic 的 Claude、Google 的 Gemini 也都支持 Tool Calling,机制基本一样,但在 Schema 处理上有差异。

比如 Gemini 不支持 patternProperties 和 additionalProperties 这些关键字,xAI 不支持 minLength、maxLength 约束,OpenAI 要求参数顶层必须是 type: “object”。实际工程里通常需要一层 Schema 归一化来抹平这些差异。

工具结果的上下文管理

工具返回的数据量可能非常大。一次代码搜索可能返回 50KB 的内容,直接塞进上下文会快速吃掉 token 配额。

生产级系统一般会做截断处理,常见的策略是 head + tail 保留,取开头和结尾各一部分,中间用省略标记替代,同时设置单条结果的上限,比如一般会设置单条结果占上下文窗口比例的上限(如 20%-30%)以及字符数硬上限。

安全和权限控制

工具调用是 Agent 系统里最容易出安全问题的环节。LLM 可能被 prompt injection 诱导去调用不该调的工具,比如删除文件、发送邮件。所以生产环境至少要做三件事:

1)工具白名单,只暴露当前场景必需的工具。

2)参数校验,不能完全信任 LLM 填的参数,服务端必须做 Schema 验证和业务规则校验。

3)敏感操作加人工确认,像 LangChain 的 HumanApprovalCallbackHandler 就是在执行前弹一个确认。

212195e350ae1bfacc3b38f0f0e3ebb1.png

错误处理和重试

工具执行失败是常态,网络超时、API 限流、参数格式错误都可能发生。好的做法是把错误信息也包装成 tool_result 返回给 LLM,让它自己决定怎么处理。大多数模型看到错误后会尝试修改参数重新调用,或者换一个工具,或者直接告诉用户”这个信息暂时查不到”。

但要注意设置最大重试次数,不然 LLM 可能陷入死循环不停地调同一个工具。一般 3-5 次就够了,超了就强制返回。

面试官追问

提问:如果 LLM 返回的工具参数格式不对,比如少了必填字段或者类型不匹配,你怎么处理?

回答:两层防线。第一层是在系统侧用 JSON Schema 做参数校验,不合规直接拦住不执行,把校验错误信息包装成 tool_result 返回给 LLM,大多数模型看到具体的报错信息后会自己修正参数重新调用。第二层是设置重试上限,一般 3 次,避免来回纠错死循环。

提问:parallel tool calls 并行执行多个工具的时候,如果其中一个失败了怎么办?

回答:各工具的执行结果是独立回传的,失败的那个单独返回错误信息,成功的正常返回结果。所有 tool_result 一起发回给 LLM,让它自己判断:可能只用成功的那几个结果就够了,也可能决定重试失败的那个。不需要全部成功才继续,这跟 Promise.allSettled 的思路一样。

提问:Tool Calling 和 RAG 都是给 LLM 补充外部信息,它们的边界在哪?

回答:RAG 是”提前检索、一次性注入”,把相关文档片段塞进 prompt 就完事了,适合知识查询类的场景。Tool Calling 是”按需执行、多轮交互”,LLM 在推理过程中动态决定要不要调、调哪个,适合需要实时数据、需要执行副作用的场景,比如查数据库、发请求、操作文件系统。简单说,RAG 解决”LLM 不知道的事”,Tool Calling 解决”LLM 做不到的事”。

提问:怎么让 LLM 更准确地选择正确的工具?

回答:工具的 description 写得好不好直接决定调用准确率。description 要写清楚这个工具干什么、什么场景该用、什么场景不该用。参数的 description 也一样重要,别偷懒写个”id”就完了,要写明白”用户的唯一标识符,必须是数字格式”。另外工具数量不能太多,超过 15-20 个的时候 LLM 选择准确率会明显下降,这时候要么分场景加载不同的工具集,要么做一层路由先判断意图再加载对应工具。

目录
(OpenClaw前置知识)请解释 Tool Calling(工具调用)的完整链路:工具是怎么定义的、LLM 怎么调用它、结果怎么回传