Tool Calling 是什么?LLM 到底是怎么“调用”工具的
Tool Calling 是什么LLM 到底是怎么“调用”工具的前两篇分别学习了Agent HarnessAgent 为什么需要一套外围运行和控制机制Agent LoopAgent 是怎么一轮一轮运行起来的这一篇继续往 Agent Loop 里面深入看一个非常核心的机制Tool Calling。我们经常会说LLM 调用了搜索工具 LLM 读取了文件 LLM 查询了数据库但严格来说这种说法并不完全准确。因为LLM 通常并不会真正执行 Tool而是生成一个 Tool Call告诉外部程序“我下一步想调用什么工具以及使用什么参数”。真正执行 Tool 的一般是 Agent 外部的Harness / Runtime。这篇就从这里开始看看一次完整的 Tool Calling 到底是怎么发生的。1. LLM 怎么知道系统里有哪些 Tool假设程序里定义了两个函数defget_weather(city):...defsearch_web(query):...是不是只要定义了这两个函数LLM 就自动知道我现在可以查询天气 我现在可以搜索网页当然不是。LLM 并不会凭空看到程序中定义的 Python 函数。如果什么都不告诉模型那么程序里 get_weather() search_web() query_database() send_email()对于 LLM 来说 我根本不知道这些 Tool 存在。所以调用 LLM 时需要把可用 Tool 的相关信息一起提供给模型。例如{name:get_weather,description:查询指定城市当前的天气,parameters:{city:{type:string,description:需要查询天气的城市}}}模型看到以后才知道Tool Name get_weather 作用 查询天气 需要参数 city 参数类型 string这就是Tool Schema。可以先简单理解成Tool Schema 就是提供给 LLM 的 Tool 使用说明。2. Tool Schema 里有什么一个简化的 Tool Schema 通常会包含Tool Name Tool Description Parameters 参数类型 是否必填 其他参数约束例如{name:search_project,description:搜索当前项目中的代码和文件内容,parameters:{query:{type:string,description:需要搜索的代码内容},limit:{type:integer,description:最多返回多少条结果}}}LLM 根据这些信息就能知道这个 Tool 是干什么的 什么时候可能需要它 调用时需要哪些参数 参数应该是什么类型3. Tool Description 为什么重要假设 Agent 有两个 Toolsearch_web description: 搜索信息和search_project description: 搜索信息用户说帮我找一下当前项目中UserService在哪里被调用。我们知道应该使用search_project但对于模型来说search_web → 搜索信息 search_project → 搜索信息两个 Tool 的能力边界并不清楚。虽然 Tool Name 本身也能给模型一些线索但模糊的 Description 仍然会增加误选 Tool 漏选 Tool 不合理调用的概率。所以 Description 最好不要只是search_web 用于搜索 search_project 用于搜索而应该尽可能把能力边界说明清楚。例如search_web 搜索互联网上的公开信息。 当需要查询当前项目之外的网页、文档、 新闻或其他互联网信息时使用。search_project 搜索当前项目中的代码和文件内容。 当需要查找类、函数、变量、字符串 或者定位代码引用位置时使用。这样模型就更容易理解当前项目 UserService 调用位置 ↓ search_project所以Tool Description 不只是介绍“这个 Tool 是什么”它还帮助 LLM 判断“什么时候应该使用这个 Tool以及它和其他 Tool 有什么区别”。4. Arguments 是什么假设有defsearch_project(query,limit):...用户说帮我搜索UserService最多返回 10 条。LLM 可能生成{name:search_project,arguments:{query:UserService,limit:10}}这里{query:UserService,limit:10}就是Arguments参数可以简单理解成调用这个 Tool 时具体要传进去什么值。所以Schema ↓ 规定参数应该长什么样 Arguments ↓ LLM 实际生成了什么参数例如 Schemaquery → string limit → integerLLM 根据用户请求生成query UserService limit 105. Arguments 是谁生成的这一点很重要。不是 Tool 自己生成。也不是程序员提前把所有参数写死。通常是LLM 根据用户请求、当前 Context 和 Tool Schema 生成 Arguments。例如用户 帮我搜索 UserService 最多返回 10 条 Tool Schema query → string limit → integer ↓ LLM ↓ Tool Call search_project arguments: { query: UserService, limit: 10 }但注意到这里 Tool 还没有真正执行。LLM 只是生成了我希望调用 search_project 并且参数是 query UserService limit 10真正执行还需要外部程序处理。6. LLM 生成参数以后能直接执行吗不能简单地认为LLM 生成什么 ↓ 系统就执行什么因为 LLM 也可能生成错误参数。例如 Schema 要求limit → integer正确应该是{limit:10}但模型可能生成{limit:很多}这时候Schema 要求 integer 实际 string参数不符合要求。因此在真正执行 Tool 之前通常还需要Validation参数校验7. Validation 是什么Validation 可以简单理解成检查 LLM 生成的 Arguments 是否符合 Tool Schema 的要求。例如{query:UserService,limit:10}检查query 是 string ✅ limit 是 integer ✅ 必填参数齐全 ✅ValidationPASS然后才允许执行 Tool。但如果{query:UserService,limit:很多}检查query → string ✅ limit 要求 integer 实际却是 string ❌那么Validation Failed ↓ 不执行 Tool可以把错误信息反馈回 Agent Looplimit 必须是 integer 但实际收到 string然后错误加入 Context ↓ 再次调用 LLM ↓ LLM 重新生成参数例如{query:UserService,limit:10}再次 ValidationPASS ↓ 执行 Tool所以可以记成Arguments → 传什么参数 Validation → 这些参数合不合法8. 到底是谁真正执行 Tool这是 Tool Calling 中非常容易产生误解的地方。例如 LLM 返回{name:search_project,arguments:{query:UserService,limit:10}}并不是 LLM 自己真的运行了search_project(UserService,10)更接近下面这个过程LLM ↓ 生成 Tool Call “我希望调用 search_project 参数是 queryUserService、limit10” ↓ Harness / Runtime ↓ 接收 Tool Call ↓ Validation ↓ 找到真正的 search_project ↓ 传入 Arguments ↓ 执行 Tool所以Tool Schema → 告诉模型“怎么调用” LLM → 决定“调用什么 参数是什么” Harness / Runtime → 校验并真正发起调用 Tool → 真正完成搜索、读取文件、查询数据库等操作9. Tool Selection 是什么假设 Agent 有三个 Toolsearch_web → 搜索互联网公开信息 search_project → 搜索当前项目代码和文件 read_file → 读取指定文件的具体内容用户说帮我看看UserService.java第 100 行附近写了什么。现在文件已经知道 UserService.java 位置也知道 第 100 行附近 用户需要 读取具体内容所以更适合read_file如果用户换成我不知道UserService在哪个文件帮我找到它然后看看它的代码。情况就不同了。第一步不知道 UserService 在哪里 ↓ 需要“定位” ↓ search_project得到src/main/java/service/UserService.java现在 Context 中多了一个重要信息UserService 的 path下一轮已经知道文件位置 ↓ 现在需要“读取” ↓ read_file所以完整过程用户任务 ↓ 不知道文件位置 ↓ search_project ↓ Tool Result ↓ 得到 path ↓ 加入 Context ↓ 再次调用 LLM ↓ read_file ↓ 得到代码这就是Tool Selection工具选择。可以简单理解成LLM 根据当前任务、Context 和 Tool 的能力描述判断当前应该使用哪个 Tool。10. Tool Selection 不只是看“动作”例如search_project → 搜索当前项目 search_web → 搜索互联网不能简单记只要“搜索” ↓ 就调用 search_project还需要判断我要对什么东西执行这个动作例如搜索 当前项目 ↓ search_project读取 当前项目已知文件 ↓ read_file搜索 互联网公开信息 ↓ search_web所以选择 Tool 时可以思考① 当前目标是什么 ② Context 已经有什么信息 ③ 为了完成任务还缺什么信息 ④ 哪个 Tool 最适合获得这个信息 ⑤ 调用这个 Tool 所需要的 Arguments 当前是否已经具备11. Tool 之间为什么会产生依赖假设用户找到UserService所在文件然后读取它。首先search_project(UserService)得到src/service/UserService.java然后才能read_file( src/service/UserService.java )因为read_file 需要 path ↑ │ 这个 path 来自 search_project 的 Result也就是说后一个 Tool Call 所需要的 Arguments可能来自前一个 Tool 的 Result。因此search_project ↓ Tool Result ↓ path ↓ read_file存在数据依赖必须按照顺序执行。12. Tool 很多的时候怎么办如果 Agent 只有3 个 Tool全部提供给 LLM 通常没什么问题。但如果系统有50 个 Tool 500 个 Tool每个 Tool 都有Name Description Parameters 参数说明 参数约束 ...如果全部提供给模型可能带来Context 变长 ↓ Token 消耗增加 Tool 数量很多 ↓ 选择难度增加 大量无关 Tool ↓ 干扰模型判断例如用户只是搜索当前项目里的UserService。却给模型search_project read_file search_web send_email get_weather create_order query_stock book_hotel create_calendar ...很多 Tool 和当前任务根本没有关系。因此可以在真正交给 LLM 之前先筛选 Tool。13. Tool Filtering 是什么Tool Filtering工具筛选可以简单理解成所有 Tool ↓ 先筛选 ↓ 留下当前任务可能需要的 Tool ↓ 提供给 LLM例如系统共有 50 个 Tool ↓ Tool Filtering ↓ 候选 Tool search_project read_file ↓ LLM Tool Selection ↓ search_project所以Tool Filtering → 缩小候选范围 Tool Selection → LLM 从当前可用 Tool 中决定调用哪个这两个概念并不完全一样。14. Tool Routing 是什么Routing 可以简单理解成根据当前任务把请求导向合适的 Tool 或 Tool 集合。例如项目开发类 ├── search_project ├── read_file ├── write_file └── run_test 互联网类 ├── search_web └── fetch_webpage 数据库类 ├── query_database └── update_database 邮件类 ├── search_email └── send_email用户找一下项目里的UserService。Router 判断项目代码相关任务 ↓ 项目开发类 Tool ↓ search_project read_file write_file run_test然后再把这些候选 Tool 提供给 LLM。实际项目中 Routing 和 Filtering 的具体实现和叫法可能不同不必死抠定义。更重要的是理解背后的工程思想Tool 很多时不一定每一轮都要把所有 Tool 全部提供给 LLM可以先根据当前任务缩小候选范围。15. 什么是关键词过滤一种比较简单的 Filtering 方法就是关键词匹配。例如search_project keywords: 项目 代码 文件 类 函数用户帮我找一下项目里的 UserService。发现“项目” ↓ 命中 search_project于是所有 Tool ↓ 关键词 Filtering ↓ 项目相关 Tool ↓ 提供给 LLM这是一种非常简单的 Tool Filtering / Routing 思路。16. 关键词一个都没匹配上怎么办关键词过滤并不可靠。例如关键词项目 代码 文件用户却说帮我定位一下UserService的实现。人可以理解这是代码搜索但简单字符串匹配可能发现项目 ❌ 代码 ❌ 文件 ❌于是0 个 Tool 命中但没有命中不代表系统里真的没有合适的 Tool。只是当前过滤策略没有找到。所以一种简单的 Fallback 策略可以是关键词命中 ↓ 提供匹配 Tool 关键词未命中 ↓ Fallback ↓ 回退全部 Tool ↓ 让 LLM 自己判断为什么这样做因为如果过滤器把真正需要的 Tool 错误排除了LLM 根本看不到这个 Tool模型再聪明也没办法选择它。17. 能保证 LLM 一定选择某个 Tool 吗不能简单认为Tool Description 写得很好 ↓ LLM 就一定按照预期调用Tool Description 可以帮助模型理解 Tool 降低误选概率 降低漏选概率但 LLM 的 Tool Selection 仍然是模型决策。因此对于普通开放式任务可以让 LLM 有比较大的自主选择空间。但对于一些关键业务流程付款 退款 删除数据 发送邮件 修改数据库 创建订单就不能只希望“LLM 应该会按照正确顺序调用吧。”18. 为什么关键流程需要服务端规则 / Workflow假设退款流程要求检查订单 ↓ 判断是否满足退款条件 ↓ 满足条件 ↓ 执行退款Toolcheck_order refund_order如果只把两个 Tool 告诉 LLMcheck_order 检查订单状态 refund_order 执行退款然后完全让模型自己决定调用顺序就可能出现还没有检查退款条件 ↓ 直接 refund_order对于这种有副作用的操作一旦执行钱已经退了 邮件已经发了 文件已经删除 数据库已经修改事后再说“刚才选错 Tool 了。”可能已经没有意义。因此关键流程通常需要更加确定性的约束例如服务端规则 Workflow 权限检查 用户确认 参数校验 ……例如服务端直接规定ifuser_wants_refund:ordercheck_order(order_id)ifnotorder.can_refund:return当前订单不满足退款条件refund_order(order_id)这样不是“希望 LLM 记得先检查。”而是程序本身强制要求先检查。19. Workflow 是什么这里先做一个简单理解Workflow 就是提前规定好的执行流程。例如退款 Workflow Step 1 获取订单 ↓ Step 2 检查退款条件 ↓ Step 3 用户确认 ↓ Step 4 执行退款 ↓ Step 5 记录结果LLM 可以参与流程中的某些判断但核心执行顺序已经被约束。可以简单理解LLM 自主 Tool Calling ↓ “根据当前情况你判断下一步做什么。”而Workflow ↓ “大的执行路线已经规定好了 你在这个流程里完成相应任务。”所以关键业务约束不应该只依赖 Tool Description 或 Prompt而应该由更加确定性的程序机制保证。20. Validation 通过就代表 Tool 选对了吗不代表。例如用户读取当前项目的config.json。LLM 却生成{name:search_web,arguments:{query:config.json}}假设search_web要求query → stringValidation 检查Tool 存在 ✅ query 存在 ✅ query 是 string ✅所以Validation PASS但是用户需要 读取当前项目文件 模型选择 搜索互联网显然 Tool Selection 不合理。所以合法性 ≠ 合理性。也就是Validation ↓ 主要判断 Tool Call 是否符合 Schema Tool Selection ↓ 判断当前任务到底应该使用哪个 Tool参数类型完全正确也可能选错 Tool。21. Tool 选错了怎么办Tool Selection 出错可能有很多原因。例如Tool Description 太模糊 ↓ 优化 Description或者候选 Tool 太多 ↓ Routing / Filtering ↓ 缩小范围还有一种情况Tool 已经执行 ↓ Result 发现方向不对 ↓ 重新规划这就是Replan重新规划例如用户 读取当前项目 config.json ↓ LLM search_web(config.json) ↓ Result 没有找到相关信息 ↓ LLM 发现原来的方向不对 ↓ Replan ↓ read_file(config.json)22. Retry 和 Replan 有什么区别这两个概念比较容易混。假设search_web(Agent Harness) ↓ 网络超时然后再次search_web(Agent Harness)更接近Retry重试因为原来的方法可能没有问题 ↓ 只是执行失败 ↓ 再试一次而search_web(config.json) ↓ 发现方向错了 ↓ 改成 read_file(config.json)更接近Replan重新规划因为原来的方案不合适 ↓ 重新判断下一步 ↓ 换一种行动方案可以简单记Retry → 方法可能没错只是执行失败 → 再试 Replan → 原来的方法不合适 → 换方案23. 一次 LLM 可以生成多个 Tool Call 吗可以。例如用户分别搜索UserService、OrderService、PaymentService在哪里被调用。LLM 一次可能生成call_001 search_project(UserService) call_002 search_project(OrderService) call_003 search_project(PaymentService)三个搜索任务之间没有依赖UserService 的搜索结果 不会决定 OrderService 搜索什么所以 Harness 可以考虑并行执行┌→ call_001 LLM ────────┼→ call_002 └→ call_003这样可以减少整体等待时间。24. 什么时候不能并行还是前面的例子找到UserService所在文件然后读取它。第一步search_project(UserService)得到src/service/UserService.java第二步才能read_file( src/service/UserService.java )因为read_file 需要的 path ↑ │ 来自 search_project Result存在数据依赖。所以有依赖 → 按依赖关系执行 互相独立 → 可以考虑并行是否并行不是简单看有几个 Tool而是看Tool 之间有没有数据依赖关系。25. 为什么需要 call_id假设 LLM 一次产生call_001 search_project(UserService) call_002 search_project(OrderService) call_003 search_project(PaymentService)调用顺序001 → 002 → 003但是并行执行以后完成顺序可能002 → 003 → 001所以不能简单认为第一个返回的 Result 就属于第一个 Tool Call。因此每个 Tool Call 可以有唯一标识call_001 call_002 call_003Tool Result 也携带对应标识Tool Result call_id call_002 result ...这样call_001 ↔ result_001 call_002 ↔ result_002 call_003 ↔ result_003即使 Tool 完成顺序发生变化也能正确对应。26. Tool Result 最后去哪Tool 执行完成以后不应该Tool ↓ Result ↓ 结束而是Tool ↓ Tool Result ↓ 根据 call_id 对应 ↓ 加入 Context ↓ 再次调用 LLM ↓ 下一轮 Agent Loop例如用户 找到 UserService 并查看代码 ↓ search_project(UserService) ↓ Tool Result src/service/UserService.java ↓ 加入 Context ↓ 再次调用 LLM ↓ LLM 现在知道 path 了 ↓ read_file(...)所以Tool Calling 通常不是孤立的一次函数调用而是 Agent Loop 中的一部分。27. 一次完整的 Tool Calling现在把前面的知识全部串起来用户请求 ↓ 当前 Context ↓ Routing / Filtering可选 ↓ 候选 Tool Schema ↓ LLM ↓ Tool Selection ↓ 选择要使用的 Tool ↓ 生成 Arguments ↓ Tool Call ↓ Harness / Runtime 接收 ↓ Validation ↙ ↘ 失败 成功 ↓ ↓ 不执行 Tool 执行 Tool ↓ ↓ 错误信息 Tool Result ↓ ↓ └──→ Context ←──┘ ↓ 再次调用 LLM ↓ 下一轮 Agent Loop如果存在多个 Tool CallTool Calls ↓ 判断依赖关系 ↓ ┌──┴──┐ 独立 有依赖 ↓ ↓ 并行 按顺序执行28. Tool Calling 中不同角色分别负责什么Tool Schema告诉模型这个 Tool 是什么 什么时候适合使用 需要哪些参数 参数应该是什么类型LLM负责理解当前任务 ↓ 选择 Tool ↓ 生成 Arguments ↓ 产生 Tool Call但通常并不直接执行 Tool。Harness / Runtime负责向模型提供 Tool ↓ 接收 Tool Call ↓ Validation ↓ 找到真正的 Tool ↓ 执行 Tool ↓ 处理 Result ↓ 把 Result 放回 ContextTool真正完成具体动作搜索代码 读取文件 查询数据库 调用 API 发送请求 ……Tool Result表示刚才 Tool 执行以后发生了什么。然后Tool Result ↓ Context ↓ LLM ↓ 下一步决策29. Tool Calling 和前两篇是什么关系现在可以把前三篇串起来Agent Harness │ │ Agent 外围的运行与控制机制 │ ├──── Agent Loop │ │ │ │ 一轮一轮调用 LLM │ │ │ └──────────────┐ │ │ │ Tool Calling │ │ │ Tool Schema │ ↓ │ Tool Selection │ ↓ │ Arguments │ ↓ │ Validation │ ↓ │ Tool Execution │ ↓ │ Tool Result │ ↓ │ Context │ │ │ ┌──────────────┘ │ ↓ │ 下一轮 Agent Loop │ └── Verification / Loop Guard / Fallback ...所以我目前的理解是Harness 是 Agent 外围的运行和控制框架Agent Loop 让模型能够一轮一轮持续执行而 Tool Calling 则让模型在每一轮中能够与外部能力进行交互。30. 总结刚开始接触 Tool Calling 时很容易把它理解成LLM ↓ 直接调用 Python 函数但真正学习以后会发现更准确的过程是用户任务 ↓ LLM 理解任务 ↓ 根据 Tool Schema 选择 Tool ↓ 生成 Arguments ↓ 产生结构化 Tool Call ↓ Harness / Runtime 接收 ↓ Validation ↓ 真正执行 Tool ↓ 得到 Tool Result ↓ 放回 Context ↓ LLM 继续判断下一步所以LLM 并不是直接“操作”外部世界而是在生成下一步行动的结构化请求真正把这个请求变成现实动作的是 Agent 外部的 Harness / Runtime。而一个真正可靠的 Tool Calling 系统还需要考虑Tool Description 是否清晰 Tool 太多时怎么筛选 模型选错 Tool 怎么办 Arguments 是否合法 多个 Tool 能不能并行 Tool Result 怎么正确对应 关键 Tool 是否需要额外约束因此 Tool Calling 并不只是tool()这么简单。它实际上连接了LLM 的推理能力 ↓ 结构化 Tool Call ↓ Harness / Runtime ↓ 真实的外部能力这也是 Agent 能够从“只会生成文本的模型”进一步变成“能够借助外部工具完成实际任务的系统”的重要机制之一。