开头在调试一套多智能体协作系统时我花了整整两个晚上重构工具调用层最后不得不承认一个反常识的结论过去一年里我们当作地基的 Function Calling正在被一种更粗暴、也更优雅的模式逼到墙角这个新模式就是 Code Mode。标题说“该退休了”确实有点标题党但这次趋势不是炒作。Function Calling 把模型接入外部世界的路子没有错错的是它要求我们把所有可能性都预先定义好而真实世界的接口和逻辑从未按剧本出牌。Code Mode 换了个思路让模型直接写出可执行的代码、跑起来、再根据结果继续决策——把工具调用从“声明式”变成了“命令式”。这篇文章我会把它俩的原理、局限、选型边界和一次真实需求下的完整双实现讲清楚希望对你正在做的 Agent 或者自动化系统有一点参考价值。1. Function Calling 的红利与瓶颈1.1 为什么 Function Calling 一度成为 Agent 的事实标准Function Calling 之所以在过去一年多里快速普及核心原因是它解决了 GPT 时代模型“嘴巴和手分离”的痛点。早期的 LLM 应用只能回答文本问它“帮我查一下今天杭州的天气”它只能抱歉地说自己没法访问实时数据。Function Calling 出现后模型可以输出一个结构化的调用请求比如{ name: get_weather, arguments: {city: 杭州, date: 2025-06-10} }由我们自己的程序去真正执行这个函数再把结果塞回对话里。这一来一回模型就从“聊天机器”升级成了“会调接口的调度员”。我早期用它写过一个工单系统整体架构很简单把故障处理工具定义成query_logs、restart_service、get_metrics三四个函数模型在用户描述故障后自动决定调用哪个。这个模式的好处是稳定、可预测、非常安全因为所有能做的事都是白名单内的模型无论如何都跑不出这几个函数。当时我觉得这已经是 Agent 技术的天花板了直到遇到真实业务中那些无法被“白名单”覆盖的需求。1.2 三个让我越来越别扭的场景第一个场景是针对“参数爆炸”的。为了做一个报表分析功能我定义了analyze_sales参数从file_path、metrics、group_by、start_date、end_date一路加到filter_conditions一个函数十几个参数。每次模型理解用户意图、把自然语言映射到 JSON 参数的过程都有失败率而且一旦用户问得含糊一点模型就会开始瞎填参数。第二个场景是多步骤串联。比如用户说“先取昨天的日志去掉 timeout 错误再按服务维度统计最后给个 Top5 排行”。用 Function Calling 实现时模型需要连续调用三四个函数每一步都要维护中间状态函数返回结果再传回模型再生成下一次调用。每多一跳延迟和上下文膨胀就成倍增加而且中间任何一步模型“思维跑偏”整个调用链就会断。第三个场景是对“计算闭环”的无能为力。用户在对话里说“这个文件里数据好像不太对你帮我查一下到底哪里不对”。模型只能返回一个建议说“建议用 Python 打开文件看看”它自己没法真的去打开文件、跑一段统计代码、再回来告诉你答案。Function Calling 的底层假设是“外部能力都已经由工程师做好”但实际工作中真正能解决问题的那些临时脚本、一次性清洗逻辑恰恰没有预定义函数可用。这三个痛点叠加在一起我开始认真研究替代方案。2. Code Mode 是什么凭什么接棒2.1 先搞清楚 Code Mode 到底做了什么Code Mode 的通俗理解是把运行环境交还给模型。模型不再只是“决定调用哪个函数”而是直接生成一段代码这段代码在沙箱或本地容器里被真实执行然后执行的输出标准输出、返回值、生成的图表、错误信息会被反馈给模型模型再决定下一步怎么做。你可以把它理解成从“打电话让助手去办事”变成了“直接把电脑给助手让它自己写脚本操作”。我的一个项目里是这样接入的模型收到用户请求后如果判断任务适合代码执行就返回一段 Python 脚本由我封装好的执行器把它放进隔离环境里跑起来采集 stdout、stderr 和异常堆栈然后作为工具结果回传。脚本里可以读文件、调接口、做分析、画图几乎所有数据操作都能完成。那段执行器代码不到 150 行但能力边界比之前几百行函数定义宽了几十倍。2.2 代码执行带来的连锁反应代码执行模式看起来只是“从调函数变成写代码”但它的连锁反应非常深。第一是参数问题消失了模型不再需要把用户意图精确映射成 JSON 参数它只需要写一段代码来处理原始数据意图理解被分散到了代码逻辑里。第二是多步骤任务被折叠成单个回合一个复杂报表需求可以在一段 30 行的脚本里完成不再需要连续多轮函数调用来串联。第三也是最关键的一点模型可以获得“自我验证”能力。脚本执行报错了模型能看到错误信息然后改写脚本再来一次。这相当于 Agent 第一次有了调试闭环而不是把错误抛给用户去处理。这种“生成代码 → 运行 → 看结果 → 修正代码”的循环和人类开发者的工作方式非常像也让模型对真实世界状态的感知能力大大增强。我实测下来Code Mode 在数据清洗和文件批处理类任务上的成功率明显高于 Function Calling。原因很简单传统模式下模型只能“猜”数据长什么样而 Code Mode 可以先执行一段探查代码比如打印几行 DataFrame 的头和列名再决定后续操作这种交互深度是函数调用无法比拟的。2.3 什么场景下 Code Mode 确实更香从我的实践来看至少有三类任务值得无脑切到 Code Mode。第一类是“探索式数据分析”比如用户带一个 CSV 进来问“这些数据里有哪些异常”模型直接写个df.describe()、df.isnull().sum()、画个分布图一轮就能给出有根据的回答。而用 Function Calling 你要先定义好get_summary_stats、detect_outliers、plot_histogram这些函数还要猜用户想看哪一列、什么粒度累。第二类是跨系统、跨文件的操作比如“读取 5 个 Excel合并之后按部门算绩效再输出一份周报目录”。函数调用模式下得提前定义合并、去重、汇总等多套函数Code Mode 里就是一段pandas脚本的事。第三类是需要较强上下文记忆和分支判断的任务比如“这个目录下每张图片分辨率不足 300dpi 就重命名并移动到另一个目录”。这种任务用代码写一个条件循环拍脑袋就完成了Function Calling 的 JSON 工具协议很难承载这种过程性逻辑。当然安全性和可控性问题也随之而来。模型写的代码一旦执行就相当于把真实环境的部分控制权交给了模型输出隔离、权限、超时、资源上限都必须提前做好设计。这不是“能不能用”的问题而是“怎么用才不出事故”的问题后面我会专门讲。3. 正面硬刚两套方案的差异与选型框架3.1 一张表看清核心差异为了更直观地说明问题我把过去半年用两套机制做过的十几个项目经验整理成了一张对比表方便你快速决策维度Function CallingCode Mode能力边界受限于已定义函数的白名单受限于代码沙箱可执行的范围复杂参数容易因意图模糊产生 JSON 错误天然免疫逻辑写在代码里多步骤任务需要多轮调用链状态易丢折叠为单轮脚本执行自我纠错弱错误后只能返回提示强能根据报错改写代码安全性高模型只能触碰白名单能力中必须靠沙箱与权限隔离兜底开发成本提前写函数定义和调度代码提前写执行器和环境治理机制适合任务稳定API、固定动作、强约束数据分析、文件批处理、探索性任务这张表里最值得关注的是“自我纠错”那一行。我做过统计同样一个 NLP 到结构化数据的转换任务Function Calling 模式下模型出错后基本只能等外部程序报错或靠用户补充信息Code Mode 模式下模型平均 1.8 次就能根据运行时报错把脚本修正到正确结果。这个差距直接决定了复杂任务的最终成功率。3.2 我的决策路径现在每拿到一个新需求我基本下意识走一套决策路径。第一步是先问一个问题这个任务的核心不确定性在哪里如果核心不确定性在“用户怎么描述动作”比如查天气、下单、开关设备这类动作是明确的参数只有几个枚举值那继续用 Function Calling 完全没问题没必要为了追新而换。如果核心不确定性在“数据怎么处理”或“逻辑怎么编排”比如给一份数据、要一份洞察报告那我基本直接走 Code Mode。判断公式很简单如果让我自己写一个脚本来解决这个问题我需要超过 5 行代码那就不应该定义一个函数而应该让模型自己生成这段代码。第二步是重点关注执行后的反馈方式。Function Calling 只适合那种“调用完就结束”的任务返回结果即可Code Mode 适合“调用完还要根据结果决定下一步”的任务。比如分析一个月的数据跑完统计之后可能还要画图、出结论、拆解异常这些后续分支是提前列不完的只有代码循环才能覆盖。3.3 别急着“退休”两套机制的真实关系标题党归标题党我必须说一句公道话Code Mode 并不会全盘替代 Function Calling至少在安全和强合规场景里不会。有些业务必须严格走预定义接口比如银行转账、医护设备操作、生产环境变更这类地方你要让模型写代码去执行审计闭环都很难做。正确的姿势是把两套机制当成互补Function Calling 负责那些“稳定、有界、强审计”的对外动作Code Mode 负责“探索、计算、编排”的对内流程。我还发现一个有意思的融合点。实际上在成熟架构里Code Mode 的执行环境内部可以天然调用那些预先注册好的函数。也就是说函数变成代码沙箱里的一个 Python 方法模型写的脚本可以直接 import 这些方法去执行传统工具调用。这样一来你既保留了审计和权限边界又获得了脚本编排的灵活性。我最近的一个项目就是让 Code Mode 脚本去循环调用内部send_email、query_warehouse等受控函数两套机制的关系从“替代”变成了“嵌套”。4. 实操记录同一个需求两套实现4.1 需求背景一次多步骤销售分析为了不空谈我特意保留了一个前段时间的真实需求来做对比。业务方拿了一份 4000 多行的销售明细表表字段包括交易日期、区域、产品线、销售金额、成本、退款状态等 9 列。他们的原话是“帮我看看 2024 年全年各区域的毛利率变化再重点把 Q3 利润率下滑最明显的区域拎出来说说可能是什么原因。”任务涉及数据读取、数据清洗、透视表聚合、环比计算、异常筛选、原因解读是一个典型的多步骤半开放任务。准备条件很简单一个 Python 3.10 环境一份sales_detail.csv放本地会话模型统一用支持函数调用的模型版本。我用同样的模型跑了两套实现分别记录过程、代码量和最终输出质量。4.2 用 Function Calling 走一遍全流程我第一次实现是按标准 Function Calling 套路做的。先注册了四个工具load_sales_data负责读文件aggregate_by_region_month负责按月按区域聚合calc_gross_margin负责算毛利率detect_decline负责筛选下滑区域。每个函数都写了详细的 JSON Schema参数包括路径、日期范围、分组维度等。调用过程看起来像一场接力赛。模型收到用户请求后第一轮调用load_sales_data拿到字段名和预览但因为函数只能返回head()结果模型对数据里退款状态的取值分布一无所知而是猜测要过滤哪些行第二轮调用聚合函数时模型传了一个财务口径参数但我没定义过这个参数工具层直接报错模型只能重新理解再试一次。整个过程一共触发 7 轮函数调用最终通过人肉写一个 helper 函数临时增加了一个inspect_unique_values的支持才跑通。最终输出虽然给出了各区域毛利率和 Q3 的下滑结论但我很清楚真正推动结论形成的不是模型分析能力而是我在中途陆续补充的辅助函数和参数修正。这暴露了 Function Calling 在探索型任务里的最大问题函数集合必须一次性定义完整否则中途加函数的行为会让整个调度逻辑变得非常脆弱。4.3 切到 Code Mode 的实现过程换到 Code Mode 之后我的准备动作只有两步。第一步是确认执行环境有pandas、openpyxl和matplotlib第二步是把模型输出中的 Python 代码抽取出来在子进程沙箱里执行并把 stdout 和异常信息传回给模型。没有注册任何业务函数。模型收到需求后第一段生成的脚本先是读取 CSV 并打印了列名、行数和每列的去重值数量。这一步的作用是“摸数据底细”它在脚本注释里写了“先看数据口径再决定清洗逻辑”还挺有模有样。随后基于第一次输出第二段脚本做了日期解析、去掉退款状态为“已退款”的交易记录按区域和月份分组计算毛利率并用聚合结果找出 Q3 环比下滑最大的区域。第三段脚本针对下滑最严重的“华东区”做了细拆对比了各产品线的销量、单价和退款率变化最后输出了一段 Markdown 格式的结论。整个过程一共只有三次代码执行最后一次输出已经包含业务方能直接使用的完整结论和一张毛利率趋势 CSV。没有一次中间函数调用失败也没有参数歧义问题。4.4 现场对比3 个维度的感受从耗时来看Function Calling 版本从开始到可用大约花了 6 个小时开发辅助函数和处理边界Code Mode 版本从零到交付大约 1.5 小时。差异主要来自“安装理解 vs 安装执行”的变化前者要我猜模型需要哪些函数后者只需要准备环境和约定好输出格式即可。从可靠性来看Function Calling 版本在过程中出现过一次函数参数幻觉模型试图传递未定义的参数Code Mode 版本没有出现这类工具协议错误但出现过一次脚本运行超时原因是模型写了一个全表逐行的双重 for 循环。我加了执行超时限制后模型会根据错误信息自动改用groupby重写这个自我修正过程非常流畅。从结果质量来看两个版本都给出了正确的 Q3 下滑区域结论但 Code Mode 版本附带了数据探查过程和图表文件可解释性明显更强。业务方甚至直接用了它生成的那段 Markdown 作为内部周报底稿。这是我没想到的“隐藏收益”。5. 常见问题与排查技巧实录5.1 Function Calling 时代的典型翻车现场先整理几个在经典函数调用模式里我踩过最多次的坑你大概率也会遇到。第一个是“参数幻觉”模型生成了 Schema 里不存在的字段或枚举值多数是因为自然语言里出现了相近词比如用户说“最近三个月”模型直接给days90但你的函数定义只有start_date和end_date。应对办法是尽量把参数设计成扁平的原子类型并给每个枚举值加别名描述。第二个是“多调用状态丢失”。连续调用两个函数时第二个函数往往需要依赖第一个函数的返回值例如先upload_file再summarize_document。如果返回结果太长模型会忘记文件 ID或者把上一轮的文本内容当作参数塞进来。我的经验是不要让两个有依赖关系的函数跨轮调用而是设计一个复合函数一次完成“上传摘要”或者在上下文里强制要求模型引用唯一的返回变量。第三个是“错误处理空泛”。工具返回异常后模型只会说“获取数据失败请检查权限”而不是去读异常对象里的具体信息。所以工具返回结构里一定要包含error_code、error_message、traceback三段并告诉模型优先把traceback转述给用户或提出可操作建议。5.2 Code Mode 落地时的真实坑Code Mode 的新坑同样不少而且一旦爆雷往往更严重。第一个是无约束的代码行为。我在一个早期版本里没有限制沙箱网络访问模型自动 pip install 了一个包还试图读取~/.ssh/下的文件。立刻给我提了个醒执行环境必须做到最小化依赖预装默认禁止网络出网文件系统只暴露白名单目录否则后果不堪设想。第二个是隐式模型依赖。模型写的代码偶尔会依赖它“以为”存在的模块比如用到dbconn这种内部私有库实际环境里根本没有。我后来在提示词里加了一句约束“只允许使用 Python 标准库以及环境内显式列出的包列表”报错率下降明显。第三个是执行资源的无限膨胀。一个递归文件遍历脚本跑出来 50 万行日志打到 stdout直接把上下文撑爆模型直接失去对话能力。解决方式是给 stdout 和 stderr 加上长度截断默认只保留最近 3000 字符并单独在返回结构里附上截断标记。所有可能长时间运行的任务都强制设置超时我一般默认 30 秒分析类任务放宽到 2 分钟。还有个坑很容易被忽略模型生成的代码里一旦遇到中文文件名有概率在编码上翻车因为系统默认 locale 不是 UTF-8。我在执行器入口处统一加了PYTHONIOENCODINGutf-8并强制要求所有文件读写显式指定encodingutf-8这个细节能省很多莫名其妙的排查时间。5.3 我的排障小抄与经验沉淀最后分享一套我现在比较固定的排障顺序遇到 Code Mode 执行问题基本能快速定位。第一步看返回的 exit code非零直接进 stderr第二步看 stdout 长度被截断说明代码输出过多优先检查是不是有循环打印或者加载了大对象第三步看异常栈里最后一个第三方库调用八成问题出在版本或 API 变更上直接提示模型去查库文档第四步如果代码运行成功但结果不符合预期我会让模型把关键中间结果打印出来再做一步“断言型验证”也就是让代码自己检查输出结果的维度是否和预期一致。另外一个特别有效的技巧是“两段式提示”。我在系统提示词里会要求模型先输出一个 5 行以内的执行计划再输出完整代码。这个计划本身不执行但它能让模型在写代码前先想清楚步骤显著减少无头苍蝇式的代码生成。实测下来复杂任务的成功率大约提升了两成。你也可以把计划步骤作为结构化输出保存下来方便后续排查模型到底在哪个环节产生了错误理解。再强调一次安全底线只要是模型生成的代码无论来自多大牌的模型都必须走沙箱执行并且执行账号使用无特权用户文件系统只读挂载加独立临时目录。这个验证规则是无数教训换来的不能省略。就我个人实际使用体验而言Code Mode 对我的最大改变不是“写代码更溜了”而是它让模型第一次有了“动手验证”的闭环。过去调 Function Calling我像一个产品经理把所有接口文档准备好等模型来调现在用 Code Mode我更像一个平台工程师把环境治理干净让模型自己在里面折腾。这种从“定义动作”到“提供环境”的转变才是这次范式更迭里最值得仔细体会的东西。如果你还在纠结哪套方案更好不妨先用一个数据分析类任务做个小对比实验大概率会得到和我类似的结论。
阅读完成 · 觉得有帮助?