目录
一、Function Calling
1、工具定义
2、完整的调用流程
3、并行工具调用
4、大模型能够调用工具的原理
二、MCP
1、MCP的Client-Server架构
2、MCP的三类核心能力
3、MCP的底层通信
4、MCP由几部分组成
三、Skill
1、为什么需要skill
2、skill的结构
3、skill的渐进式加载
一、Function Calling
Function Calling其实就是给大模型提供了一种调用工具的方式,开发者用JSON格式把工具描述好传给模型,模型判断需要调工具的时候不输出自然语言,而是输出一段结构化的JSON,包括要调用哪个函数以及参数是什么。代码层面执行完函数后把结果返回给大模型,大模型再根据结果生成最终答案。
比如我们问大模型“查询北京的天气”,它会生成一个JSON,里面包含要用的函数getWeather和参数city为北京,然后代码手动调用这个函数拿到实际的天气结果,再返回给大模型,大模型最后根据这个结果生成自然语言的回答。
其实也就是模型只负责做决策,执行的事统一交给代码层面来完成,职责分工明确,这样做会更安全、更可控。
在没有Function Calling之前,如果想让模型去调用工具,完全是靠解析自然语言,比如用户问“我想要查北京的天气”,那传到代码层面的就是“我想要查北京的天气”,然后我们在代码层面去判断它要干什么,然后再去执行,再将执行结果返回给模型。这种相当于就是我们去猜大模型要干什么,我们不好去控制,有了funtion calling就将这种猜意图变成了标准结构化调用,可以实现精准调用。
1、工具定义
工具其实就是一份结构化的工具说明书,用JSON格式来写,告诉模型这个工具叫什么、能做什么、需要哪些参数。例如我们定义一个查询天气的工具:
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的实时天气,包含气温、天气状况、风向风速", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如「北京」「上海」,不要带省份" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位,默认用摄氏度" } }, "required": ["city"] } } } ]其中最关键的字段是description,只有描述准确了,模型才能够更好的判断去调用哪个工具,需要填写什么参数,所以我们需要在描述中尽可能的写好格式要求、示例值、限制条件这些,这样模型才能够正确填写参数。
2、完整的调用流程
它的调用流程其实就是两轮对话+中间执行。
第一轮,我们把工具列表和用户的问题一起传给问题。模型读完后,会判断是否需要去调用工具,如果需要调用的话它就会输出一个finish_reason为"tool_calls"的响应,里面包含了要调用的工具名和参数。
拿到这个响应后,就到了中间执行环节,由代码层面解析tool_calls中的函数名和参数,然后去找对应的函数去跑,拿到执行结果。
第二轮,将工具执行结果以及role:"tool"封装成json格式拼接在对话历史上,再去调用一次模型,这次是带着实际的执行结果的,因此最终能够生成最准确的答案。
3、并行工具调用
对于没有依赖关系的工具调用,是支持并行调用的,比如用户问“帮我查询北京、上海、深圳三个城市的天气”,那其实是牵扯到三次工具调用的,function calling允许一次性并行去执行这三个调用。
并行工具调用是大模型层面的设计,模型会根据用户问题判断多个工具调用之间是否有依赖关系。如果没有依赖,模型会在一次响应里直接输出多个tool_calls,代码层面拿到后并发执行所有调用,把所有结果封装起来统一返回给大模型,大模型再生成最终答案。如果不支持并行,那就只能串行:模型输出第一个调用→代码执行→结果返回→模型再输出第二个调用→代码执行→结果返回……需要多轮对话才能完成,非常耗时。并行调用把多轮串行压缩成一轮,大幅减少了整体耗时。
但是对于有依赖关系的,比如“先查订单号,再查具体的物流信息”,就需要串行去调用了,因为后一步要依赖前一步的结果,所以只能串行。大模型会自动去判断是否有依赖关系。
4、大模型能够调用工具的原理
其实主要是通过训练,靠两个阶段,SFT监督微调和RLHF基于人类反馈的强化学习。
1)SFT监督微调:它让大模型学会了怎么调用工具。给大模型喂了大量的“工具调用示范对话”,让它通过模仿学会了“看到工具描述->判断要不要调用->输出结构化JSON请求”。
2)RLHF基于人类反馈的强化学习:它让大模型知道什么时候调。收集人类对“那种回答更好”的判断,训练一个打分器,再用这个分数反复调整模型,让它学会什么时候不应该调工具。
在运行层面,每次我们提问时,会将用户问题和工具描述都传给模型,模型去判断是否需要工具,如果需要的话就会输出结构化的tool_calls JSON,代码层面拿到这个JSON去运行,再将结果塞回对话,模型生成最终答案。
二、MCP
MCP中文意思是模型上下文协议,它是为了解决模型接工具太碎片化的问题。在MCP出现之前,每接一个新的工具都要单独些集成代码、处理认证、适配格式,而且这套代码和具体模型强绑定,换个模型就得重新写。
在没有MCP之前,比如我们要给Claude介入GitHub工具,我们得手写GitHub API的调用代码、处理认证(token怎么传)、处理各种返回格式、把API响应转换成模型能够理解的格式。但如果Claude升级了,接口有变化,我们的对接代码也得改。又比如说,我给Claude同时接了十个工具,每个工具都有自己的一套对接代码,各自的格式、认证方式、错误处理逻辑都不一样。又比如我们要把工具给Cursor用,又得重新写一遍这套方式,工作量非常大。
MCP做的事其实就是为AI接工具这件事定了一个统一的协议标准。工具提供方按MCP规范实现一个MCP Server,里面封装好各种操作。任何支持MCP的AI客户端都能直接连上这个Server,自动发现里面的工具并使用,不需要写任何定制化对接代码。工具只需要实现一次就可以在任何地方复用。
可以将MCP比作USB接口,鼠标、键盘、打印机就是给大模型提供的工具,台式机、笔记本就是模型本身。如果没有USB这样的统一规范,不同设备需要不同接口,不同电脑也可能用各自的方式连接外设,开发时就得准备m种工具适配n种模型的m×n种方式。而有了MCP这个“USB接口”,所有工具都遵循同一标准,任意工具插到任意模型上都能直接工作,工具厂商只需做一次适配,所有模型就都能用了。
1、MCP的Client-Server架构
MCP采用标准的 Client-Server 架构。Server是工具的实现方。比如GitHub官方维护了一个GitHub MCP Server,里面封装了“列出PR”、“创建ISSUE”、“搜索仓库”、“查看Diff”等操作;Client是AI应用那一侧,比如Claude或者Trae,连上Server后就自动获得了这些工具的能力。
一个Client可以同时连接多个Server。比如我们把文件系统Server、GitHub Server、SQL Server都接上,那模型就同时拥有了操作本地文件、读写仓库代码、查询数据库这三套工具的能力,我们不需要再去写任何对接的代码,只需要在配置文件中加几行JSON就可以了。
2、MCP的三类核心能力
MCP Server可以向Client暴露三类能力:Tools、Resources、Prompts。
· Tools(工具):这是最核心的能力,对应Function Calling中的函数。Tools的本质是有副作用的操作,因为它执行后会改变外部的状态。比如创建文件、提交代码、调用第三方api,这些都是tools,这些操作执行完后往往是不可逆的,因此Tools通常需要用户授权才能执行,不能让模型想调用就调用;
· Resources(资源):Resources不会改变任何东西,只把数据提供给模型看。比如读取日志文件、查询数据库记录、获取文档内容。我们可以把Resources理解为工具的资料库,模型可以进来查资料,但不能修改里面的东西。它是只读,没有副作用,因此可以更宽松的暴露给模型,不需要像tools那样谨慎授权;
· Prompts(提示词模版):它在团队合作的场景下非常有用,其实就是预定义的提示词模版,带参数占位符,解决了每次都需要手写重复prompt的问题。比如代码审查模版、测试用例生成模版。
3、MCP的底层通信
MCP的底层通信是JSON-RPC,它是一种轻量级的远程函数调用协议,用JSON格式来表达调用这件事。其实就是客户端发起一个JSON请求,里面说清楚调用哪个方法、参数是什么、这次请求的ID是多少;服务端执行完后,返回一个JSON响应,里面是执行结果或者错误信息。用JSOn是因为相比于二进制,更加易读、易调试、语言无关。MCP用的是JSON-RPC2.0版本,相比于1.0版本加了批量请求、通知消息等功能。
4、MCP由几部分组成
MCP由三层组成,可以分为角色层、能力层和协议层。
· 角色层:角色层有三个,Host是应用本身(比如Trae)、Client是Host里负责和Server通信的模块,Server是工具提供方实现的独立进程,一个Host可以同时连多个Server;
Client是Host内部的连接模块,一个Client对应一个Server链接,它负责初始化和Server的连接、向Server查询“你有哪些工具/资源/模版”、把模型的调用请求转发给Server并把结果带回来。
Host负责启动和管理所有 MCP Client,控制连接哪些Server、什么时候断开连接,是整个MCP系统的调度中心。
· 能力层:能力层定义了Server能暴露的三类东西:tools、resources和prompts;
· 协议层:底层通信,消息格式用JSON-RPC 2.0,传输方式支持stdio(本地子进程通信)和远程HTTP连接两种。
消息格式用JSON-RPC 2.0,每条消息都是一个JSON对象,格式固定,包括“调哪个方法”、“参数是什么”、“这次请求的ID是多少”,Server返回响应时带上执行结果或者错误信息,通过ID匹配请求和响应。
MCP的传输方式,一种是接本地MCP Server,那其实就是在本地启动一个子进程来运行Server,Host通过操作系统的管道和他通信;
一种方式是通过Http来传输,Server作为一个Http服务独立部署,Client通过Http连接和他通信,那就支持多个Client共享同一个MCP Server。这种方式Client用POST来发请求,Server根据情况灵活返回,短请求直接返回普通JSON,长请求则讲HTTP响应升级为SSE流持续推送中间结果。
三、Skill
Agent Skill是把指令、脚本、模版一体化打包成可复用能力包的机制,关键在于Agent可以自动发现它、按需加载它、在需要时调用里面的脚本和资源。每个Skill都是一个文件夹,里面有一份SKILL.md指令文件,还可以带上脚本、模版、参考文档这些资源。
skill和prompt的主要区别是skill能被agent自动发现和按需加载,不用我们每次手动输入;和MCP的最大区别是MCP给agent提供外部工具和数据的访问能力,而skill是教agent拿到这些工具和数据之后该怎么用。
1、为什么需要skill
举个例子,比如我们让ai来帮助我们做代码审查,每次都需要贴一大段指令,告诉它“检查哪几类问题、用什么格式输出、重点关注什么”,每次新对话都需要从头粘贴一遍,如果漏掉某个细节就会导致输出质量不稳定。
在团队协作的情况下,每个人的prompt都不一样,审查标准没办法完全统一。skill要解决的就是将我们反复在用的指令、流程、模版打包成一个标准化的模块,agent自己知道什么时候用它、怎么用它,不需要我们再手动复制粘贴。
2、skill的结构
skill的结构其实就是一个文件夹,里面最核心的是SKILL.md文件,再加上一些可选的辅助资源,比如scripts、references、assets。
· SKILL.md是核心指令文件;
· scripts目录下是可执行的脚本,比如鉴权脚本等;
· references目录下是参考文档;
· assets目录下是模版、资源文档等,比如报告的输出模版。
普通的prompt只是一段文字,用完就没了;而skill是一个完整的文件夹,里面的指令、脚本、模版都可以持续维护、版本管理。
3、skill的渐进式加载
skill的渐进式加载分为三层机制来解决这个问题:
·第一层相当于是只看简历,agent启动时,只加载每个skill的name和description这两个字段,大概每个skill只占用少部分token,通过这一步agent就可以知道自己是有哪些能力是可以使用的;
·第二层是翻开详细资料。当用户提了一个任务,agent先判断这个任务和哪个skill相关,只会去加载相关的skill的skill.md文件正文完整加载进来,读取详细的指令,不相关的skill始终不会被加载;
·第三层是需要时再取。执行过程中,如果指令里提到了使用什么模版,agent才会在那个时刻去读取这个模版文件。参考文档、脚本这些也一样,用到时才加载。
这样设计的原因主要是因为上下文窗口是agent最宝贵的资源,如果把所有skill的全部内容都塞进去,真正有用的用户任务信息就可能被淹没,agent的注意力被分散,从而导致输出质量下降。渐进式加载的本质也就是让agent按需加载知识。