Function Calling vs Code Mode:AI Agent 工具调用的原理、差异与选型指南
本文面向正在设计 AI Agent、工具调用层或 MCP 集成的开发者,系统介绍 Function Calling 与 Code Mode,比较两者的执行模型、性能和安全边界,并给出可落地的选型方法。
一、先说结论
Function Calling 和 Code Mode 不是完全对等的两种 API 功能,更准确地说,它们位于不同层次:
- Function Calling 是调用协议:模型返回结构化的函数名和参数,由应用程序执行函数,再把结果交回模型。
- Code Mode 是编排模式:模型生成一段受控代码,在沙箱中组合多个工具,完成循环、分支、过滤、聚合等中间处理,再返回精简结果。
可以先按下面的规则选择:
| 任务特征 | 默认选择 |
|---|---|
| 单次查询、单次动作、工具数量少 | Function Calling |
| 每一步都需要模型重新判断下一步 | Function Calling |
| 多个调用之间有确定的数据流 | Code Mode |
| 需要循环、并行、过滤、排序、聚合大量结果 | Code Mode |
| 涉及写入、删除、支付、发邮件等副作用 | Function Calling,默认直连并加审批 |
| 工具目录很大 | 先用 Tool Search/渐进式发现;编排阶段再考虑 Code Mode |
工程上最稳妥的方案通常是混合模式:让 Function Calling 负责意图判断、审批和关键动作,让 Code Mode 负责边界清晰的数据编排。
二、为什么需要工具调用
大语言模型本身只能生成内容,不能天然读取业务数据库、查询订单或修改账户。应用需要把外部能力以"工具"形式提供给模型,例如:
get_customer_profile(customer_id):查询客户资料;search_orders(customer_id, status):搜索订单;create_refund(order_id, amount):发起退款;search_document(query):检索知识库。
这里的工具可以是应用内函数、HTTP API、MCP Server,也可以是平台提供的内置工具。
关键问题不在于"模型能不能调用工具",而在于:
- 工具定义如何传给模型;
- 谁决定调用哪个工具;
- 多个工具如何传递中间结果;
- 哪些操作必须经过审批;
- 如何控制上下文、延迟、成本和风险。
Function Calling 和 Code Mode 的区别,主要就体现在这些问题的答案上。
三、Function Calling 是什么
3.1 定义
Function Calling(也称 Tool Calling)是让模型按照预先给定的 JSON Schema,输出一个结构化的函数调用请求。
模型不会直接执行你的函数。它只负责提出调用意图和参数,真正执行函数的是你的应用程序。
以 OpenAI Responses API 为例,官方定义的基本流程是:
- 应用向模型发送用户输入和可调用工具;
- 模型返回一个或多个
function_call; - 应用解析参数并执行对应函数;
- 应用把
function_call_output连同call_id回传给模型; - 模型生成最终回答,或继续提出新的工具调用。
完整流程可表示为:
text
用户问题
|
v
模型 + 工具定义
|
+-- 普通文本回答 ----------------------> 返回答案
|
+-- function_call(name, arguments)
|
v
应用程序执行函数
|
v
function_call_output
|
+---------- 回到模型,继续判断
官方参考:OpenAI Function Calling 指南。
3.2 最小伪代码
下面是与具体 SDK 无关的伪代码,展示直接工具调用的核心循环:
javascript
const tools = [
{
name: "get_customer_profile",
description: "根据客户 ID 查询客户资料",
parameters: {
type: "object",
properties: {
customer_id: { type: "string" }
},
required: ["customer_id"],
additionalProperties: false
},
strict: true
}
];
let input = [{ role: "user", content: "查询客户 CUST-1001 的资料" }];
while (true) {
const response = await model.generate({ input, tools });
input.push(...response.output);
const calls = response.output.filter(item => item.type === "function_call");
if (calls.length === 0) {
return response.output_text;
}
for (const call of calls) {
const args = JSON.parse(call.arguments);
const result = await dispatch(call.name, args);
input.push({
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(result)
});
}
}
3.3 Function Calling 的优点
- 边界清晰:模型只能请求已经暴露的函数;
- 容易审计:每次调用都有函数名、参数和调用 ID;
- 适合副作用操作:可以在执行前做鉴权、审批、幂等和风控;
- 容易接入已有服务:函数内部可以调用数据库、HTTP API 或消息队列;
- 控制权在应用侧:应用决定是否执行、如何重试以及返回哪些数据。
3.4 Function Calling 的局限
直接调用的每一步通常都要把结果交回模型,再由模型决定下一步。当任务包含大量工具调用时,可能出现:
- 中间结果反复进入上下文,增加输入 Token;
- 多次模型往返带来更高延迟;
- 模型需要处理本来可以由代码完成的过滤、排序和聚合;
- 工具定义过多,模型选择工具的难度上升。
这并不意味着 Function Calling 不适合多步任务。只要每一步都需要模型进行语义判断,它反而更合适。
四、Code Mode 是什么
4.1 术语说明
Code Mode 目前不是一个所有厂商都严格遵循的统一标准名称。它通常描述一种工具使用模式:
模型不再逐个请求工具,而是生成一段代码;代码在受控运行时中调用工具、处理结果,并输出最终需要交回模型的数据。
Cloudflare Agents 文档将 Code Mode 定义为"模型编写代码,而不是分别请求每个操作",并强调它适合组合依赖调用、循环、分支、结果过滤和结果整形。Cloudflare Code Mode 文档
OpenAI 的对应能力称为 Programmatic Tool Calling :模型生成并运行 JavaScript,通过 tools.* 编排工具调用。官方指南说明,这种方式可以并行调用工具、使用循环和条件,并把中间结果保留在托管运行时中。OpenAI Programmatic Tool Calling 指南
因此,本文所说的 Code Mode,主要指"代码编排工具"的架构模式;在 OpenAI 语境下,可将其理解为 Programmatic Tool Calling 的同类实现,而不是普通的 Code Interpreter 或让模型随意执行服务器代码。
4.2 Code Mode 的执行流程
text
用户问题
|
v
模型 + 一个代码执行入口 + 工具类型定义
|
v
模型生成编排代码
|
v
受控沙箱执行代码
| \
| +-- 调用工具 A
| +-- 调用工具 B
| +-- 过滤、排序、聚合、循环
|
v
返回精简结果
|
v
模型生成最终回答
4.3 最小伪代码
例如,用户要求"找出库存不足的商品,并计算缺口"。如果库存查询和需求查询之间没有复杂的语义判断,可以让代码完成编排:
javascript
const skus = ["SKU-001", "SKU-002", "SKU-003"];
const rows = await Promise.all(
skus.map(async sku => {
const [inventory, demand] = await Promise.all([
tools.get_inventory({ sku }),
tools.get_demand({ sku })
]);
return {
sku,
available_units: inventory.available_units,
requested_units: demand.requested_units,
shortage_units: Math.max(
demand.requested_units - inventory.available_units,
0
)
};
})
);
const shortages = rows.filter(row => row.shortage_units > 0);
text(JSON.stringify(shortages));
这里的关键不是"让模型写更多代码",而是把确定性的中间处理从模型上下文中移到运行时中:
- 多个独立请求可以并行;
- 后续参数可以由前一步结果直接计算;
- 大量原始结果可以在沙箱中先过滤;
- 最终只返回必要的结构化结果。
4.4 Code Mode 的优点
- 适合组合调用:可以在一段代码中串联多个工具;
- 减少中间上下文:原始结果可以留在运行时,模型只接收摘要或聚合结果;
- 支持程序控制流:循环、条件、并行、映射和排序由代码执行;
- 更适合大工具目录:可以配合渐进式工具发现,只加载当前任务需要的能力;
- 可复用编排逻辑:稳定的多步流程可以形成模板或片段。
4.5 Code Mode 的局限
- 安全边界更复杂:必须运行在沙箱中,并限制可调用工具和副作用;
- 调试难度更高:需要同时观察模型生成的代码、工具调用和运行时状态;
- 模型仍可能生成错误代码:类型、字段、循环终止条件和异常处理都要验证;
- 不适合所有自适应任务:如果每个结果都需要模型进行语义判断,代码编排可能过早固定流程;
- 运行时存在限制:不同平台的沙箱在网络、文件系统、依赖安装、持久化和超时方面差异很大;
- 部分实现仍处于实验阶段:例如 Cloudflare 文档明确提示其 Code Mode 可能发生破坏性变更,生产使用需谨慎。
以 OpenAI 的 Programmatic Tool Calling 为例,生成的 JavaScript 运行在隔离的 V8 运行时中,不能直接使用 Node.js、安装包、访问任意网络、使用通用文件系统或启动子进程;它只能通过请求中启用的工具与外部系统交互。具体限制应以当前平台文档为准。
五、两者的核心差异
| 对比维度 | Function Calling | Code Mode |
|---|---|---|
| 抽象层次 | 工具调用协议 | 工具编排模式 |
| 模型输出 | 函数名 + 结构化参数 | 一段受控代码或程序计划 |
| 执行者 | 应用程序执行函数 | 沙箱执行代码,代码再调用工具 |
| 控制流 | 主要由模型和应用循环共同控制 | 循环、分支、并行由代码控制 |
| 中间结果 | 通常回到模型上下文 | 可留在运行时,最后返回摘要 |
| 最适合 | 单次调用、语义决策、审批动作 | 确定性多步调用、批量处理、聚合 |
| 工具边界 | 应用可以精确控制每个函数 | 需要控制代码能看到和调用的工具集合 |
| 可审计性 | 调用链直接、容易记录 | 需同时记录程序、嵌套调用和运行状态 |
| 安全重点 | 参数校验、鉴权、审批、幂等 | 沙箱隔离、工具权限、资源配额、审批 |
| 失败处理 | 每个调用可单独重试或转人工 | 需要定义程序级超时、重试和部分失败策略 |
| 上下文和 Token | 工具定义和中间结果可能较多 | 通常能减少中间结果回传,但会增加程序执行管理成本 |
| 延迟 | 多次模型往返可能较慢 | 确定性编排可减少模型往返,但沙箱和工具执行仍有成本 |
| 成熟度 | 主流模型和 SDK 普遍支持 | 平台实现差异大,部分实现仍较新或实验性较强 |
六、最容易混淆的三个概念
6.1 Code Mode 不等于 Code Interpreter
两者都涉及"模型生成代码",但目标不同:
- Code Interpreter:让模型用代码完成计算、数据分析、文件处理等任务;
- Code Mode:让模型用代码编排已暴露的工具,完成多个业务操作的组合。
Code Mode 的代码通常不能任意访问业务网络或数据库,必须通过受控工具访问外部系统。
6.2 Function Calling 不等于函数自动执行
模型返回调用请求后,应用仍然需要:
- 校验函数名和参数;
- 检查用户身份、权限和租户范围;
- 判断是否需要审批;
- 执行函数或拒绝执行;
- 将安全、可用的结果回传给模型。
把模型输出的函数名直接映射到任意本地函数,是常见且严重的安全错误。
6.3 Code Mode 也不是"让模型拥有一台服务器"
Code Mode 的正确实现应当是:
- 在隔离运行时中执行;
- 只注入明确允许的工具;
- 对工具参数继续做服务端校验;
- 对写操作保留审批和审计;
- 限制执行时间、并发数、内存和输出大小。
如果实现允许生成代码直接访问生产网络、任意文件或操作系统命令,应将其视为高风险远程代码执行面,而不是普通的工具调用功能。
七、如何做选型
7.1 选择 Function Calling 的场景
以下条件满足一项,就应优先考虑直接 Function Calling:
- 任务只需要一次或少量工具调用;
- 每个工具结果都需要模型重新理解后再决定下一步;
- 工具具有副作用,例如创建、修改、删除、支付、发信;
- 必须在每个动作前进行用户确认或人工审批;
- 需要保留原始引用、来源、原生文件或其他不可丢失的产物;
- 团队还没有成熟的沙箱、运行时和程序级观测能力。
典型例子:
text
用户:把订单 ORD-1001 退款 50 元
模型判断意图
-> Function Calling: get_order(ORD-1001)
-> 应用校验订单归属和可退款金额
-> 用户确认
-> Function Calling: create_refund(...)
-> 返回退款结果
这里每一步都涉及权限和业务规则,不应该为了减少往返而隐藏在模型生成的代码里。
7.2 选择 Code Mode 的场景
以下条件同时成立时,Code Mode 通常更有价值:
- 调用链的控制流相对确定;
- 中间结果可以由代码可靠地过滤、排序、连接或聚合;
- 工具调用数量较多,直接回传所有结果会浪费上下文;
- 独立调用可以并行执行;
- 运行时有清晰的沙箱、权限、配额和审计机制。
典型例子:
- 查询多个仓库后找出共同依赖;
- 批量读取多个客户的指标并计算排名;
- 分页拉取数据,去重后只把前 10 条交给模型;
- 先获取商品列表,再批量获取详情并计算库存缺口;
- 从大量工具结果中筛选满足条件的记录。
7.3 需要保留 Function Calling 的场景
Code Mode 并不意味着所有工具都应该改成"代码调用"。尤其要注意:
- 写入和删除操作应默认走直接调用;
- 审批点应显式存在,不能藏在复杂程序里;
- 需要模型逐条判断的搜索和研究任务,不要强行改成固定代码流程;
- 需要最终引用、原生文件或外部凭证时,要确保程序保留并验证这些信息;
- 如果程序无法可靠处理部分失败,就不要让它批量执行高影响操作。
7.4 大规模工具目录的处理
工具数量增加后,不要简单地把所有 Schema 一次性塞给模型。工具定义本身会占用上下文,也可能降低选择准确率。
可以按顺序采用:
- 按领域划分命名空间,例如
crm、billing、shipping; - 只把当前阶段必需的工具暴露给模型;
- 使用 Tool Search 或类似的渐进式发现机制延迟加载工具定义;
- 在 Code Mode 中只提供已经发现且明确授权的工具;
- 记录工具命中率和无效调用率,再决定是否继续拆分。
OpenAI 官方建议控制初始可用函数数量,并在工具目录较大时考虑 Tool Search;Tool Search 指南还说明了命名空间和延迟加载的使用方式。
八、推荐的混合架构
对于大多数生产 Agent,可以采用下面的分层方式:
text
用户请求
|
v
模型:识别意图、拆分阶段、判断风险
|
+-- 需要确认/有副作用 ------> 直接 Function Calling
| |
| +-- 鉴权
| +-- 审批
| +-- 幂等和审计
|
+-- 可确定性批处理 ----------> Code Mode
|
+-- 并行调用只读工具
+-- 过滤、聚合、排序
+-- 返回小而明确的结果
|
v
模型:解释结果并生成最终回答
8.1 路由规则示例
不要只在提示词中写"请高效使用 Code Mode"。应明确规定工具使用边界,例如:
text
工具编排规则:
1. 对客户资料、订单状态等只读查询,可以使用 Code Mode 做并行查询和结果聚合。
2. 对退款、删除、发邮件、修改账户等操作,只能使用直接 Function Calling。
3. 任何副作用操作必须在应用侧完成鉴权,并在执行前获得用户确认。
4. Code Mode 只能使用本阶段列出的工具,不得自行发现或调用其他工具。
5. 程序最多重试一次临时错误,不得重复执行已完成的副作用操作。
6. 如果必要字段缺失,返回结构化失败,不要猜测参数。
8.2 工具设计建议
不要只比较"模型看起来会不会调用工具",应使用相同任务集做 A/B 测试,至少关注以下指标:
| 指标 | 说明 |
|---|---|
| 任务完成率 | 是否得到业务上正确的最终结果 |
| 工具选择准确率 | 是否调用了正确工具 |
| 参数校验通过率 | 参数是否满足 Schema 和业务约束 |
| 无效调用率 | 是否出现不存在的工具、字段或重复调用 |
| 平均和 P95 延迟 | 用户实际感受到的速度 |
| 输入/输出 Token | 工具定义和中间结果带来的上下文成本 |
| 沙箱执行成本 | Code Mode 的运行时、并发和资源成本 |
| 审批绕过率 | 高影响操作是否始终经过审批 |
| 重试与部分失败率 | 批量任务失败后的恢复质量 |
| 可审计性 | 能否重建一次任务的决策和执行链 |
测试集应覆盖:
- 正常请求;
- 缺少参数和格式错误;
- 多租户越权;
- 工具超时和部分失败;
- 重复请求;
- 恶意提示词和工具描述注入;
- 大量结果、空结果和边界值;
- 需要人工审批的高风险动作。
十一、常见反模式
反模式 1:把所有工具都暴露给模型
后果是工具 Schema 占用上下文,模型更容易选错。应使用命名空间、阶段化暴露或 Tool Search。
反模式 2:为了少一次模型调用,把副作用藏进 Code Mode
这会模糊审批和审计边界。副作用操作应保持直接、可见、可拦截。
反模式 3:把模型生成代码当作可信代码
代码可能访问不存在的字段、进入无限循环或重复调用。沙箱和运行时限制是必须项,而不是优化项。
反模式 4:只验证最终文本,不验证工具执行链
最终回答看起来正确,不代表中间操作安全或真实。必须核对调用参数、权限、返回值和审批记录。
反模式 5:忽略工具返回值的大小
把数千条原始记录逐条交回模型会造成上下文浪费。应在服务端分页,或用 Code Mode 先过滤和聚合。
反模式 6:使用模糊的编排提示词
"尽量高效""必要时重试"无法定义可执行边界。应明确工具范围、停止条件、重试上限、结果格式和失败处理。
十二、最终决策清单
在架构评审时,可以逐项回答:
- 这个任务是否只需要一次或少量工具调用?
- 每一步是否需要模型进行新的语义判断?
- 中间结果能否由确定性代码处理?
- 是否需要循环、并行、过滤、排序或聚合?
- 工具是否包含写入、删除、支付或其他副作用?
- 是否存在强制审批和人工接管点?
- 是否有可靠的沙箱、配额、超时和审计能力?
- 工具定义是否过多,需要渐进式发现?
- 是否保留了引用、原生文件或其他不可丢失产物?
- 是否用真实任务集比较了完成率、延迟、Token 和安全指标?
可以用以下简化决策树收尾:
text
是否涉及副作用或强审批?
|-- 是 --> 直接 Function Calling
|
|-- 否 --> 是否需要模型在每一步做语义判断?
|-- 是 --> 直接 Function Calling
|
|-- 否 --> 是否存在批量、循环、并行或聚合?
|-- 是 --> Code Mode
|
|-- 否 --> 直接 Function Calling
十三、结语
Function Calling 解决的是"模型如何请求一个外部能力";Code Mode 解决的是"如何用代码把多个外部能力编排成一个受控流程"。
二者不是简单的替代关系:
- 需要清晰边界、逐步判断、审批和审计时,优先 Function Calling;
- 需要确定性的数据编排、批量处理和结果压缩时,考虑 Code Mode;
- 复杂生产 Agent 通常采用混合架构,把高风险动作留在直接调用路径,把只读和可确定的中间处理放进受控程序。
最重要的选型问题不是"哪种模式更先进",而是:
这一步的控制流,应该由模型决定,还是可以由经过限制和验证的代码决定?
回答清楚这个问题,Function Calling 和 Code Mode 的边界通常就清楚了。