← 返回 AI Agent 课程
【中文精读】Executable Code Actions Elicit Better LLM Agents(CodeAct)
导读:这篇 UIUC 等机构 2024 年发表于 ICML 的论文,是「让 agent 用代码而非 JSON 工具调用来行动」这条技术路线的代表作。它回答的问题极其根本:LLM agent 每一步到底应该以什么格式输出动作?主流做法(OpenAI function calling、绝大多数框架)让模型生成 JSON 或固定文本格式的工具调用,而本文系统论证并实证了第三种选择——直接生成可执行 Python 代码作为动作——在复杂任务上成功率最高可拉开约 20 个百分点。它同时贡献了框架(CodeAct)、基准(M3ToolEval)、数据集(CodeActInstruct 7k 轨迹)和开源模型(CodeActAgent),形成完整闭环。后来的 OpenDevin/OpenHands 等开源 agent 直接以它为思想源头(OpenHands 的核心 agent 至今叫 CodeActAgent),Claude Code、各类「code interpreter」式 agent 的设计也都可视为这一路线的延伸。读懂这篇,你就理解了「工具调用 agent」与「写代码 agent」两大流派分野的原点。
一、动机:JSON / 文本格式动作的两大枷锁
LLM agent 的标准工作方式是:感知环境 → 生成动作 → 观察结果,循环往复。问题在于动作的表达格式。此前几乎所有工作都让 LLM 以预定义格式(pre-defined format)生成动作——要么是 JSON(如 function calling),要么是约定的文本模板(如 Action: tool_name, Args: ...)。论文开篇即指出这类做法的两个核心局限:
- 动作空间受限(constrained action space):原文称动作"通常为特定任务量身定制,局限于预定义工具的范围"(the scope of pre-defined tools)。agent 能做什么,完全由开发者事先枚举的工具清单决定,遇到清单之外的需求就无能为力。
- 灵活性受限(restricted flexibility):即"无法在单个动作中组合多个工具"(inability to compose multiple tools)。JSON/文本格式一次只能调一个工具,没有原生的控制流(control flow,如 if/for)和数据流(data flow,把一个工具的输出作为变量传给另一个工具)。论文表 1 里写得很直白:要在 JSON 里表达复杂逻辑,"若可行也需要精心的工程设计"(requires careful engineering if feasible)——比如得专门定义一个模拟 if 语句的伪工具。
论文还点名了另一条相关路线的不足:此前用代码控制机器人的工作(如 Code-as-Policies)是单轮生成整段程序,"难以根据新的环境观察动态调整或发出动作"(struggle to dynamically adjust or emit actions based on new environmental observation),依赖预设控制原语和精心手写的提示词。CodeAct 的关键区别是把代码生成放进多轮交互(multi-turn interaction)循环里。
论文图 1 上半部分给出一个直观例子:任务要求对一批输入逐个调用工具并汇总。text/JSON 格式的 agent 必须"对每个输入都单独执行一次动作"(have to take action for every input),一轮一个;而 CodeAct 用一个 for 循环(控制流)加上把工具 A 的输出直接传入工具 B(数据流),一个动作就处理完全部输入。
二、方法:CodeAct 框架机制
2.1 核心设计:代码即动作
论文提出的可执行代码动作(executable code actions,CodeAct)框架,一句话概括:"用可执行 Python 代码把 LLM agent 的动作整合到统一的动作空间"(use executable Python code to consolidate LLM agents' actions into a unified action space)。选择 Python 的理由很务实:它是最流行的语言(TIOBE 排名第一),预训练语料里代码占比极高,且拥有海量现成开源包。
框架含三个角色:agent、用户(user)、环境(environment)。每一轮交互中:
- agent 接收一个观察(observation)——要么是用户的自然语言指令,要么是上一轮代码的执行结果;
- agent 可选地先做思维链(chain-of-thought)式的推理规划;
- agent 发出一个动作(action)——"发给环境的每个动作都是一段 Python 代码"(each emitted action to the environment is a piece of Python code);
- 环境(Python 解释器)执行这段代码,把标准输出、返回值或报错信息(traceback)作为下一轮观察返回给 agent。
预定义工具在这个框架里并没有消失,而是降格为普通的 Python 函数/API:agent 可以像调用任何库函数一样调用它们,也可以把它们嵌进循环、条件、变量赋值中自由组合。
2.2 「代码即动作」相对 JSON/text 的优势论证
论文(§2 及表 1)系统列出了代码作为动作格式的几大优势,这是全文的理论骨架:
- 动态调整与多轮纠错:与解释器集成后,agent 可以"基于多轮交互中收到的观察,动态调整先前动作或发出新动作"。特别地,"来自环境的报错信息使它能通过自我调试(self-debugging)自主纠正错误"——代码跑错了,traceback 就是免费的、结构化的失败反馈,而且这套自动反馈机制"在绝大多数编程语言里已作为基础设施实现好了",JSON/text 格式则需要开发者自己搭一套错误反馈管道。
- 开放的动作空间:直接复用现有软件包:agent 可以直接
import 任何现成 Python 包来扩展能力,而不必等开发者为每个任务手工打造专用工具。论文图 3 展示了一个无上下文示例(zero-shot)的真实案例:CodeActAgent 用 Pandas 下载并处理表格数据、用 Scikit-Learn 划分训练/测试集并训练回归模型、用 Matplotlib 画图可视化——全程没有任何一个"预定义工具"。
- 吃到预训练分布的红利:代码数据在预训练语料中无处不在,"这些模型本来就熟悉结构化编程语言"(already familiar with structured programming languages),因此采用 CodeAct 几乎零适配成本;相反,特定 JSON 工具调用格式是模型没怎么见过的分布,往往需要专门微调。
- 原生控制流与数据流:if/for 是语言内建的,中间结果可以存进变量在后续动作里复用,一段代码就能把多个工具组合成复杂逻辑——这正是第一节里 JSON/text 的死穴。
三、实验一:CodeAct 作为格式本身有多强(M3ToolEval 与 API-Bank)
论文用两个实验回答两个递进的问题:简单的单工具调用场景下 CodeAct 是否不吃亏(API-Bank),以及复杂多工具组合场景下 CodeAct 优势有多大(M3ToolEval)。共评测 17 个 LLM(8 个开源 + 9 个闭源),每个模型分别用 CodeAct / JSON / text 三种动作格式跑。
3.1 M3ToolEval:复杂任务上最高约 20 个百分点的绝对提升
由于现有基准没有覆盖"需要多轮、多工具组合"的场景,作者自建了 M3ToolEval:共 82 个人工策划的任务,覆盖网页浏览、金融、旅行行程规划、科学、信息处理五个领域,每个领域配独特的手工工具集;零样本评测(不给示范,考察模型开箱即用的指令跟随),最多 10 轮交互,最终答案与标准解精确匹配判分。指标是成功率和平均交互轮数。核心结果:
- CodeAct 在 17 个模型中 12 个拿到最高成功率,同时 12 个所需交互轮数最少。
- 最强模型 gpt-4-1106-preview:CodeAct 74.4% vs text 53.7% vs JSON 52.4%——比次优格式绝对高出 20.7 个百分点;平均轮数 5.5 vs 7.7(text)/ 7.6(JSON),平均少约 2.1 轮(约 30% 的动作数减少)。
- 其余代表性数字(成功率 %,CodeAct/JSON/text):gpt-4-0613 为 67.1/56.1/45.1;claude-2 为 54.9/39.0/29.3;gpt-3.5-turbo-0613 为 51.2/26.8/20.7;gemini-pro 为 22.0/19.5/11.0;开源侧 Llama-2-70b-chat 为 11.0/3.7/3.7,lemur-70b-chat 为 13.4/15.9/12.2,7B/13B 级开源模型普遍接近 0,三种格式都做不动。
- 论文强调一个趋势:"模型能力越强,这种性能差距越大"(these performance gains widen as the capabilities of the LLMs increase)——弱模型连基本指令都跟不住,谈不上格式差异;模型越强,越能兑现代码带来的组合能力。
3.2 API-Bank:简单原子调用场景下 CodeAct 至少不输
API-Bank 测的是单步原子 API 调用的正确率,这里没有组合优势可言,是对 CodeAct 的"下限测试"。结果:CodeAct 在 17 个模型中 8 次成为最佳格式(开源 4、闭源 4),JSON 5 次、text 4 次。代表性数字(正确率 %,CodeAct/JSON/text):claude-2 为 76.7/59.4/73.7;gpt-4-1106-preview 为 76.7/82.7/73.4;Llama-2-13b-chat 为 38.1/8.5/37.3。一个有意思的观察:闭源模型 JSON 成绩不错(GPT-4 系列 JSON 反而最高),作者推测是因为它们专门针对 function calling 做过微调;而开源模型的 JSON 表现一致地差。结论:即便在 JSON 的主场(简单单工具调用、且对手被专门调优过),CodeAct 也大体打平或更好;一旦任务变复杂,优势立刻拉开。
四、CodeActInstruct:7k 条多轮交互轨迹的构造
格式的优势验证完,下一步是把它蒸馏进开源小模型。作者构造了指令微调数据集 CodeActInstruct:7,139 条多轮 agent-环境交互轨迹,共约 1,058 万 token(平均每条约 1,482 token)。图 2 的三角色框架在此起了设计指导作用:CodeActInstruct 专注 agent-环境交互,通用对话数据负责 agent-用户交互。数据来源覆盖四个任务族:
| 任务族 | 数据源 | 轨迹数 | Token 数 |
| 信息检索 | HotpotQA(提供 wikipedia_search API) | 1,664 | 约 247 万 |
| 软件包(工具)使用 | MATH(数学推理,可用 sympy 等) | 1,732 | 约 172 万 |
| APPS(代码生成) | 647 | 约 124 万 |
| 外部记忆 | WikiTableQuestion(SQL/sqlite3 与 Pandas 双变体) | 1,065 | 约 132 万 |
| 机器人规划 | ALFWorld(机器人控制 API 包装成 Python 函数) | 2,031 | 约 384 万 |
(注:任务提示里描述的"APIBank/M3ToolEval 相关任务族",指的正是这四类能力方向;M3ToolEval 本身留作 out-of-domain 测试,不进训练集。)构造与筛选上有三个值得复述的细节:
- 轨迹生成:用 gpt-3.5-turbo-0613、claude-instant-1、claude-2 跑任务生成轨迹(代码题用 16k 上下文版 gpt-3.5),对所有模型都解不出的困难子集再上 gpt-4-0613。最终保留 411 条 GPT-4 轨迹 + 6,728 条 gpt-3.5/claude 轨迹。
- 只留难题:先对源数据集下采样,剔除现有 LLM 轻松能解的简单实例,把预算花在难例上;单轮任务(APPS/MATH/WikiTableQuestions)按 MINT 框架改造成多轮交互(允许先执行测试再提交)。
- 刻意筛选自我改进轨迹:专门保留那些"模型最初出错、随后在后续轮次中修正错误"的轨迹——它们天然包含出错后的自我反思与 self-debug 行为,这正是希望蒸馏给小模型的核心能力。
规模对比:相比同期的 AgentInstruct(1,866 条)和 FireAct(2,063 条),CodeActInstruct 轨迹数是其 3.8 倍 / 3.5 倍,token 数是 5 倍 / 19 倍。
五、CodeActAgent:训练与评测
5.1 训练细节
基座取 Mistral-7B 和 Llama-2-7B,全参数 SFT。训练混合 = CodeActInstruct(7,139 条)+ 通用对话数据(共 69,230 条 / 约 5,515 万 token):OpenOrca CoT 子集下采样 50,000 条、ShareGPT 下采样 10,000 条(去掉单轮)、ShareGPT-GPT4 版 4,583 条、CapyBara 4,647 条。超参:序列长度 Llama-2 用 4,096、Mistral 用 16,384(Mistral 长上下文是它后来表现更好的因素之一);chatML 格式、只在 assistant 回复上算 loss、短样本打包 + flash attention;4×A100-40GB,学习率 1e-5(50 步 warmup、cosine 衰减到 1e-6),batch size 32,训 5 个 epoch 取第 3 个 epoch 的 checkpoint。
5.2 评测:in-domain 与 out-of-domain
评测分两块:agent 任务(MINT in-domain、MINT out-of-domain、M3ToolEval、MiniWob++ 网页操作、ScienceWorld 文本动作)+ 通用能力(MMLU、HumanEval、GSM8K、MT-Bench)。关键数字(MINT 成功率取 k=5 轮):
| 模型(7B) | MINT-ID | MINT-OD | MiniWob++ | SciWorld | MMLU | HumanEval | GSM8K | MT-Bench | 总平均 |
| CodeActAgent (Mistral) | 57.4 | 32.4 | 46.2 | 15.9 | 59.1 | 34.7 | 58.0 | 8.2 | 42.5 |
| CodeActAgent (Llama-2) | 51.3 | 20.4 | 25.5 | 17.6 | 50.6 | 18.1 | 38.3 | 7.5 | 30.7 |
| Mistral-Instruct | 18.8 | 9.7 | 0.5 | 4.0 | 53.8 | 29.3 | 43.3 | 6.4 | 25.6 |
| Llama-2-Chat | 3.2 | 11.0 | 0.0 | 5.9 | 48.0 | 13.9 | 27.7 | 6.3 | 21.1 |
| AgentLM | 8.7 | 6.1 | 28.9 | 13.7 | 48.7 | 15.4 | 24.6 | 6.1 | 24.8 |
| FireAct | 0.0 | 0.3 | 0.0 | 6.8 | 44.1 | 3.5 | 12.4 | 4.5 | 14.0 |
要点解读:
- CodeActAgent-Mistral-7B 全面领先同尺寸开源基线:总平均 42.5,同基座下相对 AgentInstruct 系(AgentLM)提升约 24%、相对 FireAct 提升约 119%;作为参照,gpt-3.5-turbo-0613 平均 54.0、gpt-4-0613 为 71.7——7B 模型显著缩小了与 gpt-3.5 的差距。
- 通用能力不掉反升:HumanEval 34.7、GSM8K 58.0、MT-Bench 8.2 均超过 Mistral-Instruct。消融实验显示 CodeActInstruct 与通用对话数据对 agent 任务都有贡献,而通用对话数据对保持通用能力至关重要(仅 Mistral 版 MMLU 有轻微下降)。
- 跨格式泛化:虽然只在 CodeAct 格式上训练,CodeActAgent-Llama-2 在 文本动作任务(MiniWob++、ScienceWorld)上也能与专门做文本动作调优的 AgentLM-7B 相当。
- 异常点:Llama-2 版在 M3ToolEval 上得 0.0(Mistral 版 12.2),论文附录 H 有专门讨论,主要与其指令跟随和上下文长度限制有关。
六、结论与局限
结论:CodeAct 用可执行 Python 代码统一了 LLM agent 的动作空间;在复杂多工具场景下比 text/JSON 格式最多高约 20% 成功率、少约 30% 交互轮数;配套的 CodeActInstruct(7k 轨迹)训出的 CodeActAgent 能与 Python 生态无缝集成——用现成库完成模型训练这类复杂任务,并通过 self-debug 自主纠错。框架、基准、数据、模型全部开源。
局限(论文 Impact Statement 部分,如实转述):
- CodeActAgent 仍是原型系统,会出现 LLM 典型幻觉——例如变量没打印出来就臆测其内容,需要进一步对齐(alignment)工作缓解;
- 未来方向是让 agent 通过大规模环境交互自我迭代、从历史错误中学习;作者也提示更强的自主 agent 可能冲击劳动力市场;
- 安全:CodeAct 允许 agent 在沙箱中自由执行代码,最坏情况下 agent 可能突破沙箱造成实际危害(如网络攻击),必须为自主 agent 设计更好的安全机制——这一点对今天所有"给模型一个 shell"的产品(包括各类 code agent)仍然是第一性的工程约束。
对开发者的启示
- 动作格式是 agent 的一等设计决策,不是实现细节。同一个模型,只是把 JSON 工具调用换成可执行代码,复杂任务成功率就能差 20 个百分点。做 agent 先想清楚:任务需不需要控制流、多工具组合、中间变量?需要,就该考虑代码动作。
- 模型越强,越值得用代码动作。论文明确观察到格式收益随模型能力增大而扩大;7B 级弱模型三种格式都做不动。给 GPT-4/Claude 级模型套一层狭窄的 JSON schema,是在浪费它的预训练代码分布。
- 报错信息是免费的监督信号。解释器 traceback 天然构成 self-debug 回路,这套"基础设施语言层面已经实现好了";自己搭 JSON 工具管道时,至少要把等价的结构化错误反馈补上,否则 agent 无从纠错。
- 简单场景不必迷信代码动作。API-Bank 显示,单步原子调用上专门微调过 function calling 的闭源模型 JSON 反而更好。单工具、低组合度的场景,JSON 调用更可控、易校验,按需选型。
- 训 agent 小模型的两个数据配方:刻意保留"先犯错、后修正"的轨迹来蒸馏 self-debug 能力;agent 轨迹必须掺足量通用对话数据(本文约 1:10),否则通用能力会塌。另外,给模型执行任意代码的自由,就必须同时给沙箱和权限边界——这是论文自己划出的安全红线。