文章目录

AI Agent 工具调用体系深度剖析:从 Tool 到 Skill,为什么是这一套分层架构?

不是讲"这些词分别是什么",而是讲"为什么 AI 工程界最终演化出了这一套分层架构,每一层在解决什么不可替代的问题"。


前言:从一个真实问题讲起

最近带学弟做 AI Agent 项目,我被反复追问一个问题:

“Function Calling、MCP、CLI、Tool、Skill……这些词到底是什么关系? 是并列、是替代、还是分层?”

我观察到一个现象:很多同学在学 Agent 的时候,都是零散地学这些词——网上看到一篇讲 FC 的,看一篇讲 MCP 的,又看到一篇讲 Skill 的。每篇都看懂,但合在一起就懵:“为什么 AI 圈要造这么多名词?它们之间到底是什么关系?”

这篇文章从设计工具调用体系会遇到的真实问题出发,把每一个概念放到它所属的层级里,讲清楚,读完后你会得到一张AI Agent完整工具调用体系图,而不再是零散的名词。


一、为什么大模型需要"工具调用"?

1.1 模型能力的三大硬伤

纯 LLM 本身有三个绕不开的限制:

硬伤 表现 实际后果
知识截止 训练数据有截止时间 问"昨天股市怎么样",模型编给你听
不会执行 只会输出文字 让它"算 123 × 456",它会算错
没法感知现实 不知道当前时间、本地文件、网络状态 问"我电脑里有哪些文件",它答不上来

这些限制不是 LLM 的 bug,是架构决定的——LLM 本质是"输入文本 → 输出文本"的概率模型,它没有手脚

所以"工具调用"(Tool Use)从一开始就是 LLM 能力模型的必要补充,不是可选项。

1.2 设计目标:工具调用要解决什么

把上面三个硬伤翻译成工程目标:

1. 让模型能获取训练数据之外的信息(实时数据、私有数据)
2. 让模型能执行具体操作(写文件、调接口、跑命令)
3. 让模型能在执行后继续思考(基于结果再决策)

这三个目标,就是后面所有架构演进的方向

1.3 Tool:系统最小原子执行单元

在正式进入三层架构之前,必须先定义整个体系的最小单元——Tool(工具)

定义:Tool 是一个可以被 Agent 调用的功能单元,它包含三个核心要素:

要素 说明 示例
名称(name) 唯一标识,让模型知道"调谁" search_productsread_file
描述(description) 自然语言说明,模型靠这个决定要不要用它 “根据关键词搜索商品信息”
参数约束(parameters) JSON Schema 定义,保证参数类型正确 {keyword: string, page: int}

一个 Tool 的本质把一段后端功能,包装成 LLM 能"看懂"和"选中"的接口

// 一个 Tool 的标准定义
{
  "name": "get_weather",
  "description": "查询指定城市的实时天气信息",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {"type": "string", "description": "城市名,如'北京'"}
    },
    "required": ["city"]
  }
}

关键理解:Tool 是静态定义,它只描述了"我能做什么",不包含"谁来调我"和"怎么调我"。后面 FC、MCP、CLI 这三种机制,都是在解决"Tool 怎么被调用"这个问题的不同侧面

Tool 是积木,FC/MCP/CLI 是三种不同的"搭积木方式"。


二、第一层:Function Calling —— 为什么必须先有"模型选工具"的能力?

2.1 一个朴素的设计:把工具写成 Prompt 描述

最早的思路很朴素——把 Tool 定义写在 System Prompt 里,让模型"假装"调用

你有一个工具:get_weather(city)
当用户问天气时,输出:<CALL>get_weather(city="北京")</CALL>

这个方案的 4 个致命问题

问题 后果
参数不可控 模型可能输出 get_weather("北京市")get_weather(city=北京),格式五花八门
无结构化校验 后端用正则解析,模型稍微改个格式就漏了
容易"假装调用" 模型可能直接编一个 get_weather("火星") 的结果
Token 浪费 所有 Tool 说明一直占着 System Prompt 位置

核心矛盾Prompt 是给模型"看"的,不是给机器"解析"的。我们需要让模型按结构化格式输出,让程序能稳定解析。

2.2 真正的解法:Function Calling

主流模型厂商给出的方案是把 Tool 调用作为模型的一个"原生能力"

输入:用户问题 + 可用 Tool 列表(JSON Schema)
   ↓
模型判断:要不要调 Tool?调哪个?参数是什么?
   ↓
输出(结构化):
{
  "tool_calls": [
    {"name": "get_weather", "arguments": {"city": "北京"}}
  ]
}
   ↓
后端程序:根据结构化输出执行 Tool
   ↓
把 Tool 结果回传,模型再生成最终回答

2.3 为什么这种设计是"对的"

设计决策 为什么必须这样做
结构化输出 程序能稳定解析,不再是正则猜格式
JSON Schema 描述 Tool 自动校验参数类型、必填项
模型只做"决策"不执行 模型是"无状态函数",执行交给后端,安全可控
结果回传对话历史 模型能"看见" Tool 结果,继续推理
支持多 Tool 调用 一次响应可并行调多个 Tool,降低延迟

2.4 完整代码示例

import json
from openai import OpenAI

client = OpenAI()
tools = [{"type": "function", "function": {
    "name": "get_weather",
    "description": "查询指定城市的实时天气",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "城市名"}
        },
        "required": ["city"]
    }
}}]

def get_weather(city: str) -> str:
    return f"{city}:晴,25°C"

messages = [{"role": "user", "content": "北京今天天气怎么样?"}]

while True:
    response = client.chat.completions.create(
        model="gpt-4o", messages=messages, tools=tools
    )
    msg = response.choices[0].message

    if not msg.tool_calls:  # 模型决定不调 Tool → 输出最终答案
        print(msg.content)
        break

    messages.append(msg)
    for tc in msg.tool_calls:
        args = json.loads(tc.function.arguments)
        result = get_weather(**args) if tc.function.name == "get_weather" else None
        messages.append({
            "role": "tool", "tool_call_id": tc.id, "content": result
        })

2.5 Function Calling 的设计动机

Function Calling 是把"Tool 描述"和"Tool 调用"从 Prompt 字符串,提升到了模型原生的结构化能力。

它解决了"模型怎么选 Tool、怎么生成参数"的问题,但留下了下一个问题:Tool 本身怎么管理?


三、第二层:MCP —— 为什么工具多了之后必须有"标准化协议"?

3.1 FC 解决不了的问题:Tool 管理

FC 让单个 Agent 能调 Tool,但当 Tool 数量爆炸、Agent 类型增多时,FC 不够用了。

场景推演

第 1 天:Agent 只有 3 个 Tool,硬编码进代码
第 30 天:Tool 变成 30 个,代码里 if-else 越来越长
第 90 天:换了个 Agent 框架(Cline → Cursor),Tool 全部重新对接
第 180 天:团队 5 个 Agent 各自维护一套 Tool

FC 留下的 4 个核心痛点

痛点 具体表现
Tool 发现靠硬编码 想加新 Tool?改代码 + 改配置 + 重新部署
跨平台无法复用 Cursor 写的 Tool,Cline 用不了
协议碎片化 OpenAI / Anthropic / LangChain 各有一套 Tool 定义
Tool 与 Agent 强耦合 Tool 逻辑和 Agent 代码混在一起,难维护

3.2 传统解法的局限

解法 A:写一个"Tool 注册中心"

每个 Agent 框架自己造一个——LangChain 有 Tool 类,AutoGen 有 Function 类,Cline 有自己的工具配置。结果:从一个平台迁移到另一个平台,Tool 代码全部要重写

解法 B:搞一套"通用 Tool 协议"

已有 OpenAPI 规范,为什么不能复用?问题在于:OpenAPI 是给程序员用的,没法直接被 LLM 理解。LLM 需要自然语言描述 + 结构化参数的组合。

3.3 MCP 的设计:把 Tool 变成"可插拔"的

面对 3.1 节提出的四个痛点,MCP(Model Context Protocol)给出的方案是把"Tool 定义、注册、调用"从 Agent 代码里抽出来,定义成一个独立的协议。这样,Tool 不再属于任何一个 Agent 框架,而是作为一个独立的服务运行——任何遵循 MCP 协议的 Host 都能发现并调用它。

为什么是"协议"而不是"框架"? 框架天然绑定语言和生态(LangChain 的 Tool 类只能在 LangChain 里用,AutoGen 的 Function 类只能在 AutoGen 里用)。协议是语言无关、框架无关的——只要实现了 MCP 协议,Python 写的 Tool Server 可以被 TypeScript 的 Agent Host 调用,反之亦然。这和 HTTP 协议的道理一样:Nginx 用 C 写,浏览器用 C++ 写,但它们能通信,因为它们遵循的是 HTTP 协议,而不是同一个框架。

这个设计通过以下四个关键决策落地:

设计决策 解决 3.1 的哪个痛点 为什么必须这样做
Host / Server 分离 Tool 与 Agent 强耦合 Tool 独立部署、独立升级,一个 Server 可以被多个 Host 同时使用,互不影响
JSON-RPC 2.0 协议 协议碎片化 任何语言只要有 JSON-RPC 库就能实现 MCP,不再受限于某个框架的 Tool 定义方式
Tool 自动发现 Tool 发现靠硬编码 Host 启动时通过 tools/list 自动拉取 Tool 列表,新增 Tool 无需改 Host 代码
基于 STDIO / SSE 传输 跨平台无法复用 STDIO 适合本地子进程通信(零网络开销),SSE 支持远程 HTTP 长连接,覆盖本地和云端两种部署场景

这四个决策加起来,恰好对应了 3.1 节的全部四个痛点。MCP 不是"更好的 FC",它根本不关心模型怎么选 Tool——那是 FC 的事。MCP 解决的是"Tool 怎么被多个 Agent 共享"这个 FC 完全没碰的问题。

3.4 MCP 完整工作流

① Host 启动 MCP Server 进程
② 握手:initialize,交换协议版本 + 能力集
③ Tool 发现:tools/list,Server 返回所有 Tool 的 JSON Schema
④ 用户提问,Host 把「问题 + Tool 列表」发给 LLM
⑤ LLM 决策:调用哪个 Tool?参数是什么?(FC 能力)
⑥ Host 通过 tools/call 转发给 MCP Server
⑦ Server 执行 Tool,返回结果
⑧ Host 回传 LLM
⑨ LLM 生成最终答案

3.5 MCP Server 示例

from fastmcp import FastMCP

mcp = FastMCP("company-tools")

@mcp.tool()
def query_employee(name: str) -> str:
    """
    查询公司员工信息
    Args:
        name: 员工姓名
    """
    return f"{name}:开发部,员工编号 10086"

if __name__ == "__main__":
    mcp.run()

关键点函数注释 = Tool 描述,类型注解 = 参数 Schema。写好 Python 函数 = 写好 MCP Tool。

3.6 真实案例:Figma MCP

方式 还原度 数据颗粒度
截图给 AI 60-70% 像素级猜测
Figma MCP 95%+ 结构化精确数据(尺寸/字体/间距/颜色)

MCP 的设计动机把 Tool 从"Agent 私有的功能"变成"独立的服务",让 Tool 可以像 USB 设备一样即插即用。

3.7 MCP 的本质

维度 FC 解决 MCP 解决
Tool 发现 硬编码 协议自动发现
跨平台 框架绑定 任何 Host 都能用
Tool 复用 每 Agent 重写 一次开发多处复用
协议标准 碎片化 统一规范

FC 解决的是"模型怎么调 Tool",MCP 解决的是"Tool 怎么被多个 Agent 共享"


四、第三层:CLI —— 为什么"老古董"在 AI 时代反而不可或缺?

4.1 MCP 也有解决不了的问题

MCP 看似完美,但所有 MCP Tool 的元数据都要塞进 LLM 上下文

接入 20 个 MCP Server,每个 Server 5 个 Tool
→ 上下文里有 100 个 Tool 的 JSON Schema
→ 每个 Schema ~200 Token → 20000 Token 的"工具税"
→ 模型还没开始干活,一半上下文没了

MCP 的 3 个隐性成本:Token 消耗、选不准、响应延迟。

4.2 回到最朴素的能力:bash

解法出人意料地简单只给模型一个 bash Tool,命令让模型自己生成

Tool 列表(只有一个):
{"name": "bash", "description": "执行 shell 命令"}

模型自己知道 git lognpm testcurlgrep……这些知识全在训练数据里,不需要塞进上下文

4.3 CLI 的核心优势

优势 说明
Token 消耗极低 只需一个 bash 描述(~50 Token)
能力原子化 命令可任意组合(管道、重定向)
复用现有生态 git、docker、npm 全是现成的
模型自带知识 LLM 训练时见过这些命令

4.4 真实场景:让 AI 修复 bug

npm test 2>&1 | tee error.log        # 跑测试,保存错误日志
grep -E "FAIL|Error" error.log         # 提取错误信息
sed -i 's/oldApi/newApi/g' src/*.ts    # 自动替换 API
npm test                                # 重新验证
git diff                                # 查看修改

一次对话完成:测试 → 定位 → 修复 → 验证 → 展示 diff。

4.5 CLI 的设计动机

CLI 不是"Tool 调用",是"让模型直接操作计算环境"

它绕过了"Tool 定义 → Tool 注册 → Tool 调用"的链路,让模型直接面对操作系统

4.6 CLI 的边界与风险

风险 后果
权限过大 rm -rf / 也能调
参数易错 特殊字符、路径引号问题
不可结构化校验 只能事后看输出
不可移植 Linux 命令在 Windows 上跑不了

所以 CLI 是"重型操作的兜底",不是"通用 Tool 的替代"


五、横向维度:Skill —— 从"单个 Tool"到"可复用技能包"

5.1 三层架构还缺什么

FC、MCP、CLI 这三层解决的是"Tool 怎么被调用"的问题"。但当 Tool 多到一定量级,一个新的问题浮现了:

你有一个"写前端页面"的 Agent
它需要:
  - 读 Figma 设计稿(Figma MCP Tool)
  - 读本地代码(fs MCP Tool)
  - 写代码文件(fs MCP Tool)
  - 查组件库文档(web_search Tool)
  - 格式化代码(CLI:prettier)
  - 跑测试(CLI:npm test)

每次做这个任务,你都要把 6 个 Tool 的 Schema 塞进上下文
而且每次都要写一大段 Prompt 告诉模型"怎么做前端页面"

问题Tool 是原子的,但任务是复合的。你需要一种机制,把"一组 Tool + 一段流程规则"打包成一个可复用的单元

这就是 Skill。

5.2 Skill 是什么

定义:Skill 是把多个 Tool、Prompt 模板、输出规范、业务规则打包成的一个"可复用技能包"

对比 Tool Skill
粒度 原子操作 复合能力
内容 单个功能 + 参数 Schema 多个 Tool + Prompt + 规则 + 输出模板
调用方式 模型一次调一个 加载后整体生效,模型按规则组合使用
类比 单个 API 接口 一个"技能模块"(含接口 + 业务逻辑 + 输出规范)

一个 Skill 的典型结构

# skill: frontend-page-builder
## 描述
根据设计稿和需求描述,生成符合规范的前端页面代码

## 可用工具
- figma_mcp: 获取设计稿数据
- fs_mcp: 读写本地文件
- bash: 执行 prettier 格式化和 npm test

## 工作流程
1. 用 figma_mcp 获取设计稿
2. 分析布局、组件、样式
3. 生成代码并写入文件
4. 用 prettier 格式化
5. 用 npm test 验证

## 输出规范
- 使用 React + TypeScript
- 组件遵循单一职责
- 必须包含 loading 和 error 状态

5.3 Skill 的设计为什么重要

Skill 解决的是"Tool 多了之后怎么组织"的问题

没有 Skill 有 Skill
每次做前端页面,Prompt 从头写 加载 Skill 一次,后续复用
6 个 Tool 的 Schema 全塞上下文 只加载当前 Skill 需要的 Tool
团队成员各自写 Prompt,风格不统一 共用同一个 Skill,输出风格一致
新人上手慢 加载 Skill 即用,降低学习成本

5.4 Skill 的加载机制:按需加载,节省 Token

现代 Agent 框架(如 Cline、Trae、Dify)对 Skill 普遍采用三层渐进式加载

元数据层(常驻):Skill 名称 + 一句话简介
  ↓ 匹配到任务
指令层(按需加载):完整的工作流程和规则
  ↓ 触发特定条件
资源层(条件加载):参考文档 + 可执行脚本
层级 内容 Token 消耗
元数据层 所有 Skill 的名称 + 简介 极低
指令层 匹配到的 Skill 的完整规则 中等
资源层 条件触发的参考文档

核心设计没用到的 Skill 不占上下文,只有匹配到的 Skill 才加载完整指令。这和 MCP 的"所有 Tool Schema 全塞"形成了互补。

5.5 Skill 在架构中的位置

在这里插入图片描述

Skill 不是第四层,它是一个横向维度——它不替代 FC/MCP/CLI,而是把这三层的能力按照业务场景打包成可复用的单元


六、完整协作:一次 Agent 任务,五者如何配合?

6.0 体系全景图

下面这张图清晰展示了从 AI Agent 智能主体出发,到调度协议层,再到业务技能集群和原子工具层的完整体系结构:

在这里插入图片描述

图解说明

层级 包含内容 作用
【顶层】业务技能集群 联网检索 Skill、数据分析 Skill、代码执行 Skill 等 把多个 Tool 组合成可复用的复合能力
【中间层】调度协议层 Function Calling、MCP Protocol、CLI Command 解决 Tool 怎么被调用、怎么被发现
【底层】Tool 原子工具层 HTTP API Tool、DB Tool、沙箱执行 Tool、文件操作 Tool 提供实际可执行的能力原语
核心 AI Agent 智能主体 决策调度中心,向下驱动整套体系

横向关系:技能集群和工具层是"业务维度的封装"和"技术维度的原语",二者通过中间层(FC/MCP/CLI)打通。

纵向关系:AI Agent 作为决策主体,根据任务需要选择合适的 Skill 或直接调用 Tool。

6.1 完整架构流程图

在这里插入图片描述

6.2 真实任务:性能优化一个 React 项目

第 1 步:Skill 加载
  → 加载 "frontend-performance-optimizer" Skill
  → 该 Skill 注册了 3 个 Tool:fs MCP、web_search FC、bash CLI

第 2 步:CLI - 探索项目结构
  $ ls -la && cat package.json

第 3 步:MCP - 读取关键文件(结构化)
  → fs MCP Server 的 read_file
  → 拿到 App.tsx、index.tsx

第 4 步:FC - LLM 分析代码定位瓶颈
  → 基于代码,模型找到:大列表没分页、图片没懒加载

第 5 步:CLI - 执行优化
  $ npm install react-window
  $ npm run build && du -h dist/*

第 6 步:FC - LLM 总结优化报告

6.3 选型决策矩阵

场景 推荐方案 原因
跨多个 Agent 平台共享 Tool MCP 一次开发,多处复用
高频调用的结构化 Tool FC 稳定可控,参数校验强
探索性操作 CLI 无需定义 Tool,命令原子化
开发者重型操作(git/build/deploy) CLI 复用 Unix 生态,Token 低
安全敏感操作 MCP/FC 权限可控
团队共享重复任务流程 Skill 一次封装,团队复用

七、设计动机总结:这套架构为什么是"对的"?

7.1 四个核心设计原则

原则 在体系中的体现
关注点分离 FC 管决策,MCP 管协议,CLI 管执行,Skill 管组织
标准化与灵活性的平衡 MCP 标准化跨平台,FC 标准化调用格式,CLI 提供灵活性,Skill 提供业务适配
能力边界递进 FC = 能想,MCP = 能被发现,CLI = 能动手,Skill = 能复用
按需加载 MCP 自动发现 Tool,Skill 渐进式加载,不用的不占上下文

7.2 这套体系解决的问题全景

在这里插入图片描述

7.3 一句话总结各层关系

Tool 是积木,FC 选积木,MCP 管积木,CLI 动手搭,Skill 把常用搭法打包。

五者不是替代关系,是分层协作——一个生产级 AI Agent 项目,必然是五者结合的工程


八、常见误区澄清

误解 真相
“MCP 会取代 Function Calling” 不在同一层,MCP 是协议层,FC 是模型能力层,二者协同工作
“用 CLI 就够了,不需要 FC/MCP” CLI 适合重型操作,但安全性和结构化校验弱
“Tool 多了 = 加更多 MCP Server” Tool 多会导致 Token 爆炸,需要 Skill 做按需组织
“Skill 就是高级 Prompt” Skill 是Tool + Prompt + 规则 + 加载机制的完整封装,不是单纯的 Prompt

真正的设计决策点

问自己 3 个问题:

1. 这个 Tool 是只给我用,还是给团队/社区用?
   → 团队用:必须 MCP 化
   → 自己用:FC + CLI 即可

2. 这个任务是重复且标准化的吗?
   → 是:封装成 Skill
   → 否:直接用 Tool 组合

3. 这个操作有安全风险吗?
   → 有风险:MCP/FC(带权限控制)
   → 无风险:CLI 即可

九、核心要点速查

读完本文后,把全文最关键的 4 个核心要点浓缩在这里,方便后续随时回顾。

要点 1:为什么 AI 圈会演化出 FC / MCP / CLI / Skill 这一整套体系?

LLM 本身有 3 个硬伤,必须借助 Tool 调用来扩展能力。但 Tool 调用在不同场景的问题不同:

  • 单 Agent 内部 → FC
  • 跨平台复用 → MCP
  • 重型操作 → CLI
  • 重复任务 → Skill
    这四者是问题驱动演化出来的,不是凭空设计的

要点 2:Tool 和 Skill 的本质区别是什么?

Tool 是原子操作(一个 API 调用),Skill 是复合能力(一组 Tool + Prompt + 规则 + 输出模板)。类比:Tool 是单个 API,Skill 是一个微服务。

要点 3:Function Calling 和 MCP 到底什么关系?

不在同一层,是互补。FC 是模型原生能力(决定调谁),MCP 是 Tool 协议(Tool 怎么被注册和调用)。完整链路:模型通过 FC 决策 → 底层通过 MCP 执行。

要点 4:怎么判断一个操作应该用 FC、MCP、CLI 还是封装成 Skill?

  • 跨平台共享 → MCP
  • 高频结构化 → FC
  • 重型/探索性 → CLI
  • 重复标准化 → Skill
    实际项目里四者组合使用

十、写在最后

工具调用体系是 AI Agent 工程化里最容易被低估的环节——很多人觉得"不就是让 AI 调用个函数吗",但真要做生产级 Agent,这一整套分层架构的设计权衡直接决定系统的可维护性、可扩展性和成本。

记住这套架构背后的设计动机比记住名词更重要:

  • Tool 是一切的基础——不理解 Tool,就不可能理解其他
  • FC 出现,因为 Prompt 描述 Tool 有 4 个致命问题
  • MCP 出现,因为 Tool 多到必须标准化
  • CLI 被重新重视,因为 MCP Tool 多了会 Token 爆炸
  • Skill 出现,因为 Tool 多了需要组织,重复任务需要复用

每一层都是为了解决上一层的痛点——这就是这套架构"对"的根本原因。


参考资料


如果你觉得这篇技术架构剖析对你有帮助,欢迎收藏 + 点赞 + 关注

更多推荐