资讯详情

Function Call与MCP:让大模型从会说到会干活的工具调用进化指南

📅 2026/10/17 5:21:01 | 华诺云谱 👁 阅读
Function Call与MCP:让大模型从会说到会干活的工具调用进化指南
1. AI 从会说话到会干活手脚进化前传1.1 只会输出文字的大模型天生缺手缺脚这两年我拿大模型做应用被业务方追问最多的一句话是它不是啥都会吗为什么连个天气都查不了这个问题的根源在于模型的训练目标。以对话模型为例它学的核心能力是从前文推断下一个最合适的 token本质上是在续写文本。你问它明天某城市天气怎么样它要么根据训练语料里可能存在的信息瞎编要么直接承认我没有实时数据因为它从来就没有长出过感知外界的器官。这个阶段的应用形态普遍是问答盒子喂进去一段文档、问一句话、吐一段总结。它解决知识型问题还行但对执行型问题无能为力。你让它帮忙把文件转成 PDF它只能告诉你转 PDF 可以用哪款软件而不是真的帮你转完给你一个文件。模型有脑子但手和脚都没有所有需要触碰真实世界的动作都得隔着屏幕靠人肉完成。1.2 RAG 解决了知识过期Function Call 解决没法动手业界先想到的补救手段是检索增强生成也就是常说的 RAG。做法简单粗暴在模型回答问题之前先把外部资料库里的相关片段检索出来拼接进上下文让模型看着材料说话。这确实缓解了幻觉和知识陈旧的问题但它只解决了读的问题没有解决写和做的问题。模型可以引用你的内部文档但它还是没法帮你下单、改数据、发消息。顺着这条思路往前走大家逐渐发现与其让模型自己去执行不如让模型决定执行什么由外部代码来跑。这就是 Function Call 的雏形。对话系统给模型额外提供一组函数声明模型在合适的时候返回一个结构化的信号我要调用 query_xx 函数参数是 xx真正去查数据库、调服务的动作由应用层完成。模型从亲自动手退成指挥调度反而把事情做成了。1.3 进化的两个岔路口单函数调用 vs 标准化协议如果说模型是大脑那 Function Call 就是大脑第一次长出来的手。它的优点是简单直接缺点是每个工具的接入都靠开发者手写胶水定义参数、写调用逻辑、处理错误、拼接回传。接一个天气接口需要一套代码接一个订单接口又需要另一套换一个模型平台参数格式往往又要调整。MCPModel Context Protocol模型上下文协议的出现改变了这个局面。它是在 2024 年底作为开放协议被推出来的核心目标很明确把模型如何发现工具、如何描述工具、如何调用工具这几个环节抽出来做成一份统一标准。工具方只要实现一个 MCP Server客户端只需要遵循 MCP 协议去发现和调用就能同时对接任意数量的工具。用个接地气的比喻Function Call 是给 AI 装了一双手MCP 则是给这双手配上了标准化的神经系统和关节让它能灵活抓取各种长成标准接口形状的工具。今天各类 AI Agent 应用能跑起来靠的就是这一套手脚进化打下的地基。个人体会是弄懂 Function Call是理解 Agent 的第一步看清 MCP是决定你下一步架构怎么走的关键。接下来把这两层分别拆开讲。2. Function CallAI 的第一双手2.1 核心原理决策和执行分离要理解 Function Call先记住一条原则模型不执行函数模型只做决策。整个链条是模型提出申请 → 应用层执行函数 → 执行结果回填给模型 → 模型组织最终回答。拆开看就很好懂。在一次对话请求里你除了把聊天消息传给模型还可以传一份工具清单清单里列出这次对话允许模型申请调用的函数包括函数名、作用描述和参数格式。模型读完用户的话结合这份清单做判断如果当前请求需要外部信息来支撑它就会先不回最终答案而是返回一个特殊的调用指令格式通常长这样{ name: query_weather, arguments: {\city\: \某城市\} }注意它返回的是指令不是结果。参数是模型根据对话内容推断出来的往往以字符串形式包裹着 JSON。应用层拿到指令后负责真正执行query_weather这个函数把查到的天气数据封装成一条工具消息再一次发给模型。模型这时看到了自己申请工具后拿到的真实数据才生成面向用户的最终回答。这里有个很需要纠正的认知很多人以为函数调用是模型内部完成的或者以为模型会自己写好代码去执行。都不是。真正的执行者永远是你的应用代码模型只负责理解意图、选择工具、填充参数这动脑的部分。这两个角色一旦混淆排查问题时很容易抓瞎。2.2 一次完整调用流程的请求拆解我用一个朴素的 HTTP 风格调用来示意假设某个模型平台提供了对话补全的接口{ model: your-model, messages: [ { role: user, content: 帮我看看某城市今天适合穿什么衣服 } ], tools: [ { type: function, function: { name: query_weather, description: 查询某个城市的实时天气信息当用户询问天气、气温、穿衣建议时使用, parameters: { type: object, properties: { city: { type: string, description: 需要查询天气的城市名称 } }, required: [city] } } } ] }模型读完之后可能返回这样一个结构{ message: { role: assistant, content: null, tool_calls: [ { id: call_1, type: function, function: { name: query_weather, arguments: {\city\: \某城市\} } } ] } }此时应用层要做的事有三件检查到tool_calls字段根据name找到对应的本地函数解析arguments里的 JSON把函数执行出结果。执行完毕之后将结果作为一条角色为tool的消息带回去同时用tool_call_id与之前的调用指令配对保证模型知道这个结果对应的是哪一次调用{ role: tool, tool_call_id: call_1, content: 某城市今日多云气温 22 到 28 摄氏度东南风 2 级 }把这个消息拼回messages列表再次调用接口模型就会基于查到的天气数据给出穿衣建议。这一来一回就构成了一次完整的函数调用闭环。整个流程里的关键不是某个字段而是两次请求第一次决定用什么工具、传什么参数第二次基于工具结果做最终回答。调试的时候先分清你卡的是哪一次请求再去查消息结构就快得多。2.3 函数描述怎么写决定模型选得准不准用了一段时间之后我发现函数的name和description写得好不好直接决定了模型会不会在关键时刻选错手。实践里比较有效的写法有几个要点。第一函数名要短且有语义。query_weather比get_data强得多因为模型会在语义空间里把用户意图和函数名做匹配名字越贴近真实行为匹配越准。第二description至少包含三样东西这个函数做什么用户问什么样的问题时应该用到它有什么边界或前置条件。举例当用户询问某地当前天气、未来天气预报、穿衣建议时使用如果用户询问历史天气不要使用此函数。描述里明确什么时候不用能有效减少模型在边界场景乱调的情况。第三参数定义要细致。JSON Schema 里每个属性的description都会成为模型的参考依据比如city的描述可以写城市中文名称完整填一下不要用简称模型就更容易填出合理的值。同时要把必填项列清楚缺少required约束模型可能会自作主张地漏传参数应用层一执行就抛异常。第四如果同一场对话里注册了多个工具一定要保证彼此的描述没有语义重叠。比如query_weather和query_temperature听起来很像模型选错概率会明显上升。我遇到过某个项目里同时挂了两个订单查询工具模型经常把查历史订单误路由到查退款单后来把工具合并、把边界写清楚准确率才回到正常水平。2.4 容易吃暗亏的细节token 开销与并发安全Function Call 不是免费的。每一份工具描述都会被放进请求里模型每次都要读一遍这些描述这部分会持续消耗上下文空间和费用。如果工具多、描述长、对话轮次多token 开销可能比对话正文还高。我维护过的一个客服系统一次请求里塞了 12 个工具定义每个定义几百字光工具部分就占了近三千 token几乎占掉整个请求预算的一半。优化方式有两个一是精简描述每句话都换成高信息密度的短句删掉修饰词和重复表述二是按场景动态装配工具比如先判断用户问题是售后类就只挂售后相关的 3 个工具不要把 12 个全塞进去。动态挂载的效果通常比单纯压缩描述更显著因为它从源头减少了一次请求里工具的绝对数量。并发和安全也值得留意。函数一旦暴露模型和应用层都可能成为被绕过的入口。比如有人故意构造提示词诱导模型调用高权限函数或者反复调用同一个工具把账单跑爆。稳妥的做法是在应用层加人工确认或白名单机制敏感操作发消息、改数据、转账默认不直接执行而是让模型先返回一个待确认动作由用户点击确认之后应用层再去真正调用函数。多数平台只负责把协议层的事情做好业务层面的安全边界一定要自己补上。3. MCP给 AI 的手脚装上标准接口3.1 为什么还需要一套协议Function Call 解决了模型能不能调用工具的问题但它留下了一个集成层面的麻烦每接一个新工具都要在应用代码里写适配逻辑每换一个模型平台又要按平台的格式调整工具描述工具与工具之间、客户端与客户端之间完全隔离形成一个个孤岛。MCP 这个思路本质上是把工具接入从点对点变成中心化标准。工具方只需要实现一个标准的 MCP Server把自己拥有的能力以统一格式暴露出去任何遵循 MCP 协议的客户端聊天应用、IDE、自动化脚本、Agent 框架都可以直接发现并调用这些工具。用生活例子来类比以前每台设备都有自己的专用充电线手机一根线、相机一根线、耳机一根线桌面乱成一团MCP 想做的是那个 USB 口大家遵循同一个形状规格去接插上就能用。我第一次听到这个思路时也犯嘀咕觉得这不就是把工具描述挪了个地方吗。等到自己动手接了一次才发现协议层的价值不在第一次接入而在后续的扩展同一个 MCP Server 可以给多个客户端共享客户端侧不需要为每家数据源单独写适配器社区里已经有大量现成的 Server下载配置就能用。工具接入的门槛从写一套完整集成代码降到了配置一个服务地址。3.2 核心构件Server、Client 与四个能力原语MCP 的架构分两层MCP Server 和 MCP Client。Server 是能力提供方可能是本地的独立进程也可能是远程服务Client 是主动发起连接的通常是你的 AI 应用、IDE 插件或者智能体框架。两者通信基于 JSON-RPC 2.0 消息协议第一步是握手Client 发一条initialize消息声明自己支持的协议版本Server 回应自己能提供哪些能力双方确认协议一致后再进入正式调用阶段。这一步很像两个设备在确认彼此的接口规范。协议里把能力抽象为四类原语我用一张速查表记录过原语含义典型场景Tools可执行的操作有副作用查天气、发邮件、写数据库Resources可读取的数据资源类似文件读取项目配置、查看文档内容Prompts可复用的提示词模板服务端提供标准指令模板减少客户端拼装成本Sampling服务端反向请求模型生成智能体链式推理、多模型协作这四种原语里日常用到最多的是 Tools 和 Resources。前者解决动手做后者解决读资料。最简单的理解角度是Tools 是有副作用的动作Resources 是无副作用的上下文。把两者分开客户端才能更好地决定数据的来源和动作的边界。客户端侧通常先发一个tools/list请求拿到 Server 的全部工具清单再把这份清单映射成模型能理解的工具描述和 Function Call 里的 tools 参数格式很接近。当模型决定使用某个工具时客户端再发tools/call请求把工具名和参数传给 ServerServer 执行完把结果返回。所以说MCP 并没有取代 Function Call而是把 Function Call 里工具从哪里来这件事标准化了——底层仍然需要模型具备函数调用能力上层则统一了工具的描述与调用通道。3.3 快速搭一个最小 MCP Server纸上谈兵不够我实际搭过的最小 MCP Server 大概是这个感觉以下代码遵循社区常见 SDK 的 API 风格具体包名以你使用的 SDK 为准from mcp import Server # 某个 MCP SDK 的入口 server Server() server.tool() def query_weather(city: str) - str: 查询指定城市的实时天气信息 # 在这里执行真实的天气接口调用例如请求某个天气服务 return f{city}今日多云气温 22℃~28℃ if __name__ __main__: # 通过标准输入输出与 MCP 客户端通信 server.run()这个示例的精髓在于工具注册是声明式的函数签名直接变成工具描述服务端通过 stdio 与客户端通信客户端启动子进程来运行这个 Server两者通过 stdin/stdout 交换 JSON-RPC 消息。我在实际操作中喜欢先从本地 stdio 模式调试因为不用处理端口和鉴权启动快、日志直出定位问题最方便。在客户端侧接上这个 Server 后的调用路径是客户端启动子进程 → 与 Server 完成 initialize 握手 → 请求tools/list拿到query_weather的声明 → 把这个声明连同用户消息一起发给模型 → 模型返回调用请求 → 客户端向 Server 发tools/call拿天气数据 → 结果回填给模型。整条链路里真正需要手写的代码少了很多新的工具接入只需在 Server 代码里多写一个函数。3.4 两种传输方式和部署选型MCP 目前常见的传输方式有两种stdio 和流式 HTTP。stdio 模式适合本地场景。客户端启动一个子进程进程间通过标准输入输出通信天然拥有进程级隔离配置简单不需要开端口。缺点是只能被一个客户端独占使用不适合跨机器共享。流式 HTTP 模式适合服务化部署。Server 变成一个远程端点多个客户端可以同时连接可以做鉴权、负载均衡适合团队共享能力、中心化管控。缺点是网络环境复杂时调试成本高要处理超时、重试、鉴权等问题。选型建议很直接本地开发、单机工具、快速验证走 stdio需要在多台机器、多个团队之间共享工具或者工具要被 Web 产品使用走 HTTP 服务化。我见过不少团队一上来就追求远程化结果把简单的工具接入搞得很慢又很难排查。其实很多内部工具先做成本地 Server 就够了等有了共享需求再升级传输层改动成本并不高。4. Function Call 和 MCP 怎么选落地方案与演进路线4.1 两者不是替代关系而是不同层级不少同学把 Function Call 和 MCP 放进同一个选择题里我一开始也这样想后来逐渐意识到这个框架是错的。Function Call 是模型能力层的东西——模型支不支持在回复中返回结构化的工具调用指令MCP 是工具接入层的东西——工具如何被描述、发现、调用。MCP 的下游往往离不开 Function Call 这类模型能力二者是上下游协作而不是非此即彼。放在对比维度上看差异主要体现在接入方式、扩展性、复用范围和学习成本上维度Function CallMCP定位模型 API 层面的工具调用能力工具与客户端之间统一通信协议接入成本直接写代码、传参数最快当天上需要理解协议、实现或引用 Server上手略高多工具扩展性每加一个工具都要改客户端代码与请求参数工具侧加 Server 函数客户端自动发现跨客户端复用差工具与特定应用强绑定好同一 Server 可服务多个客户端跨模型迁移各家接口细节有差异需要适配协议统一迁移成本低适用阶段快速原型、固定工具集平台化、生态化、工具数量增长期4.2 一套可落地的决策框架我现在给团队做方案时会按下面这个顺序判断。第一先看工具是否固定、数量是否有限。如果只是两三个内部接口接入方只有一个应用直接用 Function Call 最省事。这时候引入协议层属于给简单问题加复杂度得不偿失。第二看是否需要多客户端复用同一个工具。如果同样的工具既要在智能体里用又要在聊天应用里用未来还可能被脚本和 IDE 插件调用MCP Server 一次接入、多处复用的优势就非常明显。第三看团队对生态的依赖程度。MCP 生态里已经有大量现成 Server比如读写本地文件、接各种数据库、连常见开发工具等直接用现成组件比自己给每种能力写工具要快得多。如果你的场景恰好是少写适配器、多组合现成能力MCP 非常合适。第四看安全与权限模型。MCP 在协议层把工具名、参数、调用会话都标准化了更容易做统一鉴权、审计和限流。如果需求只是模型帮我查询一个只读接口的数据手写 Function Call 能管好一旦工具有写操作或者要跨团队开放标准化的鉴权链路能省下大量心力。我倾向于把两者看成单点呼叫 vs 标准总线单点功能用前者平台底座用后者。4.3 推荐的演进路线先接通再标准化如果从零搭建一个会调用工具的 AI 应用我推荐的路线不是一步到位上 MCP而是分三步走。第一步先选一个功能点用 Function Call 把闭环跑通。比如做一个天气查询把工具定义、模型调用、结果回填这几个环节都亲手走一遍感受一下决策与执行分离到底是怎么回事。这一步的目的不是选型而是建立直觉。很多抽象概念没有亲手跑通一次闭环都只是背下来的名词。第二步在跑通的基础上把工具抽象成独立服务。用 MCP Server 包一层把原来硬编码在客户端里的工具逻辑挪到 Server 里客户端通过协议去发现和调用。这个过程会逼着你思考工具边界、参数约定、错误处理这些架构问题。第三步引入动态装配和生态组件。根据用户意图动态挂载工具分组接入社区现成的 MCP Server把客户端从一个特定功能的壳升级成一个通用的工具执行引擎。走到这里你的应用才真正有了手脚而且脚上穿的是标准尺寸的鞋。这条路我走过几次前两步快的话一两天就能完成第三步的收益在工具数量超过五六个之后会非常明显。5. 踩坑实录与常见问题排查速查5.1 Function Call 不生效先查这三件事场景一模型明明支持工具调用但就是死活不返回tool_calls。我踩过几次后发现大部分原因出在函数描述和用户问题之间关联太弱。比如工具描述写得很泛就写查询信息用户问今天几点落日时模型根本意识不到这个工具跟它有关。解决方式是回到描述里把触发条件写得具体到什么话术、什么问题。场景二模型返回了tool_calls但参数解析失败。最常见原因是 arguments 不是合法 JSON或者应用层直接强转导致异常。我在代码里会加一层兜底解析JSON 解析失败就打日志同时回传一条参数解析失败请重新提供参数给模型让它自己修正而不是让整个流程直接崩溃。这种把错误回塞给模型的办法意外地好用模型通常会在下一轮重新生成合理的参数。场景三函数调用成功了但模型回答得驴唇不对马嘴。这种情况通常是回填格式出了问题。注意 role 字段一定要是tool同时要带上正确的tool_call_id和调用时的对应关系不能乱。我见过有人把工具结果简单拼成字符串塞进 user 消息模型收到后一脸茫然上下文里出现了多份工具结果回答自然混乱。5.2 MCP 接入最容易翻车的三个点第一是环境依赖问题。stdio 模式下客户端通过子进程启动 Server子进程用的 Python 或 Node 环境和客户端环境可能不一样一旦 Server 依赖的包没装好客户端只会看到连接失败或者一份难懂的报错信息。我的做法是给每个 MCP Server 单独建虚拟环境并在启动配置里显式声明解释器路径避免互相污染。第二是工具返回内容过大。一个工具返回 20 万字符的 JSON回填进上下文之后对话长度直接爆炸费用和延迟同步上升。要根治只能做结果瘦身服务端在把结果返回给模型之前先截断、摘要或只保留关键字段。这个瘦身逻辑放在 Server 侧是对的因为只有 Server 最清楚哪些字段对模型有价值。第三是安全边界。让模型去调一个查询数据库的工具如果不对查询条件做限制它可能直接把整张表都捞出来。我个人的经验是涉及数据读取的工具参数 schema 要尽可能严格涉及写操作或高消耗操作的工具应用层要加确认门槛所有工具调用都应该完整留日志方便事后追溯模型为什么会调用这个工具。5.3 一个实战排查案例上下文膨胀与工具误选我整理一个之前实际处理过的场景细节做了脱敏。某智能客服系统接了一批工具用户提问后系统响应越来越慢而且经常风马牛不相及地调用错工具。我去看日志时发现三个问题叠在一起。第一工具定义全量挂载。系统把十几个工具全部塞进每次请求每个工具描述又写得很长一次请求光工具定义就占了几千 token。第二工具结果没有瘦身。某些查询工具返回的原始 JSON 有上万字模型回填后上下文迅速膨胀多轮对话之后上下文超过模型容量的一半。第三工具之间语义重叠比如查询订单状态和查询物流信息用户问我买的东西到哪了时两个工具都会被模型选中选错概率很高。修复措施不复杂把工具描述整体改写删掉冗余形容词统一触发条件把工具按业务域分成几组根据用户问题的分类结果动态挂载对应分组其他分组不进请求在 Server 侧对工具结果做摘要和截断只保留关键状态字段。改动之后单次请求体积降了约三分之一工具选型准确率明显回升响应耗时也回到正常范围。这次排查给我的真实体会是工具调用系统的性能问题往往不是模型能力问题而是工具工程问题。把一个五百字的工具描述压缩成一百字价值不亚于换一个更强的模型。写在最后工具调用这条链路我从最开始用 Function Call 手写硬编码一路做到 MCP 标准化接入最深的感受是AI 应用复杂度的拐点通常不在模型本身而在于手脚的数量。工具少的时候手写编码很快工具一旦多起来没有标准的接入和描述方式整个系统就会迅速变成一团乱麻。踩过几次坑之后我反而越来越认同一个朴素的道理给 AI 装手脚之前先想好这些手脚以什么协议被调度比单纯追新工具更重要。如果让我给一个排序建议那就是先把 Function Call 的闭环彻底跑通建立对决策与执行分离的体感再引入 MCP 做工具标准化。前者是你理解 Agent 的最小必要知识后者是你把 Agent 做成产品的地基。工具描述写清楚一点调用日志留得全一点对工具的权限想得狠一点——这三条做扎实你的 AI 手脚起码能跑稳一大程。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑