欢迎光临
我们一直在努力

小白入门,一文看懂Function calling,MCP与Skill

Agent 区别于 LLM 的本质,在于它让模型跑在一个思考—行动—观察的循环里:推理出下一步该做什么,执行,拿到反馈,再推理。这个闭环才是 Agent 真正“活”起来的原因。

但闭环跑起来,问题也跟着来了。模型只会输出文本,它怎么让外部系统知道自己要做什么?任务一旦复杂到需要多步操作,谁来把这些流程沉淀下来,下次不用重写?工具越来越多,模型怎么发现和理解它们?

这些正是 Function Calling、Skill、MCP 要各自回答的问题。下面一一拆开。

Function Calling回答的是第一个问题,让LLM说清楚“我想调用什么”,是给LLM装上“手脚”最直接的方式,他的定义是:Agent根据用户需求,自主的生成正确的参数,交由外部系统调用函数或接口。先看一个最小化的示例,以OpenAI SDK为例:

import openai

tools = [
{
"type": "function",
"function": {
"name": "query_order",
"description": "查询订单状态,仅接受以 ORD 开头的订单编号",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号,格式为 ORD 后跟 8 位数字"
}
},
"required": ["order_id"]
}
}
}
]

response = openai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "帮我查下 ORD12345678 的订单状态"}],
tools=tools
)

# 模型返回的不是文本,是 tool_calls
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.function.name) # query_order
print(tool_call.function.arguments) # {"order_id": "ORD12345678"}

到这一步,tool_calls会记录模型想要调用什么,他大概长这样:

{
"id": "call_abc123", # 这次调用的唯一标识,后面回传结果时要带上
"type": "function", # 固定为 function
"function": {
"name": "query_order", # 模型想调用的函数名
"arguments": '{"order_id": "ORD12345678"}' # 这是一个 JSON 字符串,不是 dict
}
}

然后再由我们的系统根据 Agent 给出的参数调用真实函数,需要注意的是整个链路模型都无法自主去调用工具,而在 LangChain 这类框架中,函数签名、参数类型和 docstring 会被自动转成工具的 schema 和 description——@tool 装饰器负责这个转换,bind_tools 负责把工具声明注入给模型。后续的执行循环则由框架封装,但底层流程和手写完全一致:模型返回 tool_calls,框架执行函数,结果回传,继续推理,这里各大厂商在基座模型训练阶段就已经混入了大量工具调用数据,让模型学会根据工具声明生成合法的结构化调用,从而显著提高 tool_calls 参数符合 schema 的概率,所以相较于纯prompt的方式可以让AI输出JSON效果更好,但也不是100%不会出错,在工程上我们可以进一步加入正则表达式等方法进一步校验与结构提取。

Function Calling解决了单步调用工具的问题,是Agent工具调用中最底层,最原子化的操作,可是,模型一次生成一个或一组无依赖的工具参数,执行后再返回,拿到结果后再决策,真实的复杂任务往往需要多个步骤调用,比如“查订单→判断延迟→发优惠券→写道歉信”这样的流程,如果每次都让Agent在循环内自己编排,在比较复杂的任务里可能逐渐走偏,钻牛角尖,以及过高的推理成本的浪费,最关键的点在于,即使Agent侥幸完成了任务,已经证实的经验无法在下一次同样的问题面前复用,于是Skill出现了,他能够对经验,流程与工具调用链进行封装,形成一个可复用的工作流,并且对外只暴露一个入口点来触发整个流程,稳定还能省token。

Skill的物理形态不是一段prompt,也不是一个脚本,就是一个各种资源整合的文件夹,这些资源能够被模型发现,理解与执行。一个典型的Skill目录如下:

compensate-delay/
├── SKILL.md # 元数据与工作流:模型先读,决定何时用、怎么做
├── scripts/ # 可执行代码:确定性逻辑的实现
│ └── compensate.py
├── references/ # 补充文档:模型按需加载,不常驻上下文
│ └── coupon_policy.md
└── assets/ # 静态资源:供代码引用,模型不直接读
└── apology_template.txt

渐进式加载是Skill的核心机制之一,模型不会一口气去读取整个文件夹,而是分层按需加载。

1.第一阶段,只看SKILL.md中的元数据,name与description,这一层信息量极轻,最重要的是description,模型会根据Skill的具体描述来判断是否要加载,也就是说description写的好坏直接决定了Skill是否会被用到。

2.第二阶段,当模型确定要使用某个Skill后,会加载完整的SKILL.md正文,获取按步骤执行、规则、约束等。

3.第三阶段:执行过程中若触及特定步骤或触发条件,模型才会按需加载 references/ 下的文档。这些文档通常篇幅较长、不一定触发,因此与 SKILL.md 分离,避免无谓占用上下文。

渐进式加载的优越不仅仅在于省token,更重要的是它重新划分了模型什么时候该知道什么,按需加载,如果一股脑把文件夹的内容塞给模型,反而会造成注意力稀释,模型对下一步该做什么的判断力就会下降。这与RAG的思路是一脉相承的,他们都基于同一套假设:模型不需要在参数里记住所有的内容,他只需要在恰当的时机知道去哪里找,所以,当然开发Skill的过程中,我们也要反过来想,在每一层模型都需要多少信息,才足以做出下一步正确的决定?

Skill是为了解决单个流程诞生的,但当任务进一步复杂,涉及到多个流程,则需要Skill之间的协作,比如“投诉处理Skill”可能触发“用户退款Skill”,之后还要接“用户安抚Skill”,当Skill的数量足够多,Skill之间的编排也会催生新的问题,这就需要“Skill的Skill“-元技能,负责在不同的场景下编排Skill,为什么不直接交由模型编排呢?因为这样会重新走上注意力稀释与偏离目标的老路。

Skill成功解决了流程沉淀与复用,但它与Function Calling都还有一个共享的隐患,这些的工具都是我硬编码到系统里的,我的Agent之所以知道query_order的存在,是因为它被写死到了我的系统里,之所以能调用scripts下的代码,那是因为它被放在了触手可及的地方,可如何才能访问到别人的数据和调用别人的工具?这就是MCP要解决的问题。

MCP的全称是Model Context Protocol(模型上下文协议),他的核心可以一句话概括,让模型能够动态发现并调用符合协议的外部工具,而不用将他写死在代码里。

在Function Calling和Skill下,工具是”个人财产“,MCP则将他变成了开放服务:只需要对方暴露一个MCP Server,你的Agent连上就能用。

MCP的架构中有3个组成角色:

1.Client:运行在Agent侧,负责发起连接,发送请求与接受结果。

2.Server:运行在工具提供方,暴露工具列表,接受调用并返回结果。

3.真实服务:Server背后的系统,比如数据库,API服务,文件系统等等。

MCP底层基于JSON-RPC 2.0协议进行通信。这是一个极简的远程调用和响应协议,所有请求和响应都是结构化JSON内容,本质与之前Function Calling的tool_calls是同一个思路,但是把他标准化成了网络协议。

一次请求示例:

{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}

一次返回示例:

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "query_order",
"description": "查询订单状态",
"inputSchema": {
"type": "object",
"properties": {
"order_id": {"type": "string"}
}
}
}
]
}
}

当模型决定调用时,Client再发送一条请求:

{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "query_order",
"arguments": {"order_id": "ORD12345678"}
}
}

这两个方法看似简单,但它们的全部约束力就在于“约定”:所有 MCP Server 都实现 tools/list 和 tools/call,Client 就不需要为每个工具单独写适配层。Agent 的工具接入从“开发行为”变成了“连接行为”——连上之后先问有什么,再决定调哪个。

但这里有一个容易被忽略的边界:MCP 只保证工具“可发现、可调用”,不保证模型“选得对、用得好”。 协议解决的是连接问题,工具描述质量和模型判断力仍然决定最终效果。换句话说,MCP 让你的 Agent 能看见更多工具,但看见更多并不等于更聪明——反而可能重新引入注意力稀释的问题,这也是为什么 MCP 之上往往还需要一层工具筛选或路由。

很多人说 MCP 就是微服务的翻版,这个类比有道理,但有一个关键差异:传统微服务是“程序对程序”的精确调用,MCP 是“模型对程序”的语义调用。

传统微服务之间,调用方必须精确知道参数类型、枚举值、错误码。而模型调用工具时,更依赖 description 的语义描述来判断“什么时候该用”。所以 MCP 的每个接口设计都多了一层考量:不仅要让机器能解析,还要让模型能理解。 这是它区别于普通 RPC 框架的根本所在。

MCP 协议本身还在快速迭代。不同 Server 可能实现不同版本的协议,字段名和握手流程都有差异。Client 侧需要做版本协商和向下兼容,否则接入一个老版本 Server 可能直接握手失败。这要求 Client 不能假设所有 Server 都是最新实现,而要把版本差异当作常态来设计。

总结

Function Calling、Skill、MCP,三者并非替代关系,而是 Agent 工具体系的三层递进。

Function Calling 回答了“怎么调用”——它让模型能生成结构化的工具调用意图,是 Agent 最底层的原子操作。但它只管单步,不管流程;只生成意图,不负责执行。

Skill 回答了“怎么沉淀”——当任务从单步走向多步,模型即兴编排的代价越来越高。Skill 把验证过的流程封装成可复用的文件夹,通过渐进式加载让模型按需获取信息,把稳定性从“运行时祈祷”变成了“开发时固化”。但它仍然假设工具都在自己的系统里。

MCP 回答了“怎么发现”——当工具需要跨系统、跨组织共享时,硬编码 schema 的维护成本不可持续。MCP 用标准化的 JSON-RPC 协议,让工具自己向模型“毛遂自荐”,把工具接入从开发行为降维成连接行为。但它解决的是连接,不是选择;是发现,不是判断。

三者各管一层,组合起来才构成完整的 Agent 工具生态:

Function Calling 是“手”——决定怎么动;Skill 是“动作”——把常用的连贯动作打包;MCP 是“插座标准”——让手能接上任意符合标准的工具。

最终,Agent 的能力上限不取决于这三者中的任何一个,而取决于模型判断力、工具质量和工程约束的共同作用。协议和框架能降低接入成本,但选得对不对、用得好不好,永远是系统设计者的责任。

赞(0)
未经允许不得转载:171主机测评 » 小白入门,一文看懂Function calling,MCP与Skill
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址