Function Calling是什么:从工具声明到结果回填,模型调用外部系统的四步链路
模型只会输出文字,为什么能查数据、发邮件?工具调用把「调用意图」与「执行动作」拆开:模型按声明的 JSON Schema 产出 tool_calls,后端执行后用 tool_call_id 回填结果,多轮循环直至生成回答。本文按声明、决策、执行、回填四步讲清链路原理、流式拼接与常见踩坑。
很多团队第一次接入大模型时,都会问同一个问题:模型明明只能输出文字,为什么它能帮我查到明天的天气、读取数据库里的库存、甚至替我发出一封邮件?答案就在工具调用这条链路上。模型本身并不联网,也不持有任何业务系统的账号,它做的只是把「我想调用哪个函数、传什么参数」这件事,用一段结构化的 JSON 表达出来,真正的执行动作发生在你自己的服务端。
承接这个疑问,「Function Calling 是什么」可以给出一句朴素的回答:它是模型输出结构化调用意图的一种能力约定。开发者提前把可用函数的名称、用途、参数类型写成声明,模型在对话中判断需要外部信息时,不再生成自然语言,而是产出一段符合声明的调用请求。整条链路可以拆成声明、决策、执行、回填四步,每一步都有各自的工程细节。
第一步是工具声明,通常用 JSON Schema 描述:函数名、功能描述、参数对象、每个参数的类型、必填项与取值范围。这里最容易被低估的是描述的价值,模型靠 description 判断该不该调用这个工具,描述写得含糊,模型就会在闲聊场景里乱调,或者该调的时候不调。参数层级尽量扁平,嵌套超过三层后模型填错字段的概率会明显上升;枚举值能写全就写全,别让模型自己猜。
第二步是模型决策。命中工具时,返回体里会出现一个 tool_calls 数组,每项包含调用 id、函数名和 arguments 字符串,注意 arguments 是一段 JSON 文本而不是对象,需要自己解析。一次返回可能包含多个并行调用,比如同时查三个城市的天气,也可能一个都不含,直接给出自然语言回答。判断依据是 finish_reason,出现工具调用标记就说明这一轮不是最终答案。
第三步才是真正的执行,这是整条链路里唯一「有副作用」的环节。函数由你的后端实现,模型碰不到数据库和第三方接口,因此权限校验、参数二次校验、超时控制、幂等设计都要在这一层做。实践中最稳妥的做法是把每个工具当成一个对外接口来治理:入参必须校验,写操作必须带幂等键,敏感动作走人工确认或审批,读操作也要限制数据范围。
第四步是结果回填。把函数执行结果作为一条 role 为 tool 的消息追加进对话,并用 tool_call_id 与前面的调用请求一一对应,再整体提交给模型。模型拿到结果后生成面向用户的自然语言回答。如果结果里包含错误,建议保留错误码和可读原因,让模型能决定是重试、换工具还是如实告知用户,而不是把异常吞掉,变成一个含糊的「查询失败」。
多轮循环是这里的关键。一次完整的工具调用往往不是一问一答就结束:模型可能先查订单状态,发现需要物流单号,再调另一个工具查轨迹,最后才组织回答。工程上通常用一个循环包住调用,并设置最大轮次上限,常见做法是三到五轮,防止模型陷入反复调用。每轮都要把历史消息完整带上,否则模型会丢失上下文,重复执行同一个工具。
流式输出会让这条链路复杂一些。开启流式后,函数名和 arguments 会以增量片段的形式分多次返回,需要按 index 拼接后再解析,不能收到第一个 chunk 就去反序列化,那几乎必然报错。稳妥的写法是等到最终结束原因明确指向工具调用时再做整体解析。只做文本展示的场景可以让增量直接透传给前端,但只要涉及工具调用,拼接逻辑就必须写对。
踩坑清单里频率最高的几条值得单独说。一是参数幻觉,模型给出了声明之外的字段名,或者把字符串填进数字字段,靠严格的 schema 和示例能压下去一部分。二是单位与格式歧义,「时间」到底是时间戳还是 ISO 字符串,必须在声明里写清楚。三是把工具返回的超长内容原样回填,把上下文塞爆,建议回填前做裁剪或摘要。四是权限设计缺失,让模型间接获得了不该有的数据访问能力。
当业务需要同时对接多个模型来源时,工具调用还会遇到一层额外的麻烦:不同上游对工具调用的字段命名、并行调用支持程度、流式增量格式并不完全一致。同一份工具声明,在某个上游能正常返回,换到另一个上游可能要改写字段。中转层的价值正在这里体现,把这层差异收拢到服务端,让业务代码只面对一种调用结构,减少重复适配和排查成本。
快米兔的模型 API 中转就属于这一类基础设施,注册送 5 元测试金、按量计费,适合先用小流量把工具调用链路跑通再放量。对研发团队来说,这种计费方式的好处是压测成本可预期,可以用真实的多轮工具调用场景去验证解析逻辑、并发表现和超时重试策略,而不是只看一次简单问答的返回是否正常。具体的模型清单与接入方式以官方说明为准。
回到最初的问题,工具调用与其说是模型能力的延伸,不如说是一次职责划分:模型负责判断该不该调、调哪个、传什么参数,业务系统负责能不能调、怎么执行、结果怎么用。把这条边界划清楚,Function Calling 就不再神秘。真正决定线上稳定性的,从来不是模型有多聪明,而是你在声明、校验、回填和兜底这几步上写得够不够扎实。
