一句话主线:矛盾始终是「能力可以无限多,但上下文窗口有限」,三代方案是在「注册成本、Token开销、结构化约束、安全可控」之间不断取舍
前言
在 Agent 工程落地里,工具调用是绕不开的核心。早期大家直接上手 FunctionCall,很快撞上一个硬墙:工具列表无限膨胀。 工具越多,塞进系统提示词里的 JSON Schema 就越长,Token 成本飙升、模型注意力被稀释、选错工具概率变高。
为了解决这个问题,业界先后走出两条路线:
- CLI 探测方案:收敛到单一 shell 入口,把工具发现交给操作系统;
- Skill 按需加载方案:索引常驻、详情延迟加载,也就是现在 Claude Code、各类企业 Agent 广泛使用的渐进式披露(Progressive Disclosure)。
很多人误以为它们是简单的替代关系,其实每一代都解决了上一代的痛点,同时引入新的取舍。本文梳理三者的原理、trade-off、适用边界。
第一代:FunctionCall(全量静态 Schema 注册)
原理
FunctionCall 是最早标准化的工具调用范式(OpenAI 首发,后来统一为 tools 接口)。
- 每一个能力,单独定义一份 JSON Schema:包含工具名称、描述、参数结构、必填项、枚举等;
- 所有工具 Schema 在会话初始化时一次性全部注入上下文;
- 模型在全部工具列表里选择,返回结构化工具调用,框架解析参数、执行,再把结果返回模型。
示例单条工具的结构:
{
"name": "ffmpeg_transcode",
"description": "视频转码,支持裁剪、修改编码格式",
"parameters": {
"type": "object",
"properties": {
"input": {"type": "string", "description": "输入文件路径"},
"output": {"type": "string"}
},
"required": ["input","output"]
}
}
优点
- 强结构化、强契约:参数类型、约束明确,框架可以做校验,不容易乱传参数;
- 原生支持模型结构化输出,调试链路清晰;
- 权限、审计、拦截容易在框架层统一实现。
致命痛点:工具膨胀问题
- 开销是 O(N):工具越多,初始 Prompt 越长;无论本次任务用不用,全部带上;
- 单条 Schema 很重:一条工具描述+参数定义动辄上百 token;几十上百个工具之后,前置开销直接爆炸;
- 注意力衰减:工具清单太长,出现 Lost in the middle,模型容易混淆相似工具、选错;
- 新增能力成本高:新增工具必须新增 Schema、修改代码/配置、重新发布 Agent,运维无法直接接入二进制程序。
适合场景:工具数量少、能力稳定、需要强参数校验、多租户隔离的线上服务。 不适合:内部工具几十上百个、工具持续迭代、运维脚本、本地二进制快速接入场景。
第二代:CLI 探测方案——单一 Shell 入口,把工具发现交给操作系统
诞生背景
为了解决 FunctionCall「新增工具必须改Schema、全量Schema膨胀」的痛点,工程师想到一个极简思路:不再给模型一堆独立工具,只暴露一个工具:执行 shell 命令。
核心原理
Agent 框架只注册唯一一条工具:execute_bash。
- 模型不再从预定义的 Schema 列表选工具,而是自己生成 shell 命令字符串;
- 模型识别可用能力两种方式:
- 预训练知识:
git、jq、ffmpeg这类通用命令,模型本身见过; - 运行时探测:会话启动时自动执行
compgen -c/which xxx,把本机 PATH 下所有可执行程序名称列表拿出来,发给模型; - 按需拉取帮助文档:模型打算调用某个陌生命令时,再执行
xxx --help,把用法加载进上下文。
- 预训练知识:
重点澄清一个容易踩坑的误区: 探测出来的命令列表,同样会占用 Token。它不是消灭 Token 开销,只是改变了开销形态: FunctionCall:每个工具 = 完整Schema(高token密度) CLI探测:工具清单 = 一堆命令名字(极低token密度),详情(help)延迟加载
优点
- 零注册成本:新增能力只需要在服务器上部署二进制脚本,放到PATH;不需要改Agent代码、不需要新增Schema、不需要改系统提示词;
- 入口固定,工具列表永远只有一条,框架层面不会膨胀;
- 天然支持管道组合
a | b | c,是运维、脚本场景的天然优势; - 存量CLI生态直接复用,不用为每个老命令包装一层Function。
硬短板
- 安全风险极高:模型可以生成任意命令,删文件、遍历内网、窃取凭证,必须强沙箱、白名单、超时、输出截断;
- 输出是非结构化文本(stdout/stderr),模型解析容易出错;没有内置参数校验;
- 私有内部命令:只有名字,没有规范说明,模型很容易写错参数;
- 探测列表本身依然会膨胀:如果机器上几千个命令,名字列表本身也会吃掉大量上下文;
- 很难做细粒度权限管控、标准化审计。
适合场景:本地开发Agent、运维自动化、代码编程Agent、可信沙箱环境。 不适合:公有云多租户、高安全要求、复杂业务参数校验场景。
第三代:Skill 范式,渐进式按需加载(Progressive Disclosure)
CLI 方案解决了「框架注册膨胀」,但依然有两个问题:
- 要么一次性塞一长串命令名列表;
- 命令是非结构化的,没有统一的能力说明书、工作流约束。
于是 Skill 方案登场,现在是 Claude Code、企业自研 Agent 的主流路线。
核心原理
Skill 本质是能力资产化,分层加载:
- 每一个 Skill 对应一个目录,核心是
SKILL.md,里面写清楚:适用场景、调用步骤、参数约定、限制、可调用的脚本/工具; - 分层加载:
- 索引层(常驻上下文):Agent 启动时扫描全部Skill目录,只加载「名称 + 一句话简介」元数据,体积很小;
- 正文层(按需加载):当模型判断当前任务匹配某个Skill,主动调用
load_skill工具,把完整SKILL.md读入上下文; - 附属资源(再次延迟):文档里引用的脚本、SQL模板、配置文件,用到的时候再读取。
一句话对比 CLI:
- CLI:能力清单 = 操作系统PATH里的二进制名;
- Skill:能力清单 = 本地/远端索引目录里的Skill元数据;能力的契约、流程、边界写在 SKILL.md。
优点
- Token 效率最优:大量能力只保留极简索引,绝大多数Skill永远不加载全文;从根源避免全量灌入;
- 结构化能力描述,自带业务流程、约束、异常处理,比单纯
--help文本更适合模型理解; - 治理友好:Skill可以版本管理、权限控制、评估、上架下架;
- 兼容混合模式:Skill内部依然可以调用 FunctionCall、Shell、MCP工具。
短板
- 有工程治理成本:需要目录规范、索引扫描、加载缓存、过期回收机制;
- 依赖模型意图判断:模型判断错了,该加载的Skill不加载,或者加载无关Skill;
- 依然有边界:索引太多的时候,索引列表本身也会变长,需要做索引路由、分类检索。
适合场景:企业内部Agent、复杂业务流程、大量领域能力、需要可治理、可沉淀的技能资产。
横向对比总表
| 维度 | FunctionCall | CLI 探测方案 | Skill 按需加载 |
|---|---|---|---|
| 能力注册方式 | 预先写死Schema,静态注册 | 部署二进制到PATH,运行时探测 | 编写SKILL.md,扫描元数据索引 |
| 初始上下文开销 | 高,全量Schema | 低,仅1个shell工具 + 可选命令名列表 | 极低,仅名称+一句话简介索引 |
| 详情加载时机 | 启动一次性全部加载 | 调用该命令时拉取 --help | 意图匹配后加载完整SKILL.md |
| 参数结构化校验 | ✅ 原生支持 | ❌ 无,靠人工help文本 | ⚠️ 文档约定,可搭配底层工具校验 |
| 新增能力成本 | 高,改代码、新增Schema | 极低,部署二进制即可 | 中等,编写SKILL.md |
| 安全边界 | 容易做权限、参数校验 | 风险高,必须沙箱白名单 | 可控,Skill层面做权限、准入 |
| 适合规模 | 少量工具(<20) | 运维/编程沙箱,大量存量CLI | 几十~上百个领域业务能力 |
演进关系总结(重点)
不是后者彻底淘汰前者,而是抽象层次往上走:
- FunctionCall:把能力封装成标准化函数,契约优先,但是静态全量,规模上限低;
- CLI探测:把执行层收敛到Shell,复用操作系统生态,解决「注册膨胀」,代价是结构化和安全让步;
- Skill:在CLI/FunctionCall之上增加一层「能力编排层」,用索引+按需加载解决上下文膨胀,同时沉淀标准化的能力说明书、工作流。
现实工程里经常是混合形态:
一个Skill内部,既可以调用FunctionCall接口,也可以在沙箱内执行shell命令。Skill是能力的说明书与流程,不是替代底层执行方式。
写在最后
很多人讨论工具膨胀的时候,很容易陷入一个误区:追求“零Token开销”。 但看完三代方案就能明白:不存在免费的午餐,所有方案都只是把开销转移到不同位置:
- FunctionCall:开销前置在Schema;
- CLI探测:开销转移到命令名列表和help按需读取;
- Skill:开销转移到索引检索 + 按需加载正文。
选型的判断标准非常简单:
- 工具少、稳定、面向公网服务 → FunctionCall;
- 本地编程、运维、可信沙箱、大量存量脚本 → CLI;
- 企业业务、大量领域能力、需要沉淀、治理、长期迭代 → Skill。
注意:本文归作者所有,未经作者允许,不得转载