论文精读 06|CodeAct:以可执行代码统一动作空间
论文档案
| 项目 | 内容 |
|---|---|
| 论文 | Executable Code Actions Elicit Better LLM Agents |
| 作者 | Xingyao Wang 等 |
| 发表 | ICML 2024 |
| 原文 | PMLR / arXiv |
| 代码 | xingyaoww/code-act |
| 本文定位 | 将 Python 作为 Agent 与环境交互的统一动作语言 |
核心命题
JSON function call 适合一次调用一个工具,但遇到循环、条件、变量和多个工具之间的数据传递时,会迅速变成多轮、冗长且难组合的调用序列。CodeAct 让模型直接生成可执行 Python:
items = search_products(query)
eligible = [x for x in items if x.price < budget and x.in_stock]
best = min(eligible, key=lambda x: x.price)
reserve(best.id)解释器执行代码,把结果或异常作为 Observation 返回,模型下一轮继续修改。这不是“让模型写最终程序”,而是用代码作为动作协议。
为什么代码是特殊的动作空间
自带控制流和数据流
JSON 描述一次调用很清楚,却不能自然表示“遍历这些结果并筛选”“失败后走另一分支”“把 A 返回值传给 B”。Python 原生表达变量、函数、循环和条件,因此复杂动作可以在一次执行中完成。
复用现成软件生态
代码动作可以直接调用 Python 包、解释器与已有 API,不需要为每种组合人工包装新工具。论文演示了数据处理、可视化、模型训练等任务,Agent 能导入库并根据错误信息自我调试。
模型已经见过大量代码
作者假设预训练中的代码数据使模型比自定义文本协议更熟悉 Python。对 17 个模型的原子 API 调用实验表明,CodeAct 在多数模型上与 JSON/文本相当或更好,开源模型中的优势更明显;但并非每个模型都如此。
解释器提供高密度反馈
语法错误、类型错误、堆栈和返回值都是具体反馈,比“动作无效”更容易修正。代价是执行能力大幅扩大,安全边界必须更强。
M³ToolEval:专门测多工具组合
现有工具基准多聚焦一次原子调用,难以显示代码控制流优势。论文构造 82 个人工任务,覆盖网页浏览、金融、行程等场景,通常需要多次调用多个工具并在多轮中修正。
对 17 个模型比较 CodeAct、JSON 与文本动作,指标是成功率和平均回合数。CodeAct 在 12/17 个模型上成功率最高,也在 12/17 个模型上平均回合最少。
| 模型 | CodeAct | JSON | Text | CodeAct 平均轮数 |
|---|---|---|---|---|
| GPT-4-1106-preview | 74.4% | 52.4% | 53.7% | 5.5 |
| GPT-4-0613 | 67.1% | 56.1% | 45.1% | 6.6 |
| GPT-3.5-turbo-0613 | 51.2% | 26.8% | 20.7% | 7.0 |
| Claude 2 | 54.9% | 39.0% | 29.3% | 7.2 |
| 最佳开源模型 Lemur-70B | 13.4% | 15.9% | 12.2% | 9.1 |
GPT-4-1106 上 CodeAct 相对次优格式提高 20.7 个百分点,平均少 2.1 轮。但最佳开源模型的绝对成功率仅约 15.9%,说明表达能力不是全部瓶颈。
CodeActInstruct 与 CodeActAgent
作者进一步收集 7,139 条、约 1,058 万 token 的多轮交互轨迹:
| 能力 | 来源 | 保留实例 |
|---|---|---|
| 信息检索 | HotpotQA | 1,664 |
| 数学与软件包 | MATH | 1,732 |
| 代码与自调试 | APPS | 647 |
| 外部表格记忆 | WikiTableQuestions | 1,065 |
| 机器人规划 | ALFWorld | 2,031 |
生成轨迹经过筛选,特别保留能根据执行反馈改善后续动作的多轮样本。作者把 CodeActInstruct 与一般对话数据混合,对 Llama-2 7B 和 Mistral 7B 做全参数监督微调,得到 CodeActAgent。
混合一般对话数据很关键:消融显示 CodeActInstruct 提升 Agent 任务,但一般对话数据有助维持知识问答、指令遵循等通用能力。附录还报告 Llama-2 与 Mistral 底座反应不同,不能假设同一训练配方对所有模型等效。
CodeAct 与 function calling 不是二选一
可以把动作空间看成三层:
| 层 | 表达力 | 易验证性 | 典型用途 |
|---|---|---|---|
| 单个结构化工具调用 | 低 | 高 | 发信、付款、数据库写入 |
| 受限代码/DSL | 中到高 | 中 | 浏览、数据变换、多工具组合 |
| 任意 Python/Bash | 极高 | 低 | 隔离沙箱中的研发与分析 |
最合理的 Harness 往往混合使用:模型可在沙箱里用 Python 处理数据,但真正产生外部副作用的操作仍只能通过有 schema、权限与审批的工具。
安全代价:表达力越强,能力泄露越危险
可执行 Python 不只支持循环,也支持文件、网络、进程、反射和依赖安装。CodeAct 论文证明性能收益,但不是完整沙箱安全方案。生产 Harness 至少需要:
- 容器或更强隔离,非宿主直接执行;
- 默认禁网,只开放显式代理;
- 只读文件系统与限定工作目录;
- CPU、内存、磁盘、进程数和时间限额;
- 秘密不进入环境变量或 prompt;
- 高风险 API 通过 capability token 暴露;
- 完整记录代码、依赖、stdout/stderr、返回值和副作用;
- 执行后销毁或回滚环境。
“代码可读”也不等于“行为可预测”。动态导入、网络响应和包版本都可能让同一代码产生不同结果。
Harness 实现建议
将代码解析与执行分离
先从模型响应中提取代码块,做语法解析、静态策略扫描和资源预估,再送往沙箱。解析失败应返回结构化错误,不能用不受控正则拼接命令。
把执行事件完整建模
{
"action_id": "a17",
"language": "python",
"code_hash": "...",
"sandbox_image": "...",
"policy_decision": "allow",
"exit_code": 1,
"stdout_ref": "blob://...",
"stderr_ref": "blob://...",
"duration_ms": 412,
"side_effects": []
}模型可见的 Observation 只是一段裁剪视图,审计日志必须保留完整引用。
对循环设置双重预算
模型轮数与单次代码内部循环是两种预算。需要同时限制 Agent step 数和代码执行时间/资源;否则“更少交互轮次”可能只是把大量计算藏进一次动作。
可复现实验
用同一组多工具任务比较三种动作格式:
- 每个任务需要至少两种工具和一次中间结果变换;
- 为 JSON、文本、代码提供等价工具能力;
- 固定模型与总 token,同时限制代码 CPU 时间;
- 记录成功率、模型轮数、实际工具调用数、执行时间和安全拒绝数;
- 注入工具异常,测量能否根据 traceback 恢复;
- 加入恶意工具返回,检查代码路径是否会执行未授权副作用;
- 对 CodeAct 再消融原生 Python、受限 AST 和高层 DSL。
只有这样才能判断收益来自代码语法、少轮交互,还是更大的隐性计算预算。
局限
- M³ToolEval 只有 82 个任务,规模较小;
- 成功率依赖论文的工具实现、prompt 与模型快照;
- 代码动作扩大安全面,论文性能评估不能替代生产威胁模型;
- 平均轮数没有完全反映单轮内部计算量;
- 训练数据由强模型生成并筛选,可能继承 teacher 偏差;
- 开源模型绝对表现仍低,底座能力限制明显;
- 并非所有任务都值得用图灵完备动作,简单高风险操作更适合窄工具。
CodeAct 的核心遗产是把“动作语言”提升为关键设计变量。对 Harness 工程来说,正确结论不是全面改用 Python,而是让表达力按任务逐级开放,并让越强的动作空间对应越严格的隔离、权限和审计。