Function Call、Tool Call、MCP
发布时间:
被反复问一个问题:Function Call、Tool Call、MCP,不都是让 AI 调工具吗,只是换了名字,旧瓶装新酒? 纯文字解释确实容易绕。把同一个需求分别用三种方式过了一遍代码,很容易看代码就懂了。
就和当初学 transformers 架构的时候,看那个图看一天也是理解到一定程度就卡住了,但是当你打开哈佛的The Annotated Transformer网站,然后看代码的运行的时候,很容易就知道架构和数据的流向了 。
先给结论
它们不是互斥关系,而是分层关系。Tool Call 是要做什么动作,MCP 是这动作通过什么标准接出去。 Function Call和Tool Call是模型的调用能力层,AI 决定该调用什么工具,而MCP是工具接入标准层,定义了工具如何被发现、描述、调用。
用同一个需求对比:创建 GitHub Issue
我用同一个任务来对比:用户说给 repo 创建一个 bug issue。
1)纯 Function Call(手写工具 + 手写分发)
# 伪代码:手动注册工具
tools = [
{
"type": "function",
"function": {
"name": "create_github_issue",
"description": "Create an issue in GitHub repository",
"parameters": {
"type": "object",
"properties": {
"repo": {"type": "string"},
"title": {"type": "string"},
"body": {"type": "string"}
},
"required": ["repo", "title"]
}
}
}
]
resp = llm.chat(messages=messages, tools=tools)
call = resp.tool_calls[0]
# 关键点:需要我们自己写分发逻辑
if call.name == "create_github_issue":
args = call.arguments
run(["gh", "issue", "create",
"--repo", args["repo"],
"--title", args["title"],
"--body", args.get("body", "")])
这套方式的问题是:工具一多,if/elif 会越来越长;每加一个工具,主程序就要改。
2)Tool Call(并行调用能力)
# 伪代码:模型一次返回多个 tool calls
resp = llm.chat(messages=messages, tools=tools)
for call in resp.tool_calls:
# 我们仍然要自己做分发,但可并发处理
dispatch(call.name, call.arguments)
它不是完全不同的技术,而是OpenAI后来的标准能力升级:模型可在一个回合里给出多个工具调用请求,减少往返。 这个是背景:
2023 年 11 月 6 日(OpenAI 首届 DevDay 开发者大会)当时和 gpt-4-1106-preview 模型一起作为重磅更新发布。官方博客文章: 标题是 “New models and developer products announced at DevDay”。文章中有一节专门叫 “Function calling updates”,里面明确指出了弃用旧模式,推出支持并行调用 (Parallel function calling) 的新架构。假设你的系统需要对比三个不同数据库表的数据来生成一份报告。在 Function Call 时代,LLM 只能请求查表 A -> 等待代码返回结果 -> LLM 再思考 -> 请求查表 B -> 等待结果 -> 请求查表 C。这是极度低效的串行阻塞。每一次往返(Round-trip)都在消耗几十到几百毫秒的网络延迟。 引入 Tool Calling 后,模型可以一次性输出一个包含 3 个调用的 JSON 数组。代码拿到这个数组,可以直接起多个并发线程/协程,同时去查表 A、B、C,然后把三个结果一次性塞回给模型。
整体耗时从 T_a + T_b + T_c 骤降到 max(T_a, T_b, T_c)。
3)MCP 模式(工具发现和调用标准化)
# 伪代码:client 只认 MCP,不写死具体工具
mcp = MCPClient("github-mcp-server")
# 动态拿工具定义,而不是手写 tools 列表
tools = await mcp.list_tools()
resp = llm.chat(messages=messages, tools=tools)
for call in resp.tool_calls:
# 通用转发,不再写 if/elif,这个是关键
result = await mcp.call_tool(call.name, call.arguments)
feed_back_to_llm(result)
这里的关键变化是:主程序和具体工具解耦。新增工具通常改 MCP Server 端,不用频繁改客户端分发逻辑。
看代码会豁然开朗
因为差别不在有没有调用工具,而在谁负责适配: 纯 Tool Call是我们自己适配每个工具,MCP是协议帮我们做了通用适配层 。所以看起来都在调工具,但工程成本完全不是一个量级。总结下,Tool Call 解决模型怎么发起工具请求,MCP 解决工具怎么标准化接入并跨客户端复用。