1. 从“会调用工具”到“会干活”:Agent 的能力分水岭
这两年 AI 领域最热闹的词,从“大模型”变成了“AI Agent”,GitHub 上关于 agent 的仓库、企业里关于 agent 的项目、社区里关于 agent 的讨论,数量都在井喷式增长。但你有没有发现一个有趣的现象:同样是“用 AI 干活”,有些演示让人惊呼“这玩意真的能替我上班了”,有些则让人觉得“这不就是个带记忆的自动补全吗”。
后者和前者的差距,恰恰就藏在“工具调用”和“Code Mode”这两个看似相近、实则天差地别的概念之间。
所谓“工具调用”,指的是 Agent 调用一个预先定义好的 API、函数或插件来完成任务。比如让模型查个天气、算个数学题、调一下日历接口,本质上就是“模型决定该用哪个函数,然后把参数填好交给系统去执行”。这种方式在过去一年里是主流架构,OpenAI 的 function calling、各家大厂的 plugin 生态,基本都是这个路子。
但“Code Mode”完全不同。它不依赖预定义的“工具”,而是让 Agent 直接进入一个“可以自由写代码、执行代码、观察结果、修改代码”的环境。在这个模式下,Agent 面对的是一片空地,它自己砌砖、自己抹水泥、自己验收,甚至自己推翻重来。没有“工具清单”,只有“目标”和“沙盒”。
这两种模式背后,其实是一次完整的能力跃迁:从“会调用工具的人”到“会自己造工具的人”。这篇文章想拆开的,就是这次跃迁的底层逻辑、技术细节和落地场景。
我写这篇文章的视角比较特别:我不是站在某个厂商的立场,也不是在给某个框架背书,而是以一个过去两年里反复在“工具调用”和“Code Mode”两种模式之间来回切换的开发者身份,把我实际踩过的坑、对比过的架构、总结出来的选型思路,完整地摊开来讲。
2. 工具调用:第一阶段的能力上限与隐形天花板
2.1 工具调用的本质:模型只是一个“调度器”
先回到最基础的问题:工具调用(function calling / tool use)到底是怎么回事。
我在接触 agent 开发的早期,其实对“工具调用”这个概念是有些误解的。我当时以为,所谓工具调用,就是模型在生成文本的过程中,突然“觉醒”了,自己决定要去访问某个服务、执行某段代码。但真正动手之后才意识到,事情比这朴素得多。
工具调用本质上是一个结构化输出问题。模型本身不会“直接”调用任何东西,它只是在生成文本的时候,按照训练时学会的格式,输出一个特殊的 JSON,这个 JSON 里包含了你预先定义好的函数名和参数。你的程序在拿到这个 JSON 之后,解析它、执行对应的函数、把结果以文本形式塞回到模型的下一次输入里。
也就是说,在这个架构下,模型扮演的角色是调度器,它本身不直接接触“工具”,只负责“决定”。真正去执行工具、真正去读数据、真正去发 HTTP 请求的,是外围的程序代码。
这就带来一个很关键的特性:工具调用的可靠性,取决于你给模型定义了多好的工具、多大的搜索空间、多明确的描述。
我在一个内部项目里做过一个对比实验。同一个模型,用“描述极度详细、参数校验到位”的工具定义去跑,任务成功率能到 90% 以上;换成“功能相同但描述含糊、参数示例缺失”的版本,成功率直接掉到 60% 出头。模型还是在“调度”,但它调度得准不准,和你的工具描述质量强相关。
2.2 为什么工具调用在快速原型验证阶段是好东西
尽管工具调用有上面提到的种种限制,它在快速原型验证阶段的优势依然是不可替代的。
你自己动手搭过一个工具调用的 agent 就会明白,这种模式的开发成本低得惊人。模型厂商基本都把 function calling 的基础设施做好了:你可以直接定义一个 JSON Schema,把它塞进 API 的 tools 参数里,模型就会自动在合适的时机返回“调用请求”。
整个流程大概是这样:
- 定义工具:写一个函数,比如
get_weather(city: str),同时为它写一个 JSON Schema 描述。 - 注入上下文:在 messages 里带上这个工具的定义,同时在 system prompt 里解释“你有这个工具可用,当用户问天气的时候,你要调用它”。
- 解析返回:拿到模型返回的
tool_calls字段,执行对应函数,把结果作为新的 message 返回给模型。 - 循环迭代:模型拿到工具结果之后,再去生成最终回复。
这套流程,我在半个小时以内就能搭出一个能完成“查询信息、给出答案”的 demo。对于验证某个场景是否适合 agent 化,工具调用给了我们一个极高的试错速度。
还有一点也挺重要:工具调用的过程是可观测、可审计的。因为每次调用都会明确记录在 message 历史里,哪个函数被调用了、参数是什么、结果是什么,一目了然。这在企业的早期试点阶段非常有用,能帮助团队建立信任。
2.3 工具调用的天花板:预定义的“穷尽”与现实的“无限”
但如果你把工具调用的架构放到真实业务场景里去压一压,天花板很快就现出来了。
第一个天花板:你不可能把世界上所有可能的需求都预定义成工具。
一个稍微复杂的业务场景,可能涉及几十个甚至上百个可能的动作。你不可能为每一个动作都写一个函数、都做参数校验、都写描述。更现实的是,很多动作在开发的时候根本想象不到。
我遇到过最典型的例子:一个数据分析 agent,前期设计时我们只想到了“查表”“筛选”“聚合”这三个工具。结果用户在实际使用中问出了“你能不能把这张表和那张表按照这个字段关联一下,然后按周做一个趋势图”这种需求。本质上这需要组合现有工具、并且生成一段逻辑上全新的处理流程。工具链中根本没有这个“工具”,模型也无从“调度”。
第二个天花板:工具调用在长链路任务中的误差累计。
工具调用通常是“一步一停”的:模型判断,执行;再判断,再执行。每一步之间,模型其实是在靠着上一次的输出去推断下一步,一旦中间某一步返回到不期望的结果,后续的整个链路都会跟着跑偏。让模型在一个 8 步的工具调用任务里每一步都完全正确,概率衰减得非常快。
第三个天花板:工具调用很难处理“全新操作”。
模型只能调度你已经写好的函数,它没有任何能力在运行时生成一个新的函数、写一段新的脚本、装一个新的包、执行一条之前没见过的命令。这个限制意味着,工具调用模式的 agent,本质上是一个“国内手机上的应用商店”——预装了什么 App,才能用什么 App,商店里没有的,就只能干瞪眼。
这三个天花板叠加起来,让我对工具调用模式产生了一个很明确的判断:它适合“封闭的、预定义的、任务边界清晰的”场景,比如客服助手、内部知识库问答、特定单据查询。但一旦任务变得开放、非结构化,或者需要自主探索,工具调用的肌肉就会明显不够用。
3. Code Mode:让 Agent 从“预定义”走向“自主构建”
3.1 Code Mode 是一种完全不同的交互边界
聊到 Code Mode,就必须先统一一个认知:它到底和工具调用有哪些关键差异。
我第一次接触 Code Mode 是在一个开源的 agent 项目里。当时我观察到,这个项目里没有一个传统意义上的“工具”,取而代之的是一个“工作区环境”——agent 可以在里面执行 shell 命令、打开文件、编辑代码、运行测试。你给它一个目标,比如“修复这个仓库里的某个 bug”,它不会去查一个“修复 bug”的工具,而是自己去里面翻代码、写补丁、跑测试。
那一刻我才意识到,这才是 Agent 该有的样子。
Code Mode 的核心特征,我用三句话概括:
- 从“函数调用”到“代码生成”:不再限于调用预定义函数,而是生成任意代码来实现目标。
- 从“黑盒执行”到“可观测循环”:每次生成的代码执行后,模型都能看到执行结果(输出、报错、状态码),并据此决定下一步动作。
- 从“有限动作集”到“无限动作集”:只要沙盒环境允许,agent 可以做任何事——装依赖、改配置、调用 API、启动服务、甚至自我修改。
用大白话讲,工具调用模式下的 agent 是一个“挑菜单的点菜员”,菜单里有什么才能点什么;而 Code Mode 模式下,agent 是一个“拿着食材和厨房随便发挥的厨师”,它不需要一份完整的菜单,它只需要一个目标和一堆食材。
这并不表示 Code Mode 完全不需要工具。它也需要工具,只不过它用的“工具”是底层环境(终端、文件系统、进程管理器),而工具本身的构建过程,也被交给了模型自己。这是一种工具层级上的“升维”:从使用工具,进化为构建工具。
3.2 Code Mode 的技术拆解:从选型到架构
如果你想要自己搭一个 Code Mode 模式的 agent,核心不是选哪个模型,而是你要为模型提供一个什么样的“行动环境”。我自己的实践下来,下面这几个组件是必不可少的。
第一,一个安全可控的沙盒环境。
模型要跑代码,但这不代表你应该让它直接跑在你的生产服务器上。我的选择是 Docker 容器,把每个 agent 的“工作区”隔离在一个独立的容器里,挂载一个临时目录,限制网络访问,超时杀掉。这样模型不管是装了一个什么奇怪的包、跑了一段失控的循环,都不会波及宿主机。
第二,一个能让模型“观察”的接口。
Code Mode 的循环是“生成代码 -> 执行 -> 观察结果 -> 再生成”。所以模型必须要能拿到执行结果的反馈。这个接口可以是终端输出的文本,可以是文件内容的 diff,也可以是运行日志。最理想的形态是结构化:既有 stdout/stderr,也有退出码,还有文件系统变化。让模型看得越清楚,它的下一次决策就越准。
第三,一个足够清晰的“目标上下文”。
这一点容易被忽视,但实际很重要。Code Mode 不是没有 prompt,只是 prompt 的重点从“枚举工具”变成了“描述目标”。我在实际使用中,会把目标拆成三层:最终验收标准(比如“所有测试通过”)、过程性提示(比如“优先检查 xxx 模块”)、环境约束(比如“不要修改 lock 文件”)。清晰的上下文,能让模型少走很多弯路。
第四,一个“拉回正轨”的机制。
自主行动意味着出错的概率也更大。所以我会在循环里设定一些护栏:最大执行轮数、单次命令超时时间、禁止的命令黑名单、文件变更白名单。一旦触发护栏,agent 就需要停下来向用户解释它的计划,而不是闷头乱莽。
下面这张表可以帮你快速理解工具调用和 Code Mode 在架构选型上的对应关系:
| 维度 | 工具调用模式 | Code Mode 模式 |
|---|---|---|
| 动作来源 | 预定义函数 | 模型运行时生成的代码 |
| 行动边界 | 工具清单 | 沙盒环境(几乎无边界) |
| 反馈方式 | 函数返回值 | 命令输出、文件变化、日志 |
| 失败恢复 | 固定重试逻辑 | 自主调试、修改后重跑 |
| 上下文核心 | 工具定义 + 参数描述 | 目标描述 + 环境约束 |
| 安全性依赖 | 工具权限控制 | 沙盒隔离 + 命令白名单 |
| 开发成本 | 低,快速起步 | 高,需要环境基建 |
| 适合场景 | 封闭、明确、低频变更 | 开放、探索、复杂链路 |
3.3 Code Mode 是从工具调用平滑演进,不是推倒重来
这里要特别澄清一个误区:Code Mode 并不是要彻底抛弃工具调用。
我在自己的项目里,这两种模式其实是共存的。我的 agent 在面对“明确的小任务”时,依然会调用预定义的函数(比如发送通知、查询数据库);但当它遇到“需要探索、需要写脚本、需要改代码”的任务时,就会切换到 Code Mode,进入沙盒环境自己动手。
你可以把 Code Mode 理解成工具调用的一个“逃生舱”——当预定义的工具覆盖不了需求时,agent 有了一条自己创造工具的退路。这种“先工具、后代码、再回工具”的混合架构,在实际使用中非常顺滑。
比如我之前做的一个数据清洗 agent:平时它就是用工具调用来处理结构化表格;但如果用户抛来一个格式特别诡异、完全不在预定义工具支持范围内的文件,它就会自己写一个 Python 脚本来解析,然后把解析结果整理成一个新的结构化表格,再用工具存进数据库。整个流程用户无感,但能力边界被拉大了一整圈。
4. 从工具到代码的范式转移底层逻辑:为什么“生成程序”比“调用函数”更接近智能本质
4.1 关键差异不是“代码”,是“对反馈的闭环处理”
很多人听到 Code Mode 的第一反应是:这不过就是让模型写代码而已,写代码早就有 Copilot 了,这算什么范式转移。
这个看法只看到了表面。
Code Mode 的本质差异,不在“生成代码”这个动作本身,而在于对执行结果的闭环反馈处理。Copilot 是生成一段代码交给你,你自己去跑、自己去看结果、自己去改;而 Code Mode 下的 agent,是自己写代码、自己执行、自己读结果、自己发现错误、自己修改、再执行。它形成了一个完整的“行动-观察-修正”闭环。
这个闭环,和人类工程师的工作方式本质上是同构的。
我自己平时写代码的流程大概是:先根据需求写一版,跑一下测试,看报错信息,定位问题原因,改一版再跑。Code Mode 里的 agent 也是这套流程,只是它的“行动频率”可能比人高得多,而且不会疲劳。
这个闭环能力,才是“智能感”的来源。它不再是一个“一次性的生成器”,而是一个“持续的、自适应的、目标驱动的执行体”。
4.2 Code Mode 对 Agent 能力边界的三次扩展
从工具调用到 Code Mode,Agent 的能力边界被扩展了三次,每一次都对应一类新的任务场景。
第一次扩展:从“执行指定操作”到“完成未知目标”。
工具调用模式下,你交给 agent 的指令必须足够具体,因为它的“能力边界”就是工具集。Code Mode 下,你可以交付一个模糊的目标,比如“帮我整理一下这个项目里所有 TODO 注释对应的代码块”,agent 自己会去探索项目结构、定位文件、写脚本提取、输出报告。它不需要一个预先定义的“提取TODO工具”。
第二次扩展:从“单一动作”到“长链路编排”。
工具调用模式下的每一步,本质上都是一个离散动作;而 Code Mode 可以生成一个几十行、上百行的脚本来一次性完成多个步骤,中间还包含条件判断、循环、异常处理。这种能力让 agent 不再局限于“一问一答”,而是能执行“一整个流程”。
第三次扩展:从“环境的被动适应者”到“环境的主动改造者”。
最典型的一个表现是:agent 可以通过修改配置文件、安装依赖、调整目录结构来改变它自己的执行环境。它不再被“环境是否支持”所限制,而是可以主动把环境改造成自己需要的样子。这个能力听起来很简单,但在“自主智能体”这条技术路径上,它是一个极其重要的分水岭。
4.3 范式转移的代价:Code Mode 需要付出的三重成本
范式转移从来不是免费的午餐。Code Mode 在能力大幅提升的同时,也带来了三重不可回避的成本。
第一重成本:安全风险的指数级上升。
让模型可以自由执行代码,意味着你正在把一个“可能出错的程序生成器”放进你的系统里。它可能会删文件、会装恶意包、会误改配置。这就是为什么我之前强调沙盒隔离是 Code Mode 的底线——不是可选项,而是必需品。
第二重成本:算力与时间损耗。
Code Mode 的执行循环通常比工具调用慢一个数量级。每一轮“写代码、执行、看结果”都会消耗大量的 token 和等待时间。一个长链路任务可能要在沙盒里跑几分钟甚至几十分钟。这要求用户对“响应速度”的预期要和传统工具调用完全区别开来。
第三重成本:结果的可控性与可审计性降低。
工具调用的结果是结构化的、离散的,你只需要检查那个函数返回值对不对就行;Code Mode 的产物是一堆代码跑出来的复杂结果,你需要看日志、看文件变化、看最终的输出。中间一旦出错,定位问题的难度会上升好几个级别。
这三重成本,也是为什么我不认为 Code Mode 会彻底取代工具调用。它是一个“更高阶的能力档位”,而不是一个“更优的默认选择”。
5. 实操落地:从工具调用到 Code Mode 的迁移路径
聊完了理论,回到动手环节。如果你现在手上已经有一个基于工具调用的 agent,想逐步往 Code Mode 方向演进,我的建议是别急着推翻重写,按照下面这条路径走。
5.1 第一步:先给 agent 一个“最小执行沙盒”
在你的 agent 服务里,加一个可选的 action 类型:run_command。这个 action 负责在 Docker 容器里执行任意 shell 命令,并把 stdout、stderr、退出码返回给模型。只加这一个动作,你的 agent 就已经从“纯工具调用”变成了“具备 Code Mode 雏形”的混合体。
这个阶段的重点是先打通“写命令-执行-看结果”的闭环。你不用急着让 agent 写大段代码,先让它跑跑 python -c、看看文件列表、改改小配置文件。等模型适应了“我可以用命令来探索环境”之后,自然就会开始写多行脚本了。
5.2 第二步:设计“目标导向”的任务协议
工具调用时代,我们的 user prompt 往往是“请调用 get_weather 获取北京天气”。但 Code Mode 时代,user prompt 更应该是“请确认北京当前天气是否适合户外跑步,并把结论写在 report.md 里”。
要完成后面这个任务,agent 必须自己判断:需要找到天气数据源(可能是一个 API)、需要写脚本去请求、需要解析结构、需要判断是否适合跑步、需要把结论写入文件。这里面没有任何一个现成的“工具”,全部要靠它自己生成代码去解决。
我在实践中的做法是:把任务协议拆成“目标描述 + 产出物 + 验收标准”。比如上面那个任务,验收标准可以是“report.md 存在且内容包含天气数据和判断结论”。这样模型在沙盒里探索时,知道自己的旅行终点在哪里。
5.3 第三步:逐步沉淀“可复用代码段”
Code Mode 跑得多了,你会发现一个规律:模型经常在写同类代码。比如“读取一个 JSON 文件”“递归扫描目录”“调用某个 API 并解析结果”。这些代码如果每次都让模型从零生成,一方面浪费 token,一方面稳定性也差。
我的做法是,维护一个提示词级的代码模板库:把常用的代码片段作为“参考实现”注入到 system prompt 里,告诉模型“如果遇到类似需求,可以参考这些实现,也可以自行修改”。这相当于在 Code Mode 的世界里重新引入了“预定义”的元素,但它不再是一个动作级的工具,而是一个思路级的范式参考。它让模型在保持自主性的同时,减少了一些低级错误。
5.4 第四步:build 一个“观察者”组件(最关键的一步)
Code Mode 落地最大的门槛不在于让模型写代码,而在于让代码的“执行结果”能被模型有效地理解。所以我强烈建议,你花力气去建设一个观察者组件。
这个组件负责:
- 汇总命令执行的 stdout/stderr。
- 监控工作区文件变化(哪些文件新增了、哪些文件被修改了)。
- 提取关键日志(比如测试框架输出的“x passed, y failed”)。
- 总结当前任务目标的完成度。
观察者组件的产出,会作为一个“结构化观测报告”重新注入给模型。这样,模型每次做决策时,看到的不只是光秃秃的终端输出,而是一个逻辑清晰的“当前进展汇报”。
举个例子,你让 agent 跑完测试后,观察者组件不会只给它看几百行 pytest 日志,而是给它一段摘要:“18 个测试通过,3 个失败,失败的测试分布在 auth 模块。”这直接决定了模型下一步是宣布任务完成还是动手修 bug。
我在自己项目的实测中,加上这个组件之后,长链路任务的完成率提升了将近 30%。它不是多数人以为的“模型差”,而是“模型的观察渠道太差”。
5.5 Code Mode 实际体验记录:一个修复 bug 的真实过程
为了让你更直观地理解 Code Mode 的闭环,我记录一次真实的 agent 修 bug 过程。
用户提交的任务是:“修复 login 接口在密码错误时返回 500 的问题。”
我的 agent 在 Code Mode 下的行为是这样展开的:
第一轮,它先执行了ls和cat main.py,确认项目结构和入口文件。 第二轮,它发现了 login 接口定义,继续读取 login 函数内部逻辑,找到抛 500 的地方。 第三轮,它发现异常是因为对数据库结果直接取下标,而数据库查询在密码错误时返回空列表。 第四轮,它生成了补丁代码,将取下标逻辑改为“先判断长度,再做取值”。 第五轮,它运行 pytest 验证两个 login 相关测试,通过了。 第六轮,它把修改后的代码文件作为最终的产物清单输出。
整个过程耗时约 4 分钟,全程没有人为干预。如果把同样的任务交给工具调用模式,我需要给它预先定义一个“修改函数逻辑”的元工具——这个概念在传统工具调用框架里本身就是一个悖论。
这个例子也说明了一个关键点:Code Mode 适合的任务,本质上都是“开放式的、需要探索和修改的任务”。如果任务是封闭式的——“查一下余额并转回数据”,那还是工具调用更高效。
6. AI Agent 主流架构中的 Code Mode 定位:生态、框架与未来场景
6.1 主流 Agent 框架开始把 Code Mode 作为一级公民
如果你关注近一年的开源 agent 框架,你会发现一个明显的趋势:几乎所有的主流框架,都在从“工具调用为中心”转向“环境执行为中心”。
一些头部开源项目,已经把“让 agent 进入一个可执行环境”作为核心抽象,而不是把“工具列表”作为核心抽象。在这种框架里,你可以给 agent 开一个 Docker 容器、一个本地沙盒、甚至一个远程服务器的 SSH 会话。agent 的行动模式不再是你预定义的函数,而是它自己在环境里生成命令、写文件、跑程序。
这个趋势在我看来是一个极为健康的信号。因为它表明行业的共识正在从“让模型在受限的对话界面里解决问题”转变为“让模型在真实的环境里解决问题”。后者才是 agent 能承担真正工作的前提。
另外一些厂商也开始在端侧探索类似思路,把轻量的 Code Mode 变体嵌入到本地推理环境中。考虑到未来 edge 场景的离线自主性需求,这个方向值得持续跟踪。
6.2 从“API 编排”到“环境构建”:Agent 开发者的能力模型变化
范式转移不仅影响 agent 本身,也影响开发者。
工具调用时代的开发者,核心技能是 API 设计、函数描述、参数定义。你花很多时间在思考“如何把一个业务动作抽象成一个模型能够读懂的函数签名”。
Code Mode 时代的开发者,核心技能则变成了环境工程:你怎么构造一个安全的执行沙盒?你怎么设计观察者组件让模型的反馈信息更有效?你怎么设定护栏让自主行动不失控?你怎么把“目标描述”写得既清晰又不限制模型的探索空间?
这本质上是一套工程能力栈的切换。对我来说,这是 agent 开发中最有意思的部分:从“教模型用 API”到“为模型建造一个可以用代码改造世界的工作间”。
6.3 Code Mode 影响范围内的高价值场景:数据分析、测试与运维
根据我在实践中的观察,Code Mode 模式下最能立刻产出价值的三类场景,分别是数据分析、自动化测试和运维排障。
数据分析场景里,以往要预定义“画图工具”“透视工具”“清洗工具”,现在只需要把数据文件扔进沙盒,给 agent 一句“分析这份销售数据,找出异常月份并给出归因”,它自己会安装 pandas、写分析脚本、生成图表、写结论报告。
自动化测试场景里,以往要维护一堆测试工具链,现在可以让 agent 直接读测试用例、跑测试、修复失败用例。最理想的状态是它还能帮你生成覆盖边界条件的新测试。
运维排障场景里,以往要预先集成监控工具和日志查询接口,现在则可以让 agent 自己登录到沙盒环境、查看日志文件、分析错误栈、执行诊断命令。只要权限边界划得清楚,它能处理很多“凭经验判断”的排查工作。
这些场景的共同特征是:任务的正确路径不是预先知道的,需要探索、试错、迭代。而这恰恰是 Code Mode 的主场。
6.4 对“AI Agent 搭建”的建议:别盲目追风,先想清楚任务边界
最后,给所有正在搭建 AI agent 的团队一个建议——不是每个任务都需要 Code Mode。
如果你的业务场景是“固定流程、高并发、低延迟、强可控”,那么继续使用工具调用模式,是最理性、最可靠的选择。因为模式更贴近工程化约束,也更容易做质量保障。
但如果你的业务场景是“复杂多变、长链路、需要探索式分析”,那么认真考虑引入 Code Mode 的能力:从一个最小沙盒开始,逐步建设观察者组件、积累代码模板库、设定护栏参数。它带来的能力扩展,会在你意想不到的地方体现出来。
关于这个话题,我个人的另一个体会是:别只看 demo 的炫酷程度。Code Mode 每一步看起来都很爽,但要让它稳定、可靠、可审计地生产使用,工程投入并不比传统工具调用少。真正成熟的 agent 架构,往往是两种模式都在合适的位置上发光。
回到标题那句话:从工具调用到 Code Mode,确实是一场范式转移。不是因为“代码”这个词,而是因为 agent 第一次具备了“在开放环境中自主构建解决方案”的能力。它离“智能体”这个称呼,又近了一步。