枢纽
数据集VirtualHome
上真机
实时
输入模态language

ProgPrompt:别让 LLM「自由发挥」,让它写 Python 代码来计划机器人任务

Ishika Singh, Valts Blukis, Arsalan Mousavian, Ankit Goyal, Danfei Xu, Jonathan Tremblay, Dieter Fox, Jesse Thomason, Animesh Garg(USC + NVIDIA)。arXiv:2209.11302 (2022, ICRA 2023)。 项目页 · foundation modelsLLM 任务规划code as policies

🧠 一句话心智模型:让 LLM 用自然语言说「先把三文鱼放进微波炉」,它会幻觉出机器人根本没有的动作(比如「拿出生食」),还会用到场景里没有的物体。但如果你让 LLM 写 Python 函数——把可用动作当 `import`、把场景物体当列表、把任务当函数名——它就自动被语法和示例约束在可行空间内,还能用 `assert` + `else` 实现状态检查和错误恢复。

🎯 为什么重要:LLM 进机器人规划的两种「翻车」方式

2022 年 LLM 开始被用于机器人高层规划,但有两种主流路线都翻车:

两条主流路线 + 各自翻车点
  1. SayCan 路线(打分):枚举所有 admissible actions,让 LLM 打分挑下一个。问题:动作空间组合爆炸时枚举不动;每个 step 都要 query LLM,慢。
  2. Huang [2] 路线(自由文本生成):直接让 LLM 写出整段动作描述。问题:可能写出机器人不会做的动作(「拿出微波炉里的碗」),或用到场景里没有的物体;并且没有环境反馈,开环执行容易崩。

ProgPrompt 的洞察:LLM 同时是「世界知识库」和「代码生成器」——前者负责常识,后者负责约束。把两件事合起来:让 LLM 写一段 Python 程序来表达计划,用 Python 的语法结构天然约束输出

💡 核心思想:把「任务规划」翻译成「Python 代码补全」

ProgPrompt 的 prompt 长这样(论文 Fig. 2/3 简化):

# 1. 告诉 LLM 有哪些动作(机器人能力)
from actions import walk, find, grab, open, close, putin, switchon, switchoff, ...

# 2. 告诉 LLM 场景里有哪些物体(situated state)
objects = ['salmon', 'microwave', 'plate', 'fridge', 'bookshelf', ...]

# 3. 给 3 个示例任务(few-shot)
def throw_away_lime():
    # 0: find lime
    find('lime')
    # 1: grab lime
    assert('close' to 'lime')
        else: find('lime')
    grab('lime')
    ...

# 4. 让 LLM 补全下一个任务(answer search)
def microwave_salmon():
    # 0: walk to kitchen
    walk('kitchen')
    # 1: find microwave
    find('microwave')
    ...

这个结构里有四个关键编程语言特性同时被利用:

① import 语句 声明可用动作 → LLM 输出被限制在 「机器人能力」内 ② objects 列表 声明场景物体 → situated awareness 不会幻觉出场景没有的东西 ③ # 注释(Comments) 每个动作前的自然语言子目标 → 类似 chain-of-thought 显著提升成功率 ④ assert + else 前置条件检查 + 恢复 → 环境反馈闭环 assert 失败就走 else 分支 LLM(GPT-3 / Codex) 「补全这段 Python 函数」 输出:一段可执行 Python 程序 = 任务计划
图 1:ProgPrompt 的四个编程语言特性,每个都对应一个 LLM 翻车点。这不是「换个 prompt 写法」那么简单,而是用 Python 的语法结构把 LLM 的输出空间硬性约束住。

🛠️ 程序结构的四个特性(全做了消融)

特性形式解决的问题
① import 声明from actions import walk, grab, open, ...LLM 不会输出「机器人不会做的动作」(比如「拿出」)。换机器人时只需换 import 列表
② 物体列表objects = ['salmon', 'microwave', ...]LLM 不会用到场景里没有的物体。situated awareness 不靠训练,靠 prompt 注入
③ 自然语言注释# 1: grab salmongrab('salmon')把任务拆成子目标,类似 chain-of-thought;防止 LLM 输出重复、发散、不连贯的动作序列
④ assert + else 恢复assert('close' to 'salmon')
else: find('salmon')
闭环环境反馈:执行前检查前置条件,不满足就走恢复分支。失败时不用整个计划推倒重来
🔮 直觉:为什么「写代码」比「写自然语言」更适合 LLM 规划?
因为代码有语法约束:函数名必须在 import 列表里、参数必须是 objects 列表中的字符串、注释和函数体要对得上。LLM 在「补全代码」任务上已经被海量 GitHub 数据训练过,它天然遵循这些约束。而自由文本生成的 LLM 输出虽然语义通顺,但没有任何机制阻止它「说人话但做不了」。

📊 关键结果(论文 Table I / III / IV)

实验对比关键数字(SR↑ / Exec↑ / GCR↑)
Table I · VirtualHome 主结果Huang et al. [2](GPT3)0.00 / 0.45 / 0.21
LangPrompt(GPT3,自然语言 prompt)0.00 / 0.36 / 0.42
ProgPrompt + GPT3(3 注释 + 3 反馈)0.34 / 0.84 / 0.65
ProgPrompt + DaVinci(2 示例,prompt 限制)0.22 / 0.60 / 0.46
ProgPrompt + Codex(代码预训练,总最佳)0.40 / 0.90 / 0.72
Table I · 消融(GPT3)3 注释 + 7 反馈0.28 / 0.82 / 0.56(反馈太多反而降)
7 注释 + 3 反馈0.30 / 0.65 / 0.58
7 注释 + 7 反馈0.18 / 0.68 / 0.42(overload)
注释过多(行 5 vs 3:3→7 注释)SR 0.34 → 0.30;GCR 0.65 → 0.58
Table III · 新场景泛化ENV-0(原场景)SR 0.34 / GCR 0.65
ENV-1 / ENV-2(未见场景)SR 0.56 / 0.56;GCR 0.81 / 0.72(新场景反而更好
Table IV · Franka 真机桌面 pick-and-place(含干扰物)Plan SR 几乎全 1.0;Exec 始终 = 1;唯一失败是 sort 任务把 soup can 错认为 bottle
三个最值得记的结论
(1) LangPrompt 完全失败(SR=0),ProgPrompt 在同一个 GPT3 上 SR=0.34——差距完全来自 prompt 结构,模型不变。
(2) Codex 比 GPT3 更好(0.40 vs 0.34),证明「代码预训练」和「代码风格 prompt」是双向匹配的。
(3) 注释 + 反馈都不是越多越好:3+3 最佳,7+7 反而 SR 掉到 0.18——LLM context 容量有限,信息密度比信息量更重要。

🌐 在领域中的位置

SayCan(LLM 打分 admissible actions) Huang [2](LLM 自由文本生成计划) Inner Monologue(加环境反馈) ProgPrompt(Pythonic prompt + assert)⭐ Code as Policies / Voxposer / RT-2(LLM 直接出代码或动作)

ProgPrompt 是「LLM as code generator for robots」这条线的开山之作之一(与同期的 Socratic Models [3] 并列)。它建立了「用编程语言结构约束 LLM 输出」的范式,之后的 Code as Policies(Liang 2023)把这个思路推到「LLM 直接生成 Python 调用 perception / control API」,Voxposer 则进一步生成 3D 空间中的 value maps。这条线最终汇入 VLA 大模型(RT-2、π0)的「动作 token 化」思路。

❓ 常见误区

ProgPrompt 是不是就是「让 LLM 写 Python」那么简单?

形式上是的,但价值在于「用哪种 Python 结构约束 LLM」。论文做了系统消融——import / object list / comments / assert-else 四件套,每一件都贡献正向。把注释从 3 条加到 7 条,SR 从 0.34 掉到 0.30(信息过载);反馈过多同理。所以不是「写 Python」牛,而是「用 Python 的特定结构」牛。

assert + else 真的能闭环纠错吗?真机上似乎没用?

对,真机实验中 ProgPrompt 没用反馈机制(Table IV 的 SR 是 Plan SR,假设 pick-and-place 成功)。原因是真机状态追踪不够稳定,assert 无法可靠检查。这是 ProgPrompt 的诚实局限——仿真里 assert 闭环很好,真机要靠感知系统先到位。

为什么不用更大的 LLM、不用 chain-of-thought?

论文用的是 2022 年的 GPT-3 / Codex,那会儿 CoT 还刚出来。但 ProgPrompt 的注释本质上就是一种 CoT——只不过「子目标 + 代码」比「自由思考 + 答案」结构化更强。论文 Table I 也专门验证了注释(即 CoT)的功效。

和 Code as Policies 比有什么区别?

ProgPrompt 生成的是高层任务计划(动作是 `find`、`grab`、`putin` 这种符号 API),Code as Policies 生成的是含感知/控制 API 调用的代码(比如直接调 `get_object_position`、`move_to`)。前者依赖环境提供 primitive,后者更接近端到端。ProgPrompt 是「LLM 做高层规划」,Code as Policies 是「LLM 做中低层控制」。