工具系统:Agent 的双手
本章摘要:
工具系统贯穿 Fail-Closed 安全哲学——默认工具是不安全的。优先拒绝、deny 永远优先于 allow、安全检查不可绕过。
三层过滤管线按顺序独立判断:模式(白名单)→ 规则(黑名单)→ 状态(自检查),一层失守下层仍能拦截——这就是纵深防御的价值。
ToolSearch 把工具描述从初始上下文剥离改为按需加载,让模型看不到的工具完全没有被误调用的可能——最小暴露本身就是安全策略。
如果把 Agent Loop 比作 Agent 的心跳,那工具系统就是它的双手。没有手,Agent 只能说话;有了手,Agent 才能动手。
Agent 都有哪些"手"
一个 Agent 通常需要这些类别的工具:
- 文件操作:读取、写入、编辑文件,编辑笔记本——读写代码和配置的基础
- 系统操作:执行 Shell 命令、按文件名搜索、按内容搜索
- 代理与任务管理:创建子代理、创建/查询/更新/停止任务——把大任务拆解委派
- 网络:网页搜索、网页内容抓取
- 调度与监控:创建/删除定时任务、监控进程事件
- 扩展接入:执行自定义技能、对接外部协议
不同类别的工具有不同的安全敏感度:只读工具风险低、删除类工具风险高、网络外发工具风险特殊。后续的"权限管线"章节会根据这些敏感度做不同的处理。
工具是怎么定义的
工具定义包含两类信息:
基本信息(具备什么能力):
- 名称:唯一标识符,模型用它来调用
- 描述:告诉模型这个工具做什么、什么场景该用它
- 参数 schema:调用它需要什么参数、参数是什么类型
安全配置(该工具的安全性如何):
- 是否只读:只读取信息还是会修改状态
- 是否破坏性:操作是否不可逆(如强制永久删除)
- 是否并发安全:多个子代理同时使用是否安全
- 是否启用:当前环境是否允许使用——不同环境下能用的工具不一样,比如断网时网络搜索工具自动不可用
- 权限检查:访问控制,每次调用时实时验证。检查用户的权限配置、当前运行模式等,决定放行还是拦截
- 是否延迟加载:性能优化,初始时是否加载到
tools列表中,或是等需要时再发现
这些元数据大多支持动态判断——可以用方法(函数)的形式定义,可接收参数来动态做出判断。例如同一个 BashTool,
ls(查看目录的内容)时,"是否只读"的判断为"是",rm -rf /时返回否;
安全元数据是"声明",但真实放行还要经过运行时检查(见下一节"过滤管线"和后续"权限管线"章节)。
工具过滤管线:谁能上场
不是所有工具在任何时候都能用。工具过滤管线是一个多层筛选机制,决策主体分别是系统、用户、工具自身,确保模型只接触到当前环境允许的工具。
| 层 | 决策者 | 做什么 |
|---|---|---|
| 模式过滤 | 系统 | 根据运行模式保留部分工具集 |
| 规则过滤 | 用户 | 移除被用户配置拒绝的工具 |
| 状态检查 | 工具自身 | 根据当前环境判断工具是否可用 |
任何一层独立拦截即可移除工具。纵深防御的价值在于:即使某一层失守,下一层仍能拦截。

Fail-Closed:贯穿管线的安全哲学
Fail-Closed 这个词来源于电磁门锁:断电时门锁关闭(fail-closed),而不是打开(fail-open)。核心理念:无法确定安全时,默认拒绝。
它体现在三条原则上:
- 拒绝优先于允许。用户在多份配置项(项目级别或用户级别)中为某个工具同时配置了 allow 和 deny 规则时,deny 总是优先。
- 安全检查不可绕过。即使开启"完全访问"模式,对一些核心资源和文件的写入仍会被拦截。
- 默认保守。破坏性操作、外部数据外发、不可逆操作都需要额外确认。
这三条原则不只在工具过滤中起作用,它们也是后续"权限管线"章节的基本原则。
ToolSearch:按需加载
经过安全管线过滤,仍然还会有大量工具,把他们都塞进每次对话的初始提示(tools 字段,详见与模型对话:LLM API 工作模式)仍有两个问题:
- 浪费空间——工具描述占用的空间很大
- 干扰选择——模型在太多工具中反而难以准确选择
更深层的好处是安全:模型看不到的工具,就不会被误调用。最小暴露本身就是安全策略。
系统因此把工具分为两类:
- 立即可用工具:核心工具(执行命令、读写文件等)在对话开始时就加载
- 延迟工具:使用频率较低的工具以及外部工具在初始时不加载,由一个特殊的,叫做 "工具搜索" 的工具管理(这个工具本身是一个立即可用的工具)。当模型意识到需要某个尚未加载的工具时,它就会调用工具搜索工具,其 query 参数可以有如下几种:
| query | 实现方式 | 例子 |
|---|---|---|
select:Name1,Name2 | 精确选——按工具名直接抓取 | select:Read,Edit,Grep |
| 裸工具名 | 精确匹配——整串 query 等于某个工具名 | Read、mcp__slack__send_message |
mcp__ 前缀 | 前缀匹配——匹配所有以该前缀开头的 MCP 工具 | mcp__slack → 返回 mcp__slack__send_message 等 |
| 其他字符串 | 关键词搜索——按词评分排序 | notebook jupyter 或 +playwright click |
划重点:框架不为模型猜测意图。模型想用哪条路径,就主动把 query 写成对应形式。如果模型凭印象写错了语法(比如本来想精确选 Read 却写 +Read),会落到关键词搜索,可能搜到一堆无关结果。框架只做"按字符串特征分发",不做"按语义意图纠错"。
这里的设计哲学和搜索引擎类似:先匹配最确定的(精确名),再扩大范围(前缀、模糊),最后按相关度排序。多种信号共同决定排序——工具名、开发者标注的关键词、描述文字等。
关键词搜索的词分类
关键词搜索这一步还会再做一次细分:模型可以用 + 前缀主动标记必需词。例如 +playwright click 中,playwright 是必需词(必须出现在工具名或描述中),click 是可选词(参与评分但不淘汰候选)。
如果模型不写 +(比如 playwright click),所有词都按可选处理——不做预筛选,所有延迟工具都进入评分。必需 vs 可选是模型的搜索策略选择,不是系统自动判断。
本章要点
- 工具定义同时包含模型视角(描述能力)和框架视角(安全元数据)
- 工具过滤管线是三层纵深防御:模式(白名单) → 规则(黑名单) → 状态(自检查)
- Fail-Closed 哲学贯穿整个系统:deny 优先、安全检查不可绕过、无法证明安全就拒绝
- 按需加载不只是性能优化,也是最小暴露的安全策略
- 模型看到的是"被过滤、被按需加载后的工具集"——框架在背后做了大量安全工作
工具经过过滤筛选和按需加载,终于出现在模型面前。但模型真正发起一次工具调用时,还有最后一道防线——那正是下一章"权限管线"的内容。