我最早接触 function calling 的时候,其实把它看得有点小。它在很多产品文档里经常只是一个能力项,和 JSON mode、structured output、tool use 并列放着,看起来像是你接 API 时顺手打开的一个选项。可后来我越看越觉得,很多今天被叫作 AI agent 的系统,真正开始“动起来”的那一刻,靠的其实就是它。
如果没有 function calling,模型大多数时候还是停留在“说得像会做事”的阶段。它可以解释、可以建议、可以总结,但一旦你想让它去查天气、搜数据库、发请求、调用内部系统,问题就变成了另一种,模型怎么把自然语言里的意图,稳定地翻译成你系统真的能执行的动作。
我后来才慢慢意识到,function calling 真正重要的地方,不是它让模型多了一个功能,而是它把“生成文本”往前推成了“选择动作”。这也是为什么我现在更愿意把它看成 agent 的最小工作闭环,而不是一个附属特性。
它为什么总被讲小了
我现在回头看,function calling 之所以总被讲小了,一个原因是它太容易被演示成一个天气 demo。问一句“今天北京天气怎么样”,模型返回一个 get_weather 的调用参数,然后程序去执行,再把结果回给模型。这个例子当然没错,但它也很容易让人误会,误会 function calling 只是让模型学会了“按格式输出 JSON”。
实际上它更像是另一种分工方式。模型不再只负责回答问题,而是开始负责在一组可调用动作里,判断下一步应该做什么。它选哪个工具,怎么填参数,需不需要先澄清问题,这些决定会直接影响后面的系统行为。
它不是模型执行函数。 它是模型在可用工具集合里,选出要调用的工具,并返回一组结构化参数。
所以我现在更认同这样一种理解,function calling 不等于模型执行函数,它做的是把用户请求映射成一个工具名和一组结构化参数,真正执行动作的仍然是你的应用。这个边界很重要,因为它决定了责任没有消失,只是被重新分配了。模型负责判断,应用负责落地。
也正因为这样,function calling 其实不是一个小功能。它是很多系统第一次从“能聊”走向“能做”的那道门槛。可一旦你接受这个前提,下一个问题就自然来了,它到底在系统里做了什么。
它到底在做什么
把 function calling 说清楚,最有效的方法不是看 API 参数,而是先看它在系统里的最小闭环。对我来说,这个闭环至少有四个角色,用户、模型、应用和外部工具。
用户先给出一个自然语言请求,比如“帮我查一下今天上海的天气”或者“帮我找出上周新增但还没关闭的工单”。模型收到的不是一个必须直接回答的问题,而是一个需要判断“下一步动作”的输入。它会在当前可用的工具集合里选择最合适的那个,给出一个工具名,再补上一组参数。然后应用程序去真正执行这个函数或 API 调用,拿到结果之后,再决定要不要把结果交回模型,让模型组织最终回复。
这不是“模型直接干活”,而是一个 用户意图 → 动作选择 → 应用执行 → 结果回流 的闭环。
Figure 1: Function calling 的最小工作闭环。
这也是为什么我后来越来越不喜欢把它理解成“模型会调函数了”。那个说法太容易制造错觉,好像模型本身直接碰到了你的系统。更准确的说法是,模型现在会输出一种机器可消费的动作建议,而你的应用再根据这个建议去执行真正的调用。
这个过程里最关键的,不只是“有没有函数可以调”,而是模型是不是知道什么时候该调、该调哪个、该传什么参数。比如用户说“帮我看看那件蓝色跑步衫的细节”,模型如果前面对话里已经出现过商品列表,它就需要把“那件”解析成具体产品 ID。如果用户说得太模糊,它甚至不应该直接调用搜索,而应该先触发一个澄清动作。
这也是 function calling 和普通 JSON 输出很不一样的地方。普通 JSON 输出更像是把答案包进一个结构里,function calling 则是在做动作选择。一个偏“怎么说”,一个偏“下一步做什么”。
理解到这里之后,我自己最大的改观是,它本质上不是格式控制,而是 orchestration 的开始。模型第一次不只是回答,而是在参与一个明确的执行回路。而一旦你把它放进真实系统,就会发现真正难的地方远不只是把 tools 参数传进去。
真正难的不是调 API
如果只看官方 demo,function calling 很容易显得很顺。定义一个 schema,把几个函数描述清楚,模型就会返回一个漂亮的结构化调用结果。可我后来越看实际案例,越觉得 demo 和 production 之间其实隔着一整层工程工作。
最表面的区别是,demo 里的 function calling 只需要“能调用”,生产里的 function calling 需要“能稳定调用”。这两个要求看起来只差两个字,背后却是完全不同的系统压力。
在 demo 里,你通常只有一两个函数,参数也很简单,函数描述写得稍微模糊一点,模型偶尔传错参数,也不会出大事。但到了真实系统里,工具数量一多,参数一复杂,用户表达一含糊,整个链条马上就开始暴露问题。schema 定义得不清楚,模型就会在字段上摇摆。dispatch 写得不严,错误调用就可能落进意料之外的分支。缺少参数校验,模型给出的 JSON 哪怕格式正确,也可能语义错误。
| 维度 | Demo 里的 function calling | Production 里的 function calling |
|---|---|---|
| 工具数量 | 1–3 个简单工具 | 多工具并存,名称和职责容易重叠 |
| Schema 要求 | 能跑通就行 | 字段边界、必填项、枚举值都要足够明确 |
| Dispatch | 简单 if/else | 需要显式路由、权限边界和失败分支 |
| Validation | 常常省略 | 必须做参数校验、语义校验和 fallback |
| Error handling | 调失败就报错 | 要处理超时、空结果、重试、补问用户 |
| Security | 几乎不展开 | 必须限制动作空间,避免越权和危险执行 |
Figure 2: Demo 和 production 的差别,不是规模变大而已,而是工程要求整层抬高。
我现在很认同一种说法,function calling 的质量,很多时候不是模型能力的直接函数,而是 schema、系统 prompt、参数设计、验证逻辑和 guardrails 的综合产物。你给模型的函数描述越含混,它越难做稳定判断。你对参数类型和必填项定义得越松,后面应用侧就越容易接到看起来合法、实际上没法执行的调用。
再往下一层,就是安全和控制边界。很多文章都会提醒,模型不会真的执行函数,这句话当然重要,但只说到这里还不够。因为模型虽然不执行,它却可以影响你执行什么。如果你的应用把模型返回的工具名和参数不加约束地丢进动态执行链路,风险并不会因为“模型没亲手执行”而消失。
所以我现在越来越觉得,真正成熟的 function calling 不是“让模型自己决定一切”,而是给模型一个受约束的动作空间。它可以在这个空间里做判断,但不能越权。显式 dispatch、参数白名单、失败处理、重试策略、权限边界,这些才是把一个 function calling demo 变成可靠系统的关键。
还有一点我以前容易低估,错误恢复。真实世界里,工具调用失败不是例外,而是常态。超时、参数缺失、外部 API 404、权限失效、返回数据不完整,这些都很常见。如果系统没有设计好错误处理,function calling 就会从“模型会做事”迅速退化成“模型一直在失败地尝试做事”。
所以我现在会把 function calling 理解成一层非常薄、但非常关键的动作接口。它不负责把所有问题都解决掉,它只是决定系统从哪里开始行动。真正决定这件事能不能长期站住的,是你在这层接口周围补了多少工程纪律。
它和 Structured Outputs 不是一回事
我觉得现在这个话题里最常见的混淆之一,就是把 function calling 和 structured outputs 混在一起。它们确实都和“结构化输出”有关,但解决的其实不是同一个问题。
function calling 解决的是动作层的问题,也就是模型在一组可用工具里要不要调用、调用哪个、参数是什么。structured outputs 解决的则是响应层的问题,也就是当模型要返回一段机器可读内容时,怎么保证这个内容严格符合某个 schema。
换句话说,一个更接近 action selection,一个更接近 response formatting。一个是在说“下一步该干什么”,一个是在说“这次回答长成什么样”。这两个能力经常一起出现,所以很多人第一次看文档时很容易把它们当成同义词。
Figure 3: 三者都和“结构化”有关,但分别对应动作选择、响应形状、提示约束。
我自己是后来才想明白这个边界的。如果你想让模型去调用外部系统,比如查数据库、发 HTTP 请求、执行一个内部服务函数,那你需要 function calling,因为重点是工具选择和参数生成。可如果你只是想让模型返回一个稳定结构,比如一个工单摘要对象、一个分类结果、一个固定 schema 的表单草稿,那你更在乎的是 structured outputs,而不是工具调度。
纯 JSON prompting 则更弱一点。它常常只是通过提示词要求模型“请用某个 JSON 格式回答”,这在很多简单任务里也能工作,但稳定性通常不如专门的 structured outputs 机制,更谈不上 tool orchestration。
把这三者分清楚以后,我现在会更自然地判断需求属于哪一类。如果任务的本质是“去做一件外部动作”,优先看 function calling。如果任务的本质是“把回答压进一个可靠结构”,优先看 structured outputs。如果只是轻量约束格式,JSON prompting 有时也够用。
我觉得这个区分很重要,因为很多系统设计问题,其实不是模型不够强,而是一开始就选错了抽象层。你以为自己缺的是 function calling,实际上你缺的是 response schema。或者反过来,你以为只要把输出格式约束好就够了,但系统真正要解决的是工具选择和执行编排。
它和 MCP 应该怎么放在一起看
把 function calling 和 MCP 放在一起看时,我现在不太喜欢“谁替代谁”这种问法。更有帮助的问法可能是,它们分别在系统的哪一层工作。
function calling 更像应用内部的一层工具调度能力。你在一次模型请求里,把可用函数及其 schema 带进去,让模型决定用哪个,再由你的应用去执行。这种方式的优点很明显,简单、直接、上手快,尤其适合工具数量不多、系统边界比较清晰的场景。
MCP 则更像把工具接入协议化、模块化。它关心的不只是某次请求里模型怎么调某个函数,而是不同模型、不同客户端、不同工具提供方之间,能不能通过一个统一协议暴露和消费能力。一个偏局部编排,一个偏外层连接标准。
更接近的关系不是“谁替代谁”,而是:Function Calling 负责局部动作调度,MCP 负责更外层的能力接入与复用。
Figure 4: Function calling 和 MCP 更像分层关系,而不是简单替代关系。
这也是为什么我更愿意把它们理解成分层关系,而不是简单竞争关系。对于一个小型产品原型,或者只有几个应用内函数的 agent,直接用 function calling 往往就是最实用的方案。你不一定需要额外引入协议层,把问题做大。
但如果工具越来越多,接入来源越来越杂,或者你希望这些能力可以被不同模型、不同宿主环境复用,那光靠把 schema 嵌在每次请求里,就会越来越重。这个时候,MCP 这类协议化思路才会开始显得有价值。
所以我现在的判断不是“function calling 过时了”,恰恰相反,它还是很多 agent 的默认起点。只是它更适合做局部、直接、应用内的工具编排,而不是单独承担一个越来越复杂的工具生态。
这个视角也帮我避免了另一种误解,就是把 function calling 想成某种低级方案,把 MCP 想成高级方案。其实不是高低关系,而是规模、边界和复用需求不同。你是要快速把几个动作接进自己的应用,还是要构建一套更标准化、更可迁移的工具接入层,这两种问题本来就不一样。
我现在的判断
我现在对 function calling 的看法,比最早认真得多。它不是一个漂亮但边缘的小特性,而是很多 agent 系统第一次真正形成动作闭环的起点。模型不再只是生成回答,而是开始参与“下一步做什么”的判断,这个变化本身就足够关键。
但我也越来越确定,function calling 的价值不能只看 demo。真正决定它好不好用的,不是模型有没有返回一段像样的 JSON,而是你有没有把 schema、dispatch、validation、guardrails 和错误恢复这些环节搭起来。
所以如果要我现在用一句话总结,我会说,function calling 不是 agent 的终点,但它仍然是今天大多数 AI agent 最小可工作闭环的起点。先把这层想清楚,后面再谈 structured outputs、MCP、tool platform,很多东西都会顺得多。