Code as Policies:让 LLM 写代码,而不是写「计划」
🎯 为什么重要:自然语言是「粘合剂」,代码才是「真策略」
在 CaP 之前,LLM 接机器人主要有两种范式:
瓶颈:自然语言没有「数值」。说「向左 0.3 米」简单,但说「把红色块每隔 5 厘米排成一行」就崩了——LLM 没法在自然语言里精确表达算术、几何、循环。
CaP 的洞察:代码本身就是一种「带数值、带逻辑、带库函数」的策略表示。LLM 已经在海量 GitHub 代码上训练过,写机器人代码只是把它的能力换个出口——无需任何微调、无需任何机器人数据。
💡 核心思想:用「层级代码生成」把模糊指令变精确程序
CaP 的方法分两层。低层是few-shot prompting:给 LLM 一些「注释→代码」范例,让它照葫芦画瓢。高层是层级代码生成(Hierarchical Code-Gen)——这是 CaP 真正的「招」:
这种设计有个意外收益:因为函数名本身就是「文档」(如 get_objs_bigger_than_area_th),LLM 能从命名推断语义(「bbox_xyxy 一定是 [x1,y1,x2,y2]」)。等于把「chain-of-thought」从自然语言搬到了函数式编程里。
🛠️ 三个关键设计:Hints / Examples / Safety
| 设计 | 心智 | 为什么必要 |
|---|---|---|
| ① Hints(导入 + 类型) | prompt 开头先 import numpy as np; from utils import get_pos, put_first_on_second | 告诉 LLM「你能调哪些 API」——API 名字就是最强的 grounding 信号 |
| ② Examples(few-shot) | 给数个「注释→代码」范例(论文附录里每个 prompt 含 2–3 个,覆盖不同任务模式) | 示范如何把模糊语言("a bit to the left")映射成具体数值(+ [-0.1, 0]) |
| ③ 安全 exec() | 禁止 __ 变量、import、exec/eval;用受限 globals 调 exec() | LLM 生成的代码要在真机上跑,必须防注入、防逃逸 |
三个杀手锏:
(1) 精确数值:"faster" →
speed=0.2,由 LLM 从训练分布里取合理值;(2) 算术 + 几何:「排成 20cm 长的线」→
np.linspace(0, 0.2, n),免费继承 NumPy/Shapely;(3) 反馈控制:
while not detect("apple"): move_right()——一行代码就是一个 reactive policy,自然语言做不到。
📊 结果:在 unseen 任务上完胜端到端方法
| 评测 | 任务/数据 | 关键数字 |
|---|---|---|
| 通用代码生成 | HumanEval (OpenAI) | 层级 Codex (davinci) Greedy 53.0% / P@100 95.7%(当时 SOTA) |
| 机器人代码生成 | RoboCodeGen (37 题,本文新提) | 层级 Codex davinci 95%(Flat 81%);GPT-3 6.7B 仅 5% |
| 桌面操作(seen 属性) | Long-Horizon | CaP 97.2%(CLIPort 78.8%,NL Planner 86.4%) |
| 桌面操作(unseen 属性) | Long-Horizon | CaP 97.6%(CLIPort 暴跌至 36.8%) |
| 桌面操作(unseen 任务+属性) | Spatial-Geometric | CaP 62.0%(CLIPort 几乎为 0.01%) |
| 真机演示 | UR5e / Everyday Robots | 形状绘制、桌面操作、厨房移动操作——零训练、few-shot 即可 |
🌐 在领域中的位置
CaP 是「LLM 接机器人的中间时代」的代表作——在 VLA 出现之前,它把 LLM 当作「外挂的策略生成器」,靠 prompt 完成零训练部署。它给后人留了两个遗产:(1) 「写代码」是比「写计划」更强的策略表示,(2) RoboCodeGen 这个评测集。后续 Voxposer 把这个思路推广到 3D 场景,Eureka 把它推到奖励函数设计,而 VLA 则是把这种能力蒸馏进模型权重——但范式起点都是 CaP。
❓ 常见误区
CaP 是端到端学习吗?
不是。CaP 不训练任何模型——它只是 prompt 一个预训练好的 Codex。所有「学习」都发生在 LLM 预训练阶段(在海量代码上)。论文里所有真机演示都是 zero-training,靠 few-shot prompt + 已有 API。
那它算「策略」吗?策略不是应该把 observation 映射到 action?
算,但「策略」的层级更高。CaP 生成的代码本身就是一个策略——它调用 detect() 读感知,用 if/while 做反馈,调 set_velocity() 出动作。它是一个由 LLM 即时合成、由 Python 解释器执行的策略。论文 §III-D 专门讨论了这种「policy code」的层级定位。
它有什么硬伤?
论文自己列了三条:(1) 受感知 API 表达力限制——没法说「这条路更 bumpy」;(2) 受控制原语覆盖限制——没给 grasp_cube() 就做不了抓取;(3) 指令长度/复杂度上去后会崩——LLM 长上下文推理仍不稳。更根本地,它没有「学习」能力:同样的指令永远生成同样的代码,不会从失败里改进。
为什么后来的 VLA 取代了它?
因为 VLA 把「写代码」这个能力内化了。RT-2 / π0 直接用神经网络把「观察+指令」映射到动作,不需要中间那层 Python 解释器。优势是端到端可微、可以微调。但 CaP 的「可解释、可组合、免训练」优势,VLA 至今仍难完全替代——很多 robot-code 工作今天仍在用它做 long-horizon 规划。