Function Calling 是怎么让模型连接外部系统的,为什么模型自己不能直接执行函数?

工具调用(Function Calling)是模型输出一段符合约定格式的结构化调用意图,由宿主程序校验参数、代为执行外部函数,再把结果回填到上下文,模型据此生成最终回答。模型本身不直接访问数据库或第三方接口,也不保存调用状态,因此它只适合把已有能力包装成可描述函数的场景。

工具调用(Function Calling)的本质是模型只输出「要调用哪个函数、传什么参数」的结构化意图,真正的执行由宿主程序完成;模型自身不联网、不读写数据库、也不保存调用状态,所以它只适合把已有能力包装成可描述函数的场景,不能替代接口鉴权、限流和权限校验。 Function Calling 和工具调用说的是同一件事,差别只在叫法出现的阶段。「函数调用」强调模型输出的是函数名加参数,而「工具调用」把范围放宽到函数、检索接口、代码执行器等一切外部能力,行业里两者基本混用,看到 tools、tool_calls 这类字段就是在讲这条链路。 一次完整的工具调用由六步构成:宿主把工具清单和用户消息一起送进模型;模型返回工具名与参数;宿主校验参数;宿主执行真实函数;把执行结果以工具角色消息回填进对话;模型基于结果生成自然语言回答。这条链路里模型只参与第二步和第六步,中间三步全部发生在宿主程序内部。 工具描述和参数结构(schema)的质量,直接决定模型选对函数的概率。模型不是靠读代码判断函数用途,而是靠你给的名称、描述和参数定义,描述含糊或两个工具职责重叠,模型就会在相近场景下误调用;用类型、必填项、枚举值把参数边界写死,误填和漏填才会明显下降。 模型是无状态的,每一轮都要把完整上下文重新送进去,所以工具执行结果必须写回消息历史,否则下一轮模型会当作没发生过。这也解释了为什么同一个结果在长对话里会被反复占用窗口,以及为什么「只把结果存在程序变量里、不放进消息列表」的做法一定失败。 需要连续调用多个工具时,链路会变成循环:模型给出调用、宿主执行、结果回填、模型判断是否还要再调一次,直到模型不再输出工具调用为止。循环必须有终止条件,比如最大轮数、总耗时上限或明确的完成信号,否则一次含糊的任务指令就可能让它在两个工具之间来回打转。 模型给出的参数属于不可信输入,必须按 schema 做类型、范围、枚举和必填校验,校验失败时不要抛异常中断,而是把结构化的错误信息回填给模型让它重试。查询类参数写错通常只浪费一次调用,写操作参数写错则可能直接产生业务副作用,所以校验的严格程度应该按工具的危险等级分级。 涉及下单、退款、改配置这类写操作,工具侧必须做幂等设计和二次确认。模型在同一轮里可能重复请求同一个工具,也可能因为重试把同一笔操作提交两次,幂等键能挡住重复提交;金额、数量等敏感参数超过阈值时,应由宿主程序拦截并要求人工确认,而不是交给模型自己判断。 权限校验的位置必须在宿主程序,不能写进提示词。提示词是模型输入的一部分,可以被用户话术影响,把「只能查自己的订单」写成系统提示,本质上是把安全边界交给了模型的服从性;正确做法是把调用者身份传进执行层,由执行层按身份裁剪数据范围。 模型一轮里可能同时返回多个工具调用,宿主可以并发执行以缩短等待,但每个工具的失败要单独处理。并发的前提是这些调用之间没有依赖关系;一旦存在先后依赖,并行执行会拿到不完整的数据,让模型基于错误结果继续推理,错误反而会被放大。 工具定义和工具结果都占用上下文,工具越多、返回越啰嗦,留给用户任务的窗口就越小。常见做法是把工具控制在必要数量、合并职责相近的接口,对大结果做字段裁剪或摘要后再回填,只保留模型继续推理真正需要的字段,而不是把原始响应整包塞进去。 判断一个系统有没有真正实现工具调用,看三点就够了:模型是否输出结构化的调用意图,外部执行是否由独立程序完成,执行结果是否回到上下文参与后续生成。只满足第一点而没有回填结果,本质上只是一次格式化的文本输出,模型的能力并没有和外部系统接上。