news 2026/6/3 1:32:24

【Agent 从零到一】S02:Tool Calling —— 极简代码打通 Agent 工具调用核心能力

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Agent 从零到一】S02:Tool Calling —— 极简代码打通 Agent 工具调用核心能力

在上一篇《不到 30 行代码,写一个完整的 Agent》中,我们拆解了 Agent 最核心的本质:一个 while 循环 + 一个停止条件。这个极简的循环是所有 Agent 的基石,无论多么复杂的智能体,最终都跑在这个无限循环之上。

但 s01 的 Agent 有一个致命的缺陷:它只有一个 bash 工具。读文件要拼 cat,写文件要拼 echo,改文件要拼 sed。模型明明想的是 “读这个文件”,却要先翻译成 shell 命令,多了一层不必要的转换。

今天我们来看 s02——整个系列中最具启发性的一章。它用一行代码的改动,解决了 Agent 工具扩展的核心问题,并且提出了一个可以泛化到所有 Agent 系统的设计范式。

一、s01 的 “原罪”:单一工具的局限性

s01 的 Agent 虽然能用,但本质上是一个 “会写 shell 脚本的模型”。所有能力都建立在 bash 之上,这带来了三个无法回避的问题:

翻译开销与错误率:模型需要将自然语言意图翻译成 shell 语法,这不仅浪费 token,还极易出错。比如处理包含特殊字符的文件名、多行内容时,sed 和 echo 经常会出现意想不到的行为。

完全失控的安全面:bash 没有任何沙箱限制。一句rm -rf /就能摧毁整个系统,Agent 在执行复杂任务时,很容易误生成危险命令。

能力边界模糊:模型不知道自己能做什么、不能做什么。它可能会尝试调用不存在的命令,或者用错误的参数执行命令,导致大量无效的工具调用。

关键问题来了:我们需要为每个新能力都修改 Agent 的核心循环吗?

s02 给出了一个响亮的答案:不需要。

二、s02 的核心突破:工具分发机制

s02 对 s01 的代码改动只有一行。

s01 的硬编码工具调用

# s01: 硬编码只能调用bashforblockinresponse.content:ifblock.type=="tool_use":output=run_bash(block.input["command"])# 这里写死了run_bashresults.append({"type":"tool_result","tool_use_id":block.id,"content":output,})

s02 的查表式工具调用

# s02: 用字典查表分发TOOL_HANDLERS={"bash":run_bash,"read_file":run_read,"write_file":run_write,"edit_file":run_edit,"glob":run_glob,}forblockinresponse.content:ifblock.type=="tool_use":handler=TOOL_HANDLERS[block.name]# 一行改动:查表output=handler(**block.input)# 通用调用results.append(...)

就是这一行改动,彻底改变了 Agent 的扩展性。

现在,给 Agent 加一个新工具,只需要做两件事:

  1. TOOLS数组中添加工具的 JSON Schema(告诉模型 “我能做什么”)
  2. TOOL_HANDLERS字典中添加一个映射(告诉系统 “怎么做”)

核心循环一行都不用改。

这就是 s02 最伟大的洞察:Agent 的核心循环应该与具体工具实现完全解耦。循环只负责消息流转和停止判断,工具能力通过可插拔的方式注入。

三、泛化:通用工具系统的设计原则

s02 的设计绝不是 Claude Code 特有的技巧,而是一个可以应用到所有 Agent 系统的通用架构范式。我们可以从中提炼出四个核心设计原则:

1. 核心循环与工具实现的完全解耦

这是整个架构的基石。核心循环只关心三件事:

  • 把消息发给 LLM
  • 检查是否需要停止
  • 处理工具调用并返回结果

它不应该知道任何具体工具的存在,也不应该包含任何工具相关的业务逻辑。

反模式:在循环里写满if tool_name == "read_file": ... elif tool_name == "write_file": ...的条件判断。每加一个工具就要改循环代码,很快就会变成无法维护的面条代码。

2. 声明式定义与命令式实现的分离

声明式定义(Schema):告诉模型工具的名称、描述、参数格式。这是模型和工具之间的接口契约。

命令式实现(Handler):工具的具体执行逻辑。这是系统和真实世界之间的连接。

这种分离带来了巨大的灵活性:

  • 你可以修改工具的实现,而不需要告诉模型
  • 你可以给同一个工具提供不同的实现(比如本地文件系统 vs 云存储)
  • 你可以轻松地为不同的模型生成不同格式的 Schema

3. 统一的分发层作为横切关注点的入口

所有工具调用都经过同一个分发点,这让我们可以在不修改任何工具代码的情况下,添加全局的横切关注点:

  • 权限检查:这个工具允许被调用吗?(s03 的核心内容)
  • 日志与审计:记录所有工具调用的参数和结果
  • 监控与指标:统计工具调用的成功率、耗时
  • 重试与熔断:处理工具调用失败的情况
  • 输入验证:统一校验工具参数的合法性

Claude Code 的工具调用实际上经过了一个完整的 5 步验证管线:

Schema验证 → 工具级输入验证 → PreToolUse钩子 → 权限检查 → 执行

所有这些逻辑都在分发层完成,工具本身只需要关心自己的业务逻辑。

4. 多工具调用的编排能力

现代 LLM 几乎都支持一次返回多个工具调用。比如模型可能会说:“先读 a.py 和 b.py,然后列出所有 .py 文件”。

s02 的教学版采用了最简单的顺序执行策略:按照模型返回的顺序逐个执行。

而 Claude Code 的生产级实现则更加智能:

  • isConcurrencySafe(input)判断每个工具调用是否可以并发执行
  • 将工具调用按连续块分区,并发安全的块内并行执行
  • 非并发安全的块串行执行,保证操作顺序
输入: [read A, read B, glob *.py, bash "rm x", read C] 输出: batch1(并发): [read A, read B, glob *.py] batch2(串行): [bash "rm x"] batch3(并发): [read C]

这种策略在保证正确性的前提下,最大化了工具调用的并发度,显著提升了 Agent 的执行速度。

四、关键实现细节解析

1. 路径沙箱:安全的第一道防线

s02 引入了safe_path()函数,这是所有文件操作工具的基础:

defsafe_path(p:str)->Path:path=(WORKDIR/p).resolve()ifnotpath.is_relative_to(WORKDIR):raiseValueError(f"Path escapes workspace:{p}")returnpath

这个简单的函数确保了 Agent 只能操作指定工作区内的文件,防止了路径遍历攻击。

2. 工具粒度的选择

s02 没有设计一个万能的 “文件操作” 工具,而是拆成了read_filewrite_fileedit_fileglob四个独立的工具。这是一个非常重要的设计决策:

细粒度工具:模型更容易理解和正确使用,参数更简单,错误率更低

粗粒度工具:减少工具数量,但模型需要更复杂的参数来表达意图

Claude Code 的经验是:优先选择细粒度的专用工具。模型使用edit_file(old_text, new_text)的准确率,远高于使用一个通用的modify_file(content)工具。

3. 工具结果的大小限制

Claude Code 的每个工具都有一个maxResultSizeChars字段。当工具返回的结果超过这个阈值时,结果会被写入临时文件,模型只会看到一个预览和文件路径。

这是一个非常关键的细节,它防止了大文件内容撑爆 LLM 的上下文窗口。特别需要注意的是,read_file工具的这个值应该设为Infinity,否则会出现 “读文件→结果落盘→再读落盘文件→再落盘” 的无限循环。

五、进阶思考:工具系统的设计权衡

专用工具 vs 通用 bash

一个永恒的争论是:我们应该提供尽可能多的专用工具,还是保留一个万能的 bash 工具?

答案是两者都需要。

专用工具:安全、可靠、模型使用准确率高,应该覆盖 90% 的常见场景

bash 工具:灵活、强大,可以处理专用工具覆盖不到的边缘情况

Claude Code 的做法是:保留 bash 工具,但对其进行严格的权限控制。危险命令需要用户批准,并且默认禁止执行交互式命令。

工具的幂等性设计

理想情况下,所有工具都应该是幂等的:执行多次和执行一次的效果相同。

比如 s02 的edit_file工具只替换一次匹配的文本:

text.replace(old_text,new_text,1)# 注意最后的1

这就是为了防止因为网络重试或者模型重复调用,导致文件被错误地修改多次。

流式工具执行

Claude Code 还有一个非常酷的特性:流式工具执行。它不需要等模型把整个响应输出完,只要工具调用的参数完整了,就可以立即开始执行工具。

这意味着,当模型还在输出 “我来分析一下这个文件的内容…” 的时候,read_file工具可能已经跑完了。这种 “边生成边执行” 的模式,可以显著减少用户的等待时间。

六、总结与预告

s02 用一行代码的改动,为我们展示了 Agent 架构的极简之美。它的核心贡献不是增加了几个文件工具,而是提出了“循环不变,工具可插拔”的设计范式。

s02 的公式:

可扩展的Agent = 不变的核心循环 + 可插拔的工具分发字典

这个公式的威力在于它的无限扩展性。无论你想给 Agent 加什么能力 —— 调用 API、访问数据库、控制硬件、发送邮件 —— 你都只需要:

  1. 写一个处理函数
  2. 加一个 Schema 定义
  3. 在字典里加一行映射

核心循环永远不变。

现在,我们的 Agent 有了 5 个安全的专用工具,可以可靠地操作文件系统。但还有一个问题没有解决:我们怎么知道 Agent 要做的操作是安全的?它会不会删除重要文件?会不会修改生产环境的配置?

这就是 s03 要解决的问题:权限控制。我们将在工具执行之前加一道门,让 Agent 在执行危险操作之前必须获得用户的批准。

附录:s01 → s02 变更速查表

组件s01s02
工具数量1 (仅 bash)5 (+read, write, edit, glob)
工具执行硬编码run_bash()TOOL_HANDLERS字典查表分发
安全机制safe_path()路径沙箱
核心循环while True + stop_reason完全不变
扩展方式修改循环代码加 handler + 加 Schema
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 1:32:21

信管2402班康文恺作业

index界面<% page contentType"text/html; charsetUTF-8" pageEncoding"UTF-8" %><!DOCTYPE html><html><head><title>首页</title><meta charset"UTF-8"><!-- 引入 Bootstrap --><link hre…

作者头像 李华
网站建设 2026/6/3 1:31:03

GlosSI 入门指南:让 Steam 控制器在任意游戏和应用中畅玩

GlosSI 入门指南&#xff1a;让 Steam 控制器在任意游戏和应用中畅玩 【免费下载链接】GlosSI Tool for using Steam-Input controller rebinding at a system level alongside a global overlay 项目地址: https://gitcode.com/gh_mirrors/gl/GlosSI 想要在 Windows 商…

作者头像 李华
网站建设 2026/6/3 1:30:19

基于树莓派与APDS9960传感器的智能闹钟制作:放下即贪睡的交互设计

1. 项目概述&#xff1a;一个能“放下即贪睡”的智能闹钟早上被闹钟吵醒&#xff0c;迷迷糊糊地伸手去按“贪睡”键&#xff0c;几乎是每个人的日常。但有没有想过&#xff0c;如果连手都不用抬&#xff0c;只需要把闹钟轻轻放倒就能多睡十分钟&#xff0c;会是种什么体验&…

作者头像 李华
网站建设 2026/6/3 1:28:32

在银河麒麟V4上手动编译Qt5.12.7源码,我踩过的那些坑和最终配置方案

银河麒麟V4系统Qt5.12.7源码编译实战&#xff1a;避坑指南与性能调优在国产操作系统生态建设中&#xff0c;银河麒麟V4作为主流Linux发行版之一&#xff0c;其软件适配工作常需要开发者从源码级进行深度定制。Qt框架作为跨平台开发的利器&#xff0c;在图形界面、嵌入式等领域应…

作者头像 李华
网站建设 2026/6/3 1:26:27

[开源] 双通道药房对账协调器:面向医院药房与零售药店处方协同的CLI对账工具

本项目是专为解决「双通道」药品管理场景下医院药房与零售药店之间处方执行结果不一致问题而设计的轻量级对账协调工具。我们直面医保双通道政策落地中普遍存在的系统割裂、编码不一、时间不同步、责任难追溯等现实堵点&#xff0c;不依赖中间数据库或定制化Web界面&#xff0c…

作者头像 李华