快速导览
两个真实的 episode,从第一次模型请求到结果:agent 1.4.1(1.4.2 只改变了由谁接手传送带任务;1.5.0 把这些运行的设置,即不用 Molmo2、不用世界模型,设为默认),使用 DeepSeek V4.1 Flash,在 RoboDojo 的评测包 0 上,遵循 XPolicyLab 契约(夹爪为指令值,不提供任务名,不报告步数上限)。第一个 episode 中,模型把任务交给学习型策略 Simate;第二个中,模型自己的程序完成任务。第一种是常见情况:在那次运行中(42 个任务,每个三个 layout),125 个 episode 中有 113 个被移交,平均每个 episode 2.3 次模型调用;程序自己完成的 12 个用了 16 次。
Episode 1:策略学过的任务
Section titled “Episode 1:策略学过的任务”“Stack the three bowls together.”(stack_bowls,layout 0)
程序 1:观察
Section titled “程序 1:观察”driver 发送系统提示词(RoboDojo 的 prompt pack,按 agent 的 skill 组装),然后发送一条用户消息:指令、机器人状态和三幅相机图像。决策原因是 start。模型在 28 s 内作答:
# PLAN: three white bowls to stack. Check policy list and locate bowls first.print(list_policies())obs = get_observation()for o in locate("white bowl"): print(o)观察类操作可以在 start 之前运行;任何移动手臂的操作都不行。list_policies() 返回了 Simate 的描述(services/simate.toml 中的 client.description),说明它从起始姿态出发能做什么,按操作的种类划分,从不按任务名划分。摘录如下:
It cannot pick one object out of several by reading what identifies it: a name, a number or labelwritten on it, or a picture shown as the target. [...] Often does part and leaves the rest: packingseveral objects into a box; stacking blocks in a pile or tower; [...] nesting bowls or cups; [...]locate 用 SAM 3 在每个相机中测量了每只碗。循环打印的是 locate 返回的字典的键(objects、text),而不是物体本身,但每个程序结束后,driver 都会重新测量已定位的物体并发送场景报告:
o1 'white bowl' at (-0.275, -0.066, 0.805) (seen in cam_head and cam_left_wrist; SAM 3 match 0.98), 12 x 11 cm, long side at 83 deg, a container; reached straight down by the left arm; too wide for the open jaws: pick takes it by an edge.o2 'white bowl' at (0.044, -0.127, 0.785) (seen in cam_head only, so assumed 0.02 m above the table; ...o3 'white bowl' at (0.248, -0.039, 0.804) (seen in cam_head and cam_right_wrist; SAM 3 match 0.98), ...下一条消息带有输出、场景报告、每个物体的特写(用于核对每个物体确实是所指名的那个),以及勾勒出物体轮廓的相机图像。
程序 2:移交
Section titled “程序 2:移交”# PLAN: three white bowls (o1 left, o2 center, o3 right) — task is nesting/stacking bowls.# Policy lists "nesting bowls or cups" under "Often does part and leaves the rest" -> strategy C.start("C", "stacking/nesting three bowls together")start("C", ...) 记录这一选择并立即运行策略,与 RoboDojo 自己的评测客户端的做法相同:从未被动过的起始姿态出发,使用任务本身的指令和两只手臂,每个动作块完整执行,每个 tick 都取新的观测。(A:移交并监督,用于描述中说它通常能完成的种类;C:先交给策略,再完成它留下的部分;B:用程序自己的工具。)Simate 把碗叠好,在第 230 步 RoboDojo 的成功检查通过,并在程序内部结束了该 episode,此时距程序开始 173 s,因此之后没有模型调用:
{"reward": 1.0, "success": true, "terminated": true, "truncated": false, "process_score": 100.0, "steps": 230}{"status": "episode_ended", "success": true, "steps": 230, "model_calls": 2, "verifier_calls": 0, "cells": 2, "decisions": {"start": 1, "program_end": 1}, "input_tokens": 19874, "output_tokens": 428, "cost_usd": 0.0025, "strategy": {"strategy": "C", "kind": "stacking/nesting three bowls together", "policy": "simate", "step": 0, "policy_ran": true}}如果策略在任务未完成时停下(手臂静止 5 s),程序就会在那里结束,模型会看到重新测量的场景,并剩下 4 次调用(post_policy_calls)来处理明显剩下的部分;用完之后,策略继续运行到 episode 结束。
Episode 2:程序自己完成的任务
Section titled “Episode 2:程序自己完成的任务”“Pick up the blue race toy car by 10 cm.”(general_pickup,layout 4)
程序 1:选择,然后观察
Section titled “程序 1:选择,然后观察”# PLAN: Task = pick up the blue race toy car and lift it 10 cm. It is named in the# instruction -> strategy B, my own tools. Step 1: start(B), locate the blue car.start("B", "pick up the blue race toy car by 10 cm")res = locate("blue toy car; blue race car")print(res["text"])for o in res["objects"]: print(o["id"], o["name"], o["position"], "reach:", o["reach"], "jaws:", o["jaws"], "size:", o["length"], o["width"])桌上还有其他物体;要拿哪一个需从指令中读出,因此提示词中的规则把任务交给 B。start("B", ...) 记录这一选择,不移动任何东西。SAM 3 匹配到相同像素的两个名称是同一个物体:
o1 'blue toy car' (also what 'blue race car' matched: the same pixels) at (0.255, -0.170, 0.790) (seen in cam_head and cam_right_wrist; SAM 3 match 0.91), 10 x 6 cm, long side at -13 deg; reached straight down by the right arm; the jaws close across its width.程序 2:一个错误
Section titled “程序 2:一个错误”r = pick("right_arm", "blue toy car", lift=0.10)ValueError: No object 'blue toy car' in the scene; locate first (ids: o1)工具接收的是场景报告中的 id。程序抛出了异常,kernel 把 traceback 裁剪到程序自己的栈帧,然后以原因 error 调用模型。
程序 3:一次接触
Section titled “程序 3:一次接触”# PLAN: o1 blue car at (0.255,-0.170,0.790). Use id "o1". Right-arm pick, lift 0.10, then finish holding.r = pick("right_arm", "o1", lift=0.10)print(r)print("holding:", holding("right_arm"))pick 是一次接触,程序在它之后结束,以便模型查看。其中的每个 skill 调用都是一次以事件结束的有界执行:
| 执行 | Tick 数 | 事件 |
|---|---|---|
move_eef(抓取点上方 6 cm,夹爪转到横跨小车的方向) |
23 | COMPLETED |
move_eef x4(下降:快速到抓取点上方 3 cm,然后 2、1、0) |
7, 9, 7, 9 | COMPLETED |
close_gripper、hold(夹紧 0.24 s) |
10, 6 | COMPLETED |
move_eef(抬起 10 cm) |
11 | COMPLETED |
{'ok': True, 'arm': 'right_arm', 'object': 'o1', 'grasp': 'across its width', 'grasp_point': [0.266, -0.1787, 0.7777], 'yaw_deg': 77.1, 'pitch_deg': 0.0, 'lift': 0.1, 'check': "'blue toy car' rose with the jaws (measured with SAM 3): held", 'note': 're-measured from above, the object was +11, -9 mm off; the grasp followed'}holding: True在抓取点上方,腕部相机重新测量了小车,抓取点随差值移动。契约不提供测得的夹爪开度,因此 pick 的检查测量物体是否随夹爪一起升起;只有无法测量时,视觉模型才会查看腕部相机,就像 holding() 那样。随后的场景报告显示小车(现在是 o2)被右臂抓着、高了 8 cm,并给出抓握情况:
Grip, right_arm holds o2 'blue race car': measured now in the air, the held object's centre is(-0.007, +0.006) from the grasp point (on the table before the pick it was (+0.000, +0.000));release applies this offset. Its lowest point hangs about 0.013 m below the grasp point.程序 4:结束
Section titled “程序 4:结束”# PLAN: Task = pick up the blue race toy car by 10 cm. Done: right_arm holds o2 'blue race car'# lifted ~0.10 m (grasp point 0.778 -> now 0.871). Task is to lift it, so keep holding and finish.print("holding right:", holding("right_arm"))finish("Picked up the blue race toy car with the right arm and lifted it 10 cm (held in air).")finish 结束了程序。driver 执行收尾,这是大多数 RoboDojo 任务需要的:每个没有抓着东西的夹爪张开(右夹爪抓着小车,保持闭合),然后两只手臂回到起始姿态。收尾期间 RoboDojo 在第 95 步因成功结束了该 episode。driver 用一个模型从不会看到的 token 读取结果:
{"reward": 1.0, "success": true, "terminated": true, "truncated": false, "process_score": 0.0, "steps": 95}{"status": "finished", "success": true, "steps": 95, "model_calls": 4, "verifier_calls": 0, "cells": 4, "decisions": {"start": 1, "program_end": 2, "error": 1}, "input_tokens": 43673, "output_tokens": 1601, "cost_usd": 0.0029, "vision_calls": 2, "strategy": {"strategy": "B", "kind": "pick up the blue race toy car by 10 cm", "policy": null, "step": 0}}四次模型调用,四个短程序:观察、一个错误的参数、一次接触、结束。vision_calls 统计程序内部发出的视觉请求(两次 holding() 查看)。程序与决策点解释设计;episode 运行时解释每个部分。