mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-24 18:26:11 +00:00
feat(smolvla2): --mode flag, skip task picker with --task, timed /action
Lets the operator skip the interactive startup entirely and go straight
to the command line:
- New --mode {action,question} arg; when given, the startup mode prompt
is skipped.
- When --task is passed explicitly on the CLI, the startup task picker
is skipped (the dataset-bootstrap task still shows the picker so you
can override it).
Also adds a timed action burst: /action <seconds> runs the robot for N
seconds, then the autonomous loop auto-reverts to question mode and
clears the action queue. Plain /action stays unlimited.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -139,7 +139,23 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
dest="task",
|
dest="task",
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="Initial task. If omitted, the first stdin line is treated as the task.",
|
help=(
|
||||||
|
"Initial task. When given, the startup task picker is skipped "
|
||||||
|
"and this task is used directly. If omitted, the picker is "
|
||||||
|
"shown (or the first stdin line is treated as the task)."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
p.add_argument(
|
||||||
|
"--mode",
|
||||||
|
dest="mode",
|
||||||
|
type=str,
|
||||||
|
choices=["action", "question"],
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
"Start-up run mode. 'action' runs the robot; 'question' starts "
|
||||||
|
"paused for VQA. When given, the startup mode prompt is "
|
||||||
|
"skipped. Can still be flipped at runtime with /action /question."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--no_robot",
|
"--no_robot",
|
||||||
@@ -991,6 +1007,7 @@ def _print_runtime_help() -> None:
|
|||||||
print(
|
print(
|
||||||
"[smolvla2] commands:\n"
|
"[smolvla2] commands:\n"
|
||||||
" /action run the robot (default mode)\n"
|
" /action run the robot (default mode)\n"
|
||||||
|
" /action <seconds> run the robot for N seconds, then auto-pause to question\n"
|
||||||
" /question pause the action loop; typed lines become VQA questions\n"
|
" /question pause the action loop; typed lines become VQA questions\n"
|
||||||
" /help show this help\n"
|
" /help show this help\n"
|
||||||
" task: <text> switch task (clears plan / memory / subtask)\n"
|
" task: <text> switch task (clears plan / memory / subtask)\n"
|
||||||
@@ -1003,17 +1020,40 @@ def _print_runtime_help() -> None:
|
|||||||
def _handle_slash_command(runtime: Any, line: str) -> bool:
|
def _handle_slash_command(runtime: Any, line: str) -> bool:
|
||||||
"""Handle ``/action`` / ``/question`` / ``/help``.
|
"""Handle ``/action`` / ``/question`` / ``/help``.
|
||||||
|
|
||||||
``/vlm`` and ``/vqa`` are kept as aliases for ``/question``. Returns
|
``/action`` accepts an optional duration — ``/action 10`` runs the
|
||||||
``True`` when ``line`` was a recognised command (and was consumed),
|
robot for 10 seconds, then the autonomous loop auto-reverts to
|
||||||
``False`` otherwise.
|
``question`` mode. ``/vlm`` and ``/vqa`` are aliases for
|
||||||
|
``/question``. Returns ``True`` when ``line`` was a recognised
|
||||||
|
command (and was consumed), ``False`` otherwise.
|
||||||
"""
|
"""
|
||||||
cmd = line.strip().lower()
|
parts = line.strip().split()
|
||||||
|
if not parts:
|
||||||
|
return False
|
||||||
|
cmd = parts[0].lower()
|
||||||
if cmd in {"/action", "/act"}:
|
if cmd in {"/action", "/act"}:
|
||||||
runtime.state["mode"] = "action"
|
runtime.state["mode"] = "action"
|
||||||
print("[smolvla2] mode: action — robot running", flush=True)
|
seconds: float | None = None
|
||||||
|
if len(parts) > 1:
|
||||||
|
try:
|
||||||
|
seconds = float(parts[1])
|
||||||
|
except ValueError:
|
||||||
|
seconds = None
|
||||||
|
if seconds is not None and seconds > 0:
|
||||||
|
import time as _time # noqa: PLC0415
|
||||||
|
|
||||||
|
runtime.state["action_deadline"] = _time.monotonic() + seconds
|
||||||
|
print(
|
||||||
|
f"[smolvla2] mode: action — running for {seconds:g}s, "
|
||||||
|
"then back to question",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
runtime.state["action_deadline"] = None
|
||||||
|
print("[smolvla2] mode: action — robot running", flush=True)
|
||||||
return True
|
return True
|
||||||
if cmd in {"/question", "/q", "/vlm", "/vqa"}:
|
if cmd in {"/question", "/q", "/vlm", "/vqa"}:
|
||||||
runtime.state["mode"] = "question"
|
runtime.state["mode"] = "question"
|
||||||
|
runtime.state["action_deadline"] = None
|
||||||
# Drop any queued chunk so no stale action fires while paused.
|
# Drop any queued chunk so no stale action fires while paused.
|
||||||
queue = runtime.state.get("action_queue")
|
queue = runtime.state.get("action_queue")
|
||||||
if hasattr(queue, "clear"):
|
if hasattr(queue, "clear"):
|
||||||
@@ -1129,14 +1169,30 @@ def _run_autonomous(
|
|||||||
|
|
||||||
def _panel_loop() -> None:
|
def _panel_loop() -> None:
|
||||||
while not _panel_stop.is_set():
|
while not _panel_stop.is_set():
|
||||||
if runtime.state.get("mode", "action") == "action":
|
st = runtime.state
|
||||||
try:
|
if st.get("mode", "action") == "action":
|
||||||
redraw()
|
# Timed burst (``/action <seconds>``): once the deadline
|
||||||
# Re-print the prompt the redraw just cleared so the
|
# passes, auto-revert to question mode and clear the
|
||||||
# operator always has a visible ``> `` to type at.
|
# queue so the robot stops.
|
||||||
print("> ", end="", flush=True)
|
deadline = st.get("action_deadline")
|
||||||
except Exception: # noqa: BLE001
|
if deadline is not None and time.monotonic() >= deadline:
|
||||||
pass
|
st["mode"] = "question"
|
||||||
|
st["action_deadline"] = None
|
||||||
|
queue = st.get("action_queue")
|
||||||
|
if hasattr(queue, "clear"):
|
||||||
|
queue.clear()
|
||||||
|
print(
|
||||||
|
"\n[smolvla2] timed action elapsed — back to question mode",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
redraw()
|
||||||
|
# Re-print the prompt the redraw just cleared so
|
||||||
|
# the operator always has a visible ``> ``.
|
||||||
|
print("> ", end="", flush=True)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
pass
|
||||||
_panel_stop.wait(0.7)
|
_panel_stop.wait(0.7)
|
||||||
|
|
||||||
panel_thread = threading.Thread(
|
panel_thread = threading.Thread(
|
||||||
@@ -1446,6 +1502,11 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
# autonomous robot mode benefit, since the model is memorised on
|
# autonomous robot mode benefit, since the model is memorised on
|
||||||
# the exact training prompts and matching wording is what gets
|
# the exact training prompts and matching wording is what gets
|
||||||
# recall to fire.
|
# recall to fire.
|
||||||
|
# Was a task given explicitly on the CLI? Captured before the
|
||||||
|
# dataset bootstrap fills ``args.task`` — an explicit ``--task``
|
||||||
|
# skips the startup task picker entirely.
|
||||||
|
cli_task_given = args.task is not None
|
||||||
|
|
||||||
bootstrap_state: dict[str, str] = {}
|
bootstrap_state: dict[str, str] = {}
|
||||||
if args.dataset_repo_id is not None:
|
if args.dataset_repo_id is not None:
|
||||||
bootstrap_state = _bootstrap_state_from_dataset(
|
bootstrap_state = _bootstrap_state_from_dataset(
|
||||||
@@ -1461,15 +1522,17 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Startup mode prompt — choose action (run the robot) vs question
|
# Startup mode prompt — choose action (run the robot) vs question
|
||||||
# (VQA only) *before* the task picker, so the operator sets intent
|
# (VQA only) *before* the task picker. Skipped when ``--mode`` was
|
||||||
# up front. It can still be flipped any time with /action /question.
|
# passed on the CLI. Can still be flipped at runtime with
|
||||||
startup_mode = _select_mode_interactively()
|
# /action /question.
|
||||||
|
startup_mode = args.mode or _select_mode_interactively()
|
||||||
|
|
||||||
# Always offer the startup task picker on an interactive terminal:
|
# Startup task picker — list the dataset's tasks so the operator can
|
||||||
# list the dataset's tasks (the canonical / --task one shown as the
|
# pick one or type a custom task. Skipped when ``--task`` was passed
|
||||||
# default) so the operator can pick another or type a custom task.
|
# explicitly on the CLI. Non-TTY runs keep the "first stdin line is
|
||||||
# Non-TTY runs keep the "first stdin line is the task" path.
|
# the task" path.
|
||||||
args.task = _select_task_interactively(ds_meta, args.task)
|
if not cli_task_given:
|
||||||
|
args.task = _select_task_interactively(ds_meta, args.task)
|
||||||
|
|
||||||
observation_provider: Callable[[], dict | None] | None = None
|
observation_provider: Callable[[], dict | None] | None = None
|
||||||
robot_executor: Callable[[Any], None] | None = None
|
robot_executor: Callable[[Any], None] | None = None
|
||||||
|
|||||||
Reference in New Issue
Block a user