先把观点摆在前面过去一年凡是做AI应用的人几乎都绕不开Function Calling。模型输出一段结构化JSON应用层老实解析按照工具描述去调API这套链路确实解决了“让模型碰外部世界”的问题。但用了大半年之后我越来越觉得它像是一个过渡产物。最近两三个月我把自己负责的智能体项目陆续迁到了Code Mode效果比预想中明显更好——不是小修小补的好而是整个开发方式都在变。这篇文章不聊空洞的概念就讲清楚三件事Function Calling到底卡在哪、Code Mode为什么能把这层天花板掀开、以及我迁移过程中的实测数据和踩坑记录。如果你正在纠结“工具调用到底该怎么设计”这篇文章大概率能帮你省掉几周调研时间。1. 同一个人问天气两条完全不同的执行路径我先用一个最常见的场景把两个模式的区别摊开。用户发来一句“明天上海下雨吗要不要带伞”1.1 Function Calling下的七步握手协议在传统Function Calling架构里这一句话要走出七步模型识别出用户意图判断需要调用get_weather工具模型输出一个符合预定义Schema的JSON参数比如{city: 上海, forecast_date: 2025-07-16}应用层拿到这个参数自己组装HTTP请求去天气服务商API天气API返回一段数据通常是一大坨JSON应用层把这坨JSON塞进新的消息回传给模型模型读取结果组织一段自然语言回答应用层把最终回答渲染给用户。单看流程每一步都不复杂但它埋了一个容易被忽略的问题执行链路的控制权始终在应用层手里。模型只是负责“说”真正“做”的动作全部由开发者写好的胶水代码驱动。你每增加一个工具就要写对应的一套参数解析、请求组装、错误处理逻辑。工具多了之后光是维护这些胶水代码就能吃掉大量排期。1.2 Code Mode下的两步对话同样的任务放到Code Mode里呈现出来的交互完全不同模型读取用户消息直接生成一段Python代码内容大致是调用某个天气API、解析返回结果、提取关键字段代码在受控沙箱中执行模型拿到执行后的真实数据直接组织语言回答。注意这里的关键差异没有“应用层翻译官”这个角色了。模型既做决策也做执行还能直接“看到”执行结果。用户问带不带伞模型可以在代码里对比明天的降雨概率和温度——人出门判断要不要带伞时做的事模型自己也做了一遍。1.3 两种路径的本质分野我后来想明白一件事Function Calling本质上是一个协议它规定模型如何“请求”外部动作Code Mode本质上是一个运行时它让模型直接拥有执行能力。协议解决的问题是“怎么说话”运行时解决的问题是“怎么做”。做工程的人都知道协议越多转换层就越多出错的概率就越高。这也是为什么我在对比了一遍之后内心越来越偏向Code Mode。2. Function Calling的核心原理与三大软肋要理解Function Calling为什么会退位就得先看它到底怎么工作、以及它的能力边界画在哪里。2.1 原理它训练的不是“调用能力”而是“结构化表达能力”很多文章把Function Calling描述成“模型学会了调用工具”这个说法其实不严谨。从模型训练的视角看Function Calling本质上是让模型学会按照给定的工具描述输出符合Schema的JSON参数。模型内部没有真的去执行任何代码、没有发任何请求它只是在做一件核心的事把意图翻译成一份严格格式的“交接单”。这份交接单交给谁交给开发者写的程序。程序拿着交接单去查数据库、调API、算结果再把结果交还给模型。所以整套链路里模型扮演的角色更接近“提需求的甲方”程序扮演的是“干活的乙方”。2.2 软肋一预定义Schema是刚性的真实场景是开放的我刚接入Function Calling时第一个引起警觉的问题就是Schema的膨胀速度。做一个“查询快递”工具一开始只需要tracking_id一个参数。但用户实际问法五花八门——有人报手机号有人报订单号还有人只说了“我前天买的那个东西到哪了”。为了覆盖这些问法你不得不加出query_by_phone、query_by_order_id、query_by_recency等等一堆工具接口。工具越添越多模型每次做工具选择时面临的候选空间就越来越大选错的概率也随之上升。到了后期我甚至要基于业务场景手动给工具打权重本质上还是在用开发者的经验去补偿模型的理解力。这已经偏离了“让模型自主解决问题”的初衷。2.3 软肋二多步调用链的脆弱性链路越长越容易断真实业务场景很少是“一次调用解决一个问题”。以“我要看明天上海天气顺便帮我把后天的高铁票也查一下”为例在Function Calling架构里这通常要拆成两次工具调用先查天气返回结果后再查高铁票。这种串联看起来没问题但中间的失败点非常密集第一步模型输出的城市名有偏差或者日期格式写错应用层拿到错误的参数第二步因为前一步返回数据超时整个链路卡住每一步网络交互都有一次往返延迟两步调用叠加后响应时间明显变长。我在压测里遇到过不少“第一次调用成功、第二次工具参数报错”的情况。排查到最后往往发现是因为第一次调用的执行结果里包含了模型意料之外的字段影响了第二次的判断。这种跨调用状态的传递问题在Function Calling架构里极难根治因为应用层本身对业务上下文的状态管理非常薄弱。2.4 软肋三模型缺乏“事后反思”的能力错了一次就断线最让我感到无力的场景是模型“说错了”之后没有办法自我纠正。有一次做用户意图识别测试模型需要调用一个数据分析工具但它第一次生成的参数缺了一个必填项。应用层直接把异常抛给用户用户再问一遍模型才换了另一个工具参数。整个过程没有任何机制让模型观察到“我刚才的调用失败了原因是缺参数”然后立刻调整思路。后来我看了不少产品源码发现大家通常会把这个问题甩给“多轮对话兜底”——用户再补一句模型再试一次。但你想想如果一个工具调用链需要用户充当错误监控器那这个系统的智能水平就还停留在“半成品”阶段。3. Code Mode的底层逻辑把“说话的人”变成“动手的人”Code Mode真正的变化不是多了一个工具而是换了一个思路与其让模型学会写“交接单”不如让模型学会写能直接运行的代码。3.1 一条清晰的历史演进脉络Code Mode并不是凭空冒出来的。往回看早期的ReAct架构已经让模型在“思考—行动—观察”的循环里做决策了只不过当时的行动集仍然限定在预定义函数里。再到Code Interpreter类产品出现模型开始能写代码、跑代码但主要用于数据分析等特定场景。现在常说的Code Mode是把这条路线产品化、通用化了——模型在对话中生成Python代码系统提供执行环境执行结果自动回传给模型。可以说Code Mode是“语言模型执行器”这条发展路线自然演进的结果。把决策和执行的距离拉到最短中间不再隔一层人手写的协议转换。3.2 为什么代码比JSON参数更有表达力我刚才做了一张对比表把两种模式下“模型输出物”的差异列出来对比维度Function Calling输出物Code Mode输出物表达形式固定Schema的JSON参数任意Python代码片段能否表达循环/分支/异常处理不能只有参数名和值可以代码天然支持能否调用多个外部API需要多次串行调用可以在同一段代码里组合能否传递中间状态需要应用层额外维护代码变量天然保存状态能否复用生态库只能调用预定义工具可直接import第三方库这几条差异里最要命的是“是否可以组合多个API”。在Function Calling模式里让模型同时查天气、查高铁、算总时间你需要定义三套工具还要写一堆状态流转逻辑。在Code Mode里模型直接在一个函数里依次调用三个接口把结果合并起来返回。这段代码你自己也能读、能改、能复用开发量完全不是同一个量级。3.3 执行环境才是Code Mode的关键工程点很多人以为Code Mode的核心只是“模型会写代码”但实际落地时真正考验工程能力的是“怎么安全地执行代码”。我在系统设计里把执行环境做成了三块隔离性模型生成的代码跑在独立容器中限制文件系统、网络IO和系统级访问写操作默认拒绝除非显式开启白名单目录。资源限制单次执行内存限制、CPU周期限制、网络超时限制全部设好避免一段失控代码拖垮宿主机。可观测性执行日志、异常堆栈、输出捕获全部结构化存储方便事后回溯模型“思考路径”。没有这套执行环境之前直接让模型写代码并运行其实是个危险动作。有了安全的沙箱Code Mode才能真正从“演示玩具”变成“生产工具”。4. 实战落地什么场景该选什么模式写到这里我不想给大家一种“Code Mode无敌”的错觉。实际上我用下来最真实的感受是它俩不是替代关系而是分层关系。决策框架可以这样理解。4.1 若你的任务核心是“要一个答案”选Function Calling典型场景是C端问答助手用户问“附近有哪些咖啡店”“这个型号的电池容量多大”。这类任务核心是把用户输入映射到固定的查询接口上输出结构也比较固定。用Function Calling做意图分发模型给出稳定的参数应用层按老规矩调接口链路成熟且快。这种场景下强行上Code Mode属于“杀鸡用牛刀”——为了一次简单的查询让模型生成代码再跑沙箱执行时间和资源开销都更大反而划不来。4.2 若你的任务核心是“要一个动作”选Code Mode典型场景是数据分析、报表生成、多步骤业务自动化。比如用户说“把上个月的销售明细按区域汇总做一张柱状图顺便算算环比”。这类任务要的不只是一个查询结果而是一整套处理流程。用Function Calling去挨个定义“汇总工具”“画图工具”“环比计算工具”定义一个礼拜都定义不完换Code Mode模型一段代码全搞定了。另外一个强烈推荐Code Mode的场景是带状态的多步任务。用户问“查一下今天股价如果涨幅超过5%就拉取相关新闻摘要”这类条件分支逻辑用JSON参数描述起来极其痛苦用代码写就是if一下的事。4.3 混合架构FC做入口Code Mode做执行器实际生产中我最终落地的是混合架构。系统入口仍然用Function Calling做意图识别判断用户请求属于“轻量查询”还是“复杂执行”如果是复杂执行就交给Code Mode生成代码并运行让模型基于执行结果组织回答。这样做的好处是兼顾了响应速度和能力边界。轻量查询不额外付沙箱执行的代价复杂执行不用堆砌几十个JSON工具定义。就像一家餐厅——既能直接点套餐也可以让厨师按你的要求现场发挥但这两件事在菜单上有清晰的区分。4.4 一个简单的判断公式我在架构评审时经常用一个判断标准这个任务是在“取值”还是在“做事”取值——只需要从某个固定接口拉数据并格式化输出——选Function Calling做事——需要多步处理、条件判断、多接口协作——选Code Mode。这套公式在我经历过的十几个需求里命中率很高。它不复杂但确实能有效避免架构方案的过度设计。5. 我的工程实践笔记迁移到Code Mode时踩过的坑这个部分我按“踩坑——原因——解决”的顺序写全部是实操中留下的记录没有美化。5.1 超时与资源限制最简单的坑最隐蔽一开始我给沙箱设置的超时是30秒觉得足够宽裕。但真上了生产发现不少模型生成的代码会卡在外部API等待上。有一次压测一个比价任务需要同时请求三家电商的接口其中一家服务响应慢整段代码就卡在了网络等待里活活拖到超时。后来我调整了策略给沙箱内每个外部请求单独设置超时并且允许模型生成的代码使用并发请求。通俗讲就是不让一个慢接口拖垮整段代码同时给模型足够的手段并行处理多个请求。改完后同类任务的P95耗时从40秒降到了8秒。5.2 代码可复现性同一条指令代码可能长不一样模型生成代码有一个特点它不像人写代码那样稳定复用。你给两条几乎一样的指令它可能生成两份结构完全不同的代码。这给测试和故障排查带来不少头疼事。我的解决方案分三层强制输出断言让模型在代码末尾写清楚预期结果的格式和关键字段执行后先做格式校验再返回给模型固定函数入口要求生成代码必须包含一个main()入口函数这样测试框架可以统一调度不必猜测代码哪里是入口失败自动重试代码执行报错时把错误堆栈回传给模型让它修改后重跑一次。大部分情况下模型能自己修复异常。前一阵做回归测试这套机制把代码执行的一次通过率从72%拉到了91%。剩下9%的失败主要来自外部依赖变动属于环境问题不是模型能力问题。5.3 依赖和库的使用别让模型被“缺库”挡住Code Mode最爽的地方是能用Python生态但这有个前提沙箱环境里得预装好依赖。一开始我把环境做得很干净结果模型每次生成代码想用pandas或者requests时都因为缺依赖而报错执行失败率直接飙升。后来我建了一个白名单依赖列表把数据分析、HTTP请求、日期处理等高频库全部预装进沙箱。再进一步还开放了“按需安装”能力——模型生成的代码里如果写了pip install并且包名在白名单里就允许执行。这套设计很接近开发者日常“缺什么装什么”的习惯模型不再被环境绑住手脚。5.4 安全边界越是自由的环境越要画好圈子这部分我特别想强调。Code Mode让模型能写代码、能跑代码、能访问网络这在带来能力跃升的同时也放大了风险。我做了四件事来兜底网络白名单沙箱只允许访问预设的域名白名单外部API一律走统一网关敏感操作拦截涉及生产数据库写入、删除、数据导出等关键动作无论代码怎么写都必须在沙箱外经过人工审批流最小权限运行沙箱内的代码进程以只读权限运行生产配置不挂载进容器的可写目录审计日志每次执行的入参、代码、输出、异常全部留存方便事后追溯事故根因。做这些不是杞人忧天。我见过一些团队为了追求“模型自由度”把执行环境做得过于开放结果出了线上事故最后只能整体下线。安全设计不是阻碍它是Code Mode能长期跑在生产环境的前提。5.5 灰度迁移旧逻辑不直接删先让新逻辑跑通最后一条建议也是最朴素的经验迁移要灰度别搞一刀切。我当时把流量分成三档第一档只跑内部测试任务确认稳定后第二档切10%的真实流量第三档再逐步放大到50%、100%。每一档都对比了响应耗时、失败率、用户投诉三个核心指标。灰度过程中确实抓到几个只会在真实流量下冒出来的问题——比如某些旧流程定义的特殊状态码格式模型生成的代码处理不了比如某些历史数据里的脏数据会让代码直接异常。这些问题都在小流量阶段被发现并修复了没有造成线上大范围事故。写在最后一次不算伤筋动骨的技术路线抉择坦白讲我不会说Function Calling彻底没用了。我自己的项目里它仍然在意图分发和轻量查询这些位置发挥作用只是逐渐从主角变成了配角。而Code Mode已经成了我默认的技术选型——凡是涉及“做事”的任务我基本都先往这个方向设计。最后分享一个实际体会这套架构迁移并没有推翻重写多少代码反而让代码量缩了不少。以前为了支撑十几个工具的JSON调用我要维护近两千行胶水逻辑现在大部分任务由模型生成代码完成胶水层只剩入口判断和执行环境封装。对一个坚持“能少写代码就少写代码”的人来说这个理由已经足够了。