Skip to content

工具系统:Agent 的双手

本章摘要

工具系统贯穿 Fail-Closed 安全哲学——默认工具是不安全的。优先拒绝、deny 永远优先于 allow、安全检查不可绕过。

三层过滤管线按顺序独立判断:模式(白名单)→ 规则(黑名单)→ 状态(自检查),一层失守下层仍能拦截——这就是纵深防御的价值。

ToolSearch 把工具描述从初始上下文剥离改为按需加载,让模型看不到的工具完全没有被误调用的可能——最小暴露本身就是安全策略

如果把 Agent Loop 比作 Agent 的心跳,那工具系统就是它的双手。没有手,Agent 只能说话;有了手,Agent 才能动手。

Agent 都有哪些"手"

一个 Agent 通常需要这些类别的工具:

  • 文件操作:读取、写入、编辑文件,编辑笔记本——读写代码和配置的基础
  • 系统操作:执行 Shell 命令、按文件名搜索、按内容搜索
  • 代理与任务管理:创建子代理、创建/查询/更新/停止任务——把大任务拆解委派
  • 网络:网页搜索、网页内容抓取
  • 调度与监控:创建/删除定时任务、监控进程事件
  • 扩展接入:执行自定义技能、对接外部协议

不同类别的工具有不同的安全敏感度:只读工具风险低、删除类工具风险高、网络外发工具风险特殊。后续的"权限管线"章节会根据这些敏感度做不同的处理。

工具是怎么定义的

工具定义包含两类信息:

基本信息(具备什么能力):

  • 名称:唯一标识符,模型用它来调用
  • 描述:告诉模型这个工具做什么、什么场景该用它
  • 参数 schema:调用它需要什么参数、参数是什么类型

安全配置(该工具的安全性如何):

  • 是否只读:只读取信息还是会修改状态
  • 是否破坏性:操作是否不可逆(如强制永久删除)
  • 是否并发安全:多个子代理同时使用是否安全
  • 是否启用:当前环境是否允许使用——不同环境下能用的工具不一样,比如断网时网络搜索工具自动不可用
  • 权限检查:访问控制,每次调用时实时验证。检查用户的权限配置、当前运行模式等,决定放行还是拦截
  • 是否延迟加载:性能优化,初始时是否加载到 tools 列表中,或是等需要时再发现

这些元数据大多支持动态判断——可以用方法(函数)的形式定义,可接收参数来动态做出判断。例如同一个 BashTool,ls(查看目录的内容)时,"是否只读"的判断为"是",rm -rf / 时返回否;

安全元数据是"声明",但真实放行还要经过运行时检查(见下一节"过滤管线"和后续"权限管线"章节)。

工具过滤管线:谁能上场

不是所有工具在任何时候都能用。工具过滤管线是一个多层筛选机制,决策主体分别是系统、用户、工具自身,确保模型只接触到当前环境允许的工具。

决策者做什么
模式过滤系统根据运行模式保留部分工具集
规则过滤用户移除被用户配置拒绝的工具
状态检查工具自身根据当前环境判断工具是否可用

任何一层独立拦截即可移除工具。纵深防御的价值在于:即使某一层失守,下一层仍能拦截。

tool-filter-pipeline

Fail-Closed:贯穿管线的安全哲学

Fail-Closed 这个词来源于电磁门锁:断电时门锁关闭(fail-closed),而不是打开(fail-open)。核心理念:无法确定安全时,默认拒绝。

它体现在三条原则上:

  • 拒绝优先于允许。用户在多份配置项(项目级别或用户级别)中为某个工具同时配置了 allow 和 deny 规则时,deny 总是优先。
  • 安全检查不可绕过。即使开启"完全访问"模式,对一些核心资源和文件的写入仍会被拦截。
  • 默认保守。破坏性操作、外部数据外发、不可逆操作都需要额外确认。

这三条原则不只在工具过滤中起作用,它们也是后续"权限管线"章节的基本原则

ToolSearch:按需加载

经过安全管线过滤,仍然还会有大量工具,把他们都塞进每次对话的初始提示(tools 字段,详见与模型对话:LLM API 工作模式)仍有两个问题:

  1. 浪费空间——工具描述占用的空间很大
  2. 干扰选择——模型在太多工具中反而难以准确选择

更深层的好处是安全:模型看不到的工具,就不会被误调用。最小暴露本身就是安全策略。

系统因此把工具分为两类:

  • 立即可用工具:核心工具(执行命令、读写文件等)在对话开始时就加载
  • 延迟工具:使用频率较低的工具以及外部工具在初始时不加载,由一个特殊的,叫做 "工具搜索" 的工具管理(这个工具本身是一个立即可用的工具)。当模型意识到需要某个尚未加载的工具时,它就会调用工具搜索工具,其 query 参数可以有如下几种:
query实现方式例子
select:Name1,Name2精确选——按工具名直接抓取select:Read,Edit,Grep
裸工具名精确匹配——整串 query 等于某个工具名Readmcp__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 优先、安全检查不可绕过、无法证明安全就拒绝
  • 按需加载不只是性能优化,也是最小暴露的安全策略
  • 模型看到的是"被过滤、被按需加载后的工具集"——框架在背后做了大量安全工作

工具经过过滤筛选和按需加载,终于出现在模型面前。但模型真正发起一次工具调用时,还有最后一道防线——那正是下一章"权限管线"的内容。

延伸阅读

MCP 协议:AI 时代的 “USB-C 接口”