说个最近亲测的现象。过去三个月我把团队里的几个Agent应用从“工具调用”重构成了“Code Mode”整体效果提升非常明显。这个问题不是哪家产品的特性而是一种正在发生的范式转移——从Agent乖乖地调用JSON格式的工具tool_call到Agent直接生成并执行代码完成原本需要一堆工具才能拼接起来的任务。如果你还没太理解这两者到底差在哪或者正在纠结“明明工具调用也能跑为什么要折腾”这篇文章应该能帮你把思路理顺。我拿一个最常见的场景举例。以前想让Agent抓某个网站的数据我们给它配一个search工具、一个fetch网页工具、一个parse工具Agent的工作就是“调完A调B再把结果塞给C”。看起来没毛病但真实场景里网页结构千奇百怪重定向、动态渲染、反爬验证码随便一个变化就能让工具链整个崩掉。而Code Mode下Agent直接写出完整的爬虫脚本requests拿页面、BeautifulSoup解析、循环翻页、出错重试、中间结果落盘一个问题就是一个程序中途哪怕报错它都能读日志自己修。这已经不是“工具变多了”而是做事的方式完全换了一层。我自己在这个切换过程中踩了不少坑也总结出一套可落地的搭建方法。下面先把旧范式的问题说透再讲Code Mode为什么能解决最后给出实现步骤和经验教训大家可以当做自己的改造清单来用。1. 旧范式Agent 调用工具本质是“打电话叫外卖”1.1 工具调用的原始形态我们聊的“工具调用”就是当前主流Agent框架里最常见的那种模式。模型通过结构化输出生成一个JSON框架去执行对应的函数再把返回值拼进上下文让模型继续决策。比如用户问天气模型输出一个get_weather(city北京)框架执行后把“晴25度”返回给模型模型再组织成一句人话。这套模式能火是因为它对底层能力要求低也不需要对模型做过多训练只要有函数声明和示例模型很快就能学会“何时调什么”。过去两年里大量搜索增强、RAG问答、数据分析Demo都是这么搭起来的我也写过不少基于MCP适配器快速接入各种第三方接口的Agent部署起来确实很快。但用久了你会发现一个尴尬的事实这种“工具调用”本质上只是在做选择题。模型永远不会自己去实现一个网页解析逻辑它只能在你预先定义好的“parse_webpage(url, template)”里选一个参数组合。等于你给Agent列了一个菜单它只是负责点单至于后厨怎么做菜、能不能做它完全无能为力。1.2 工具调用的三个瓶颈第一个瓶颈是参数冻结。工具声明里的参数是写死的一旦真实需求超出参数范围模型就开始瞎填。我试过一个文档处理Agent给它配了一个extract_title(pdf_path)工具结果遇到扫描版PDF标题其实是OCR出来的图片文字模型只能不断用不同参数重试把错误的东西填进去最后还给你一串乱码。第二个瓶颈是状态断裂。调用工具之后模型需要把结果重新读进上下文再决定下一步但上下文长度是有限的。如果你让Agent处理一个500MB的日志文件工具返回摘要之后中间过程全丢了。模型对数据真正做了什么、每一步处理成什么样它无从知晓。第三个瓶颈是长尾场景的覆盖成本。任何一个小变化都要新增或者修改工具。我见过一个项目为了做业务报表给它配了四十多个工具日报、周报、月报、毛利、净利、环比、同比……维护成本高到离谱新增一种维度又得写一个工具声明和示例有这功夫不如直接用SQL或Python处理。2. 新范式Code Mode 把“后厨”交给了模型2.1 代码作为思维介质Code Mode的核心变化一句话就能说清模型不再预想“该调哪个工具”而是自己写一段程序把问题解决掉。它可以直接生成一段Python代码操作数据、做HTTP请求、解析结果、生成表格然后把最终产物甚至整个可执行脚本返回给你。这就像把“菜单点单式”的Agent变成了“带着系统思考去做事”的智能体。在程序代码里模型可以写for循环处理一百个文件而不需要你预设一百次调用可以写if retry_count 3: break来实现自动容错可以把它自己的中间输出文件read进来观察处理到哪一步了于是它在整个执行过程中拥有了“可见性”。关键在于代码本身就是结构化的思维表达。一个优秀工程师看到需求时脑子里会浮现模块拆分、数据结构、异常处理这些思考如果只能通过“调用按钮”表达就永远无法释放出来。而在Code Mode下模型的推理能力直接转化为程序逻辑解决复杂问题的上限高了好几档。2.2 为什么代码比工具调用更接近“智能体”我自己的理解是工具调用是给Agent装了“假肢”而代码生成是给Agent装了“神经系统”。举个例子。让Agent处理一批Excel成绩表要求按班级汇总平均分并生成图表。工具调用范式你需要定义一个read_excel、一个group_calc、一个plot还得告诉它班级字段名是什么、成绩在哪些列。如果文件的列名不统一工具立刻短路。Code Mode呢模型直接写import pandas as pd import glob files glob.glob(grades/*.xlsx) stat [] for f in files: df pd.read_excel(f) grouped df.groupby(班级)[分数].mean() stat.append(grouped) result pd.concat(stat, axis1).fillna(0) result.to_csv(summary.csv)然后它自己执行发现某个表里班级列不叫“班级”而叫“班别”它会读报错信息再写一段代码做列名归一化继续跑。这里面的核心优势就是试错闭环它每一步都能看到程序的真实执行结果而不是干等一个工具返回固定格式的JSON。这种灵活性工具调用完全没法比。2.3 关键特征闭环控制如果要给这场范式转移找一个关键词我认为是“闭环”。工具调用是开环的模型发出指令工具执行返回结果——但模型不关心“工具内部怎么执行的”。Code Mode是闭环的模型写了代码执行完能看到错误输出、日志、中间文件、最终文件于是它可以根据执行结果动态修整程序。闭环带来三种直接能力。一是自我反思看到报错就知道哪里逻辑不对二是数据试探它可以先跑一小段数据看结构再写完整处理流程三是结果验证生成完报表后再写一段断言去检查数值是不是合理。这些原本需要人的介入才能完成的事现在Agent自己就能在循环里消化。基于这一点Code Mode做出来的应用特别适合一条判断任务结果是否可复用。工具调用式Agent完成一次是一次代码式Agent生成的脚本可以反复执行相当于每一次任务都在积累资产。3. 为什么必须转向 Code Mode价值与代价3.1 复杂任务的可组合性真实生产任务几乎都是多个步骤叠加而成的。你给我一个API我往往还要清洗数据、做特征工程、跑模型、画图、写报告——每一步之间还有依赖关系。工具调用模式里这些依赖关系只能靠模型在对话上下文中记忆链条稍微一长就会忘。而代码天然支持函数化、模块化可以把一个复杂任务拆成若干函数甚至可以导入上一个脚本的输出结果。我实际做过一个“竞品价格监控”的Agent。以前我用工具范式抓取、解析、入库、收集趋势四个环节各自为政经常抓到一半任务超时。改造后Agent直接用Python写了fetch_price()、parse_price()、write_to_db()三个函数再用一个主函数串起来妥妥跑完还能自动记录历史。这就是可组合性的意义——代码让复杂任务不再是“一锤子买卖”。3.2 自我修复与容错工具调用模式下一旦工具内部报错模型一般只能告诉用户“调用失败”。它看不到堆栈看不懂底层数据问题也不具备修代码的能力。但Code Mode下模型面对一个KeyError或ValueError第一反应是去读异常信息然后写一段兼容代码再次尝试。我印象最深的一次是处理一个编码特别乱的CSV文件工具范式下无论如何都会乱码。Code Mode下模型先读文件的原始字节尝试sniff编码发现是GBK之后在open里指定encodinggbk问题直接解决。这种“程序会自己迭代”的特性才是Agent真正具备生产力的关键。3.3 稳定性与回归测试工具调用每次都是“现场执行”参数稍微有差异结果就不可控。但代码可以沉淀下来变成固定的脚本下次任务只需微调参数跑出来的模式完全一致。这等同于给Agent的效果加了“回归测试”能力——你甚至可以把历史处理过的输入输出对做成测试集让Agent的代码生成模块跑一遍回归看看改造之后是不是仍然符合预期。这一点对我们的交付项目特别有价值。客户验收的时候我们不仅展示了Agent的处理结果还把自动生成的脚本和运行日志给对方告诉他们“这套流程以后可以离线复现”。这种可信度是黑盒式工具调用很难提供的。代价显而易见所有沙箱环境搭建、权限控制、依赖管理之类的工作量全跑到了Agent开发者头上。后面实操部分我重点讲怎么把这些“脏活”做好。4. 实操把 Agent 改造成 Code Mode 的完整步骤4.1 环境准备沙箱比你想的更关键让Agent既能开心写代码又不会把宿主机搞崩沙箱是必需品。我推荐用Docker容器原因很简单依赖隔离、网络可控制、销毁重建成本低。我在项目里用的是python:3.11-slim基础镜像重建一个专用容器docker run -d --name agent-code \ -v /data/agent_workspace:/workspace \ -w /workspace \ --network agent_net \ --cpus 1.0 \ --memory 2g \ --read-only \ python:3.11-slim \ sleep infinity几个参数是重点。--read-only保证容器文件系统只读能有效防止Agent自己乱改系统文件安全级别直接拉满-v把宿主机的工作目录映射给Agent数据输入输出都走这个目录好追溯也好清理--cpus 1.0 --memory 2g限制资源抢占防止一个Agent任务把整台机器拖垮。依赖也要预装好pandas、requests、lxml、openpyxl省的Agent每次装包浪费大量时间。注意如果你不打算用Docker至少也要用venv或conda环境并把执行用户设置成无权限的普通账号。直接跑在宿主机上遇到Agent生成个rm -rf命令够你哭一宿的。4.2 暴露一个执行接口Agent的执行模块是最核心的接口。通常我们给它一个execute_python(code: str) - str的工具函数内部把所有输出捕获下来返回给模型。我自己的实现可以直接参考import subprocess, tempfile, os def execute_python(code: str, timeout: int 60) - str: with tempfile.NamedTemporaryFile(modew, suffix.py, deleteFalse) as f: f.write(code) script_path f.name try: result subprocess.run( [docker, exec, -i, agent-code, python, script_path], capture_outputTrue, textTrue, timeouttimeout, cwd/data/agent_workspace ) return ( fSTDOUT:\n{result.stdout[-3000:]}\n fSTDERR:\n{result.stderr[-3000:]} ) except subprocess.TimeoutExpired: return Error: execution timeout finally: os.unlink(script_path)注意这几件事。第一脚本路径必须挂载进容器所以我会把生成的脚本写到/data/agent_workspace里再执行这样才能让容器内外的文件同步第二输出截断取最后3000字符避免上下文被刷爆第三timeout一定要设不然后台任务能把成本拖到飞起。这里有个很多人容易忽略的细节不要直接把脚本写到容器里再执行而是通过宿主机挂载目录中转。相信我直接用docker exec ... python -c ...传长代码经常被shell转义搞崩。4.3 任务设计先规划、后写码、看结果光有执行接口还不够Agent的提示词和流程设计是决定成败的关键。我现在的框架分三步走。第一步给模型下发任务时先让它输出一个执行计划而不是直接写代码。比如“目标统计销售数据并生成月度报表。请先分三步规划1.数据读取与清洗2.统计分析3.结果输出。”这能明显减少它写到一半才发现“这个数据格式不对”的情况。第二步让模型把每一步拆成独立函数一次只写一个函数并且立刻执行。我们把这个流程叫“内聚短循环”好处是报错容易定位而且语言模型能自己读取上下文的报错精准修改代码不用从头开始整理思路。第三步要求模型在完成脚本后再写一个verify()函数检查输出文件是否存在、数据行数不为零、关键字段无缺失。这个习惯一开始是模仿人类测试的流程后来我发现这招对生产环境特别友好——至少你不会把一个空报表当成果交给客户。下面是Agent实际生成的一段代码示例import pandas as pd def load_data(path): df pd.read_excel(path, engineopenpyxl) df.columns [c.strip() for c in df.columns] return df def monthly_summary(df): df[日期] pd.to_datetime(df[日期]) df[月份] df[日期].dt.to_period(M) summary df.groupby([月份, 地区])[销售额].sum().reset_index() return summary def verify(path): df pd.read_csv(path) assert len(df) 0, empty output assert not df[销售额].isnull().all(), all null sales if __name__ __main__: data load_data(raw_sales.xlsx) clean data.dropna(subset[销售额]) summary monthly_summary(clean) summary.to_csv(report.csv, indexFalse) verify(report.csv) print(done)这种结构化的编码模型只需要生成load_data和monthly_summary两个函数就能复用而每次执行我们都能清晰看到哪一步出了问题。对比工具调用——一堆JSON来回传出错时往往连“哪一步出的错”都看不出来——效率差距非常大。4.4 与既有工具的共存代码 MCP 混合话虽这么说并不是所有事情都适合丢给代码。比如实时性很强的天气查询、需要专业黑盒计算的信贷风控接口这些场景与其让模型自己写HTTP调用不如直接提供一个工具。正确姿势是“代码为主、工具为辅”的混合模式让Agent先用代码处理数据流转遇到阻塞时再调用专用工具。我们最近做了一个MCP适配层的混合实验Agent先通过代码连接数据库mysql-connector拿到原始数据并清洗然后调用一个MCP里的“风控评分”工具把清洗后的数据集传过去拿到结果后再用代码生成图表。整体链路比“全工具调用”稳定得多也保留了专业服务的准确性。这套做法的好处是老的工具资产不会被浪费而是下沉为特殊能力接口让代码去完成更普适的数据处理逻辑。从工程过渡的角度来看也比直接推翻重来温和得多。5. 常见问题与排查技巧实录5.1 沙箱与数据目录的权限边界我在初期遇到最多的坑就是Docker容器里压根看不到宿主机挂载的数据或者看到了却因为权限不足读不了。排查思路很简单先在容器里手动执行ls -l /workspace看文件夹是否存在、属主是谁一次性把映射路径搞对。另外容器内用户最好固定为uid1000在docker run时加一行--user 1000:1000否则宿主机的文件经常被容器内的root改成无法读写。实操心得宿主机的工作目录一定要让Agent“只读输入、独立输出”。把原文件放在/data/input输出统一写在/data/output。这样既不会污染原始数据也方便出问题后翻日志排查。5.2 依赖安装与网络隔离的冲突沙箱环境为了安全一般会断外网。但Agent执行任务时偶尔要pip install一个包断网直接卡死。我之前就吃过这个亏写好的Agent跑了几轮突然要求在容器里装scikit-learn结果网络不通任务直接失败。解决思路基础镜像里把常用依赖一次性装齐或者配置一个私有镜像源让容器能走内网安装。还有一种做法是把网络策略设置为“仅允许访问pip镜像和白名单域名”其余全部拒绝。既保证依赖可扩充又避免Agent乱访问外部接口。5.3 模型陷入“写码-报错-再写码”的循环Code Mode最大的风险不是能力不够而是模型容易死循环报个错改两行再报错再改。尤其是遇到环境问题或者数据异常时可能反复十几次都回不了正常流程。我用的办法有两个。第一是在提示词里写清楚“如果同一个错误连续出现两次请停止修改给出错误信息并请开发者介入。”第二是在执行接口层做次数的硬限制比如允许单次任务最多执行15次代码块超过直接终止并返回阶段性日志。这两个兜底能保你半夜睡觉时Agent不会自己跑一夜然后刷爆账单。5.4 成本与超时控制很多人以为Code Mode比工具调用贵——生成代码确实比生成一个JSON工具调用要消耗更多token。但放在现实场景里工具调用经常要“多次对话”才能做完一个任务一次给一句话、来回十几个回合而Code Mode一轮就能写完并执行完毕总成本往往反而更低。不过还是要设防线子任务超时30秒强制终断、脚本运行时长上限60秒、每轮任务成本上限可以在后端做一个计数器超过阈值就自动截断。我踩过坑一个数据清洗任务跑到第23分钟还没停成本系统报警了我才发现是subprocess超时设太长了。6. 一点个人体会翻过头来看从“工具调用”到“Code Mode”的范式转移其实是在回答一个更底层的问题——Agent到底是一个“会调接口的聊天助手”还是一个“能独立解决问题的执行者”我身边越来越多的开发者正在用脚投票宁可多花时间打磨沙箱和提示词也要让Agent直接写代码干活。我也在持续推进一个新的方向让Agent在写完代码后自动把脚本整理成报告附件和可复现仓库相当于每一次任务都是一个小型交付物。这样做下去Agent真正沉淀下来的不止是答案还有一套完整的问题解决流程这对团队的知识资产积累非常有帮助。如果你正打算改造自己的Agent建议从最简单的“代码执行接口 日志回传”做起先愚公移山再登高望远。