1. 面试官为什么总爱把 Function Call、MCP、Skill 放一起问
如果你最近在准备大模型方向的面试,大概率会遇到这样一道题:说说 Function Call、MCP、Skill 的区别。很多人背了概念,一到追问就露馅,因为这三者根本不在一个抽象层级上,硬放在一起比较,就像问「螺丝刀、USB 接口、宜家说明书有什么区别」。
先把定位说清楚。Function Call 是模型 API 层面的原子交互机制,解决的是「模型怎么结构化地请求执行一个外部函数」。MCP 是协议层的连接标准,解决的是「模型怎么标准化地发现、调用、获取外部系统的能力与上下文」。Skill 是框架或产品层的能力封装体,解决的是「模型应该按什么流程把一件事做完」。三者是层层包裹的关系,不是三选一的替代品。
我试过在一个最小 Agent 链路里把三者串起来跑,才发现面试时最容易答错的点在于触发时机。Function Call 在单轮推理里被触发,模型输出结构化参数,宿主执行后回填结果。MCP 在会话初始化阶段就要完成握手和能力发现,工具列表是提前注入上下文的。Skill 则是在意图匹配阶段被激活,它可能内部同时用到 Function Call 和 MCP Tool。
这篇文章不空谈概念,我会用 TaoToken 作为统一 Key 和 API 通道,搭一个能跑起来的最小 Agent,把三者的调用日志逐项打印出来,让你亲眼看到谁在什么时候被触发。面试时你只要能讲清这个链路,追问基本都能接住。
适合谁看:正在准备大模型应用岗面试的同学、刚接触 Agent 开发想理清概念的工程师、以及需要给团队做技术选型的负责人。核心检索词就是 Function Call、MCP、Skill 的区别,以及它们在 Agent 链路中的分工。
2. 用 TaoToken 统一 Key 打通三种能力的接入底座
在动手之前,得先解决一个现实问题:Function Call 的格式各家不一样。OpenAI 用 tools 数组,Anthropic 用 tool_use 内容块,Google 又是 function_declarations。如果你要在一个 Agent 里同时演示三种能力,光是适配不同厂商的请求格式就够折腾半天。
TaoToken 在这里的价值是提供一个统一的 API 通道。你只需要一个 Key、一个 Base URL,就能用 OpenAI 兼容的请求格式去调用不同模型,Function Call 的 JSON Schema 声明方式保持一致。这样我们搭最小 Agent 时,可以把精力放在三者分工的演示上,而不是浪费在格式适配上。
接入前你需要准备三样东西,我把它叫做三件套:Base URL、API Key、Model ID。Base URL 填https://taotoken.net/api,API Key 在控制台的 API Keys 页面生成,Model ID 根据你选的模型填,比如gpt-4o或claude-3-5-sonnet这类。这三件套在后面的配置文件里会反复出现,先记牢。
注意:API Key 属于敏感凭证,不要硬编码进提交到 Git 的代码里,建议用环境变量或本地配置文件管理。
如果你用的是 Claude Code 这类编码工具,配置方式略有不同,需要写进 settings 文件。但本文的重点是演示三者分工,所以我们用最通用的 HTTP 请求方式来搭链路,这样你能看清每一步的原始报文。
关于 Key 的获取和额度管理,可以直接去控制台操作,接入文档里有各语言的示例代码。我建议你先用模型对话页面手动发一条带 tools 的请求,确认通道通了,再往下写代码。这一步能帮你排除掉大部分环境问题。
3. 可复制配置:最小 Agent 链路的请求与工具声明
这一节是全文的核心,我会给出可以直接复制的配置片段。整个最小 Agent 包含三个部分:一个 Function Call 工具(查天气)、一个 MCP Server 配置(文件系统)、一个 Skill 定义(周报生成)。它们会在同一个任务里被依次触发。
先看 Function Call 的工具声明。这是标准的 OpenAI tools 格式,通过 TaoToken 通道发送时不需要改动:
{ "model": "gpt-4o", "messages": [ {"role": "user", "content": "帮我查一下北京今天的天气,然后根据结果生成一份简短周报"} ], "tools": [ { "type": "function", "function": { "name": "query_weather", "description": "查询指定城市指定日期的天气情况", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"}, "date": {"type": "string", "description": "日期,格式 YYYY-MM-DD"} }, "required": ["city", "date"] } } } ], "tool_choice": "auto" }这段请求发出去后,模型不会直接回答天气,而是返回一个 tool_calls 结构,里面包含函数名和参数。这就是 Function Call 的触发时机:模型判断需要外部信息,输出结构化指令,等你执行完回填。
接下来是 MCP 的配置。MCP 走的是 JSON-RPC 2.0,初始化阶段客户端要发 initialize 请求完成握手,然后调 tools/list 做能力发现。下面是一个文件系统 MCP Server 的配置片段,放在客户端的配置文件里:
{ "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/yourname/workspace"], "env": {} } } }这个配置告诉 Host:启动一个文件系统 Server,允许访问指定目录。握手完成后,客户端会拿到这个 Server 暴露的工具列表,比如 read_file、write_file、list_directory,然后把这些定义注入到 LLM 上下文里。注意,MCP 的工具发现是提前完成的,不是等到用户提问才去拉。
最后是 Skill 的定义。Skill 没有统一标准,我用一个贴近实际的 SKILL.md 结构来演示,放在~/.claude/skills/weekly-report/SKILL.md:
--- name: weekly-report description: 根据天气、日程、任务数据生成结构化周报,适用于每周总结场景 --- # 周报生成 Skill ## 执行步骤 1. 调用 query_weather 获取本周天气概况 2. 通过 MCP filesystem 读取 workspace/tasks.md 3. 按「本周完成 / 遇到的问题 / 下周计划」三段式组织 4. 输出 Markdown 格式,控制在 300 字以内 ## 输出约束 - 不使用 emoji - 每段不超过 5 行Skill 的三层加载机制在这里体现得很清楚:启动时只注入 name 和 description,用户提问后模型判断相关才读取完整 SKILL.md,执行阶段才真正去调 Function Call 和 MCP Tool。这就是为什么 Skill 是「能力封装体」,它把提示词、工具调用、执行逻辑打包成了一个可复用单元。
把这三份配置放在一起,你就有了一个完整的最小 Agent 链路。接下来我们跑一遍,看日志。
4. 验证请求:逐项打印调用日志确认职责划分
配置写好了,现在跑一遍看结果。我用 curl 发第一条请求,观察 Function Call 的返回:
curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -d @request.json返回的报文里,choices[0].message 会包含 tool_calls 字段,结构大致是这样:
{ "role": "assistant", "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "query_weather", "arguments": "{\"city\":\"北京\",\"date\":\"2026-05-16\"}" } } ] }看到这个就说明 Function Call 被正确触发了。注意 arguments 是字符串形式的 JSON,你需要解析后再执行真实逻辑。执行完把结果作为 role 为 tool 的消息追加到对话历史,再发一次请求,模型才会生成最终回复。
MCP 的日志要看客户端侧。握手阶段你会看到 initialize 请求和响应,能力发现阶段会看到 tools/list 的返回列表。我实测下来,一个文件系统 Server 通常会暴露 5 到 8 个工具。这些工具的定义会被缓存,后续 LLM 决定调用时,客户端发的是 tools/call 消息,Server 执行后返回 text 或 resource 内容。
Skill 的日志最隐蔽,因为它发生在提示词层面。你可以在系统 Prompt 里看到被注入的 Skill 列表,只有 name 和 description。当模型决定激活某个 Skill,它会通过一次 tool calling 去读取 SKILL.md 的完整内容。这一步在日志里表现为一次普通的工具调用,但读的是本地文件。
把三者的日志按时间顺序排一下,分工就很清晰了:MCP 在会话初始化时完成握手和工具发现,Function Call 在每轮推理中按需触发,Skill 在意图匹配后被激活并编排前两者。同一个任务里,Skill 是总指挥,Function Call 是执行单步动作的手,MCP 是提供工具和上下文的插座。
如果你想验证模型侧的响应,可以用模型对话页面手动发请求对照,看返回结构是否一致。接入文档里有完整的字段说明,遇到不认识的字段可以查。
5. 本篇常见错排查:401、local proxy failed 与 OAuth 报错
跑链路的过程中,有几个报错几乎一定会遇到。我把它们和对应的排查思路列出来,你对照着看。
第一个是 401 Unauthorized。这个最常见,原因通常是 API Key 没传对。检查三件套:Base URL 是不是https://taotoken.net/api,Key 有没有多余空格,请求头是不是Authorization: Bearer xxx格式。如果你用的是 Claude Code,Key 要写在 settings 的对应字段里,不是环境变量。还有一种情况是 Key 过期或被禁用,去控制台确认一下状态。
第二个是 local proxy failed。这个报错通常出现在 MCP Server 启动阶段,说明客户端连不上本地 Server 进程。排查顺序:先确认 command 和 args 写对了,npx 能不能手动跑起来;再看路径权限,文件系统 Server 访问的目录是否存在;最后检查端口占用,如果是 HTTP 传输的 Server,端口被占也会报这个。Stdio 传输的话,进程直接终止就会触发这个错误。
第三个是 reading choices 相关的报错,比如cannot read property 'choices' of undefined。这通常说明返回体结构和你预期的不一样,可能是请求根本没成功,返回的是错误对象。打印完整响应体再解析,不要直接取 choices。如果返回的是{"error": {...}},先看 error.message。
第四个是 OAuth 相关报错。部分 MCP Server 需要 OAuth 授权才能访问远程资源,比如 GitHub、Sentry 这类。报错信息里会带 authorization 字样。这时候需要按 Server 文档完成授权流程,拿到 token 后写进配置的 env 字段。注意 token 也有有效期,过期了要重新授权。
还有一个容易忽略的点:Function Call 的 arguments 解析失败。模型返回的是字符串,如果里面有转义字符没处理好,JSON.parse 会抛异常。建议用 try-catch 包一层,解析失败时把原始字符串打出来看。
排障时建议开 verbose 日志,把请求和响应都打全。很多问题看一眼原始报文就清楚了,比猜快得多。如果确认是通道侧的问题,可以去接入文档对照示例,或者用模型对话页面发同样的请求做对比测试。
6. 面试怎么答:把三者串成一条链路讲
回到面试场景。当面试官问 Function Call、MCP、Skill 的区别,你不要平铺直叙地背定义,而是用一条链路把它们串起来。我的答法是:先讲抽象层级,Function Call 是 API 级原子操作,MCP 是协议级连接标准,Skill 是框架级能力封装;再讲触发时机,MCP 在会话初始化时握手和发现能力,Function Call 在单轮推理中按需触发,Skill 在意图匹配后被激活并编排前两者;最后讲协作关系,Skill 是总指挥,Function Call 是执行手,MCP 是插座。
这样答的好处是,面试官能看出你真正跑过链路,而不是只背了概念。如果追问「那 MCP 能不能替代 Function Call」,你可以说不能,MCP 的工具调用底层依然可能走 Function Call 的机制,只是把发现和连接标准化了。如果追问「Skill 和 Agent 什么关系」,你可以说 Skill 是 Agent 的能力单元,Agent 是调度器。
想自己动手复现这条链路的,可以从 API Keys 页面拿一个 Key,照着第 3 节的配置跑一遍。需要长期做编码和 Agent 开发的,可以看看 Coding Plan,额度更划算。验证模型返回结构的话,模型对话页面最方便,改个参数就能发请求。接入过程中遇到报错,接入文档里的示例和字段说明能帮你快速定位。