大模型怎么调工具:Function Calling机制与三家格式差异

Claude Code、Codex 这类 AI Agent 能读文件、跑终端命令、改整个项目的代码,而大模型本身只是按上下文预测下一个 Token。中间的桥梁就是 Function Calling(函数调用):一种让模型选择工具、生成结构化调用请求、再由外部程序执行的机制。

从一个天气查询说起

问一个没有工具能力的大模型「北京现在的天气怎么样」,它只能基于训练知识解释气候特点,拿不到此刻的气温——模型内部知识不等于实时外部信息。要回答实时问题,就得借助天气 API。

传统程序调 API 不难,难在用户说的是自然语言:「北京现在多少度」「我出门要不要带伞」,表达千变万化,程序需要确定的函数名和参数。早期方案靠 Prompt 约定输出格式,比如让模型输出 ACTION: get_weather("Beijing"),再由代码解析。能用,但有三个硬伤:格式不可靠,模型可能输出错误的工具名或参数;解析成本高,容错逻辑全得自己写;行为不稳定,换个模型或 Prompt 表现就漂移。

2023 年 6 月 OpenAI 在 API 里推出原生 Function Calling,工具调用从「Prompt 里的文本约定」变成模型 API 的原生能力,Anthropic、Google 随后跟进。

一次调用拆成两半

定义上就一个核心:模型负责决策(要不要调、调哪个、参数是什么),外部程序负责执行(跑真正的函数、把结果递回去)。

模型生成的是这样的请求:

{
  "name": "get_weather",
  "arguments": {
    "city": "Beijing"
  }
}

一个必须刻在脑子里的区别:模型生成工具调用请求,不等于工具已经被执行。真正发 HTTP 请求查天气的始终是你的程序。

完整流程四步。第一步定义工具,告诉模型有什么可用:

{
  "name": "get_weather",
  "description": "查询指定城市的实时天气",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名称,例如 Beijing"
      }
    },
    "required": ["city"]
  }
}

parameters 用 JSON Schema 描述结构:type 管类型、properties 管字段、required 管必填、enum 管候选值。description 不是摆设——模型靠它判断什么场景该用这个工具,写得含糊工具就会被误选或漏选。

第二步,模型结合用户输入和工具定义,生成结构化调用请求;第三步,程序解析参数、校验权限、执行函数;第四步,把执行结果作为工具响应回传,模型生成自然语言回答。结果不够时模型可以继续发起调用,多轮交互是所有 Agent 系统的地基。

三家格式对照

各家思路一致,数据结构不同:

对比维度 OpenAI Anthropic Gemini
工具定义 tools tools functionDeclarations
参数 Schema parameters input_schema parameters
工具调用 function_call tool_use functionCall
调用参数 arguments input args
参数表示 JSON 字符串 对象 对象
执行结果 function_call_output tool_result functionResponse

OpenAI 的 arguments 是 JSON 字符串,Anthropic 的 input 直接是对象——这个差异是真实的高频坑:拿到 OpenAI 风格的 arguments 直接当字典取键会报 TypeError,得先 json.loads 反序列化。同一家厂商的不同 API 之间格式也可能不一致,OpenAI 的 Chat Completions 用 tool_calls,Responses API 用 function_call,接新接口前先看文档别凭肌肉记忆。

大量第三方服务兼容 OpenAI 格式,但「兼容」不等于高级特性全对齐,strict mode、并行调用这些能力逐个验证。

Schema 不是护身符

参数格式正确不代表调用决策正确。{"city": "Beijing"} 完全符合 Schema,但用户想查的可能是上海。JSON Schema 约束的是参数结构,不保证模型选对工具、理解意图、给出语义正确的参数。部分 API 提供严格模式(受约束解码),支持情况因接口而异。生产环境里参数校验、错误处理、权限控制一样不能少——模型说要用什么参数是一回事,程序放不放行是另一回事。

和 MCP、Harness 的分工

Function Calling 回答「模型如何表达工具调用」;MCP(Model Context Protocol)回答「AI 应用如何标准化连接外部工具」——它不只管工具,还定义了 Resources、Prompts 等能力,是应用与外部服务之间的开放协议。两者不是替代关系,一个典型编程 Agent 里它们各占一层:

用户:帮我读取 main.py
        |
      Agent
        |
   模型 Function Calling
        |
   read_file("main.py")
        |
   Agent Harness
        |
     MCP Client
        |
   文件系统 MCP Server
        |
     读取 main.py

Function Calling 表达调用意图,MCP 标准化与外部服务的交互,Agent Harness 把这些组件连起来、管理完整执行流程。也不是所有工具都必须走 MCP,直接调本地函数或 HTTP API 完全合法。

真正的 Agent 还需要 Harness 承担循环管理:Model Adapter 适配各家 API、Tool Registry 注册工具、Tool Executor 执行调用、Agent Loop 组织多轮推理、Context Management 维护上下文、Error Handling 处理超时重试。概念性骨架长这样:

while True:
    response = call_model(messages, tools)
    if not response.has_tool_calls:
        break
    for tool_call in response.tool_calls:
        result = execute_tool(tool_call)
        messages.append(result)

真实系统还要保存工具调用消息、按 call_id 关联请求与结果、限制循环次数、处理并行调用。特别提醒并行调用这一处:模型一次吐出多个 tool_call 时,结果必须按各自的 call_id 对号入座回传,塞错了轻则答非所问,重则把 A 工具的结果喂给 B 工具引发连锁错误。

落地建议

接多家模型时,在执行层之前加一层 Provider Adapter,把三家格式转成统一的内部调用结构,模型层随便换,工具执行层不动。测试策略上用 mock executor 做单元测试:固定模型返回的 tool_call 样本,断言解析后的参数结构、必填校验和权限拦截行为——把「模型说对」和「程序做对」分开验证,出问题时才能定位到是哪一层的锅。最后记住那条铁律:执行侧永远要有校验和权限,模型的输出只是建议,不是指令。

0

评论0

请先
显示验证码
没有账号?注册  忘记密码?