想象你是一家法式餐厅的主厨,威望极高却从不亲手颠勺。客人点单后,你飞快地在脑海中构思一道新菜,然后写成一页精确到克重与火候的菜谱,交给后厨的帮厨——他不懂创意,但能一丝不苟地照着菜谱完成每一步:切配、热锅、装盘。这家餐厅里,主厨就是你(大语言模型),帮厨就是机器人,而那张菜谱,就是本节的主角:Code-as-Policies(代码即策略)。 在传统的机器人编程里,人类工程师像“手把手教徒弟”一样,要么逐行敲出运动轨迹,要么在仿真环境里训练几百万步强化学习,才能让机器人学会一个动作。而 Code-as-Policies 提供了一条截然不同的路:让大模型直接把“把红色杯子放到托盘上”这样的自然语言指令,编译成一段可执行的 Python 代码。代码本身就是策略——它描述了机器人该如何调用底层动作 API、如何做判断、如何完成多步任务。帮厨不需要理解“优雅”,只需要忠实执行菜谱;机器人也不需要消化高深的语义,只需要逐行运行代码。这就是 LLM 与机器人之间的“翻译层”。
对 AI 从业者而言,Code-as-Policies 是理解“大模型如何落地具身智能”的第一块敲门砖:它把自然语言、代码生成与机器人控制缝合成一条完整链路,掌握了它,你就拿到了具身智能时代最实用的技术地图。
可视化演示
展示LLM生成代码控制机器人的流程、策略对比与代码执行示例,含自然语言到动作转换。
本节配有交互动效(进入视口后自动加载,约 3KB)。
代码生成流程 自然语言 LLM生成 NL→Code→Action 端到端
基本概念
在传统的机器人研发里,教会机器人一个新动作,就像一位工匠带徒弟:要么师父握着徒弟的手,一寸一寸地示范轨迹(示教编程);要么让徒弟在练习房反复试错几百万次,直到肌肉记忆形成(强化学习)。这两种方式都很慢,而且换个活儿又得从头教。现在来了一位新主厨——大语言模型。他从不亲自动手,而是听完客人的点单后,直接从胸口的菜谱集里抽出一张白纸,唰唰唰写出一份全新的菜谱,递给帮厨:照这个做。
这份“菜谱”就是代码。Code-as-Policies 的核心定义因此非常简洁:让大语言模型根据自然语言指令,直接生成一段可执行的程序,这段程序就是控制机器人的策略。整体结构分三层:最上层是自然语言指令(包括环境观察的多模态描述),中间是 LLM 推理与代码生成,底层是机器人可调用的动作 API 集合——比如 move_to、pick_up、place_down,相当于后厨的灶台、刀具和锅具。帮厨不需要聪明,只要厨房里有的工具(API),主厨就能写进菜谱里。
为什么要这么做?因为代码是一种极致的“结构化知识”。它既有严格的语法保证可执行性,又有变量、循环、条件分支等表达力来承载复杂逻辑。相比之下,直接让模型输出“关节角度序列”就像让主厨去手把手扳帮厨的胳膊,既低效又脆弱。Code-as-Policies 把策略生成问题,优雅地转化成了大模型最擅长的代码生成问题。从概率角度看,这个过程可以写成一个最优搜索问题:
理解了这个搜索问题,你就抓住了 Code-as-Policies 的命门:它不学“如何控制机器人”,而是学“如何写出控制机器人的代码”。回到餐厅:主厨的价值不在颠勺,而在把创意转化成帮厨可执行的精确步骤。Code-as-Policies 正是把“创意(自然语言)”翻译成“步骤(代码)”的那支笔。
- 策略符号$\pi_{\text{code}}$ 表示最终生成的代码策略,它是一段完整的程序而非单个动作。
- 搜索目标$\arg\max_{\pi}$ 表示在巨大的程序空间中搜索,找出让后面概率最大的那个程序。
- 条件概率$P(\pi \mid I, A)$ 表示在指令 $I$ 和动作 API 集合 $A$ 已知时,写出程序 $\pi$ 的可能性。$I$ 像客人的点单,$A$ 像后厨的可用厨具。
- 连乘分解$\prod_{t=1}^{T}$ 把整段程序的概率拆成 T 个 token 的连乘;$c_t$ 是第 t 个代码字符,$c_{<t}$ 是它之前已生成的所有字符——就像主厨写菜谱时逐字斟酌,每落一笔都参考前面写好的部分。
核心原理
后厨里一次完美的服务是什么样的?客人说“我要一杯温拿铁,配一块蓝莓芝士蛋糕”,主厨不会直接去端盘子,而是做三件事:听懂需求、拆解成动作序列(取杯、磨豆、打奶泡、切蛋糕、装盘)、然后写下来交给帮厨执行。Code-as-Policies 的完整原理,正是这条“点单→菜谱→出餐”链路的机械化。
首先是指令理解。大语言模型接收的不只是一句自然语言,还可以拼接上机器人的视觉观察——比如一张桌面俯视图、一个目标检测框的坐标列表。这相当于主厨不仅听客人说,还亲自看了一眼餐桌当前的状态。模型把这些信息编码成上下文,作为生成代码的依据。如果视觉信息以文本形式传入(如“视野中有红色杯子在坐标 (0.5, 0.3)”),这就是多模态大模型与代码策略的自然衔接点。
其次是代码生成与策略表示。策略不是一行动作,而是一段有逻辑的程序:它可能包含 if 语句做物体颜色判断,用 for 循环遍历多个待整理物品,用变量暂存中间状态。这在 Code-as-Policies 中被称为“代码快照”——一次推理生成的一段完整策略代码,像一张拍立得,定格了当前任务的解决思路。代码本身就是策略的表示形式,不需要额外的神经网络来编码动作分布。
最后是闭环反馈。菜谱不是写了就完事,帮厨做完菜,主厨要尝一口:菜咸了,就改配方。机器人的闭环同样如此:执行代码后获得新状态,如果任务未完成(比如杯子没抓稳),就把错误信息或新观察回填给大模型,让它修改代码再试。这形成了“感知 → 规划(写代码)→ 执行 → 再感知”的循环。用状态转移表达这个循环,就是简单的递推关系:
把指令理解、代码生成和闭环反馈串起来,就得到了 Code-as-Policies 的完整工作流。值得注意的是,闭环反馈并非每次都必须执行:对简单任务,一次生成的代码可以直接跑到完成;对长程复杂任务,则建议每执行几步就“看一眼”再决定是否修改代码。这种设计的优雅之处在于:代码本身既是控制器,又是沟通的媒介——无论人还是另一个智能体,都可以读懂并修改它。这正是主厨菜谱的魅力:不只帮厨能看懂,任何一位新来的厨师都能接手。
- 当前状态$s_t$ 是 t 时刻机器人的感知状态,包括机械臂关节角、物体位置、任务进度等,相当于帮厨眼前看到的一切。
- 策略输出$a_t = \pi_{\text{code}}(s_t)$ 表示把状态喂给代码策略,代码中的条件分支和函数调用根据输入决定此刻的动作 $a_t$。
- 状态转移$g(s_t, a_t)$ 是环境动力学,动作施加后世界如何变化——比如机械臂合拢夹爪后物体被抓起来了。$g$ 像灶火的化学反应,帮厨照做,食材就变了形态。
- 时间推进下标从 $t$ 变为 $t+1$,代表一个完整的“感知-动作-感知”循环结束,新状态进入下一轮规划;这个箭头就是主厨“尝菜”的动作。
关键算法
主厨写菜谱时并不是一气呵成的,他会想:“先写备料,再写热锅,然后写翻炒……”每写一步,都参考前面已经落笔的文字。大模型生成代码的策略与此完全同构:自回归生成。它不一次性吐出整段程序,而是一个字符一个字符地生成,每一步都在计算“下一个字符最该是什么”。这背后是条件概率的链式法则——整段代码的概率分解成每一步的条件概率乘积,正式写法在第一节的公式里已经给出。
自回归生成的具体采样方式有三种常用变体:贪心解码、温度采样和核采样。贪心解码是每步都选概率最高的 token,像固执的主厨只认最顺手的做法,结果稳定但略显保守;温度采样通过除以温度系数软化概率分布,温度高时选择更随机,像主厨偶尔大胆尝试新配料;核采样则只从累计概率达到阈值的 token 子集中抽样,兼顾稳定与多样。实际应用中,研究者常常先用贪心解码得到基准策略,失败后再用较高的温度重采样,试图探索不同的解决路径。
代码生成之后,最关键的算法环节是执行反馈与纠错。机器人真正运行生成的代码时,可能抛出 TypeError(参数写错)、AttributeError(调用了不存在的 API),或者逻辑错误(抓了杯子却忘了解除夹爪)。这时系统进入“执行纠错”模式:把堆栈错误和当前状态重新打包成提示词,附加一条“请修复上述错误”的指令,再次调用大模型生成修正版代码。这个循环本质上是一种受限的“试错搜索”——不过试错的单位是整段代码,而不是微观的动作。如果需要更严谨的形式化描述,可以把它看成在动作空间之上的策略迭代:
这套“生成-执行-纠错”的关键算法让 Code-as-Policies 有了超越静态策略的适应能力。它不像传统 RL 那样需要人为设计奖励函数和几百万步的采样,而是利用大模型内化的世界常识,快速给出一个合理的初始解,再用执行反馈做局部修正。诚然,这种迭代方式消耗的 token 数量不小,但在任务复杂度和开发效率之间,它提供了一个相当实用的折中点——像一位经验丰富的主厨,两次试味就能端出合格的菜。
- 当前策略$\pi^{(k)}$ 是第 k 轮生成的代码,它是迭代优化的起点,可能含有 bug 或逻辑缺陷。
- 错误信号$e^{(k)}$ 是执行第 k 轮代码后获得的反馈,可能是异常堆栈、任务完成度评价或人类纠正指令。它像客人皱眉头的那一瞬间——主厨立刻知道菜出了问题。
- LLM 修复器$\text{LLM}\bigl(\cdot\bigr)$ 把“旧策略 + 错误 + 原始指令 + API 库”一起作为上下文输入,要求大模型输出修复后的代码。这是迭代公式的发动机。
- 迭代推进上标从 $k$ 到 $k+1$,表示一轮写代码、执行、看反馈、改代码的循环完成,策略质量逐步提升。这对应主厨反复试味,直到客人满意为止。
性能特性
为什么 Code-as-Policies 能在具身智能领域迅速走红?答案藏在三个关键特性里:实时生成、可解释性和组合泛化。这三个特性,恰好对应一张好菜谱的三个品质:写得快、看得懂、能迁移。
实时生成是 Code-as-Policies 最直观的优势。传统 RL 训练一个策略动辄需要数百万次环境交互,在仿真器里跑上几天几夜;而 Code-as-Policies 一次前向推理通常在秒级完成——主厨看一眼食材(视觉输入和指令),下一秒就能把菜谱(代码)拍在桌上。虽然后续需要执行验证和可能的纠错迭代,但初始策略的获取成本被压缩了几个数量级。这一点在任务频繁更换的真实场景中至关重要:机器人在产线上每换一种活儿,现场工程师只需重新输入一句指令,而不是重新训练几天模型。
可解释性源于代码的天然属性。神经网络的策略是一堆不可读的权重,就像帮厨练出的“潜意识手感”——做得好但说不清为什么。而 Code-as-Policies 输出的 Python 代码,任何工程师都能直接阅读、审查和修改。当机器人做出意外行为时,调试者可以像读菜谱一样逐行检查逻辑:是 if 条件写错了,还是 pick_up 的目标坐标给反了?这种白盒特性让安全审查成为可能,也极大降低了维护成本。在工业场景,可审计的决策逻辑往往是合规的硬性要求。
组合泛化是 Code-as-Policies 最具想象空间的特性。LLM 在训练中见过海量代码,天然理解“用已知函数组合出新功能”的编程范式。当动作 API 被设计成互相正交的“积木块”时,新任务往往只是旧积木的新组合——主厨从不需要发明新的烹饪技法,他只是把已有的切、炒、炖按新顺序排列。这意味着一个从未出现在训练数据中的任务,只要它能被分解成库中 API 的合法组合,大模型就能生成可行策略。研究表明,这种组合式泛化能力在小样本条件下显著优于传统端到端方法,让机器人从“见过才会做”升级为“想明白就会做”。
当然,这三个特性之间也存在张力:实时生成要求 API 封装足够高层,但过度高层会牺牲灵活性;可解释性要求代码简单直接,但复杂任务天然需要复杂程序。优秀的系统设计者会像主厨管理后厨一样,仔细权衡 API 的粒度——既不能细到让帮厨手足无措,也不能粗到让主厨无法表达创意。这本身就是一门平衡的艺术。
优化策略
一张平庸的菜谱和一张优秀的菜谱之间,差别常常不在烹饪天赋,而在细节:主厨会把“适量盐”改成“3 克盐”,把“热锅”改成“锅烧至 180 度”。对 Code-as-Policies 而言,优化策略同样藏在细节里,主要集中在四个方向:提示词设计、示例引导、API 扩展和错误修复。
提示词设计是第一道关口。大模型生成的代码质量,对指令的清晰度极其敏感。一个优秀的提示词应当包含三部分:角色设定(“你是一个机械臂控制专家”)、API 签名文档(列出每个函数的参数类型和功能)、任务描述与约束(“红色杯子优先于蓝色杯子”)。这就像主厨在菜谱开头先写明厨房规矩和厨具清单,帮厨就不容易拿错锅。实践中,把 API 文档直接拼入系统提示词,通常能显著降低 AttributeError 等低级错误的发生率。
示例引导,即 few-shot prompting,是提升代码质量的另一个杠杆。在提示词中附上 2-3 个“输入指令 → 输出代码”的范例,相当于给主厨看了几道标准菜的成品照片。大模型会模仿范例的代码风格、缩进习惯和注释方式,生成更稳定、更少 bug 的代码。研究经验表明,精选覆盖不同任务类型的示例(如一个移动类、一个抓取类、一个带条件分支类)比堆砌同类示例效果好得多。示例不在多,贵在典型,就像主厨的参考菜谱无需收录百道菜,关键在于覆盖不同技法。
扩展 API 是提升能力上限的战略性手段。Code-as-Policies 有个朴素的规律:API 库里有什么,大模型就能写出什么。如果机器人的动作库只有 move 和 grab,那它永远学不会“拧开瓶盖”,因为这需要 rotate_wrist 这样的原子动作。聪明的做法是不断把高频子任务固化为新 API——比如把“抓取并放置”封装成 pick_and_place(obj, target)。这像主厨把常用酱汁提前熬好存进冰箱,做菜时拿出来直接调味,效率翻倍。API 的粒度设计也因此成为 Code-as-Policies 系统的核心竞争力之一。
错误修复策略解决的是“菜做坏了怎么办”的问题。除了前面提到的堆栈信息回填,还有一种轻量级技巧:在生成的代码外层包裹断言与检查逻辑,比如在抓取后立即查询夹爪力传感器,若不达标就执行失败分支。这种“防御性编程思维”被写进提示词后,大模型生成的代码天然具备自检能力。更进一步的实践是维护一个“错误修复经验库”——把历史上出现过的错误与对应的修复方案记录下来,在后续提示词中作为检索增强的参考,让主厨每次都带着前车之鉴开工。
实际应用
如果说前面的内容都在讲“菜谱怎么写”,这一节我们走进真实的后厨,看看这些菜谱在哪些餐桌上真正被端了出来。Code-as-Policies 目前最活跃的应用场景集中在三大类:桌面操控、移动导航和多步长程任务规划。
桌面操控是 Code-as-Policies 最成熟的应用场景。在典型的科研与工业环境中,一台单臂机械臂面对一张桌面,任务是“把红色马克杯从左侧托盘移动到右侧沥水架”。传统方法需要工程师手工编写运动规划或采集演示数据训练模仿学习模型;使用 Code-as-Policies 后,操作员只需输入一句自然语言,大模型便会结合桌面上物体的检测框坐标,生成包含 pick_up("red_cup", [0.2, 0.5]) 和 place_on("drain_rack", [0.8, 0.3]) 的完整代码。即使目标物体位置微调,代码中的坐标参数也能通过视觉模块实时更新,实现跨位置迁移。
移动导航是另一个理想阵地。部署在仓储或家庭环境中的轮式机器人,需要根据指令在环境里移动并执行操作。Code-as-Policies 让导航策略可以用代码表达复杂的规则:比如“先去充电桩附近巡查,若看到人则绕行,最后回到门口等待”。这种带条件逻辑的指令,天然适合翻译成 Python 的 if-elif-else 结构。研究者甚至把语义地图(如“蓝色区域是货架,绿色区域是通道”)编码成数据变量,让代码在语义层面完成规划,再调用底层路径规划 API 执行。大模型的常识推理能力在这里大放异彩——它不会让机器人试图“穿过墙壁”,因为这违反了代码中隐式的物理约束。
多步长程任务是目前最富挑战也最激动人心的方向。想象一个实验室助理机器人接到指令:“把培养皿从培养箱拿出来,放到显微镜载物台上,调整焦距,然后给实验员发一条通知。”这种任务链条长、中间环节多,任何一环出错都会导致连锁失败。Code-as-Policies 的做法是把整条链路写成一个带状态机的 Python 脚本:每个关键步骤前检查前置条件,步骤后验证结果,失败则打印日志并进入重试分支。更关键的是,代码可以被拆分成可独立测试的函数——load_sample()、position_on_stage()、notify_experimenter(),每个函数单独调试通过后再组合运行,大大降低了长程任务的调试难度。这种“模块化菜谱”的思路,让原本需要数周开发的机器人应用,压缩到数小时内完成原型验证。
常见坑点
再老练的主厨也有翻车的时候,而 Code-as-Policies 的翻车现场,通常集中在三个坑点:代码崩溃、指令歧义和 API 盲区。认识这些坑,是让“菜谱”从演示走向可靠的第一步。
代码崩溃是最常见的失败模式。大模型生成的代码虽然是概率上的“最优解”,但概率高不等于语法对、逻辑对。典型错误包括:API 名称拼写错误(把 pick_up 写成 pickup)、参数数量不匹配(忘了传 target 参数)、引用了未定义的变量,以及更隐蔽的逻辑 bug——比如在 if 分支里把 or 和 and 用反。这类错误的典型特征是运行时抛异常,机器人停在半途不知所措。对策是建立“错误-修复”循环,把异常消息反馈给大模型,同时确保执行环境提供清晰可读的堆栈日志——就像帮厨做砸了要知道是哪一步砸的,才能向主厨准确汇报。
指令歧义是更隐性也更难缠的坑。当人类说“把那东西挪过去”,“那东西”是哪个?“挪过去”是挪到哪里?大模型不是读心术士,它会基于训练数据中的先验做出猜测,而猜测一旦错误,代码就偏离了用户的真实意图。经典案例中,研究者让机器人“把苹果放到香蕉旁边”,但不说明用哪只手、抓苹果的哪个部位、放到香蕉左侧还是右侧——不同批次生成出的代码行为可能截然不同。消解歧义的关键在于约束补全:提示词中明确列出任务约束(“使用左手”“放在香蕉右侧 5 厘米处”),或在与用户交互时通过追问澄清需求。这就像客人只说“随便来点吃的”,主厨必须追问忌口和口味偏好,才能写出不会挨骂的菜单。
API 盲区是当前技术范式的边界所在——不是看不懂指令,而是工具里根本没有对应功能。当任务需要“用螺丝刀拧开面板”而 API 库只有 pick 和 place 时,再强的大模型也无能为力;它甚至可能“幻觉”出并不存在的 screw_open() 函数,生成一段调用它的代码,执行时必然失败。这与人类新手很像:没摸过螺丝刀的人会想当然地用手拧。解决 API 盲区有两条路:一是持续拓展 API 库,把新技能固化为新函数;二是在提示词中明确罗列“可用函数清单”,并要求模型“只能调用清单中的 API”,用约束把幻觉风险圈在牢笼里。前者是治本,后者是止血,工程上两手都要抓。
工程实践
优秀的餐厅不只有好主厨,更有严密的运营流程。Code-as-Policies 从论文走向产品,同样需要一套工程实践规范来兜底。这套规范可归纳为三件事:仿真验证、安全约束、日志监控。
仿真验证相当于“空厨房试菜”。在把代码部署到真实机器人之前,先让代码在模拟器中跑一遍。仿真环境提供了物理引擎、渲染器和碰撞检测,能以极低的成本暴露大部分 bug。具体的工程做法是:为每个 API 函数建立仿真实现与真机实现的双份代码;生成的策略先指向仿真 API 执行,全部步骤无异常后,再动态切换为真机实现。有些团队会在此基础上叠加“域随机化”——随机扰动物体位置、光照和摩擦系数,检验策略的鲁棒性。这种做法让主厨有了一个随时可以练手而不浪费食材的试验灶台,大幅提升了代码首次上真机的成功率。
安全约束是不可逾越的底线。无论大模型生成的代码多么“聪明”,它终究缺乏对物理世界的敬畏——可能生成一个让机械臂以最高速度撞向桌角的动作。工程上通常在代码执行层加设三层防护:第一层是 API 内部的运动学限制(关节速度上限、工作空间边界),第二层是代码执行前的静态安全检查(扫描是否有危险函数调用或参数越界),第三层是物理急停与力控保护(碰撞检测到异常阻力立即停机)。这三层像帮厨身后的护栏、安全带和紧急刹车,确保即使菜谱本身有误,后果也在可控范围内。在部署策略中,还可以要求大模型在生成代码时附带一条“安全自述”——解释每段关键代码的安全性考虑,给审查者提供判断依据。
日志监控是持续改进的引擎。每一次策略执行都应该留下完整的记录:输入的指令、生成的代码文本、每一步 API 调用的参数、返回值、执行耗时和异常信息。这些日志构成了一个“失败案例库”,可以用于三类后续优化:一是离线分析高频错误类型,反推 API 设计或提示词模板的改进方向;二是构建“指令-代码-结果”三元组数据集,用于微调专属的代码生成模型;三是建立自动化回归测试,每次更新提示词或 API 库后,用历史案件重跑一遍,防止“修好一个 bug 引出三个新 bug”。主厨正是靠着后厨里厚厚的一摞出餐记录,才能不断精进配方,让餐厅的回头客越来越多。
AI 应用场景
仓库分拣机器人的零代码部署
阶段:策略生成与部署
解决的问题:传统仓库分拣机器人面对新品类商品时,需要工程师重新编写抓取逻辑或采集数百条演示数据训练模型,部署周期长达数周。Code-as-Policies 让现场操作员用自然语言描述分拣规则(如“把红色包装的放左边,蓝色放右边”),大模型立即生成对应的分拣策略代码,接入既有的吸盘与传送带 API,分钟级完成新任务的部署。
具体案例:某电商仓库的机械臂原用于分拣标准纸箱,突然引入一批带气泡膜的异形包装。现场主管在系统中输入“异形包装轻拿轻放,先抓取靠近传送带入口的件”,Code-as-Policies 生成的代码自动调整了吸盘负压参数与抓取顺序,分拣成功率从 61% 提升至 93%,整个过程耗时不到 40 分钟。
家庭服务机器人的个性化指令
阶段:人机交互与任务规划
解决的问题:家庭用户不具备编程能力,却希望机器人理解个性化的口头指令(如“我出门后把客厅的窗帘拉上,但别拉阳台那扇”)。传统固定逻辑无法覆盖千差万别的家庭布局;用 Code-as-Policies,机器人把用户指令与家庭环境的语义地图(窗户位置、窗帘类型)组合,由大模型生成包含条件判断的导航与操作代码,实现千人千面的服务策略。
具体案例:在一项服务机器人原型测试中,用户对机器人说“晚饭后如果下雨,就把收在储物间的雨伞拿到门口”。系统结合天气预报 API 和房间地图,生成了包含 if rain_tomorrow: 分支的 Python 策略:先导航到储物间,调用 grab("umbrella"),再移动到门口 place("umbrella_rack")。雨停时则只执行一句 no_action。测试持续两周,任务完成率达 87%。
科研实验室的自动化助手
阶段:多步实验流程编排
解决的问题:科研实验包含大量标准化但繁琐的操作流程(移液、震荡、孵育、记录),商业实验室自动化设备通常需要专业人员用专用脚本配置。Code-as-Policies 让研究员直接用自然语言描述实验步骤,大模型自动生成控制移液工作站与酶标仪的代码序列,并可随时插入条件判断(如“若吸光度超过阈值则重复测量”),极大降低了实验自动化的门槛。
具体案例:某生物实验室的日常任务“把 96 孔板从孵育箱取出,在室温平衡 5 分钟,放入酶标仪读取 450nm 吸光度,保存数据并按样本编号重命名”。研究员将这段描述输入系统,生成的代码调用了 incubator.open()、reader.measure(450) 与 save_csv() 等 API,全程在预置的双层安全约束下运行,一次执行成功,节省了约每周 3 小时的重复劳动。
Python 演示
import ast class RobotArm: """模拟机械臂:只提供四个基础动作 API。""" def __init__(self): self.x = 0.0 self.y = 0.0 self.carry = None self.events = [] def move_to(self, x, y): self.x = x self.y = y self.events.append("move_to({}, {})".format(x, y)) def pick_up(self, obj): if self.carry is not None: raise RuntimeError("hand is not empty") self.carry = obj self.events.append("pick_up({})".format(obj)) def place_down(self, obj): if self.carry != obj: raise RuntimeError("wrong object in hand") self.carry = None self.events.append("place_down({})".format(obj)) def report(self): return "\n".join(self.events) class PolicyExecutor: """执行 Code-as-Policies 生成的策略代码(文本)。""" def __init__(self, robot): self.robot = robot self.allowed = {"move_to": 2, "pick_up": 1, "place_down": 1} self.call_count = 0 def run(self, code): tree = ast.parse(code) for node in ast.walk(tree): if isinstance(node, ast.Call): self._check_and_call(node) return self.robot.report() def _check_and_call(self, node): func = node.func if not isinstance(func, ast.Name): raise ValueError("only plain function calls are allowed") name = func.id if name not in self.allowed: raise NameError("unknown API: " + name) args = [] for arg in node.args: if isinstance(arg, ast.Constant): args.append(arg.value) else: raise TypeError("argument must be a literal constant") if len(args) != self.allowed[name]: raise TypeError("wrong arg count for " + name) proxy = getattr(self.robot, name) proxy(*args) self.call_count += 1 def mock_llm_generate(instruction, api_doc): """模拟 LLM:根据自然语言指令和 API 文档生成策略代码。""" templates = { "cup": "move_to(0.5, 0.5)\npick_up('red_cup')\nmove_to(0.8, 0.2)\nplace_down('red_cup')", "book": "move_to(0.3, 0.6)\npick_up('blue_book')\nmove_to(0.7, 0.4)\nplace_down('blue_book')", } for key, code in templates.items(): if key in instruction: return code raise ValueError("no matching policy template") if __name__ == "__main__": api_doc = "move_to(x, y), pick_up(obj), place_down(obj)" instruction = "请把红色杯子放到托盘上" policy = mock_llm_generate(instruction, api_doc) print("生成的策略代码:") print(policy) robot = RobotArm() runner = PolicyExecutor(robot) runner.run(policy) print("\n执行轨迹:") print(robot.report()) print("\nAPI 调用次数:", runner.call_count)
tree = ast.parse(code)把策略代码字符串解析成抽象语法树,相当于先“读一遍菜谱”,逐行理解结构后再动手。ast.walk(tree)遍历语法树上的所有节点,找到每一个函数调用,像帮厨按菜谱顺序逐条确认要用的厨具。if name not in self.allowed:检查函数名是否在白名单 API 库中,防止大模型幻觉出不存在的函数,这是安全防护的第一道闸门。if len(args) != self.allowed[name]:核对参数个数,对应“每个 API 要传几个参数”的签名约束,提前拦截TypeError。getattr(self.robot, name)(*args)动态调用机械臂上对应的动作方法,这是“照菜谱执行”的那一刻,真正让代码变成物理动作。self.call_count += 1记录 API 调用次数,用于日志监控与性能分析,相当于统计主厨一天写了几道菜。mock_llm_generate(instruction, api_doc)用规则模板模拟 LLM 的指令理解与代码生成,现实中这里会替换为真实的gpt-4o或 Claude 等大模型接口。
常见误区
误区我一直以为 Code-as-Policies 是让大模型直接输出机器人的关节角度、力矩大小这类底层控制信号,这样执行起来最直接,精度也最高。
正解这误解了“代码即策略”的分层思想。Code-as-Policies 生成的是高层代码,调用的是精心设计的动作 API(如 move_to、pick_up),底层电机控制完全由 API 内部完成。如果把策略写到关节层,代码会退化成海量的数值数组,大模型既难生成也难纠错,可解释性和泛化能力荡然无存。就像主厨的菜谱写的是“热锅至 180 度”,而不是“分子运动速率达到多少”——菜谱必须抽象到帮厨能执行的粒度。
为何常见:初学者容易从字面理解“控制机器人”,以为越底层越精确;加上机器人学教材中策略常被定义为状态到动作(关节/力矩)的映射,因此想当然地套用到了 Code-as-Policies 上。
误区我认为一个成熟的 Code-as-Policies 系统应该一次推理就生成完美可执行的代码,如果运行报错,就说明这个模型能力太差,应该换更强的模型。
正解真实系统普遍采用“生成-执行-纠错”的迭代循环,首次生成代码的成功率能到 60%-80% 就已经非常出色。错误信息、异常堆栈和环境状态观察都是宝贵的反馈信号,会被重新打包进提示词让模型修正。这就像主厨做新菜几乎不可能不尝一口就端上桌——试味不是失败,而是流程的一部分。工程上关注的核心指标是“经过 2-3 轮纠错后的最终成功率”,而不是“一次成功率”。
为何常见:LLM 在通用代码生成评测集上的高分给了人们“生成必须一次对”的错觉,忽略了机器人代码要面对真实物理世界的不确定性。
误区既然 LLM 什么代码都会写,那动作 API 随便给几个就行,模型总能用现有接口组合出任何行为。
正解事实恰恰相反,动作 API 的设计质量往往是系统成功率的决定性因素。API 粒度过粗(如只有 do_task(task_name)),模型没有表达空间;粒度过细(如 set_joint_angle(joint_id, theta)),模型容易在细节中迷失。好的 API 设计是分层正交的,像乐高积木一样既基础又可组合。大模型解决不了“工具不存在”的问题——再强的主厨,厨房里没有蒸锅也做不出清蒸鱼,幻觉出来的 screw_open() 只会让机器人在真机上撞墙。API 库的建设是一项需要持续迭代的工程,而非模型能力的附属品。
为何常见:大模型在海量代码上的惊艳表现让很多人高估了它的“无中生有”能力,忽视了代码生成本质上仍受限于可调用函数的集合。
误区我认为既然大模型能够用常识写出策略代码,强化学习、模仿学习这些传统机器人学习方法就可以彻底退休了,Code-as-Policies 是唯一的未来。
正解Code-as-Policies 擅长的是利用常识快速生成合理的初始策略,但它在精确力控、高频反馈、极长时域任务上依然力不从心。实践中更常见的是混合架构:用 Code-as-Policies 做高层任务分解与参数选择,用强化学习学好的低层技能(如精确插孔、柔顺装配)作为被调用的 API。这就像主厨负责设计菜单和把控口味,但拉糖丝的绝活仍需专门的面点师傅来完成——两者各司其职,互为补充而不是互相替代。
为何常见:技术炒作容易让人产生“新范式推翻旧范式”的二元思维,忽略了工程系统往往是多种技术路线的有机融合。
面试题
Q1. 在 Code-as-Policies 中,“策略”的载体形式是什么?
- A.一段可执行的程序代码← 正确答案
- B.一组关节力矩的向量
- C.一个端到端的神经网络权重
- D.一张预定义的动作轨迹表
答案:A。Code-as-Policies 的核心就是“代码即策略”,大模型生成一段 Python 程序作为策略载体,通过调用高层 API 控制机器人。B 是经典控制层输出,C 是端到端 RL 的产物,D 是示教编程的轨迹存储方式,三者都不是 CaP 的策略形式。
Q2. 动作 API 库在 Code-as-Policies 中扮演什么角色?
- A.机器人可调用的基础技能集合← 正确答案
- B.大模型的训练数据集
- C.机器人的视觉传感器
- D.任务完成的评价指标
答案:A。API 库是机器人可执行的基础动作集合(如移动、抓取、放置),大模型生成的代码只能调用库内函数。它相当于后厨的厨具库——主厨的菜谱里只能出现厨房里有的工具。B 是训练语料,C 是感知设备,D 是评估标准,均不相关。
Q3. 大模型逐字生成代码时,每一步在计算什么?
- A.下一个 token 的条件概率分布← 正确答案
- B.整段代码的全局最优路径
- C.机械臂关节的逆运动学解
- D.环境奖励的期望总和
答案:A。自回归生成的核心是每一步计算 $P(c_t | c_{<t})$,即给定已生成前缀,预测下一个字符的概率分布,然后采样或取最大。B 是全局解码的理想目标但实际逐token推进,C 是运动学问题,D 是强化学习的目标函数。
Q4. 下列哪些属于 Code-as-Policies 的典型优势?
- A.策略以代码形式可读可改← 正确答案
- B.借助 LLM 常识快速生成初始解← 正确答案
- C.天然支持新任务的组合泛化← 正确答案
- D.完全不需要任何仿真环境验证
答案:ABC。代码策略可解释、可被人审查修改(A);LLM 用预训练常识秒级生成初始策略(B);只要 API 覆盖足够,新任务可以视为旧积木的新组合(C)。D 错误——工程实践中仿真验证恰恰是必要环节,没有充分验证的代码上真机风险极高。
Q5. 在 Code-as-Policies 中,通常使用哪种采样方式生成首个可行策略?
- A.贪心解码← 正确答案
- B.随机撒点
- C.蒙特卡洛树搜索
- D.遗传算法
答案:A。贪心解码每步选概率最高的 token,生成稳定且成本低,适合作为首个候选策略;失败后再用温度采样增加随机性探索。B/C/D 都不是 LLM 代码生成的常规解码策略。
Q6. 哪些原因会导致 Code-as-Policies 生成的代码执行失败?
- A.API 名称拼写错误或参数数量不匹配← 正确答案
- B.指令本身存在语义歧义← 正确答案
- C.任务需要库中不存在的能力← 正确答案
- D.机器人关节电机的固件版本过旧
答案:ABC。API 调用错误是最常见的直接失败原因(A);指令歧义会让模型猜错意图导致逻辑错误(B);API 盲区使任务根本无法表达(C)。D 属于底层硬件运维问题,与代码生成失败无直接因果,且即使是设备问题也会以 API 异常形式呈现而非生成阶段失败,因此不选。
Q7. 代码快照(code snapshot)在 Code-as-Policies 中指的是什么?
- A.一次推理生成的完整策略代码← 正确答案
- B.机器人摄像头的当前画面
- C.训练数据的缓存副本
- D.API 库的版本控制记录
答案:A。代码快照是 LLM 一次前向推理输出的整段策略代码,像一张“拍立得”定格了当前任务的解决思路。它可以在后续迭代中被引用、修改或回滚。B 是视觉状态,C 是数据集,D 是版本管理,均与概念不符。
Q8. 与端到端强化学习相比,Code-as-Policies 输出的策略有何本质不同?
- A.它是人类可读并可修改的程序← 正确答案
- B.它是压缩后的神经网络权重
- C.它是高维的连续动作流
- D.它是不可解释的状态映射表
答案:A。CaP 的策略是结构化代码,可读、可审查、可手工微调,这是它与 RL 黑盒策略的本质区别。B 是 RL 网络的形态,C 是底层控制器的输出特征,D 与代码策略相反。代码的白盒特性让安全审计和逻辑修正成为可能。
Q9. 以下哪些工程手段能有效提升 Code-as-Policies 的鲁棒性?
- A.在提示词中附加补全 API 文档← 正确答案
- B.提供多样化的 few-shot 示例← 正确答案
- C.结合仿真环境验证再部署真机← 正确答案
- D.将生成代码一次性执行到底
答案:ABC。API 文档补全可减少参数调用错误(A);多样化示例引导模型输出稳定风格(B);仿真试运行暴露大部分 bug 后再上真机(C)。D 是错误做法——工程上恰恰推荐逐步执行、逐步验证的闭环策略,而不是盲目地一次跑到头。
Q10. Code-as-Policies 最依赖大模型的哪项基础能力?
- A.指令跟随与代码生成← 正确答案
- B.图像风格迁移
- C.语音情感识别
- D.数据库事务管理
答案:A。CaP 的链路是“自然语言指令 → 代码策略”,两个核心环节分别依赖指令理解和代码生成能力。B/C/D 分别是图像生成、语音处理和数据库领域的能力,与机器人策略生成没有直接关系。