☰
从Function Calling到Code Mode:AI Agent工具调用的范式跃迁与工程实践
2026/9/30 9:32:32 网站建设 项目流程

最近跟几个做 Agent 的朋友聊天,发现一个特别有意思的现象:大家的话题已经从"怎么封装 Function Calling"变成了"我怎么又卡在 Function Calling 的工具参数上了"。然后有人丢出一句挺激进的话——Function Calling 该退休了,新神叫 Code Mode。乍一听像标题党,可真把两种方案放到真实工作流里跑一遍,会发现这个说法虽然不严谨,但方向是对的:以“预先声明函数”为核心的 Agent 交互太僵硬,而以“直接生成并执行代码”为底座的交互模式,正在从"玩具"变成生产力。

这篇文章不打算做那种纯概念的对比,我想直接用工程视角拆一遍:Function Calling 到底解决什么问题、卡在哪里,Code Mode 是什么原理、怎么落地搭一套,以及两者在实际项目里怎么共存。如果你正在做 AI Agent、自动化脚本工具、数据分析型助手,这篇文章值得你花十分钟看下去。

1. "接线员"模式的真相:Function Calling 是怎么工作的

1.1 管中窥豹:一条请求的完整链路

先快速回忆一下 Function Calling 的标准流程。你把工具清单定义成 JSON Schema,塞进 system message,让模型在合适的时机输出一个特殊结构体,比如{"name": "get_weather", "arguments": {"city": "北京"}}。你的后端代码收到这个输出后,拿着参数去执行真正的业务函数,再把结果拼回对话上下文,让模型基于工具结果继续回答。

这个过程本质上是一个"接线员"模型:模型不直接做任何事,它只负责说清楚"请谁干、用什么参数干",真正动手的是你写在服务端的那一堆函数。这种设计在 2023 年刚出来的时候确实优雅,它让大模型可以用很小的成本接入任意外部系统:天气查询、库存检索、订单创建、数据库查询,全都变成了模型口中的一句指令,代码层面只需要维护好 schema 和分发逻辑。

我在第一批 Agent 项目里就是这么干的。一开始只接了三个工具,很顺手。后来工具涨到了二十几个,问题就来了:工具的字段像传染病一样互相嵌套,一个下单函数要传优惠券 ID、收货地址、支付方式,模型经常把参数类型搞错;更麻烦的是,只要业务接口一变,我这边就得跟着改 schema,然后重新测试,一遍没跑顺,模型就开始在工具名上"编造"。

这个阶段我踩过的最大认知坑是:以为 Function Calling 是给模型增加能力的,后来才明白它只是给模型增加了一张"菜单",而且这张菜单必须由人类事先写死。菜单上的菜看似多,模型却没有任何自由发挥的空间,它只能从已定义好的选项里选,不能自己发明一个做法。

1.2 三个典型痛点,都是结构性的

第一个痛点是工具粒度不可调。Function Calling 要求你把每一个动作拆成一个函数。做数据分析的时候就特别难受:你是定义一个calculate_mean还是calculate_something? 数据分析的中间步骤可能有几十种,你不可能把 pandas 的每个方法都定义成函数。结果是工具越建越粗,模型只能在粗粒度函数里凑合,输出结果经常不是你想要的。

第二个痛点是错误恢复靠撞运气。模型生成了工具调用,你的代码执行报错了,这时候该怎么办?标准的操作是把这个错误信息重新塞给模型,求它再调一次。但模型并不知道你代码的上下文,它只能凭空猜测是参数错了还是函数不存在。一次两次还能忍,多步工具链中间断一次,整个流程就可能螺旋式下降,耗光上下文。

第三个痛点是模型并不能"真用"工具,它只是在"请求"你使用工具。真正的状态管理、异常分支、参数校验全在你的业务代码里。换句话说,模型在 Agent 里更像一个发号施令的中控,而实现细节全是人肉硬编码的。这种架构在游戏规则固定、工具数量少的系统里还挺干净,可一旦任务变成开放式的——比如"这份数据里有脏数据,帮我清洗后做个相关性分析再画三张图"——固定函数集就接不住了。你会发现自己写了一个不停膨胀的转发层,维护成本比业务逻辑还高。

2. Code Mode:把代码当作 Agent 的第一语言

2.1 理念上的彻底反转

Code Mode 这个模式,说白了就是:不要预先定义函数,而是让模型直接写一段代码,然后在受控环境里执行这段代码,执行结果再作为上下文反馈给模型。模型不再说"请帮我调用一个函数",而是说"我自己动手写一段 Python 脚本,把活干了"。

这个转变最迷人的地方在于,它把工具的形状从"人类预先定义"变成了"模型现场铸造"。Function Calling 时代,工具的形状是被 schema 固定死的,模型只能在固定的槽位里填参数;Code Mode 时代,工具的形状是模型根据任务现场生成的,任务变了,代码也跟着变,几乎没有迁移成本。

举一个我实际跑过的例子。有一回要处理一份 CSV,里面大约五十万行日志数据,任务是找出所有异常时间戳的分布规律。用 Function Calling 做法是:我先准备一个数据分析函数,参数包括文件路径、分析类型、输出格式,然后让模型填参数。模型填得对不对先不说,即使填对了,函数内部写死的分析逻辑也未必覆盖"异常时间戳"这种灵活的语义。后来我改成 Code Mode,我只需要给模型一句话:"你有一个 Python 沙箱,文件挂在 /data/raw.csv,自己写脚本做分析,最后给我结论和关键数字。"模型写出来的脚本会自己过滤脏数据、自己算时间窗口、自己输出一个摘要,我只要把摘要和结论展示给用户就行。

这就是 Code Mode 的杀伤力:它把 Agent 的功能边界从"菜单里有什么"扩展成了"语言模型会什么"。模型会 Python,Agent 就等于拥有了 Python 的整个生态;模型会 SQL,Agent 就等于能直接用数据库。

2.2 一次类比:接线员与包工头

用真人类比会更好懂。Function Calling 就像你给一个接线员打电话,告诉他要订披萨,接线员背后有一套标准流程可选,但他不能自己决定今天用哪家店,也不能临时改烹饪流程。Code Mode 则是你请了一个包工头,你描述想要的效果,他现场画图纸、买材料、安排工序,如果有偏差他可以现场调整。包工头比接线员笨一点、开销大一点、难以完全预测,但面对未知任务时,包工头的完成度和灵活性是接线员没法比的。

藏在背后的技术原因是,LLM 经过海量代码训练,对"代码执行结果"的预测能力通常比对"业务函数调用结果"的预测能力更稳定。你让一个模型预测pd.read_csv返回什么对象,它可能答错细节,但整体逻辑是对的;你让它预测一个自定义业务函数的返回值,因为业务逻辑根本不在它的知识范围内,它只能瞎蒙。

我自己衡量两种方案的效率差异,最直观的一句话是:Function Calling 适合固定的流程,Code Mode 适合变化的目标。当前 Agent 的场景正在从"问答+查询"走向"任务+执行",后者占据的比例越来越大,Code Mode 自然就显得越来越香。

3. 从零搭一套 Code Mode 执行环境

3.1 最小实现骨架

Code Mode 听起来高大上,落地架构其实不复杂。核心就三块:一个能运行代码的沙箱、一个把执行结果反馈给模型的循环、一个控制截止条件的开关。下面是我在一个内部工具里用过的最小实现思路,不是生产级代码,但足够让你理解全貌。

# 伪代码,关键节点示意 def code_mode_agent(user_task, max_rounds=5, timeout=120): prompt = build_system_prompt(user_task) for i in range(max_rounds): code = llm_generate_code(prompt, last_result=None if i == 0 else result_summary) exec_output = sandbox_run(code, timeout_seconds=timeout) if exec_output.exit_code == 0: answer = llm_generate_answer(prompt, code, exec_output.stdout) return answer else: result_summary = f"出错信息:{exec_output.stderr}" prompt += result_summary return "超过最大迭代次数,任务失败"

先看这段伪代码里的两个关键选择。第一,为什么让模型每次重新生成完整代码,而不是生成补丁?因为大多数模型在修改已有代码时的成功率,并不比分步重写高多少,反而重写能让模型少受上下文污染。第二,为什么要设置max_rounds?因为模型的自我修复能力不是无限的,如果同一段代码连续报错三到五次,强行让它继续修,只会消耗上下文和算力。我在项目里把次数限制在 5 轮以内,超过就挂起转人工处理。

沙箱本身我没用太重的方案。线上服务用容器隔离,本地跑就直接在子进程里加超时限制。Python 沙箱要注意的是别让模型能随便写磁盘和读环境变量,权限能关多少关多少。如果只是处理数据,可以给它一个只读数据目录和一个独立的输出目录,禁止联网。别小看这条,模型写的代码经常不按常理出牌,比如为了求一个聚合值,直接去访问公网 API,这在合规上是很大的隐患。

3.2 一次真实任务对比

写一个更具体的案例,方便你理解两种方案在同样任务上的差距。任务是:分析sales_data.csv中最近三个月的销售趋势,找出增长最快的品类,并计算该类目的平均客单价。

Function Calling 方案下,你至少要准备两个工具函数:query_sales_data和calculate_category_growth。前者参数是日期范围、字段名、聚合粒度;后者参数是品类、开始日期、结束日期。模型需要连续调用两个函数,If 中间环节的参数传错,就得重新来一轮。最烦的是,你必须在函数内部提前写好"增长最快的品类"这个判断逻辑,等于把业务预判写死在了代码里。

Code Mode 方案则清爽得多。我给模型一个系统提示:当前目录挂载了/data/sales_data.csv,你可以用 pandas 读取,环境里有任何常见的数据处理库,你的输出应包含结论和关键代码片段。模型自然就会写出一段脚本,自己读 CSV、自己按月份聚合、自己计算环比、选出增量最大的品类、再算客单价。整个过程中,真正的"工具"就是那只沙箱和 Python 运行时,我不必为了这个任务提前定义任何函数。

有人可能会说,这不算严格对比,因为关键在于模型写代码的能力足够强。确实,模型本身的代码能力决定了 Code Mode 的上限。但我的经验是,在 2025 年这个节点,主流模型的代码生成能力已经远超大部分业务工具的 schema 描述能力。与其费力把业务逻辑翻译成函数定义,不如直接让模型写脚本,再由人类审查脚本。这种方式反而更容易控制质量。

3.3 提示词和系统设计的几个要点

在 Code Mode 下面,提示词的设计逻辑跟 Function Calling 完全不同。Function Calling 的提示词主要是工具定义和方法说明,Code Mode 的提示词更像是在给一个实习生布置编程任务。我总结过一套比较稳的说法:

  • 告诉它环境里有什么:"你可以用 Python 3.11、pandas、numpy、matplotlib,数据文件在 /data 下。"
  • 告诉它输出要什么:"最终结果需要给出结论数字,并附上关键处理步骤的摘要,不要输出完整源码。"
  • 告诉它边界是什么:"不要尝试安装新依赖,不要访问外网,不要读取 /data 以外的文件。"

为什么要把输出限制成"结论数字+摘要"?因为在真实项目里,如果模型把整个脚本的 stdout 全部打印回来,内容动辄几千行,很轻松就把上下文窗口撑爆。更合理的做法是让脚本自己输出一个精简的结果,再由执行层把 stdout 裁剪到一定长度,只保留头部和尾部。我通常把 stdout 限制在 4000 个字符左右,超过就做截断并在末尾标注"[截断]"。

迭代机制上还要注意一点,模型的修复代码不能无限骚扰沙箱。我在执行层加的约束是:单次执行最长 120 秒,单任务最多执行 8 次,一旦超时或者超次数立即终止。这样即使模型写的代码有死循环,也不会拖垮整个服务。

4. Function Calling 没死,它在哪些位置依然正确

4.1 真正适合 Function Calling 的场景

说了这么多 Code Mode 的好话,如果直接说"Function Calling 该退休了",那是不负责任的。实际上我在生产系统里依然大量保留 Function Calling,尤其是在需要强约束、可审计、低延迟的场景里。

最典型的就是权限敏感操作。比如"给指定用户发放优惠券"、"创建订单"、"发送短信"这类动作,你必须用预先定义的函数做一层校验,防止模型乱来。Code Mode 虽然灵活,但模型写出来的代码你没法保证每一次都经过权限校验,风险很高。这个场景下,Function Calling 的价值不是"智能",而是"稳定"和"可追踪"。

还有一个场景是多 Agent 协作。多个 Agent 之间互相调用对方的工具时,接口契约比灵活性重要,函数签名是双方通信的协议。这时候 Function Calling 的规范化描述天然适合当协议,Code Mode 反而会因为太灵活导致上下文对接失控。

延迟方面也要说一句。Function Calling 只需要一次模型输出加一次函数执行,通常在几百毫秒到两秒内就结束;而 Code Mode 需要生成代码、执行沙箱、可能还要迭代修复,动辄三五秒起步。在高频查询场景下,这个延迟差异是会直接影响体验的。

4.2 长远看,这是分工问题,不是替代问题

我现在的判断是,Function Calling 与 Code Mode 更像是同一个问题的两个极端,中间还有很宽的灰度带。你在架构一个 Agent 的时候,可以把任务分成两层:需要跟外部系统刚性对接的,走 Function Calling;需要开放探索和处理复杂数据的,走 Code Mode。两者并不是敌人,而是分工。

有一个挺实际的组合用法,我最近很常用:用 Function Calling 控制"动作"——比如让模型决定"要不要发邮件、要不要下单、要不要更新数据库";用 Code Mode 控制"思考"——比如让模型分析数据、整理报告、做预测。动作层要的是安全和稳定,思考层要的是开放和灵活,各取所长。只要你把这两层边界划清楚,Agent 的整体可靠性会大幅提升。

所以,与其说 Function Calling 该退休,不如说它应该退到更合适的工位上。标题说"新神叫 Code Mode",我更愿意把这句话理解成:新一代的智能体工作流,重心正在从"调用工具"转移到"编写工具",这是更本质的范式变化。

5. 高频翻车点与实战排查实录

5.1 五个常见坑

动手做 Code Mode 的人,往往会在一开始靠得太顺,然后突然被几个隐蔽问题打趴。这里列一下我实打实碰到过的坑,你可以直接当避坑清单用。

一是沙箱没限制出网,模型主动外联。明明只是分析本地文件,模型脚本却去请求某个公开 API。不是说外联一定有害,而是这个行为你根本没法预判。解决思路是默认禁止出网,真有需要再单独开白名单。对绝大多数数据处理任务来说,本地环境完全够用,不需要外网。

二是模型疯狂打印大对象。比如直接把整个 DataFrame 打出来,输出几万行,上下文爆掉。我处理的办法是给 stdout 加截断,同时在提示词里反复强调"只打印必要结果"。另外还可以在模型生成代码后,用静态规则把print(df)这类可疑语句提示给模型,让它改成print(df.head())。

三是想让它修 bug,结果它另起炉灶。我第一次碰到的翻车是,代码报错后我把 stderr 原样丢回模型,模型直接重写了一个跟之前逻辑完全不一样的脚本,前文辛苦确定的数据口径全丢了。后来纠正的办法是:把"历史代码"和"当前错误"一起给它,并明确要求它在原代码基础上做最小改动,禁止重写整个流程。

四是依赖缺失导致连环失败。沙箱里没装某个库,模型又反复尝试pip install。这时候要么提前把常用库装好,要么在系统提示里写清楚"环境内置 pandas/numpy/matplotlib,不可安装新包"。这个提示对减少无谓迭代非常有效。

五是循环迭代失控。一个看起来很简单的问题,模型修了十几次都没好,最后把整段上下文变成了垃圾。这时候一味让它继续修,既慢又费钱。我在工程上加了一个隐蔽的"健康度"指标——比如连续 3 轮错误信息完全一样,就说明模型进入了死胡同,直接停止循环并返回当前错误给用户或人工处理。

5.2 我踩过的几个真实案例

有一个特别值得拿出来说的案例,发生在处理非结构化文本的时候。我一开始让模型自己读日志文件、自己洗数据、自己算统计,听起来很完美,结果模型每次都要把整个文件读进内存,用 Python 正则硬洗。文件一上 GB,沙箱直接内存溢出。后来我把方案改成让模型先把任务拆成两段:第一段写脚本做快速统计,第二段在统计结果里找规律。这个拆分带来的收益非常直观,执行时间从几分钟降到了十几秒。

还有一个多 Agent 架构里的问题。我让一个主 Agent 用 Code Mode 做分析,再把结果交给另一个 Agent 让执行 Function Calling 发送通知。问题出在数据格式衔接:分析 Agent 输出的 JSON 结构不稳定,同一个字段名一会是total_amount,一会是total_sales。这个问题让我明白,Code Mode 输出的数据也需要做一层格式对齐,不能直接拿去调函数。后来我给主 Agent 加了一个输出模板校验,必要的时候让模型重写输出,才把这个问题治住。

5.3 实用的排查技巧

如果你也打算把现有 Agent 从 Function Calling 往 Code Mode 方向迁移,不要一上来就全量替换。我推荐的做法是做三个月并行:先在数据分析和报表生成这类非敏感任务上用 Code Mode,等模型产出质量稳定了,再把它推向更多场景。同时,一定要保留日志系统:记录模型生成的代码、执行结果、迭代过程,这不仅能帮你复盘任务失败的原因,还能把高质量的成功代码沉淀成模板,下次任务优先让模型参考。

另一个技巧是把"固定套路"沉淀到系统提示里。我在与模型协作一段时间后,会发现某些模式成功率特别高,比如"先画数据分布再建模"、"先做数据质量检查再聚合"这类通用流程。把它固化描述进提示里,会让模型生成代码的整体质量高一大截,比让它自由发挥稳定得多。

如果你是在给本地用户提供 Code Mode 功能,还要注意一个体验问题:执行不一定按秒完成,所以交互上要做成异步任务,先给用户一个"运行中"的反馈,等执行完了再推送结果。我见过很多项目因为这一步没做好,用户觉得工具"卡住了",实际只是沙箱在跑。

最后聊点我的实际感受

如果让我给一个容易记住的结论,我会说:Function Calling 教你给 Agent 配一整套精密的工具,而 Code Mode 教你让 Agent 在没有工具时自己造工具——真正到了复杂任务里,后者往往能救你命。我自己最近的大多数新项目,已经默认先把 Code Mode 底座搭好,再在需要跟外部系统刚性对接的位置补上 Function Calling,这个组合比任何一种单用都好用。

Code Mode 也不是没有短板,它依赖模型代码能力、需要更严格的沙箱机制、延迟更高,这些我都体会过。但往未来看,模型写代码的能力只会越来越强,沙箱基建也会越来越成熟,这条路的想象空间非常大。如果你还在纠结手里的 Agent 为什么不够"智能",我建议别急着加更多工具定义,先给它一个能写代码、能跑代码的沙箱,再看效果。实测下来,那种"质变"的感觉,几乎每次都让我意外。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询