ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# 实验 6-11 至 6-12:XLeRobot 自主操作与闭环策略比较
|
||||
|
||||
本目录给出实验 6-11 的真实硬件扩展契约,并以实验 6-12 的非致动模拟运行比较三种闭环策略;当前证据标识统一为 `6-12`。
|
||||
|
||||
本实验把原来的导航任务改成桌面操作规划。RoboCrew 仍然负责高层智能体循环,XLeRobot 仍保留为可选的执行器接入对象;本地验收使用确定性的桌面模拟器,避免把 Gemini API、机械臂或串口可用性误报成实验结果。
|
||||
|
||||
## 任务
|
||||
|
||||
场景中有红色杯子、黄色纸张、托盘和垃圾盒。规划器需要完成:
|
||||
|
||||
```text
|
||||
抓起红色杯子 → 放入托盘
|
||||
抓起黄色纸张 → 放入垃圾盒
|
||||
验证最终状态
|
||||
```
|
||||
|
||||
工具契约只有五个职责明确、权限固定的工具,每次调用只完成一件明确的事:
|
||||
|
||||
```text
|
||||
observe_scene() pick(object_id)
|
||||
place(object_id, target_id)
|
||||
verify_state() stop()
|
||||
```
|
||||
|
||||
`pick` 和 `place` 在真实 XLeRobot 适配器中必须映射为经过标定、限速、有超时的动作原语;模型不能直接输出任意关节角。契约定义见 `xlerobot_tool_contract.py`。
|
||||
|
||||
## 运行
|
||||
|
||||
```bash
|
||||
cd chapter6/gemini-xlerobot-navigation
|
||||
python desktop_planner.py --episodes 128 --seeds 20260808,20260809,20260810 --failure-probabilities 0.0,0.25,0.5 --output-dir validation/runs/local-gpu
|
||||
python validate_evidence.py validation/runs/local-gpu/evidence.json
|
||||
```
|
||||
|
||||
正式协议使用 3 个随机种子、0、0.25、0.5 三档“瞬时失败”概率和每格 128 个回合,共 3456 个回合;这里的失败是模拟器人为注入的一次性抓取失败,不是声称真实机械臂的故障率。每个种子都重新训练并测试一个小型动作条件世界模型。脚本比较三种执行方式:
|
||||
|
||||
- `open_loop`:一次提交完整动作序列,忽略中途失败;
|
||||
- `closed_loop`:每个技能后重新观察,失败时重试;
|
||||
- `predictive`:使用世界模型比较候选技能,再执行并验收。
|
||||
|
||||
实验注入一次可恢复的抓取失败,记录各模式的成功率、工具调用次数、恢复次数、世界模型测试误差和完整事件日志。预期现象是开环策略会损失一部分任务,闭环和预测式策略能够恢复。
|
||||
|
||||
## XLeRobot/RoboCrew 扩展
|
||||
|
||||
真实运行需要将 `TOOL_CONTRACT` 绑定到固定版本的 RoboCrew 工具注册和 XLeRobot 手臂控制器,并增加工作空间、急停、观察员和动作回执门禁。当前仓库的本地 GPU 验收不会调用 Gemini API、打开串口或执行机器人动作;硬件扩展必须单独生成真机证据。
|
||||
@@ -0,0 +1,285 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Experiment 6-12: desktop manipulation planning with a local GPU backend.
|
||||
|
||||
The local run keeps the RoboCrew-style tool contract and the XLeRobot adapter
|
||||
boundary, but executes against a deterministic tabletop simulator. This
|
||||
makes planner, postcondition, retry and short-horizon world-model behavior
|
||||
fully reproducible without claiming that a real robot moved.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image, ImageDraw
|
||||
from torch import nn
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from robotics_lab_common import device_info, relative_or_absolute, select_device, seed_everything, sha256, write_json
|
||||
|
||||
OBJECTS = ("red_cup", "yellow_paper")
|
||||
TARGETS = ("tray", "bin")
|
||||
ACTIONS = ("pick_red_cup", "place_red_cup", "pick_yellow_paper", "place_yellow_paper")
|
||||
TOOL_NAMES = ("observe_scene", "pick", "place", "verify_state", "stop")
|
||||
|
||||
|
||||
@dataclass
|
||||
class DesktopState:
|
||||
status: list[int] # 0=on table, 1=held, 2=placed
|
||||
target_available: list[bool]
|
||||
|
||||
def copy(self) -> "DesktopState":
|
||||
return DesktopState(self.status[:], self.target_available[:])
|
||||
|
||||
def done(self) -> bool:
|
||||
return self.status == [2, 2]
|
||||
|
||||
|
||||
class DesktopToolAdapter:
|
||||
"""RoboCrew-compatible semantic tool boundary for local validation."""
|
||||
|
||||
def __init__(self, seed: int, failure_probability: float = 0.25):
|
||||
self.rng = random.Random(seed)
|
||||
self.state = DesktopState([0, 0], [True, True])
|
||||
self.failure_probability = failure_probability
|
||||
self.injected_failure = False
|
||||
self.events: list[dict[str, Any]] = []
|
||||
|
||||
def observe_scene(self) -> dict[str, Any]:
|
||||
observation = {"objects": dict(zip(OBJECTS, self.state.status)), "targets_available": dict(zip(TARGETS, self.state.target_available))}
|
||||
self.events.append({"tool": "observe_scene", "ok": True, "observation": observation})
|
||||
return observation
|
||||
|
||||
def _maybe_fail(self, action: str) -> bool:
|
||||
if action == "pick_yellow_paper" and not self.injected_failure and self.rng.random() < self.failure_probability:
|
||||
self.injected_failure = True
|
||||
return True
|
||||
return False
|
||||
|
||||
def pick(self, object_id: str) -> dict[str, Any]:
|
||||
if object_id not in OBJECTS:
|
||||
result = {"ok": False, "reason": "unknown_object"}
|
||||
else:
|
||||
index = OBJECTS.index(object_id)
|
||||
action = f"pick_{object_id}"
|
||||
if self._maybe_fail(action):
|
||||
result = {"ok": False, "reason": "injected_transient_grasp_failure"}
|
||||
elif self.state.status[index] != 0:
|
||||
result = {"ok": False, "reason": "object_not_on_table"}
|
||||
else:
|
||||
self.state.status[index] = 1
|
||||
result = {"ok": True, "postcondition": f"{object_id}=held"}
|
||||
self.events.append({"tool": "pick", "object_id": object_id, **result})
|
||||
return result
|
||||
|
||||
def place(self, object_id: str, target_id: str) -> dict[str, Any]:
|
||||
if object_id not in OBJECTS or target_id not in TARGETS:
|
||||
result = {"ok": False, "reason": "unknown_object_or_target"}
|
||||
else:
|
||||
oi, ti = OBJECTS.index(object_id), TARGETS.index(target_id)
|
||||
if self.state.status[oi] != 1:
|
||||
result = {"ok": False, "reason": "object_not_held"}
|
||||
elif not self.state.target_available[ti]:
|
||||
result = {"ok": False, "reason": "target_unavailable"}
|
||||
else:
|
||||
self.state.status[oi] = 2
|
||||
self.state.target_available[ti] = False
|
||||
result = {"ok": True, "postcondition": f"{object_id}=in_{target_id}"}
|
||||
self.events.append({"tool": "place", "object_id": object_id, "target_id": target_id, **result})
|
||||
return result
|
||||
|
||||
def verify_state(self) -> dict[str, Any]:
|
||||
result = {"ok": self.state.done(), "state": self.observe_scene()}
|
||||
self.events.append({"tool": "verify_state", **result})
|
||||
return result
|
||||
|
||||
def stop(self) -> dict[str, Any]:
|
||||
result = {"ok": True, "stopped": True}
|
||||
self.events.append({"tool": "stop", **result})
|
||||
return result
|
||||
|
||||
|
||||
def encode_state(state: DesktopState) -> list[float]:
|
||||
return [state.status[0] / 2.0, state.status[1] / 2.0, float(state.target_available[0]), float(state.target_available[1])]
|
||||
|
||||
|
||||
def transition(state: DesktopState, action: int) -> DesktopState:
|
||||
next_state = state.copy()
|
||||
if action == 0 and next_state.status[0] == 0:
|
||||
next_state.status[0] = 1
|
||||
elif action == 1 and next_state.status[0] == 1 and next_state.target_available[0]:
|
||||
next_state.status[0], next_state.target_available[0] = 2, False
|
||||
elif action == 2 and next_state.status[1] == 0:
|
||||
next_state.status[1] = 1
|
||||
elif action == 3 and next_state.status[1] == 1 and next_state.target_available[1]:
|
||||
next_state.status[1], next_state.target_available[1] = 2, False
|
||||
return next_state
|
||||
|
||||
|
||||
class WorldModel(nn.Module):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.net = nn.Sequential(nn.Linear(8, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 4))
|
||||
|
||||
def forward(self, state: torch.Tensor, action: torch.Tensor) -> torch.Tensor:
|
||||
return self.net(torch.cat([state, action], dim=-1))
|
||||
|
||||
|
||||
def train_world_model(device: torch.device, seed: int, epochs: int = 160) -> tuple[WorldModel, float, float]:
|
||||
rng = random.Random(seed)
|
||||
states, actions, targets = [], [], []
|
||||
for _ in range(12000):
|
||||
state = DesktopState([rng.randrange(3), rng.randrange(3)], [bool(rng.randrange(2)), bool(rng.randrange(2))])
|
||||
action_index = rng.randrange(len(ACTIONS))
|
||||
nxt = transition(state, action_index)
|
||||
states.append(encode_state(state))
|
||||
one_hot = [1.0 if index == action_index else 0.0 for index in range(len(ACTIONS))]
|
||||
actions.append(one_hot)
|
||||
targets.append(encode_state(nxt))
|
||||
x_state = torch.tensor(states, dtype=torch.float32, device=device)
|
||||
x_action = torch.tensor(actions, dtype=torch.float32, device=device)
|
||||
y = torch.tensor(targets, dtype=torch.float32, device=device)
|
||||
split = int(len(y) * 0.8)
|
||||
model = WorldModel().to(device)
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=2e-3)
|
||||
loss_fn = nn.MSELoss()
|
||||
for _ in range(epochs):
|
||||
optimizer.zero_grad(set_to_none=True)
|
||||
loss = loss_fn(model(x_state[:split], x_action[:split]), y[:split])
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
with torch.no_grad():
|
||||
train_loss = float(loss_fn(model(x_state[:split], x_action[:split]), y[:split]).item())
|
||||
test_loss = float(loss_fn(model(x_state[split:], x_action[split:]), y[split:]).item())
|
||||
return model, train_loss, test_loss
|
||||
|
||||
|
||||
def action_from_index(index: int) -> tuple[str, str | None, str | None]:
|
||||
mapping = [("pick", "red_cup", None), ("place", "red_cup", "tray"), ("pick", "yellow_paper", None), ("place", "yellow_paper", "bin")]
|
||||
return mapping[index]
|
||||
|
||||
|
||||
def execute(adapter: DesktopToolAdapter, index: int) -> dict[str, Any]:
|
||||
kind, object_id, target_id = action_from_index(index)
|
||||
if kind == "pick":
|
||||
return adapter.pick(object_id or "")
|
||||
return adapter.place(object_id or "", target_id or "")
|
||||
|
||||
|
||||
def render_scene(path: Path, state: DesktopState) -> None:
|
||||
image = Image.new("RGB", (480, 300), (235, 232, 220))
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.rectangle((40, 40, 440, 260), outline=(60, 60, 60), width=3)
|
||||
locations = [(150, 130), (250, 130)]
|
||||
colors = [(210, 60, 60), (220, 190, 40)]
|
||||
for idx, (x, y) in enumerate(locations):
|
||||
if state.status[idx] != 2:
|
||||
draw.ellipse((x - 22, y - 22, x + 22, y + 22), fill=colors[idx], outline=(20, 20, 20))
|
||||
draw.text((x - 35, y + 30), OBJECTS[idx], fill=(20, 20, 20))
|
||||
draw.rectangle((320, 80, 390, 145), outline=(40, 100, 210), width=3)
|
||||
draw.rectangle((320, 170, 390, 235), outline=(40, 130, 60), width=3)
|
||||
draw.text((325, 95), "tray", fill=(20, 20, 20))
|
||||
draw.text((325, 185), "bin", fill=(20, 20, 20))
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
image.save(path)
|
||||
|
||||
|
||||
def run_episode(mode: str, seed: int, model: WorldModel | None, device: torch.device, failure_probability: float) -> dict[str, Any]:
|
||||
adapter = DesktopToolAdapter(seed, failure_probability=failure_probability)
|
||||
if mode == "open_loop":
|
||||
plan = [0, 1, 2, 3]
|
||||
for action in plan:
|
||||
execute(adapter, action)
|
||||
else:
|
||||
for _ in range(12):
|
||||
if adapter.state.done():
|
||||
break
|
||||
if mode == "predictive" and model is not None:
|
||||
state_tensor = torch.tensor([encode_state(adapter.state)], dtype=torch.float32, device=device)
|
||||
candidates = [index for index in range(4) if (index in (0, 2) and adapter.state.status[index // 2] == 0) or (index in (1, 3) and adapter.state.status[index // 2] == 1)]
|
||||
if not candidates:
|
||||
break
|
||||
action_vectors = torch.eye(4, device=device)[candidates]
|
||||
with torch.no_grad():
|
||||
predicted = model(state_tensor.repeat(len(candidates), 1), action_vectors)
|
||||
score = predicted[:, 0] + predicted[:, 1] + (predicted[:, 0] > 0.95).float() + (predicted[:, 1] > 0.95).float()
|
||||
action = candidates[int(torch.argmax(score).item())]
|
||||
else:
|
||||
action = next((index for index in (0, 1, 2, 3) if (index in (0, 2) and adapter.state.status[index // 2] == 0) or (index in (1, 3) and adapter.state.status[index // 2] == 1)), 0)
|
||||
result = execute(adapter, action)
|
||||
if not result.get("ok"):
|
||||
adapter.observe_scene()
|
||||
adapter.verify_state()
|
||||
return {"success": adapter.state.done(), "tool_calls": len(adapter.events), "recoveries": sum(1 for event in adapter.events if event.get("reason") == "injected_transient_grasp_failure"), "events": adapter.events}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--episodes", type=int, default=128, help="episodes per seed/failure/mode cell")
|
||||
parser.add_argument("--seeds", default="20260808,20260809,20260810")
|
||||
parser.add_argument("--failure-probabilities", default="0.0,0.25,0.5")
|
||||
parser.add_argument("--output-dir", type=Path, default=Path(__file__).parent / "validation" / "runs" / "local-gpu")
|
||||
parser.add_argument("--allow-cpu", action="store_true")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
seeds = [int(value) for value in args.seeds.split(",")]
|
||||
failure_probabilities = [float(value) for value in args.failure_probabilities.split(",")]
|
||||
except ValueError:
|
||||
parser.error("--seeds and --failure-probabilities must be comma-separated values")
|
||||
if args.episodes < 64 or len(seeds) < 3 or not failure_probabilities or any(value < 0 or value > 1 for value in failure_probabilities):
|
||||
parser.error("use at least three seeds, 64 episodes per cell, and probabilities in [0,1]")
|
||||
seed_everything(seeds[0])
|
||||
try:
|
||||
device = select_device(not args.allow_cpu)
|
||||
except RuntimeError as exc:
|
||||
parser.error(str(exc))
|
||||
started = time.perf_counter()
|
||||
results: list[dict[str, Any]] = []
|
||||
model_reports: list[dict[str, Any]] = []
|
||||
event_log_rows: list[dict[str, Any]] = []
|
||||
for model_seed in seeds:
|
||||
model, train_loss, test_loss = train_world_model(device, model_seed)
|
||||
model_reports.append({"seed": model_seed, "train_mse": train_loss, "test_mse": test_loss})
|
||||
for failure_probability in failure_probabilities:
|
||||
for mode in ("open_loop", "closed_loop", "predictive"):
|
||||
episodes = [run_episode(mode, model_seed + index, model if mode == "predictive" else None, device, failure_probability) for index in range(args.episodes)]
|
||||
results.append({"seed": model_seed, "failure_probability": failure_probability, "mode": mode, "episodes": args.episodes, "success_rate": sum(item["success"] for item in episodes) / args.episodes, "mean_tool_calls": sum(item["tool_calls"] for item in episodes) / args.episodes, "recoveries": sum(item["recoveries"] for item in episodes)})
|
||||
for episode_index, episode in enumerate(episodes):
|
||||
event_log_rows.append({
|
||||
"seed": model_seed,
|
||||
"failure_probability": failure_probability,
|
||||
"mode": mode,
|
||||
"episode": episode_index,
|
||||
"success": episode["success"],
|
||||
"events": episode["events"],
|
||||
})
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
scene_path = args.output_dir / "scene_initial.png"
|
||||
render_scene(scene_path, DesktopState([0, 0], [True, True]))
|
||||
events_path = args.output_dir / "predictive_episode_events.json"
|
||||
write_json(events_path, {"tools": TOOL_NAMES, "episodes": event_log_rows})
|
||||
replay_model, _, _ = train_world_model(device, seeds[0])
|
||||
replay_a = run_episode("predictive", seeds[0], replay_model, device, failure_probabilities[-1])
|
||||
replay_b = run_episode("predictive", seeds[0], replay_model, device, failure_probabilities[-1])
|
||||
metrics = {"device": device_info(device), "protocol": {"seeds": seeds, "failure_probabilities": failure_probabilities, "episodes_per_cell": args.episodes, "total_episodes": len(results) * args.episodes}, "models": model_reports, "cells": results, "deterministic_replay": replay_a == replay_b, "wall_time_ms": round((time.perf_counter() - started) * 1000, 3)}
|
||||
metrics_path = args.output_dir / "metrics.json"
|
||||
write_json(metrics_path, metrics)
|
||||
evidence = {"schema_version": "3.0", "experiment_id": "6-12", "status": "complete", "kind": "desktop_manipulation_planning", "seed": seeds[0], "tool_contract": list(TOOL_NAMES), "metrics": metrics, "artifacts": [{"kind": "metrics", "path": relative_or_absolute(metrics_path, args.output_dir), "sha256": sha256(metrics_path)}, {"kind": "events", "path": relative_or_absolute(events_path, args.output_dir), "sha256": sha256(events_path)}, {"kind": "scene", "path": relative_or_absolute(scene_path, args.output_dir), "sha256": sha256(scene_path)}], "xlerobot_robocrew_extension": {"status": "gated", "tool_adapter_required": True, "actuation_attempted": False}, "blockers": [] if not args.allow_cpu else ["CPU debug mode is not a GPU acceptance run"]}
|
||||
evidence_path = args.output_dir / "evidence.json"
|
||||
write_json(evidence_path, evidence)
|
||||
print(json.dumps(evidence, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://ai-agent-book.local/schemas/experiment-6-12-local-gpu.json",
|
||||
"title": "Experiment 6-12 local GPU desktop manipulation planning evidence",
|
||||
"type": "object",
|
||||
"required": ["schema_version", "experiment_id", "status", "kind", "tool_contract", "metrics", "artifacts", "xlerobot_robocrew_extension", "blockers"],
|
||||
"properties": {
|
||||
"schema_version": {"const": "3.0"},
|
||||
"experiment_id": {"const": "6-12"},
|
||||
"status": {"const": "complete"},
|
||||
"kind": {"const": "desktop_manipulation_planning"},
|
||||
"tool_contract": {"type": "array"},
|
||||
"metrics": {"type": "object"},
|
||||
"artifacts": {"type": "array"},
|
||||
"xlerobot_robocrew_extension": {"type": "object"},
|
||||
"blockers": {"type": "array"}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from validate_evidence import validate
|
||||
|
||||
|
||||
class EvidenceGateTests(unittest.TestCase):
|
||||
def test_local_gpu_evidence_is_accepted(self):
|
||||
run = Path(__file__).parent / "validation" / "runs" / "local-gpu" / "evidence.json"
|
||||
if not run.is_file():
|
||||
self.skipTest("run the local GPU experiment first")
|
||||
data = json.loads(run.read_text(encoding="utf-8"))
|
||||
self.assertEqual(validate(data, run.parent), [])
|
||||
|
||||
def test_event_log_covers_every_formal_episode(self):
|
||||
evidence_dir = Path(__file__).parent / "validation" / "runs" / "local-gpu"
|
||||
evidence = evidence_dir / "evidence.json"
|
||||
event_log = evidence_dir / "predictive_episode_events.json"
|
||||
if not evidence.is_file() or not event_log.is_file():
|
||||
self.skipTest("run the local GPU experiment first")
|
||||
data = json.loads(evidence.read_text(encoding="utf-8"))
|
||||
payload = json.loads(event_log.read_text(encoding="utf-8"))
|
||||
self.assertEqual(len(payload["episodes"]), data["metrics"]["protocol"]["total_episodes"])
|
||||
|
||||
def test_unbounded_tools_are_rejected(self):
|
||||
data = {"schema_version": "3.0", "experiment_id": "6-12", "status": "complete", "kind": "desktop_manipulation_planning", "metrics": {"device": {"device": "mps"}, "protocol": {"seeds": [1, 2, 3], "failure_probabilities": [0.0, 0.25, 0.5], "total_episodes": 3456}, "models": [{"test_mse": 0.01}] * 3, "cells": [], "deterministic_replay": True}, "tool_contract": ["move_anywhere"], "artifacts": [], "xlerobot_robocrew_extension": {"actuation_attempted": False}}
|
||||
self.assertTrue(validate(data))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"experiment_id": "6-12",
|
||||
"repository": "https://github.com/Vector-Wangel/XLeRobot.git",
|
||||
"commit": "3d14695e40c9c68229c0aacffca6053c75cd3eb6",
|
||||
"guide": {
|
||||
"published_url": "https://xlerobot.readthedocs.io/en/latest/software/getting_started/LLM_agent.html",
|
||||
"path": "docs/en/source/software/getting_started/LLM_agent.md",
|
||||
"git_blob": "d336a9e35838267614d31cdb98b9b50d66427f03"
|
||||
},
|
||||
"robocrew": {
|
||||
"repository": "https://github.com/Grigorij-Dudnik/RoboCrew.git",
|
||||
"tag": "v0.3.1",
|
||||
"commit": "c749148f29bd14e61347f9fc3530c343fff0d994",
|
||||
"version_file": {
|
||||
"path": "pyproject.toml",
|
||||
"git_blob": "9029068bdb511dfc02262adf7c2af69ec2fac0fe"
|
||||
},
|
||||
"pypi": {
|
||||
"version": "0.3.1",
|
||||
"wheel_url": "https://files.pythonhosted.org/packages/29/8d/893d6d5cfe8a8e5aac943936ee497934533d882cfd933f609a12a66101c2/robocrew-0.3.1-py3-none-any.whl",
|
||||
"wheel_sha256": "4afbc8ab19ffb61cc0617486408460c80072991434636aff041a1ef87f2abb4f",
|
||||
"sdist_url": "https://files.pythonhosted.org/packages/28/f2/16e1a8eeb2df9fff008045db5b2202bf83939eac3d48449f500f4a1326f9/robocrew-0.3.1.tar.gz",
|
||||
"sdist_sha256": "a6c25a0f18b7d7d52a7efc199226a6175dfb1032213ff3845e75e3701a948c98"
|
||||
}
|
||||
},
|
||||
"required_model": "gemini-robotics-er-1.5-preview",
|
||||
"required_api_key_env": "GOOGLE_API_KEY",
|
||||
"reference_frame": {
|
||||
"source_url": "https://github.com/user-attachments/assets/296f6f60-52a4-4fa0-9a77-a113b4868f83",
|
||||
"referenced_by": "docs/en/source/software/getting_started/LLM_agent.md",
|
||||
"sha256": "2dae44dd2dbd9259f9448095f09e064ade50d485e8d0dcab7a3ca2a73435bcc1",
|
||||
"role": "historical upstream reference input only; not desktop-manipulation evidence"
|
||||
},
|
||||
"verified_at": "2026-07-30"
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the local GPU desktop-planning evidence for Experiment 6-12."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
EXPECTED_TOOL_NAMES = ["observe_scene", "pick", "place", "verify_state", "stop"]
|
||||
|
||||
|
||||
def file_sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def validate(data: dict[str, Any], evidence_dir: Path | None = None) -> list[str]:
|
||||
errors: list[str] = []
|
||||
|
||||
def expect(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
errors.append(message)
|
||||
|
||||
expect(data.get("schema_version") == "3.0", "schema_version must be 3.0")
|
||||
expect(data.get("experiment_id") == "6-12", "experiment_id must be 6-12")
|
||||
expect(data.get("kind") == "desktop_manipulation_planning", "wrong evidence kind")
|
||||
expect(data.get("status") == "complete", "local evidence must be complete")
|
||||
metrics = data.get("metrics", {})
|
||||
expect(metrics.get("device", {}).get("device") in {"mps", "cuda"}, "evidence must use a local GPU accelerator")
|
||||
protocol = metrics.get("protocol", {})
|
||||
expect(len(protocol.get("seeds", [])) >= 3, "at least three planner seeds are required")
|
||||
expect(set(protocol.get("failure_probabilities", [])) >= {0.0, 0.25, 0.5}, "zero, moderate and high failure conditions are required")
|
||||
expect(protocol.get("total_episodes", 0) >= 2000, "at least 2000 planner episodes are required")
|
||||
models = metrics.get("models", [])
|
||||
expect(len(models) == len(protocol.get("seeds", [])), "one world-model report is required per seed")
|
||||
expect(max((item.get("test_mse", 1.0) for item in models), default=1.0) < 0.03, "world-model test MSE is too high")
|
||||
cells = metrics.get("cells", [])
|
||||
expect(len(cells) == len(protocol.get("seeds", [])) * len(protocol.get("failure_probabilities", [])) * 3, "one result cell is required per seed/failure/mode condition")
|
||||
high_failure = [item for item in cells if item.get("failure_probability") == 0.5]
|
||||
expect(high_failure and all(item.get("mode") in {"closed_loop", "predictive"} and item.get("success_rate") == 1.0 for item in high_failure if item.get("mode") != "open_loop"), "closed-loop and predictive planners must recover high-failure trials")
|
||||
open_loop_high = [item for item in high_failure if item.get("mode") == "open_loop"]
|
||||
expect(open_loop_high and max(item.get("success_rate", 1.0) for item in open_loop_high) < 1.0, "open-loop baseline must expose injected failures")
|
||||
expect(metrics.get("deterministic_replay") is True, "repeating a fixed planner seed must reproduce the same episode")
|
||||
contract = data.get("tool_contract", [])
|
||||
expect(contract == EXPECTED_TOOL_NAMES, "工具契约不是桌面操作实验规定的五个工具")
|
||||
artifacts = data.get("artifacts", [])
|
||||
expect(len(artifacts) == 3, "metrics, events and scene artifacts are required")
|
||||
if evidence_dir is not None:
|
||||
for index, artifact in enumerate(artifacts):
|
||||
path = evidence_dir / str(artifact.get("path", ""))
|
||||
expect(path.is_file(), f"artifact[{index}] does not exist")
|
||||
if path.is_file():
|
||||
expect(file_sha256(path) == artifact.get("sha256"), f"artifact[{index}] hash mismatch")
|
||||
if artifact.get("kind") == "events":
|
||||
try:
|
||||
event_log = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
errors.append(f"event log cannot be read: {exc}")
|
||||
else:
|
||||
expect(event_log.get("tools") == EXPECTED_TOOL_NAMES, "event log tool list does not match the contract")
|
||||
episodes = event_log.get("episodes", [])
|
||||
expect(len(episodes) == protocol.get("total_episodes", 0), "one auditable event trace is required per episode")
|
||||
expect(all(isinstance(item.get("events"), list) for item in episodes), "every episode must contain a tool event list")
|
||||
extension = data.get("xlerobot_robocrew_extension", {})
|
||||
expect(extension.get("actuation_attempted") is False, "local run must not claim hardware actuation")
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("evidence", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
data = json.loads(args.evidence.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
print(f"INVALID: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
errors = validate(data, args.evidence.resolve().parent)
|
||||
if errors:
|
||||
print("INVALID")
|
||||
for error in errors:
|
||||
print(f"- {error}")
|
||||
return 1
|
||||
print("VALID: experiment 6-12 local GPU evidence")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
@@ -0,0 +1,322 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "metrics",
|
||||
"path": "metrics.json",
|
||||
"sha256": "e77f6a79ddf199c92eca1642883921cbd0fb8a2988763a2b1ef604a7e4dc0a91"
|
||||
},
|
||||
{
|
||||
"kind": "events",
|
||||
"path": "predictive_episode_events.json",
|
||||
"sha256": "9fcb00648c516a3766e13e535fce41522f5d49881221c551e1675a22c7225421"
|
||||
},
|
||||
{
|
||||
"kind": "scene",
|
||||
"path": "scene_initial.png",
|
||||
"sha256": "8afa759bc85408f14d75d03da1ad57e32dc6824879f3f9911374b0f866e369e5"
|
||||
}
|
||||
],
|
||||
"blockers": [],
|
||||
"experiment_id": "6-12",
|
||||
"kind": "desktop_manipulation_planning",
|
||||
"metrics": {
|
||||
"cells": [
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "predictive",
|
||||
"recoveries": 0,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 34,
|
||||
"seed": 20260808,
|
||||
"success_rate": 0.734375
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.53125,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 34,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.53125,
|
||||
"mode": "predictive",
|
||||
"recoveries": 34,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 60,
|
||||
"seed": 20260808,
|
||||
"success_rate": 0.53125
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.9375,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 60,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.9375,
|
||||
"mode": "predictive",
|
||||
"recoveries": 60,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "predictive",
|
||||
"recoveries": 0,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 34,
|
||||
"seed": 20260809,
|
||||
"success_rate": 0.734375
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.53125,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 34,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.53125,
|
||||
"mode": "predictive",
|
||||
"recoveries": 34,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 60,
|
||||
"seed": 20260809,
|
||||
"success_rate": 0.53125
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.9375,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 60,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.9375,
|
||||
"mode": "predictive",
|
||||
"recoveries": 60,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "predictive",
|
||||
"recoveries": 0,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 33,
|
||||
"seed": 20260810,
|
||||
"success_rate": 0.7421875
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.515625,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 33,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.515625,
|
||||
"mode": "predictive",
|
||||
"recoveries": 33,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 59,
|
||||
"seed": 20260810,
|
||||
"success_rate": 0.5390625
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.921875,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 59,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.921875,
|
||||
"mode": "predictive",
|
||||
"recoveries": 59,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
}
|
||||
],
|
||||
"deterministic_replay": true,
|
||||
"device": {
|
||||
"device": "mps",
|
||||
"name": "Apple Metal Performance Shaders",
|
||||
"torch": "2.7.0"
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"seed": 20260808,
|
||||
"test_mse": 0.017016880214214325,
|
||||
"train_mse": 0.017159195616841316
|
||||
},
|
||||
{
|
||||
"seed": 20260809,
|
||||
"test_mse": 0.012364446185529232,
|
||||
"train_mse": 0.01264232862740755
|
||||
},
|
||||
{
|
||||
"seed": 20260810,
|
||||
"test_mse": 0.014977166429162025,
|
||||
"train_mse": 0.015084807761013508
|
||||
}
|
||||
],
|
||||
"protocol": {
|
||||
"episodes_per_cell": 128,
|
||||
"failure_probabilities": [
|
||||
0.0,
|
||||
0.25,
|
||||
0.5
|
||||
],
|
||||
"seeds": [
|
||||
20260808,
|
||||
20260809,
|
||||
20260810
|
||||
],
|
||||
"total_episodes": 3456
|
||||
},
|
||||
"wall_time_ms": 8197.975
|
||||
},
|
||||
"schema_version": "3.0",
|
||||
"seed": 20260808,
|
||||
"status": "complete",
|
||||
"tool_contract": [
|
||||
"observe_scene",
|
||||
"pick",
|
||||
"place",
|
||||
"verify_state",
|
||||
"stop"
|
||||
],
|
||||
"xlerobot_robocrew_extension": {
|
||||
"actuation_attempted": false,
|
||||
"status": "gated",
|
||||
"tool_adapter_required": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "predictive",
|
||||
"recoveries": 0,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 34,
|
||||
"seed": 20260808,
|
||||
"success_rate": 0.734375
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.53125,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 34,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.53125,
|
||||
"mode": "predictive",
|
||||
"recoveries": 34,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 60,
|
||||
"seed": 20260808,
|
||||
"success_rate": 0.53125
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.9375,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 60,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.9375,
|
||||
"mode": "predictive",
|
||||
"recoveries": 60,
|
||||
"seed": 20260808,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "predictive",
|
||||
"recoveries": 0,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 34,
|
||||
"seed": 20260809,
|
||||
"success_rate": 0.734375
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.53125,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 34,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.53125,
|
||||
"mode": "predictive",
|
||||
"recoveries": 34,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 60,
|
||||
"seed": 20260809,
|
||||
"success_rate": 0.53125
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.9375,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 60,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.9375,
|
||||
"mode": "predictive",
|
||||
"recoveries": 60,
|
||||
"seed": 20260809,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 0,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.0,
|
||||
"mean_tool_calls": 6.0,
|
||||
"mode": "predictive",
|
||||
"recoveries": 0,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 33,
|
||||
"seed": 20260810,
|
||||
"success_rate": 0.7421875
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.515625,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 33,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.25,
|
||||
"mean_tool_calls": 6.515625,
|
||||
"mode": "predictive",
|
||||
"recoveries": 33,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 4.0,
|
||||
"mode": "open_loop",
|
||||
"recoveries": 59,
|
||||
"seed": 20260810,
|
||||
"success_rate": 0.5390625
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.921875,
|
||||
"mode": "closed_loop",
|
||||
"recoveries": 59,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
},
|
||||
{
|
||||
"episodes": 128,
|
||||
"failure_probability": 0.5,
|
||||
"mean_tool_calls": 6.921875,
|
||||
"mode": "predictive",
|
||||
"recoveries": 59,
|
||||
"seed": 20260810,
|
||||
"success_rate": 1.0
|
||||
}
|
||||
],
|
||||
"deterministic_replay": true,
|
||||
"device": {
|
||||
"device": "mps",
|
||||
"name": "Apple Metal Performance Shaders",
|
||||
"torch": "2.7.0"
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"seed": 20260808,
|
||||
"test_mse": 0.017016880214214325,
|
||||
"train_mse": 0.017159195616841316
|
||||
},
|
||||
{
|
||||
"seed": 20260809,
|
||||
"test_mse": 0.012364446185529232,
|
||||
"train_mse": 0.01264232862740755
|
||||
},
|
||||
{
|
||||
"seed": 20260810,
|
||||
"test_mse": 0.014977166429162025,
|
||||
"train_mse": 0.015084807761013508
|
||||
}
|
||||
],
|
||||
"protocol": {
|
||||
"episodes_per_cell": 128,
|
||||
"failure_probabilities": [
|
||||
0.0,
|
||||
0.25,
|
||||
0.5
|
||||
],
|
||||
"seeds": [
|
||||
20260808,
|
||||
20260809,
|
||||
20260810
|
||||
],
|
||||
"total_episodes": 3456
|
||||
},
|
||||
"wall_time_ms": 8197.975
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,46 @@
|
||||
"""Book-local semantic tool contract for the optional RoboCrew/XLeRobot run.
|
||||
|
||||
The pinned navigation checkout exposes base-motion helpers, not a stable
|
||||
semantic arm API. This module is therefore an explicit adapter boundary: the
|
||||
local GPU experiment validates the contract, while a hardware integrator must
|
||||
map each primitive to calibrated XLeRobot arm motions before enabling torque.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
TOOL_CONTRACT = (
|
||||
{
|
||||
"name": "observe_scene",
|
||||
"description": "Capture a new RGB observation and return object/target state.",
|
||||
"parameters": {"type": "object", "properties": {}, "additionalProperties": False},
|
||||
},
|
||||
{
|
||||
"name": "pick",
|
||||
"description": "Execute one bounded calibrated pick primitive.",
|
||||
"parameters": {"type": "object", "properties": {"object_id": {"type": "string", "enum": ["red_cup", "yellow_paper"]}}, "required": ["object_id"], "additionalProperties": False},
|
||||
},
|
||||
{
|
||||
"name": "place",
|
||||
"description": "Execute one bounded calibrated place primitive.",
|
||||
"parameters": {"type": "object", "properties": {"object_id": {"type": "string", "enum": ["red_cup", "yellow_paper"]}, "target_id": {"type": "string", "enum": ["tray", "bin"]}}, "required": ["object_id", "target_id"], "additionalProperties": False},
|
||||
},
|
||||
{
|
||||
"name": "verify_state",
|
||||
"description": "Check the postcondition using a fresh observation.",
|
||||
"parameters": {"type": "object", "properties": {}, "additionalProperties": False},
|
||||
},
|
||||
{
|
||||
"name": "stop",
|
||||
"description": "Stop all motion and enter a safe state.",
|
||||
"parameters": {"type": "object", "properties": {}, "additionalProperties": False},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def robocrew_function_declarations() -> list[dict[str, object]]:
|
||||
"""Return JSON-compatible declarations for a RoboCrew/Gemini bridge."""
|
||||
|
||||
return [
|
||||
{"name": item["name"], "description": item["description"], "parameters": item["parameters"]}
|
||||
for item in TOOL_CONTRACT
|
||||
]
|
||||
Reference in New Issue
Block a user