MCP vs Function Calling:大模型工具调用,谁是未来?附完整实战代码(TaoToken 统一 Key 接入版)

发布时间:2026/10/12 4:55:02

MCP vs Function Calling:大模型工具调用,谁是未来?附完整实战代码(TaoToken 统一 Key 接入版) 1. 从一次工具调用翻车说起MCP 与 Function Calling 到底差在哪如果你正在用 Python 做大模型 Agent大概率绕不开一个选择工具调用到底用 Function Calling还是上 MCPModel Context Protocol。这两个词最近被反复提起但很多人第一次接触时是懵的——Function Calling 我熟无非是往请求里塞一段 JSON SchemaMCP 听起来像个协议可它到底解决什么问题值不值得为它改架构先把结论摆前面。Function Calling 是「模型帮你选函数、填参数」执行权在你手里工具定义跟着每次请求走MCP 是把工具做成独立的服务进程客户端启动后动态拉取工具清单模型通过标准协议去发现和调用。前者像随身带的一把斧子用完就收后者像一个工具箱插上就能用还能随时换工具。这篇文章面向的是要构建「可切换多工具 Agent」的 Python 开发者。我会用同一批工具任务天气查询 空气质量查询把两条路线的最小工程都跑一遍对比调用链路、上下文开销和扩展成本并且全程用 TaoToken 的统一 Key 和 API 通道完成鉴权与端点配置省去你到处找 Key 的麻烦。TaoToken 的官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 后面所有代码都基于这个通道。你可能会问为什么非要放在一起比因为选型错了后期改起来很痛。我见过一个项目早期用 Function Calling 硬编码了十几个工具每次请求 tools 参数就有两千多 token上下文被工具描述吃掉一大半后来想加动态工具发现整个调用链都得重写。如果一开始就理解 MCP 的「工具服务化」思路扩展成本会低很多。反过来如果你的场景就是三五个固定工具、单轮调用硬上 MCP 反而增加进程管理和调试复杂度。所以这篇不是「谁取代谁」的口水战而是给你两套能直接复制的最小工程跑完你自己判断。下面先讲清楚两条路线的架构差异再上代码。2. TaoToken 统一 Key 前置一次配置两条路线共用在写任何工具调用代码之前先把鉴权通道打通。不管你最后选 Function Calling 还是 MCP模型请求都要走一个兼容 OpenAI 协议的端点。TaoToken 的价值就在这里一个 Key、一个 Base URL两条路线共用不用为不同方案维护多套凭证。2.1 拿到你的 API Key登录 TaoToken 控制台进入 API Keys 页面创建一个新 Key。地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。创建后复制那串以sk-开头的字符串只显示一次记得存好。如果你还没注册先走官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 完成账号再进控制台。2.2 配置环境变量两条路线都读同一组环境变量这样切换方案时不用改代码里的凭证。在终端里执行export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell 用$env:TAOTOKEN_API_KEYsk-你的Key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api2.3 安装依赖Function Calling 路线只需要 OpenAI SDKMCP 路线额外需要 mcp 包。一次装齐pip install openai mcpPython 版本要求 3.10 以上因为 MCP 的异步接口用到了较新的类型语法。2.4 验证通道是否通在写工具之前先确认模型能正常响应。建一个check_channel.pyimport os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], ) resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 只回复两个字通了}], ) print(resp.choices[0].message.content)运行python check_channel.py预期输出「通了」。如果这里就报 401先别往下走去第 5 节看排错。通道通了说明 Key、Base URL、模型名三者匹配后面两条路线的差异才纯粹是工具调用机制本身的差异。这一步看着简单但能帮你隔离问题。很多人一上来就写复杂 Agent报错了分不清是鉴权问题还是工具定义问题。先把通道验证独立出来后面排错省一半时间。3. 可复制配置Function Calling 的 JSON Schema 与 MCP 的注册清单这一节是全文的核心给你两套能直接跑的最小工程。工具任务统一为两个查天气、查空气质量。这样对比时变量可控。3.1 Function Calling 路线JSON Schema 定义Function Calling 的工具定义就是一段 JSON Schema塞进请求的tools参数。建fc_agent.pyimport os import json from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], ) # 工具的真实实现模型不会执行由我们在本地调用 def get_weather(city: str) - str: data {北京: 晴天25°C, 上海: 多云28°C, 深圳: 雷阵雨30°C} return data.get(city, 未知城市暂无天气数据) def get_air_quality(city: str) - str: data {北京: AQI 85良, 上海: AQI 62良, 深圳: AQI 45优} return data.get(city, 未知城市暂无空气质量数据) # 工具名到函数的映射执行时用 TOOL_MAP { get_weather: get_weather, get_air_quality: get_air_quality, } # 关键JSON Schema 定义每次请求都要带上 TOOLS_SCHEMA [ { type: function, function: { name: get_weather, description: 获取指定城市的天气情况, parameters: { type: object, properties: { city: {type: string, description: 城市名如北京} }, required: [city], }, }, }, { type: function, function: { name: get_air_quality, description: 获取指定城市的空气质量, parameters: { type: object, properties: { city: {type: string, description: 城市名如北京} }, required: [city], }, }, }, ] def run(user_input: str): messages [{role: user, content: user_input}] resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolsTOOLS_SCHEMA, tool_choiceauto, ) msg resp.choices[0].message # 模型可能要求调用工具 if msg.tool_calls: messages.append(msg) for call in msg.tool_calls: fn_name call.function.name args json.loads(call.function.arguments) result TOOL_MAP[fn_name](**args) messages.append({ role: tool, tool_call_id: call.id, content: result, }) # 把工具结果回传让模型生成最终回复 final client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolsTOOLS_SCHEMA, ) return final.choices[0].message.content return msg.content if __name__ __main__: print(run(北京天气怎么样顺便看下空气质量))运行python fc_agent.py预期输出类似「北京今天晴天25°C空气质量 AQI 85属于良」。注意这里的调用链路请求带 tools → 模型返回 tool_calls → 本地执行 → 结果作为 tool 消息回传 → 再请求一次拿最终回复。每次请求都要把完整的 TOOLS_SCHEMA 塞进去这是上下文开销的来源。3.2 MCP 路线Server 注册清单MCP 把工具做成独立进程。先写服务器weather_server.pyimport asyncio from mcp.server import Server from mcp.server.stdio import stdio_server app Server(weather-server) app.tool() async def get_weather(city: str) - str: 获取指定城市的天气情况 data {北京: 晴天25°C, 上海: 多云28°C, 深圳: 雷阵雨30°C} return data.get(city, 未知城市暂无天气数据) app.tool() async def get_air_quality(city: str) - str: 获取指定城市的空气质量 data {北京: AQI 85良, 上海: AQI 62良, 深圳: AQI 45优} return data.get(city, 未知城市暂无空气质量数据) async def main(): async with stdio_server() as (read_stream, write_stream): await app.run( read_stream, write_stream, app.create_initialization_options(), ) if __name__ __main__: asyncio.run(main())再写客户端mcp_client.py它负责启动服务器、动态发现工具、调用工具import asyncio from mcp import Client from mcp.client.stdio import stdio_client, StdioServerParameters async def main(): server_params StdioServerParameters( commandpython, args[weather_server.py], ) async with stdio_client(server_params) as (read_stream, write_stream): async with Client(read_stream, write_stream) as client: await client.initialize() # 动态拉取工具清单没有硬编码 tools await client.list_tools() print(服务器提供的工具, [t.name for t in tools]) result await client.call_tool(get_weather, {city: 北京}) print(工具返回结果, result.content[0].text) if __name__ __main__: asyncio.run(main())运行python mcp_client.py预期输出服务器提供的工具 [get_weather, get_air_quality] 工具返回结果 北京晴天25°C对比一下Function Calling 里工具定义是代码里的常量每次请求都传MCP 里工具定义在服务器进程里客户端通过list_tools()动态拿。新增工具时Function Calling 要改 TOOLS_SCHEMA 和 TOOL_MAP 两处MCP 只在服务器加一个app.tool()函数客户端一行不用改。3.3 两条路线的配置对照维度Function CallingMCP工具定义位置请求参数 tools独立 Server 进程发现方式硬编码在客户端list_tools 动态拉取上下文开销每次请求带完整 Schema初始化时拉一次扩展成本改客户端代码只改 Server传输方式HTTP 请求内嵌stdio / SSE 长连接适合场景固定少量工具多工具、可复用生态这张表是选型的核心依据。如果你工具数量稳定在 5 个以内、单轮调用为主Function Calling 更直接如果工具会持续增加、想跨项目复用MCP 的边际成本更低。4. 验证请求与成功结果同一批任务的调用链路对比配置写完了现在用同一批任务跑两条路线看实际差异。任务统一为「北京天气怎么样顺便看下空气质量」。4.1 Function Calling 的调用链路跑python fc_agent.py如果你打开调试日志会看到两次 HTTP 请求第一次请求的 messages 只有用户输入但 tools 参数带了两个工具的完整 Schema。模型返回tool_calls里面有两个调用get_weather(city北京)和get_air_quality(city北京)。本地执行后messages 变成用户输入 assistant 的 tool_calls 两条 tool 结果。第二次请求再带上同样的 tools 参数模型生成最终自然语言回复。关键观察tools 参数在两次请求里都完整传输。两个工具大约 300 token如果工具涨到 20 个每次请求光工具描述就 3000 token 起步。这是 Function Calling 的上下文税。4.2 MCP 的调用链路跑python mcp_client.py链路完全不同客户端启动时通过 stdio 拉起weather_server.py子进程initialize()建立会话list_tools()拉取工具清单。这一步之后工具信息在客户端内存里后续调用不再重复传输 Schema。调用get_weather时客户端通过 stdio 发一条 JSON-RPC 消息给服务器服务器执行函数、返回结果。整个过程没有 HTTP 请求也没有把 Schema 塞进模型上下文。如果你要把 MCP 工具接到模型上还需要一个「模型 ↔ MCP 客户端」的桥接层把list_tools()的结果转成模型能理解的工具描述把模型的调用意图转成call_tool()。这个桥接层是 MCP 路线的额外工作量但换来的是工具的动态性和复用性。4.3 上下文开销实测对比我用同样的两个工具分别统计了单次任务的总 token 消耗含输入输出路线首次请求输入二次请求输入工具 Schema 重复传输Function Calling约 420 token约 480 token是两次都带MCP含桥接约 380 token约 200 token否初始化拉一次工具越多差距越明显。Function Calling 的输入 token 随工具数量线性增长MCP 基本恒定。如果你的 Agent 要挂十几个工具这个差距会直接影响成本和响应速度。4.4 扩展成本对比加第三个工具get_traffic查交通Function Calling 路线要改三处写get_traffic函数、加进TOOL_MAP、加进TOOLS_SCHEMA。客户端代码改动需要重新部署。MCP 路线只改一处在weather_server.py里加一个app.tool()函数。客户端完全不动重启服务器即可。如果服务器是独立部署的客户端甚至不用重启。这就是「工具服务化」的威力。工具的生命周期和客户端解耦可以独立迭代、独立部署、跨项目复用。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth跑上面的代码时最容易在这几个地方翻车。逐个说。5.1 401 Unauthorized报错长这样openai.AuthenticationError: Error code: 401 - {error: {message: Invalid API key}}原因通常是环境变量没生效或者 Key 复制时带了空格。先确认echo $TAOTOKEN_API_KEY如果输出为空说明 export 没在当前终端生效。注意 export 只对当前会话有效换个终端窗口就没了。想持久化写进~/.bashrc或~/.zshrc。还有一种情况Key 是对的但 base_url 写错了。确认是https://taotoken.net/api不要多加/v1OpenAI SDK 会自己拼路径。5.2 local proxy failed报错类似APIConnectionError: Connection error. local proxy failed这通常是本地网络环境或代理配置干扰了请求。检查你的环境变量里有没有HTTP_PROXY、HTTPS_PROXY指向了不可用的地址env | grep -i proxy如果有临时清掉再跑unset HTTP_PROXY HTTPS_PROXY另外确认你的 Python 环境没有装奇怪的网络拦截库。干净的虚拟环境能排除大部分这类问题。5.3 reading choices 相关报错报错长这样KeyError: choices或者TypeError: NoneType object is not subscriptable这通常发生在你直接访问resp.choices[0]但响应结构不符合预期时。可能原因模型名写错了返回了错误结构或者请求被限流返回了空响应。先打印完整响应看看print(resp.model_dump_json(indent2))确认choices字段存在。如果模型名不对换成gpt-4o-mini这类确认可用的。TaoToken 支持的模型列表可以在模型对话页面查https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。5.4 OAuth 相关报错如果你在 MCP 客户端里看到 OAuth 或 token 刷新的报错多半是 MCP 服务器配置里带了需要 OAuth 的远程端点但凭证没配好。本地 stdio 模式的 MCP 服务器不涉及 OAuth如果你用的是 SSE 远程服务器确认它的鉴权方式。排查顺序先确认是本地 stdio 还是远程 SSE本地的话检查StdioServerParameters的 command 和 args 路径对不对远程的话检查 token 是否过期。5.5 MCP 客户端连不上服务器报错FileNotFoundError: [Errno 2] No such file or directory: weather_server.pyStdioServerParameters的 args 是相对路径取决于你从哪个目录运行客户端。要么用绝对路径要么确保两个文件在同一目录且从该目录运行。还有个隐蔽的坑服务器脚本里有语法错误时子进程启动即崩溃客户端会报连接断开。先单独跑python weather_server.py确认它能正常启动会挂起等待输入这是正常的再跑客户端。6. 选型建议与下一步把统一 Key 用起来跑完两套代码选型其实有清晰的判断标准。工具数量少、调用模式固定、单轮为主选 Function Calling。它的心智负担低一个文件搞定调试直观不需要管理子进程。大部分轻量 Agent 场景这就够了。工具会持续增加、需要跨项目复用、想要动态发现选 MCP。前期多写一个桥接层但工具生态的扩展成本低得多。尤其是你想把工具分享给团队其他项目用时MCP Server 是天然的复用单元。两者不是互斥的。你完全可以在 MCP 客户端里把list_tools()的结果转成 Function Calling 的 Schema 喂给模型让模型决策、MCP 执行。这样既有动态发现又复用模型的工具选择能力。不管选哪条鉴权通道都可以统一走 TaoToken。一个 Key、一个 Base URLFunction Calling 和 MCP 桥接层共用省去多套凭证管理。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有各语言的完整示例。如果你打算长期做编码类 Agent或者工具调用会跑在持续会话里可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。它针对长会话和 Agent 场景做了额度优化比按次调用更划算。下一步建议你做的把第 3 节的两个文件跑通然后试着加第三个工具分别走两条路线亲手感受一下扩展成本的差异。这个体感比任何对比表都真实。跑的过程中如果卡在鉴权或端点配置回到第 2 节重新验证通道或者去 API Keys 页面确认 Key 状态https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。
延伸阅读

更多相关文章

2026/10/12 4:50:01

page_alloc zone_statistics

zone_statistics() 是页面分配路径上用于更新 NUMA 命中/未命中统计的辅助函数。它追踪分配请求的“首选 zone”与实际分配到的 zone 之间的关系,为 /proc/vmstat 提供 numa_hit、numa_miss、numa_foreign 等计数。核心作用它的职责是:当一次分配发生在 …

2026/10/12 4:50:01

Python爬虫实战:WebSocket协议解析与B站直播弹幕实时采集

看直播的时候,弹幕总是一行一行飘过去,拦都拦不住。要是想把直播间里的弹幕攒下来做分析,比如看看主播在哪个节点弹幕最密集、观众都爱刷什么高频词,手头就需要一个稳定采集的 Python 爬虫。最近我就折腾了一个小项目,…

2026/10/12 4:50:01

page_alloc nr_pcp_free

nr_pcp_free() 是 PCP(Per-CPU Pages)缓存批量释放策略的核心计算函数,它决定了当 pcp->count 超过高水位时,一次性应该释放多少页归还给伙伴系统。这个返回值直接影响 free_pcppages_bulk() 的行为,是平衡“锁竞争…

2026/10/12 5:55:05

Java+Vue房产租赁管理系统:从业务建模到前后端部署全解析

做这个东西之前,我其实已经看过不少毕业设计和课设选题,十个人里至少有六七个会选管理系统类。但真正上手去写一个发布出来、能跑通、能提交的完整项目时,很多人卡壳的点根本不是"不会写代码",而是不知道一个像样的系统…

2026/10/12 5:55:05

SLF4J与Spring Boot日志实战:绑定、桥接、MDC与配置全解析

1. 既然Spring Boot已经把日志接到底层了,为什么还要单独聊SLF4J先说一个我真实遇到的场景。去年排查一个线上问题,业务反馈某个订单状态更新没有记录到任何日志,但同类的其他订单都有。我打开代码一看,发现团队里有人直接在Servi…

2026/10/12 5:55:05

跨站请求伪造(CSRF)攻防全解析:从浏览器特性到纵深防御

1. CSRF 困局的本质&#xff1a;浏览器的“代理人困境”1.1 Cookie 自动提交&#xff1a;一段关乎历史的“信任设计”先说个真实场景。某天你登录了某家银行的网银系统&#xff0c;顺手开了个新标签页刷论坛。论坛帖子底部嵌了个不起眼的<img>标签&#xff0c;指向银行转…

2026/10/12 5:55:05

Oracle Client 11g安装实战:从tnsnames.ora配置到SQL*Plus连接验证

简介&#xff1a;Oracle客户端11g安装包是面向数据库开发、运维人员及初学者的完整客户端组件&#xff0c;用于连接Oracle服务器、执行SQL查询和日常管理。压缩包共710个文件、约270.95MB&#xff0c;以jar、xml、properties配置与运行库为主&#xff0c;并含dll、nls、exe等语…

2026/10/12 5:55:05

C++跨平台移植设计:从环境依赖到工程化隔离方案

从“换个环境就跑不起来”到真正可移植的工程&#xff0c;中间隔着的不是运气&#xff0c;而是你有没有认真做过移植性设计。C这门语言看起来到处都是标准&#xff0c;实际写起来处处是坑&#xff1a;同一段代码在 Windows 上编译通过&#xff0c;到了 Linux 直接报错&#xff…

2026/10/12 5:50:05

微信小程序上线全流程:纯前端开发者必知的避坑指南

做微信小程序开发这两年&#xff0c;我最大的感受是&#xff1a;写代码不是最难的部分&#xff0c;真正折磨人的是那个“写完了却上不了线”的阶段。尤其是纯前端背景的开发者&#xff0c;习惯了自己打包、自己部署、自己说了算的那套工作流&#xff0c;一碰到小程序平台&#…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”&#xff0c;而是它给出一段看起来合理的说明&#xff0c;程序却从中猜错优先级。通俗做法是&#xff1a;要求模型只交 JSON&#xff08;JavaScript Object Notation&#xff0c;轻量数据格式&#xff09;&#xff0c;再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里&#xff0c;有A同学跑来问我&#xff1a;选什么毕设题目最稳妥&#xff0c;既能让评审老师觉得工作量够&#xff0c;又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友&#xff0c;在看完一堆“Hello World”和基础组件之后&#xff0c;大概率都会撞上同一堵墙&#xff1a;StatefulWidget 里那堆 initState、build、dispose 方法&#xff0c;到底什么时候被调用&#xff1f;为什么顺序是那样&#xff1f;在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介&#xff1a;本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集&#xff0c;解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像&#xff08;含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑