很多团队在单体 Agent 进入真实工具调用阶段后,都会遇到一个看似技术、其实是运行时设计的问题:到底该让模型走严格的 function calling,还是给它更开放的工具使用空间。前者看起来更稳,后者看起来更灵活,于是讨论很容易停在“哪个模型支持得更好”或者“哪个调用格式更方便”上。但真正决定成败的,通常不是协议名,而是你的任务边界、工具数量、状态复杂度和副作用风险。选错了,系统就会要么被僵硬 schema 卡死,要么在开放工具面前慢慢失控。
Toolformer 和 Gorilla 这两类研究,分别说明了模型可以学习如何调用工具,也说明了大规模 API 与开放工具面会迅速提升检索和选择难度。MCP 的引入则进一步把问题从“模型会不会用工具”推到了“运行时如何向模型暴露工具”。对 TaskPilots 这类独立运行 Agent 平台来说,function calling 和开放式工具调用不是互斥路线,而是两种不同的运行时契约。真正要选的,不是“喜欢哪种格式”,而是“哪种约束最适合当前动作与风险”。
为什么这个问题重要
这不是提示工程偏好,而是执行边界选择
一旦 Agent 会读状态、调工具、跨会话恢复并产生真实副作用,工具调用方式就会直接影响系统边界。function calling 的价值在于把输入输出和参数结构压得足够明确,让模型更像在调用一组受控接口;开放式工具调用的价值则在于让运行时可以暴露更丰富的工具面,让模型在更动态的环境里完成检索、选择和组合。两者的差异,不只是“写 JSON 还是写文本”,而是系统到底要求模型多严格地遵守契约、又允许它在多大范围内探索工具空间。
- 契约越严格,越适合高副作用、低容错动作。
- 工具面越开放,越适合变化快、探索性强的任务。
- 状态越复杂,越需要把调用方式和恢复机制一起设计。
选错之后,代价会先出现在运行时而不是模型分数上
很多团队一开始看离线样例,觉得两种方式都能“调成功”,上线后才发现问题并不体现在模型回答本身,而体现在系统行为上。function calling 用得过重时,团队会为每个小变化都补 schema、补分支、补异常处理,最后形成一套非常脆弱的调用层;开放式工具调用用得过头时,工具发现、参数选择和权限边界都会慢慢漂移,重试与恢复也更难稳定。真正要避免的不是单一失败,而是运行时逐渐失去清晰边界。
适用场景
什么时候优先用 function calling
如果当前任务的动作边界清楚、参数结构稳定、错误成本高,而且团队需要把调用结果稳定映射到业务系统,那么 function calling 通常是更稳妥的起点。典型场景包括创建工单、更新 CRM 字段、提交审批、触发通知、执行可审计写入等。这类动作的共同特征,是你更关心“参数是否正确、是否符合白名单、是否能被明确拒绝”,而不是让模型在大量工具之间自由探索。
什么时候更适合开放式工具调用
如果任务本身带有明显的检索、组合和动态发现特征,开放式工具调用往往更有优势。比如工具集合会频繁变动、同类接口很多、模型需要先理解工具能力再决定调用路径,或者运行时本来就通过 MCP 之类的机制提供一层动态工具面。这类场景更接近 Gorilla 讨论的大规模 API 连接问题,也更容易从开放式工具调用里获得灵活性。但前提是,你已经准备好了独立的注册表、权限过滤和状态恢复层,而不是单纯把更多工具堆给模型。
推荐系统结构
把“选哪种调用方式”变成运行时策略
更稳的做法,不是给整套系统永久选边站,而是让运行时按任务与风险决定调用模式。对于高副作用、固定 schema 的动作,运行时优先暴露严格的 function calling 接口;对于探索性检索、动态工具发现或多步分析任务,运行时则提供更开放的工具使用面。换句话说,选型不该被写死在模型层,而应由控制器根据当前任务阶段、工具类型和状态上下文动态决定。
一个实用的判断框架可以同时看四个维度:第一,动作是否结构稳定;第二,工具集合是否经常变化;第三,错误后果是否可逆;第四,恢复是否必须依赖精确状态。如果前两项偏低、后两项偏高,function calling 更合适;如果前两项偏高、后两项偏低,开放式工具调用通常更有价值。
把工具注册表、状态快照和会话模型分开
无论选哪种调用方式,都不应该把工具定义、权限规则和状态假设塞进提示词。更好的结构,是把工具注册表作为事实源,把状态快照作为恢复依据,把会话模型作为执行上下文,然后让调用层只负责把当前允许的工具以合适形式暴露给模型。这样 function calling 不会退化成到处复制 schema 的样板层,开放式工具调用也不会退化成“让模型面对一大堆没筛过的工具自己猜”。
MCP 的价值就在这里:它让工具暴露这件事可以由协议与运行时来管理,而不是完全依赖 prompt 手写说明。对单体 Agent 来说,这意味着开放式工具调用不等于放弃边界,而是把边界放到运行时过滤、适配和权限注入里。
把 TaskPilots 的运行时能力做成双模执行面
映射到 TaskPilots,可以把独立运行 Agent 理解成一个双模执行面。第一层是工具注册表与权限过滤,只决定当前会话此刻真正可见的工具;第二层是调用模式选择,为同一类动作决定使用严格 function calling 还是开放式工具调用;第三层是状态层,负责记录快照、失败点和恢复位置。像 工具注册表和权限边界,别放在提示词里硬写 和 按风险分级决定 Agent 的自治边界 讨论的运行时事实源与风险分档,正好可以作为这个双模执行面的基础。
风险与失效点
把 function calling 用成“万用约束”,会让系统越来越脆
function calling 的常见误区,是把它当成所有工具问题的统一答案。结果是工具只要稍微变化一点,团队就要同步改 schema、改解析、改异常路径,最后整个系统被一层巨大的结构化壳包起来。表面上看很安全,实际上变更成本越来越高,模型也会在大量近似函数之间产生新的选择错误。更糟的是,团队会误以为“只要用了 function calling 就天然安全”,从而忽略权限过滤、结果校验和状态恢复这些更关键的控制面。
- schema 爆炸会让维护成本迅速升高。
- 过度结构化会让小变更变成系统级改动。
- 如果没有结果校验,结构正确也可能业务错误。
把开放式工具调用用成“自由发挥”,会让边界慢慢漂移
开放式工具调用的高频问题,则是团队把“更灵活”误解成“更少约束”。当模型面对一组动态工具却没有足够的权限过滤、可见性控制和状态支持时,它确实能更自由地尝试,但这份自由往往也会扩大错误空间。最先暴露的通常不是工具完全用错,而是长会话里不断重复试探、参数越传越松、权限边界越来越模糊。只要系统回答不清“为什么这一步看见了这些工具”,开放式工具调用就已经开始失控。
验证指标
上线前先做一组分档对照测试
上线前不要只问“哪种方式总体成功率更高”,而要按任务类型做对照。建议至少准备三组样本:第一组是固定 schema 且副作用高的动作,验证 function calling 是否更稳;第二组是工具数量多、变化快、需要探索的任务,验证开放式工具调用是否更灵活;第三组是带中断和恢复的会话样本,验证两种模式在状态恢复上的差异。只有把任务分档后再看结果,团队才能避免被一个平均指标带偏。
上线后同时看成功率、失败率和恢复成本
生产阶段至少要持续跟踪五类指标:执行成功率、工具失败率、schema 或协议违例率、恢复时间和状态一致性。前两项帮助判断总体可用性,中间一项能直接暴露 function calling 或开放式工具调用在契约层的问题,后两项则衡量运行时是否真正能处理真实中断。除此之外,最好再看一次“工具可见性漂移事件数”,也就是模型在不应看到某类工具时实际看到了多少次,这能直接反映运行时过滤是否到位。
- 成功率高但恢复成本高,说明系统可能只是把失败延后了。
- 协议违例率高,说明调用模式和任务类型并不匹配。
- 状态一致性差,说明问题不在模型,而在运行时设计。
下一步 / FAQ
下一步先按动作类型画一张选型表
最务实的第一步,不是争论“我们平台应该统一用哪一种”,而是把现有动作按固定写入、探索检索、动态工具发现和跨会话恢复四类拆开,再为每类动作标注风险、可逆性、工具变化频率和恢复要求。只要这张表画出来,很多团队就会发现自己真正需要的不是单一答案,而是一套按动作选型的运行时策略。
FAQ:function calling 是不是一定比开放式工具调用更安全
不一定。它通常在固定 schema 和高副作用动作上更容易做约束,但如果没有权限过滤、结果校验和状态恢复,结构再严格也不代表系统整体更安全。
FAQ:开放式工具调用是不是只适合研究型系统
不是。只要你有成熟的注册表、权限过滤和状态层,开放式工具调用同样可以进入生产,只是它更依赖运行时治理,而不是只依赖调用格式本身。
FAQ:可以两种方式一起用吗
完全可以,而且对大多数进入真实工具阶段的单体 Agent 来说,这通常是更现实的路线。关键不是混用本身,而是让运行时能明确决定某一步为什么走严格契约、某一步为什么走开放工具面。