1. 从 Function Call 说起:模型终于能"碰"真实世界
最近在重构一个内部自动化助手的时候,翻出了两年前的代码。那时候实现一个"让AI能查天气"的功能,需要写一整套意图识别的正则表达式,配合一堆硬编码的参数槽位,然后根据用户说的"今天"、"明天"这种词做一堆映射转换。现在再看这整套东西,就像一个还在用电话线上网的人,突然看到了千兆光纤一样。而主线就是大家在标题里看到的这条演进路径:Function Call → MCP → SKILLS。
如果一句话说明白这三个词的关系,我倾向于这么理解:Function Call 是让模型学会了"拿起某个工具的姿势",MCP 是给所有工具统一了"插头和插座"的标准,而 SKILLS 则是把"用哪些工具、按什么顺序、怎么组合"这件事本身做成了可复用的能力模块。这篇文章不聊概念名词本身,而是结合我自己的实际改造经验,把这三代能力扩展方式的核心逻辑、踩坑经历和适用场景都拆开讲清楚。
先说背景。我做的东西是一个偏私人的知识库问答与任务执行助手,最早的形态就是一个拥抱大模型的对话服务,需要让它去调用搜索引擎、读数据库、发通知。刚开始接触 Function Call 的体验确实震撼,但真正把系统跑稳定之后,才逐渐意识到它只是起点。后来因为要接入的服务越来越多,工具的协议和格式越来越乱,我被迫开始研究 MCP。再往后,当"调用工具"这件事本身开始变得复杂——多个工具串联、条件分支、上下文回退——我发现我需要的不只是工具,而是一套能描述"流程"的东西,那就是 SKILLS。
这篇内容适合正在做 Agent 应用、被工具调用搞得焦头烂额的开发者,也适合刚入门、想知道这三层到底差在哪的读者。我不会把每个协议规范从头背一遍,而是用真实的改造过程告诉大家:这三级跳跃,每一级到底解决的是什么现实问题,以及哪些问题是它解决不了的。
1.1 在 Function Call 之前,Agent 是怎么干活的
没有 Function Call 的早期阶段,模型没有任何"行动能力",纯粹是一个文本生成器。你想要它执行动作,比如"查一下最近的订单",常规做法是:提前在 Prompt 里写清楚"当用户提到查订单时,输出JSON格式的命令,字段包括query_type和order_id",然后写一堆解析代码去处理模型的输出,再根据解析结果路由到具体的函数。
这套方案的痛点我现在想起来还想叹气,因为模型输出格式稍有变化——比如把JSON包在Markdown代码块里,或者参数名从order_id变成orderId——解析就直接报错。而且为了让模型理解各种奇怪的表达,Prompt 要反复调优,一个任务配一套模板,每套模板又要在不同模型版本上重测,维护成本高得离谱。
Function Call 的出现在我眼里是划时代的。它的核心逻辑不复杂:把"工具定义"作为结构化信息喂给模型,模型在回答之前,会根据用户的问题自己决定"要不要调用某个工具,以及给这个工具传什么参数",然后输出一个结构化的函数调用指令。我只需要按照约定好的数据结构做路由和执行就行。
但该说不说,它解决了"格式解析"的大难题,却没有解决"协议统一"的小问题。每个服务商提供的工具定义格式、参数约束、鉴权方式都不一样,今天对接一个搜索API,明天对接一个内部系统API,每个都要写一套 adapter。而每多一套 adapter,就意味着多一份会出错的代码、多一类要维护的响应格式。这才是后面 MCP 出现的真正土壤。
2. Function Call 的实战结构与隐藏问题
2.1 一次完整的 Function Call 运转流程
用我这边的实际案例来说明。我第一个接入的工具是"搜索引擎"。在 Function Call 方案下,我需要向模型声明一个工具,格式大概是这样的:
{ "type": "function", "function": { "name": "web_search", "description": "执行一次网络搜索,返回相关的网页标题、摘要和链接", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词" }, "pages": { "type": "integer", "description": "返回结果的页数", "minimum": 1 } }, "required": ["query"] } } }用户问"帮我搜一下某品牌最新发布的手机评测",模型不会直接输出答案,而是先输出一个函数调用意图。在我用的模型协议里,返回的是类似这样的结构:{"name": "web_search", "arguments": "{\"query\": \"某品牌 最新手机 评测\", \"pages\": 2}"}。我这边拿到之后,解析参数、调用搜索接口、把结果拼成文本,再重新塞回对话上下文,让模型基于这些真实搜索结果组织最终回答。
这套流程跑起来之后,整个体验确实顺畅了很多。模型的回答不再是"睁眼说瞎话"式的幻觉内容,而是能基于实时网页结果来组织语言。我自己项目里的用户留存也因此提升了不少,因为问出去的问题终于能拿到有依据的答案了。
但问题也随之而来。最典型的场景是参数抽取失败。用户说"搜一下最近一周的AI新闻",模型可能在pages参数里填了一个很大的数字,或者把 query 抽成"最近一周AI新闻汇总"这种无效检索词。这类问题本质上是模型对"参数含义"的理解不够深——它知道这个参数需要填什么类型,但并不完全理解这个参数在真实系统里会带来什么副作用。后来我加了很多 prompt 约束和响应后处理逻辑,但每次调整都像是在打地鼠,按下一个坑,冒出来另一个。
2.2 单体工具时代的三个核心矛盾
我在Function Call方案上深耕了大半年,总结出三个绕不开的核心矛盾,这直接决定了我后面转向MCP和SKILLS的决心:
第一个矛盾是 "工具语义只能靠文档传递"。Function Call 的工具声明本质上就是一份字段说明,模型对工具的理解全部来自description字符串。我写得太简单,模型就乱传参;写得太复杂,模型又抓不住重点。而且同一个工具面向不同场景时,需要的口径可能完全不同,这靠一段静态描述根本搞不定。
第二个矛盾是"调用路径越来越长"。当工具数量从3个涨到20个,模型的上下文里就要塞进全部工具声明。每次对话都重新传输一遍,Token 消耗骤增,响应延迟也变大。更崩溃的是,模型在数十个工具里做选择时,选错工具的概率会上升。我记得有个阶段,我的工具列表里同时有"查天气"和"查空气质量"两个服务,模型愣是在用户只问天气时调用了空气质量接口。
第三个矛盾是"工具与业务逻辑揉在一起"。工具函数写的是具体实现,但真正业务逻辑里可能要求:查完天气之后,如果温度低于多少度,还要再查一下穿衣建议。这种条件组合逻辑放在 Function Call 的年代,需要在代码里写死状态机。每次加一个规则,代码就膨胀一轮,到最后我自己都快分不清这个 Agent 的行为到底是模型决策的,还是我的 if else 写出来的。
提示:如果你现在还在Function Call阶段,建议一开始就把所有工具的参数设计得尽量"原子化",不要让一个函数承担两件以上独立的事情。否则后面做组合编排和迁移到MCP时,会平白多出大量重构工作。
3. MCP 为什么能成为"标准插座"
3.1 从"每个服务一套协议"到"协议归一化"
MCP(我理解它本质上是一套工具调用协议规范)解决的核心问题,就是我在上面抱怨的"协议不统一"。
在做多工具接入的时候,我每天的工作都是这样的:查天气服务商A的文档,封装一个带鉴权的HTTP客户端,解析它的响应字段,再写错误重试逻辑;然后换到搜索服务商B,重新干一遍同样的事情。每家的鉴权方式不同(有Token放在Header的,有签名加密的,还有OAuth的),响应格式也是千奇百怪。整套代码冗余严重,而且每个工具的接入都像是一次小型项目。
MCP 的逻辑是把所有工具服务包装成一个标准化的 MCP Server,对外暴露统一的接口范式:工具列表、工具调用、资源读取,全部走一套协议。我的 Agent 只需要写一个 MCP Client,就能对接任意实现该协议的服务。这个过程类比的话,就像以前每个电器都要用自己的插头规格,现在有了统一插座,电器只负责内部实现,插座孔位大家都遵守同一套物理标准。
用 MCP 之后,我接入一个新工具的成本,从过去的两三天降到了半天。大部分时间不是在写协议,而是在配置服务地址和鉴权信息。工具定义、参数校验、错误码语义这些琐碎的部分,全被协议层接住了。
3.2 MCP 的配置落地与实际收益
我的 Agent 对接 MCP 之后,整体的配置结构变得非常清晰。一个典型配置大概是这样的:
{ "mcpServers": { "web-search": { "command": "npx", "args": ["-y", "@some/search-server"], "env": { "SEARCH_API_KEY": "xxx" } }, "internal-db": { "command": "python", "args": ["mcp_servers/db_server.py"], "transport": "stdio" } } }配置里标注了每个 MCP Server 的启动方式和所需环境变量。对于我这种本地部署的 Agent 来说,这种方式比走网络远程调用更稳,进程由 Agent 拉起,不需要单独维护服务存活。而且换一个搜索服务商,只需要替换SEARCH_API_KEY或者换个 Server 包,Agent 侧代码一行都不用动,这是 Function Call 时代不敢想的维护成本。
在实际收益上,我用过一段时间之后最大的感受是:工具接入变成了"纯配置工作",再也没有写过一堆定制代码。MCP 标准化带来的生态聚合效应也很直接——社区里开源的各种 Server 拿来即用,数据库查询、文件读取、浏览器操作这些常见能力都有现成实现。
但我也必须说一句公道话:MCP 不是银弹,它标准化了"工具暴露"和"调用"的方式,却没有标准化"工具应该怎么被组合使用"这件事。用 MCP 接入 50 个工具,模型面对 50 个工具做选择,还是在裸奔。而且很多 MCP Server 的工具定义写得参差不齐,有的 description 写得不清不楚,你仍然要人工Review并作适当修正。
4. SKILLS 的诞生:从"能调工具"到"会干任务"
4.1 当工具调用本身成为瓶颈
我在 MCP 阶段稳定运行了一段时间之后,陆续给 Agent 接入了搜索、数据库、网页摘要、请求抓取、代码执行、文件操作等一二十个工具。原以为能力扩展已经"一步到位",但实际用下来,我发现新的瓶颈点不再是"能否调工具",而是"如何正确编排多个工具完成一个复杂的任务"。
举个例子,用户问"帮我整理一下某领域近期的行业动态,输出一份简洁摘要"。这件事单纯靠一个搜索工具是搞不定的。要做的事包括:搜索关键词、对多篇文章做内容摘要、根据摘要提炼关键事件、再压缩成一段通顺的简报。在 MCP 时代,我需要靠模型的上下文推理能力来即兴编排这四五个工具调用。但这有几个痛点:
第一,即兴编排不稳定。模型这次记得先搜索再摘要,下一次可能就跳过摘要直接开始生成结论了。第二,Token 开销大。每次编排都要消耗上下文来"思考流程",而流程本身是重复的。第三,中间结果难处理。摘要结果要不要结构化、要存成什么格式传给下一步,每次都靠自然语言传递,容易出现信息丢失。
于是我开始意识到,在那个阶段需要的已经不只是"工具的标准插座",而是一个更高层的抽象,把"完成某类任务的多步工具调用方案"封装成一个可以被复用的模块。这个在当时的行业讨论里,逐渐被大家叫作 SKILLS。
4.2 SKILLS 的结构化表达
我理解中,SKILLS 的精髓在于:把"任务"而非"函数"作为基本封装单元。一个 Skill 内部可以包含多个步骤,每个步骤可以是简单的 LLM 调用、单个工具调用,也可以是另一个 Skill 的组合调用,还可以定义步骤之间的依赖关系和数据流动。它其实就是一个模板化的"任务执行脚本",同时保留了给模型留出的决策空间。
举个例子,我定义了一个"生成行业简报"的 Skill。它的结构大体如下:
name: industry_briefing description: 针对指定行业生成近期动态简报 steps: - id: search tool: web_search params: query: "{{industry}} 近期动态" output: search_results - id: summarize type: llm_call prompt: > 基于以下搜索结果,提炼3-5条重要动态: 每条不超过50字。请输出要点列表。 搜索结果:{{search_results}} output: summarized_points - id: draft_briefing type: llm_call prompt: > 将以下要点组合成一段200字左右的简报,语言通顺,逻辑清晰。 要点:{{summarized_points}} output: final_draft - id: export tool: file_writer params: path: "outputs/briefing_{{date}}.md" content: "{{final_draft}}"在配置文件里,{{industry}}是用户传入的参数,{{search_results}}、{{summarized_points}}是上游步骤的产出,通过模板变量的方式向下游传递。这样整个任务流程是确定的,不会因为模型心情不同就随意变更操作顺序。
SKILLS 这种做法的优势,站在工程视角看非常明显。首先是稳定性:同样的任务无论跑多少次,执行的步骤骨架是一致的,不会出现"这次先查A再查B、下次先查B再查A"的无序状态。其次是可调试性:如果某个 Skill 产出结果不对,我可以单独调其中某一步的参数,不用在一大段在上下文中翻找。
不过这里要澄清一个容易混淆的点:SKILLS 不等同于严格的受编程工作流。在固定骨架之外,每个步骤内部仍然可以调用模型做自由决策。比如搜索那一步的关键词,我可以让模型先拆解用户意图再生成搜索词;摘要那一步也可以让模型自己决定按什么标准筛选要点。所以 SKILLS 我认为更像一个"半固定流程",是在工程确定性和模型灵活性之间取了一个平衡点。
4.3 SKILLS 在运行时如何处理多步推理
SKILLS 运行机制中有几个细节值得展开讲。
第一个是变量的作用域与传递。上面示例里每个步骤都有output字段,这个命名空间是跨步骤共享的。实现上,我维护了一个全局的执行上下文(Context),每个步骤执行完后把自己的结果写进 Context,后续步骤通过模板字符串引用对应字段。这个设计需要注意参数命名别冲突,否则下游步骤拿到的数据可能被意外的上游输出覆盖。
第二个是步骤级失败回退。在老的 Function Call/MCP 时代,一次工具调用失败通常会让整个对话陷入僵局。但在 SKILLS 框架里,我可以给 step 定义失败时的 alternate 方案。比如摘要服务超时了,自动切换到调用备用大模型生成摘要;搜索没有返回结果时,自动调整 query 再搜一次。这些能力用描述性的配置就能实现,不需要写额外的胶水代码。
第三个是嵌套 Skill 的组合。一个 Skill 可以调用另一个 Skill 作为自己的某个步骤,这让我可以搭出分层的能力体系。底层是原子工具型 Skill,比如"单篇网页摘要";中层是组合型 Skill,比如"多网页信息合并";上层是任务型 Skill,比如"生成行业简报"。上层在下层基础上进行编排,每层都可以被独立测试和复用。
注意:我在早期设计 SKILLS 时犯过一个错误,就是把所有逻辑都写进一个巨大的 Skill 里,觉得"一步到位的自动化"就是最好的。结果发现这个大 Skill 一旦卡在中间某一步,整个任务就全部失败,没有什么容错。后来我改成小 Skill 多步骤嵌套的组合模式,每个环节的职责单一,某个环节出问题可以单独重试或替换,整体的鲁棒性好了一截。
5. 三层能力扩展横向对比:不是替代,是渐进
5.1 一张表看清三者的边界
有很多人问我:现在学 Agent 能力扩展,是不是直接从 MCP 或 SKILLS 开始就行,Function Call 已经过时了?我的答案是不太对。这三层解决的是不同层面的问题,在当下它们是共生关系,而不是替代关系。我用一张表做一个相对清晰的对比:
| 对比维度 | Function Call | MCP | SKILLS |
|---|---|---|---|
| 核心解决什么 | 模型输出结构化工具调用指令 | 工具接入与调用协议的标准化 | 多步骤任务编排逻辑的沉淀 |
| 基本单元 | 函数 | 工具服务 | 技能流程 |
| 组合能力 | 无,一次对话内模型自由发挥 | 无,仍是单工具调用为主 | 有,步骤间可组合、嵌套 |
| 稳定性来源 | 依赖模型指令遵循能力 | 依赖协议实现质量 | 依赖流程模板固定骨架 |
| 接入新能力的成本 | 高,每接入一个写一套适配 | 低,标准化后纯配置即可 | 中,需定义流程步骤与参数 |
| 维护复杂度 | 随工具数量线性恶化 | 工具多时选择问题依然严重 | 需维护模板与上下文规范 |
| 适用场景 | 单一工具调用、快速验证 | 多工具接入、生态复用 | 固定任务场景、业务流程沉淀 |
其实这张表看完就很清楚了:Function Call 到现在也仍然是最灵活的一种方式,适合那些没有固定流程、模型自由发挥度要求高的场景;MCP 主要解决的是"接入的效率",它让工具生态变成了可以插拔的积木;而 SKILLS 则是在积木搭好之后,给"怎么搭积木"这件事提供了一个更通用的答案模板。
5.2 开发者视角的选型心得
如果读者正在设计自己的 Agent 应用,我给一个基于经验的选型建议:
如果你的应用只调用三到五个工具,且没有复杂的步步依赖关系,直接用 Function Call 就够了。这一层最简单直接,不要为了用 MCP 而 MCP,平白增加一套进程管理和协议负担。
如果你的工具数量往十个以上走,或者你希望快速接入社区已有的工具生态,我建议优先上 MCP。配置统一之后,后面每次加新工具都会觉得神清气爽。
如果你发现你的 Agent 经常在重复执行某几类多步任务,且每次跑出来的过程结果不稳定,那你就需要 SKILLS 这层抽象了。不用全部重写,而是在 MCP 接入工具的基础上,把重复的任务流程抽出来做成 Skill 模块,这样你的系统既有标准化的工具接入层,又有稳定的任务执行层。
这三层之间的典型组合方式,我现在的架构是这样的:底层是 MCP Server 提供工具能力,Fuction Call 的参数声明从 MCP Server 动态获取,上层由 Skill 运行时负责多步编排,Skill 中的每一步最终解析成一个或一组 Function Call 指令下发给底层的 MCP 执行。这套架构经历了几轮生产事故打磨之后,目前是我觉得工程上比较顺手的形态。
6. 实战中踩过的坑与排查思路
6.1 模型"看似在调工具"但总是选的工具不对
这是从 Function Call 迁移到 MCP 初期最容易遇到的问题。表现是:模型确实输出了函数调用,但调用的不是最合适的那个工具。比如用户问"记录一下今天的任务",模型却调用了搜索工具。
排查思路从两个方向入手。一是工具描述是否够清晰。MCP Server 里的工具 description 如果写得太泛,比如只写"搜索",模型很难判断它和"记录"目标之间的差异。我后来养成了一个习惯,所有工具描述都按照"这个工具做什么 + 在什么场景下使用 + 不要用它做什么"三部分来写,效果提升明显。
二是工具命名是否可区分。两个功能相关但侧重点不同的工具,名字最好差异拉大。比如我之前有get_weather和get_air_quality,模型经常混淆。后来我把它们改成get_weather_current和get_air_quality_index_now,配合更明确的描述文本,混淆率大幅下降。听起来很简单的调整,但实际效果比很多复杂 Prompt 优化都显著。
6.2 SKILLS 模板变量"串台"
在搭建 Skill 运行时的时候,我遇到的最隐蔽的Bug是模板变量名冲突。因为是多步骤共享一个上下文,不同步骤如果用了相同的输出字段名,后面步骤引用的数据就可能被前面的步骤意外覆盖。
排查思路是给每个步骤的输出字段加前缀,用步骤ID作为命名空间。例如search_results改成step_search.results,summarized_points改成step_summarize.points。虽然配置看起来变长了,但它能彻底避免变量覆盖问题的出现,并且在日志排查时也能一眼定位到是哪个步骤产出的数据格式不对。
另外,模板渲染阶段最好加一个"未定义变量"的显式报错提示。如果某个步骤引用了不存在的字段,系统应该立即停止并在日志里给出明确的缺失变量名,而不是静默地渲染成空字符串。空字符串导致下游模型生成垃圾结果,这个坑我踩过不止一次,排查成本非常高。
6.3 长任务执行时的超时与上下文截断
SKILLS 里一个 Skill 可能包含五六步,每一步都可能要调用大模型,整体耗时可能超过一分钟。如果不做超时管理,前端会长时间卡在"等待响应"状态,体验极差。我采用的是异步任务模式:Agent 收到任务后立即返回一个任务ID,后端执行 SKILLS 流程,前端通过轮询或者 WebSocket 获取进度和最终结果。这样长任务不会阻塞用户交互。
另一个问题是上下文管理。在多步执行中,每一步的中间结果都塞进上下文,跑几步之后上下文就爆了。我的方案是:对于只在下游一步用到的中间结果,用完即从上下文中移除;只有真正跨越多步骤的核心数据才保存在上下文中。这一步类似"中间变量及时释放",能明显延长有效工作时间。
6.4 SKILLS 模板陷入"死板"两个极端
最后想多说一句关于 SKILLS 设计的尺度问题。我在实践中发现,Skill 定义得太死板,会导致模型面对各种风格的用户请求时适应能力变差;定义得太灵活,又回到 MCP 时代"即兴编排不稳定"的老问题。
我的调整经验是在 Skill 的步骤参数中尽量多留"由模型决策"的空位。比如搜索关键词从用户请求中自动抽取,摘要的长度和风格由模型在运行时决定,而不是在模板里写死"50字以内"这种硬约束。Skill 提供的应该是一个稳定的"流程骨架",而不是把每个参数都焊死的施工图。只要让模型在骨架规定的节点上有选择和自由发挥的空间,稳定性和灵活性其实可以兼得。
7. 从这套演进中获得的体会
回头看这条从 Function Call 到 MCP 再到 SKILLS 的演进路线,我最大的感受是:每一次抽象层次的提升,本质上都是对"模型能力边界"的一次重新定位。Function Call 让模型跨过了"输出文本"到"输出指令"的鸿沟;MCP 让工具的接入和复用不再依赖人手一写的胶水代码;SKILLS 则把"流程经验"从模型的脑中抽出来,放到了工程可以掌控的静态配置里。
在实践中最深刻的体会是,不要追求一步到位。我的系统是一步步演化过来的,每次都是在旧方案实在撑不住的时候才升级,这样每个阶段的遗留包袱反而会逼着你把当前结构的边界理解得更透。如果一开始就直接设计三层架构,反而可能陷入过度抽象——空有一堆漂亮的协议和模板,却不知道真正该往里面填什么业务逻辑。
如果你现在刚起步,我建议不要纠结于"要不要用最新的 SKILLS 框架"。先把一两个关键工具用 Function Call 跑通,感受一遍指令解析的痛苦,然后等工具多了自然会发现协议统一的必要性,再上 MCP;等到你反复手写工具编排代码的时候,SKILLS 的那层抽象就变得顺理成章。按这条路线走,每一步都是被真实问题推着向前,不是追新概念,而是让架构真正服务于业务。
另外一个建议是:无论你在哪一层,都要保留日志追溯能力。我排查问题的时候,最大的帮助不是代码调试,而是每一步的执行记录——模型当时选了什么工具、传了什么参数、SKILLS 里每个步骤的输入输出是什么。这套可观测性的底子,是支撑整个三层次架构稳定运转的关键,也是任何能力扩展方案最容易忽略却最值得投资的地方。