IIWAB

从 FunctionCall 到 CLI 探测,再到 Skill:Agent 工具调用范式演进

IIWAB 12天前 ⋅ 52 阅读

一句话主线:矛盾始终是「能力可以无限多,但上下文窗口有限」,三代方案是在「注册成本、Token开销、结构化约束、安全可控」之间不断取舍

前言

在 Agent 工程落地里,工具调用是绕不开的核心。早期大家直接上手 FunctionCall,很快撞上一个硬墙:工具列表无限膨胀。 工具越多,塞进系统提示词里的 JSON Schema 就越长,Token 成本飙升、模型注意力被稀释、选错工具概率变高。

为了解决这个问题,业界先后走出两条路线:

  1. CLI 探测方案:收敛到单一 shell 入口,把工具发现交给操作系统;
  2. 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"]
  }
}

优点

  1. 强结构化、强契约:参数类型、约束明确,框架可以做校验,不容易乱传参数;
  2. 原生支持模型结构化输出,调试链路清晰;
  3. 权限、审计、拦截容易在框架层统一实现。

致命痛点:工具膨胀问题

  • 开销是 O(N):工具越多,初始 Prompt 越长;无论本次任务用不用,全部带上;
  • 单条 Schema 很重:一条工具描述+参数定义动辄上百 token;几十上百个工具之后,前置开销直接爆炸;
  • 注意力衰减:工具清单太长,出现 Lost in the middle,模型容易混淆相似工具、选错;
  • 新增能力成本高:新增工具必须新增 Schema、修改代码/配置、重新发布 Agent,运维无法直接接入二进制程序。

适合场景:工具数量少、能力稳定、需要强参数校验、多租户隔离的线上服务。 不适合:内部工具几十上百个、工具持续迭代、运维脚本、本地二进制快速接入场景。

第二代:CLI 探测方案——单一 Shell 入口,把工具发现交给操作系统

诞生背景

为了解决 FunctionCall「新增工具必须改Schema、全量Schema膨胀」的痛点,工程师想到一个极简思路:不再给模型一堆独立工具,只暴露一个工具:执行 shell 命令。

核心原理

Agent 框架只注册唯一一条工具:execute_bash。

  • 模型不再从预定义的 Schema 列表选工具,而是自己生成 shell 命令字符串;
  • 模型识别可用能力两种方式:
    1. 预训练知识:git、jq、ffmpeg 这类通用命令,模型本身见过;
    2. 运行时探测:会话启动时自动执行 compgen -c / which xxx,把本机 PATH 下所有可执行程序名称列表拿出来,发给模型;
    3. 按需拉取帮助文档:模型打算调用某个陌生命令时,再执行 xxx --help,把用法加载进上下文。

重点澄清一个容易踩坑的误区: 探测出来的命令列表,同样会占用 Token。它不是消灭 Token 开销,只是改变了开销形态: FunctionCall:每个工具 = 完整Schema(高token密度) CLI探测:工具清单 = 一堆命令名字(极低token密度),详情(help)延迟加载

优点

  1. 零注册成本:新增能力只需要在服务器上部署二进制脚本,放到PATH;不需要改Agent代码、不需要新增Schema、不需要改系统提示词;
  2. 入口固定,工具列表永远只有一条,框架层面不会膨胀;
  3. 天然支持管道组合 a | b | c,是运维、脚本场景的天然优势;
  4. 存量CLI生态直接复用,不用为每个老命令包装一层Function。

硬短板

  1. 安全风险极高:模型可以生成任意命令,删文件、遍历内网、窃取凭证,必须强沙箱、白名单、超时、输出截断;
  2. 输出是非结构化文本(stdout/stderr),模型解析容易出错;没有内置参数校验;
  3. 私有内部命令:只有名字,没有规范说明,模型很容易写错参数;
  4. 探测列表本身依然会膨胀:如果机器上几千个命令,名字列表本身也会吃掉大量上下文;
  5. 很难做细粒度权限管控、标准化审计。

适合场景:本地开发Agent、运维自动化、代码编程Agent、可信沙箱环境。 不适合:公有云多租户、高安全要求、复杂业务参数校验场景。

第三代:Skill 范式,渐进式按需加载(Progressive Disclosure)

CLI 方案解决了「框架注册膨胀」,但依然有两个问题:

  1. 要么一次性塞一长串命令名列表;
  2. 命令是非结构化的,没有统一的能力说明书、工作流约束。

于是 Skill 方案登场,现在是 Claude Code、企业自研 Agent 的主流路线。

核心原理

Skill 本质是能力资产化,分层加载:

  • 每一个 Skill 对应一个目录,核心是 SKILL.md,里面写清楚:适用场景、调用步骤、参数约定、限制、可调用的脚本/工具;
  • 分层加载:
    1. 索引层(常驻上下文):Agent 启动时扫描全部Skill目录,只加载「名称 + 一句话简介」元数据,体积很小;
    2. 正文层(按需加载):当模型判断当前任务匹配某个Skill,主动调用 load_skill 工具,把完整 SKILL.md 读入上下文;
    3. 附属资源(再次延迟):文档里引用的脚本、SQL模板、配置文件,用到的时候再读取。

一句话对比 CLI:

  • CLI:能力清单 = 操作系统PATH里的二进制名;
  • Skill:能力清单 = 本地/远端索引目录里的Skill元数据;能力的契约、流程、边界写在 SKILL.md。

优点

  1. Token 效率最优:大量能力只保留极简索引,绝大多数Skill永远不加载全文;从根源避免全量灌入;
  2. 结构化能力描述,自带业务流程、约束、异常处理,比单纯 --help 文本更适合模型理解;
  3. 治理友好:Skill可以版本管理、权限控制、评估、上架下架;
  4. 兼容混合模式:Skill内部依然可以调用 FunctionCall、Shell、MCP工具。

短板

  1. 有工程治理成本:需要目录规范、索引扫描、加载缓存、过期回收机制;
  2. 依赖模型意图判断:模型判断错了,该加载的Skill不加载,或者加载无关Skill;
  3. 依然有边界:索引太多的时候,索引列表本身也会变长,需要做索引路由、分类检索。

适合场景:企业内部Agent、复杂业务流程、大量领域能力、需要可治理、可沉淀的技能资产。

横向对比总表

维度FunctionCallCLI 探测方案Skill 按需加载
能力注册方式预先写死Schema,静态注册部署二进制到PATH,运行时探测编写SKILL.md,扫描元数据索引
初始上下文开销高,全量Schema低,仅1个shell工具 + 可选命令名列表极低,仅名称+一句话简介索引
详情加载时机启动一次性全部加载调用该命令时拉取 --help意图匹配后加载完整SKILL.md
参数结构化校验✅ 原生支持❌ 无,靠人工help文本⚠️ 文档约定,可搭配底层工具校验
新增能力成本高,改代码、新增Schema极低,部署二进制即可中等,编写SKILL.md
安全边界容易做权限、参数校验风险高,必须沙箱白名单可控,Skill层面做权限、准入
适合规模少量工具(<20)运维/编程沙箱,大量存量CLI几十~上百个领域业务能力

演进关系总结(重点)

不是后者彻底淘汰前者,而是抽象层次往上走:

  1. FunctionCall:把能力封装成标准化函数,契约优先,但是静态全量,规模上限低;
  2. CLI探测:把执行层收敛到Shell,复用操作系统生态,解决「注册膨胀」,代价是结构化和安全让步;
  3. Skill:在CLI/FunctionCall之上增加一层「能力编排层」,用索引+按需加载解决上下文膨胀,同时沉淀标准化的能力说明书、工作流。

现实工程里经常是混合形态:

一个Skill内部,既可以调用FunctionCall接口,也可以在沙箱内执行shell命令。Skill是能力的说明书与流程,不是替代底层执行方式。

写在最后

很多人讨论工具膨胀的时候,很容易陷入一个误区:追求“零Token开销”。 但看完三代方案就能明白:不存在免费的午餐,所有方案都只是把开销转移到不同位置:

  • FunctionCall:开销前置在Schema;
  • CLI探测:开销转移到命令名列表和help按需读取;
  • Skill:开销转移到索引检索 + 按需加载正文。

选型的判断标准非常简单:

  • 工具少、稳定、面向公网服务 → FunctionCall;
  • 本地编程、运维、可信沙箱、大量存量脚本 → CLI;
  • 企业业务、大量领域能力、需要沉淀、治理、长期迭代 → Skill。

全部评论: 0 条

    我有话说: