去年有个团队给我看过他们的 Agent 底层日志:一个数据重组任务,跑了二十多个工具调用轮次,中间穿插着三个 JSON Schema 解析失败,来回重试了两次,最后的结果还是错的。当时我给的反馈是——别把自己当成指挥家,天天调度 API,直接让模型在沙箱里写代码、跑代码、看结果、改代码。这个思路,就是现在圈子里反复被提到的 Code Mode。
工具调用(Function Calling)统治了 AI Agent 开发大概两三年,它把模型的能力变成一个个标准化的外部接口,确实是"能力就绪"的最快路径。但最近半年,越来越多实践者开始把底层的执行策略换掉:模型不再只是返回一段结构化调用请求,而是直接生成一段可执行代码,由运行时去执行、去反馈、去迭代。这不是一个简单的新功能,而是一套完全不同的 Agent 架构哲学。
这篇文章会把这件事儿拆清楚:工具调用的瓶颈到底卡在哪,Code Mode 的底层设计好在哪,切换到新范式之后又会踩哪些坑。写给我自己,也写给正在做 Agent 底座、或者在纠结智能体方案怎么选的朋友。
1. 复盘工具调用:它是怎么走到今天的
1.1 结构化接口让模型第一次拥有"手"
在 Function Calling 大规模普及之前,想让大模型跟外部系统交互,靠的是 Prompt 里塞格式说明,模型输出一段 JSON,你再用代码解析这段 JSON、调用目标函数。当时那套流程很脆弱,模型经常漏字段、多写注释、参数类型搞错,解析层一天到晚在报错。
后来主流平台统一做了结构化函数声明:你给模型一份 JSON Schema,明确说明函数名、参数类型、必填项,模型在推理时会在候选 API 之间做选择,返回标准的调用请求,而不是自己即兴发挥。这一步是个质变,等于给模型装了一双能被外部代码真正理解的手。从那时起,"让 Agent 调用任意 API"成了通用做法。
这带来的好处很直接。原来要人肉写串联逻辑的任务,现在只需要预先把各个能力封装成 tool,Agent 就能在会话上下文里面做判断、做路由。查天气、查数据库、发邮件、订会议室,十几个工具并排铺开,它自己会选。
1.2 当任务超过三个步骤,控制权开始流失
工具调用模型在"单步问答"式任务上表现得非常好,但遇到真正复杂的长链路任务,就逐步露出疲态。问题出在三个地方。
第一,状态维护靠上下文硬扛。Agent 每调一次工具,中间变量要么得塞回对话历史,要么得夹带在下一轮 Prompt 里。你想想,一个任务中间攒了十几个中间变量,每一步都把全部状态写进上下文,Token 开销爆炸不说,模型还容易在长距离依赖里"忘事"。转换一下视角:这就像让一个人不用草稿纸心算多位数乘除法,每一步的结果全靠脑子记,迟早出错。
第二,控制流表达能力太弱。工具调用天然适合"单发-响应"这种模式,但现实中大量任务需要循环、条件判断、异常处理。比如"遍历这 100 个文件,把符合条件的记录抽出来,再按规则汇总",你想要在工具调用框架下实现,只能把循环逻辑写到 Agent 的推理过程里,一轮走出一步,100 个文件就是 100 次往返,响应慢且不可控。
第三,工具本身的"颗粒度陷阱"。工具定义得太细,几十个工具摆在那里,模型选择成本极高;工具定义得太粗,等于放弃泛化能力,新需求一来就得重新写死一个新工具。我见过一个内部系统为了处理不同格式的报表,硬塞了十几个 parseXxx 类工具,最后模型经常调用错。工具不是越多越好,而是越贴近任务边界越好,但真实边界是动态变化的。
1.3 一个典型的"工具调度翻车现场"
拿我前阵子接触某数据平台项目里的一个场景说吧。需求很简单,把一份跨平台系统导出的原始日志文件清洗成标准化 CSV。日志格式很脏,有的行缺失字段,有的行字段顺序不一致,还有几行混入了编码异常字符。
用工具调用方案做,需要准备"读取文件""按正则提取""字段重排""编码修复""写 CSV"五个工具。每步都可能出岔子:正则写得不准,模型不知道要不要换一个规则;编码修复要尝试多种方式,模型只能靠反馈一轮轮猜。跑了二十多轮,中间好几次是同一个工具反复失败。模型也知道自己陷入了循环,但外部工具库没有给它"自己写一段临时脚本来处理异常"的选项。它被困在预设好的工具集里了。
这段经历让我强烈意识到一个矛盾:现实任务的复杂度远超工具集的设计者预判,而工具调用把"适配"责任全部压给了模型在固定接口之间做选择。一旦接口覆盖不到,模型就只能绕远路或者直接摆烂。
2. 理解 Code Mode:不只是让 Agent 写代码
2.1 心智模型翻转:从 API 调度器到运行时操作者
Code Mode 的核心心智模型和工具调用完全不同。
工具调用模型是"Agent 作为调度器":它不直接做事,而是描述"我想做某件事,请某个函数去执行"。Code Mode 模型是"Agent 作为操作者":它直接写一段代码,描述怎么做,运行时负责执行这段代码,再把执行结果交回给模型继续决策。
直接做和安排别人做的差别,普通人不够有直觉,我再解释一下。调度模型里,Agent 对"函数内部发生了什么"一无所知,它只能根据返回值猜。Code Mode 里,Agent 对代码的每一行都有控制权:它能定义变量、写循环、处理异常,代码就是它思想的直接延伸。
一个更直白的类比:工具调用像是你给下属布置任务,你说"去把那个文件处理了",下属用什么办法你不管,出错你也只能等反馈。Code Mode 像是你亲自上手写一套处理流程,每个步骤都是你设计出来的,出问题你可以立刻看到是哪一步错了,就地改正。
2.2 代码作为 Agent 的"思维语言"
为什么代码对 Agent 来说是一套那么合适的"思维语言"?
因为代码本身就是为了描述精确步骤而发明的。它天然支持循环、条件、递归、抽象、模块化。一个复杂任务,你用自然语言描述要写三页纸,用代码描述可能几行就讲清楚了。更重要的是,代码是可执行的,Agent 不需要靠猜测来验证自己的想法,直接运行就知道对不对。
这在智能体设计上是一个巨大的优势。传统的 ReAct 模式里,模型说"我想这样做",系统执行,模型看到结果,再重新推理。现在 Code Mode 里,模型可以一口气写好一整段策略性代码,执行器跑完,结果反馈回来,它就能基于真实结果做下一轮调整,而不是在脑子里做空想推演。
如果任务本身是确定的、步骤明确的,代码的精确性远胜自然语言的模糊描述。如果任务是探索式的,代码又能提供一个低成本的试错手段,改一行重跑一次就完事。
2.3 它和"让模型写脚本"有什么本质区别
有人会质疑:Code Mode 不就是让 Agent 生成一段 Python 代码然后执行吗?我们早就这么干了。
区别在于执行权和反馈机制。以前是自己写个外部脚本调用模型生成代码,再手动执行,再把结果拼进 prompt——中间每一步都靠人肉衔接。Code Mode 把它做成了内置的运行时循环:generation → execution → observation → revision,全自动闭合。更关键的是,这个循环不是一次性执行完就结束,而是让 Agent 在代码运行结果的基础上继续改代码、继续跑,直到任务完成为止。
这种运行时反馈机制带来的可能性远超手动脚本:Agent 可以写完代码先跑一遍,看到报错后自己修;可以生成用例跑测试;可以写个临时代码去检查数据质量。整个开发过程变成了 Agent 和运行环境之间的持续对话。
3. 支撑这次范式转移的三个关键设计
3.1 持久化文件系统:给 Agent 一条可追溯的"记忆"
传统 Agent 的记忆存在对话历史里,本质上是一堆文本。Code Mode 范式里,我强烈建议引入持久化文件系统,让 Agent 把中间产物、临时数据、分析结果都写进具体的文件里。
好处是颠覆性的。一方面,文件系统天然适合保存结构化中间状态,比塞进上下文可靠得多;另一方面,Agent 可以把之前步骤产出的数据文件当作输入,再启动新步骤,这就实现了步骤间的数据解耦,不再需要时刻把全部状态背在身上兜圈子。
实际操作中,我习惯规定 Agent 的工作目录有固定结构:/workspace/input、/workspace/output、/workspace/scripts。每完成一个阶段,必须把关键结果落盘,清理掉临时文件。这样既方便调试回溯,也能避免上下文无限膨胀。
3.2 执行-报错-修正循环:把调试权交给模型
Code Mode 能够真正成立,核心前提是运行时执行的"试错闭环"足够便宜。传统开发里,人类的调试循环很慢,敲代码、跑测试、看堆栈、翻文档。在 Code Mode 里,Agent 可以秒级完成一次"生成代码-执行-发现异常-修复"的迭代。
这里有个关键技术点:反馈的粒度。光给模型看"执行失败"是不够的,要把异常堆栈、标准输出、退出码、部分日志一起打包喂回去。错误信息越细,模型下一次修复越精准。我实测下来,一个带 Python 堆栈信息的反馈,比单纯 "Error occurred" 的修复成功率高出数倍。
但是,把调试权完全交给模型不等于放任不管。需要设计好中断条件:最大迭代次数、单次执行超时、失败率阈值。我之前见过一个 Agent 因为某次网络请求一直失败,在一个死循环里跑了四十分钟,token 烧了一大截。这就是缺少执行闸门的设计失误。
3.3 沙箱边界与权限清单:信任但验证
当 Agent 开始主动写代码并执行,安全边界的问题就变得极其尖锐。工具调用时你还能控制在预设函数范围内,Code Mode 里模型几乎拥有整个运行环境的控制权。代码一旦开始跑,就有能力读写文件、访问网络、安装依赖、操作数据库。
所以,Code Mode 落地时必须有资产隔离,这是底线。我的实践是三层:
- 网络层:默认禁止访问公网,可配置白名单域名。
- 文件系统层:只放行工作目录,对外部路径读写一律拦截。
- 资源层:限制 CPU、内存、磁盘配额,防止失控循环拖垮宿主。
之前有过一次事故:某次沙箱没封公网,Agent 在修复依赖问题时直接 pip 拉下来一个恶意的同名包,差点出事。从那以后我就长记性了,沙箱不是"尽量做",是"必须做"。
| 安全层 | 控制内容 | 失败代价 |
|---|---|---|
| 网络白名单 | 禁止任意外联 | 数据外泄、恶意下载 |
| 文件系统隔离 | 限定工作目录 | 删改宿主文件 |
| 资源配额 | CPU/内存/磁盘限制 | 死循环耗尽资源 |
| 命令白名单 | 控制 shell 命令范围 | 任意命令执行 |
4. 实测对比:两类任务上的差距有多大
4.1 任务 A:清洗一份"脏"CSV
我准备了两份相同的脏 CSV 数据,一份用纯工具调用方案处理,一份用 Code Mode 方案处理。数据是某个内部系统的埋点日志,大概两万行,包含时间戳、用户 ID、页面路径、设备信息,其中存在缺失值、格式不统一和个别乱码。
工具调用方案需要事先封装好工具:读文件、正则提取、替换修复、转码、去重、写文件。难点在于脏数据的形态不可预知:你没法预先把所有异常规则都写进工具定义里,模型面对没见过的异常,只能反复调用通用工具碰运气。整个流程预计十五轮起步,中途还很可能陷入某个修复工具的重复失败循环。
Code Mode 方案就干脆多了。我给 Agent 的指令是:"读取文件,自己分析格式问题,写 Python 脚本清洗,输出新 CSV。"它写了一个脚本,第一次跑完发现时间戳格式有两种,自己改了第二次;第二次跑完发现有几行编码异常,又加了异常捕获;第三次跑完检查输出,确认无问题了。总共四轮迭代,其中两轮是自我修正。最终结果我对比过,数据完整性比工具调用那次强很多。
4.2 任务 B:生成一个可用的前端卡片组件
这个任务更能体现 Code Mode 的优势:需求是给某跨平台系统生成一个带数据展示和交互的卡片组件。
工具调用模式下,Agent 只能提供静态代码文本,因为工具库里未必有浏览器环境。它"写"出来的代码是不是真的能跑、样式有没有错位、交互逻辑有没有 bug,它自己根本不知道。你把它生成的代码拿去跑,经常是各种问题——样式引错了、组件 API 用错了、依赖版本不对。
Code Mode 模式下,Agent 可以先在沙箱里搭一个最小前端环境,把代码跑起来,甚至用无头浏览器截图给自己看,发现问题再改。实测中它发现过两次样式错位、一次事件绑定漏写,都自己修掉了。这一轮的体验差异,已经不只是效率问题,而是能不能交付可用结果的问题。
4.3 两种模式在各维度上的权衡
| 对比维度 | 工具调用 | Code Mode |
|---|---|---|
| 单步可靠性 | 高,接口明确 | 中低,需要调试 |
| 复杂链路表现 | 差,状态易丢失 | 好,代码天然管状态 |
| 新场景应变 | 弱,工具覆盖不到就抓瞎 | 强,能写代码硬解 |
| 成本可控性 | 相对稳定 | 可能暴涨,需要护栏 |
| 调试可观测性 | 靠工具日志 | 靠代码执行日志 |
| 安全边界 | 天然受限 | 必须主动设计 |
核心感受一句话:工具调用赢在"简单任务的确定性",Code Mode 赢在"复杂任务的开放性"。如果你做的是高频率、接口明确、路径固定的任务,工具调用的性价比依然很高。一旦任务开放、多变、需要探索和试错,Code Mode 的体验完全是降维打击。
5. Code Mode 的隐藏成本和踩坑清单
5.1 成本失控:死循环和超长上下文
Code Mode 最大的隐患不是能力不行,而是成本不可控。模型进入"写代码-跑-失败-再写"的循环之后,每次迭代都在消耗 Token 和时间。如果任务本身定义不清、或者环境有顽固问题,Agent 可能陷入长时间空转。
必须提前设置硬性的执行预算。我在实际落地时用三个数:最大执行轮次、单轮超时秒数、任务总超时分钟数。一旦触发,Agent 必须停下并总结当前进展,而不是闷头继续。这个"强制收敛"机制救了我很多次。
还有一个隐蔽的成本点:代码执行结果通常会以文本形式回灌到上下文里。数据一多,上下文迅速膨胀,后面的请求越来越贵。所以我要求 Agent 执行完必须"沉淀结论、清理日志",不要把所有 raw 输出都塞回对话。
5.2 环境工程比想象中更费劲
让模型写代码不难,让代码能跑起来才是难点。Python 版本不一致、某个 C 依赖编译失败、网络源不可用、Node 包版本冲突,这些环境问题在开发机上可能半小时就解决,但对只有文本反馈的 Agent 来说,每一次都是盲人摸象。
最怕的是那种"重装一次就能好"的玄学环境问题,Agent 会反复重装、反复失败、反复怀疑人生。建议提前把常用开发环境制作成镜像,沙箱启动时直接复用,把模型从环境泥潭里解放出来。能预装的东西绝不指望模型现场装。
5.3 调试的暗坑:缺一条可重放的执行轨迹
Code Mode 的循环节奏很快,往往执行十几次以后,真正的问题是"模型哪一步做出错误判断导致后面接连失败"。这时候如果只盯着最终反馈,很难回溯。
我建议所有执行过程都留全量日志:执行命令是什么、退出码是什么、标准输出是什么、耗时多少、Token 消耗多少。每一条日志都要有时间戳和序号。这样事后排查时,可以把整个循环完整重放一遍,定位问题的起点。
5.4 安全沙箱的隐私合规问题
前面提到过沙箱,这里再展开一层。如果 Agent 处理的是业务敏感数据,那么沙箱环境本身必须满足合规要求:数据不能外传、执行日志不能泄露、模型服务商不能存留敏感数据。这不是纯技术问题,而是整体方案设计的问题。我建议在 Code Mode 落地的同时,同步出一份数据流向说明文档,明确哪些环节会接触原始数据、哪些环节只接触脱敏数据。
6. 我的选择框架:什么项目更适合 Code Mode
6.1 用"任务特征雷达"做判断
不是所有任务都该上 Code Mode。我自己的判断标准是四个维度:
- 任务开放性:任务是固定套路还是每次都不一样?固定套路选工具调用,开放任务选 Code Mode。
- 环境交互深度:任务是否需要频繁读写文件、调用系统能力、跑测试?需要就选 Code Mode。
- 反馈类型:任务反馈是文本信息还是需要程序运行才能验证?后者必须 Code Mode。
- 失败恢复成本:单次失败后有没有简单的兜底方案?有的话工具调用够用,没有的话 Code Mode 能提供更强的自愈能力。
举个反例:比如"根据用户提问查询天气并返回",这种任务工具调用完美胜任,你让 Agent 写代码反而是杀鸡用牛刀。再比如"分析一份新格式的报表并生成摘要",这种就是 Code Mode 的舒适区,因为它需要数据处理、需要探索性迭代。
6.2 平滑过渡:先搞清三个阶段再动手
如果你现在还没有切换到 Code Mode,我建议按三个阶段渐进走。
第一阶段,工具调用为主,但给 Agent 增加一个"写临时脚本"的工具。这个工具接收一段 Python 代码,在沙箱里执行并返回输出。不需要大幅改造现有架构,就先补齐工具调用模式的短板。
第二阶段,把"写临时脚本"升级为核心能力。固定工作目录、持久化文件系统、提供代码执行结果的结构化反馈。你会发现越来越多的任务可以在这一阶段完成。
第三阶段,完整 Code Mode。Agent 自己管理多文件项目、自己测代码、自己改 bug,系统只负责提供安全的运行环境和足够的算力。
6.3 范式转移不会停在代码领域
最后说一个我的长期观察:Code Mode 的本质是"让模型直接操作运行环境",而代码只是目前最通用的运行环境操作语言。随着 Agent 能力的延伸,这种范式会继续扩散到更多领域——不只是写程序,还包括操作浏览器、操作设计工具、操作数据分析工具。
在我看来,这场转移真正的方向是:Agent 从一个只会"发号施令"的调度器,变成一个能亲自动手、自己检查结果、自己修正错误的自主执行者。工具调用没有消失,它会退居到那些确定性高的子任务里,成为 Code Mode 的补充能力,而不是主导架构。
我自己在搭建新的智能体底座时,已经把"提供可执行环境 + 代码生成 + 反馈闭环"当成默认配置了。基本思路是:先让模型在沙箱里把任务跑起来,遇到跑不通的再考虑要不要卷工具。这个思路帮我处理了不少以前根本没辙的长链路任务,也踩了不少代码执行范式特有的坑。建议你也拿一个积压了很久的脏数据任务试试看,先跑五轮迭代,再回头看工具调用方案,你大概就知道这场范式转移真正的分量在哪里了。