# Colonnes de langue et recettes

La plupart des jeux de données LeRobot sont livrés avec une seule chaîne `task` par épisode — ce qui convient aux compétences courtes et à instruction unique, mais qui est insuffisant pour les politiques robotiques à long horizon et multimodales vers lesquelles le domaine évolue (planification de haut niveau, mémoire, interjections, VQA, utilisation d’outils). Pour prendre en charge ces politiques sans forker le format de jeu de données, LeRobot étend `LeRobotDataset` avec deux colonnes de langage facultatives et une petite couche de recette qui transforme ces lignes en échantillons d’entraînement de type chat à la volée.

La conception se découpe nettement en trois couches :

1. **Données dans le jeu de données** — annotations de langue stockées à côté des images dans `data/chunk-*/file-*.parquet` sous forme de deux colonnes facultatives (`language_persistent` et `language_events`). Les jeux de données sans ces colonnes conservent leur comportement existant.
2. **Recette** — un fichier YAML qui déclare quelles lignes d'annotation lier et comment les organiser en tours de discussion (`role`, `content`, images facultatives, appels d'outils facultatifs). Les recettes sont de la pure configuration ; aucun Python n'est requis pour en ajouter une nouvelle.
3. **Format d’entraînement** — au moment de l’échantillonnage, `RenderTrainingMessagesStep` résout la recette par rapport aux annotations par image et émet des `messages` au format HF ainsi que des accompagnements spécifiques à LeRobot (`message_streams`, `target_message_indices`) que les processeurs de politique consomment.

Cette page décrit chaque couche à son tour.

## Couche 1 — colonnes de langue dans le jeu de données

Les deux colonnes facultatives se trouvent à côté des données de trame dans `data/chunk-*/file-*.parquet` :

- `language_persistent` : une liste de lignes diffusées sur chaque image d'un épisode pour l'état qui reste actif, tel que `subtask`, `plan` et `memory`.
- `language_events` : une liste de lignes uniquement sur le cadre exact où un événement a été émis, tel que `interjection`, `vqa` et les appels d’outils vocaux.

Les deux colonnes partagent la même forme de ligne (les lignes d'événement omettent `timestamp` car la frame sur laquelle se trouve la ligne le fournit déjà) :

```text
role: string
content: string | null
style: string | null
timestamp: float32        # persistent rows only
camera: string | null     # observation.images.* feature key, view-dependent rows only
tool_calls: list[Json] | null
```

Le champ `camera` étiquette les lignes dont le `content` est ancré dans une vue de caméra spécifique. Les lignes des styles dépendants de la vue (`vqa` et `trace`) DOIVENT définir `camera` sur la clé de fonctionnalité correspondante de `observation.images.*`. Les lignes de tous les autres styles — y compris `motion`, qui décrit des primitives du repère du robot en termes articulaires / cartésiens — DOIVENT laisser `camera` à `null`. Les rédacteurs du pipeline et le validateur appliquent cette règle via `validate_camera_field(style, camera)`.

`meta/tasks.parquet` reste la source canonique pour la tâche. Le binding de recette spécial `${task}` lit toujours cette chaîne de caractères de tâche et ne dépend pas des annotations de langue.

### Architecture

La pile de langage elle-même comporte trois modules internes soutenant la couche 1 :

1. `lerobot.datasets.language` définit le schéma, le registre de styles et `column_for_style`.
2. `lerobot.datasets.language_render` résout les lignes et affiche les messages.
3. `RenderTrainingMessagesStep` transforme les échantillons du jeu de données en `messages`, `message_streams` et `target_message_indices`.

`LeRobotDataset` reste indépendant des recettes. Il transmet `language_persistent` et `language_events` lorsqu’ils sont présents, et les jeux de données non annotés conservent leur comportement existant.

## Couche 2 — anatomie de la recette

Les recettes sont des fichiers YAML adossés à `TrainingRecipe` et `MessageTurn` de `lerobot.datasets.recipe`. Elles déclarent quelles lignes d’annotation extraire (via `bindings`) et comment les composer en tours de chat (`messages`).

```yaml
messages:
  - { role: user, content: "${task}", stream: high_level }
  - { role: assistant, content: "${subtask}", stream: low_level, target: true }
```

Une recette peut également se ramifier en un **mélange pondéré** de sous-recettes. Au moment de l'échantillonnage, exactement une branche est sélectionnée de manière déterministe à partir de l'index de l'échantillon, de sorte que différentes images entraînent différents objectifs (par exemple, mises à jour de la mémoire, exécution de bas niveau, VQA) sans aucun câblage Python.

### Sémantique temporelle

Les styles persistants restent actifs après leur émission jusqu'à ce qu'ils soient remplacés :

- `active_at(t, style=subtask)`
- `nth_prev(style=memory, offset=1)`
- `nth_next(style=subtask, offset=1)`

Les styles d'événement n'existent que sur leur horodatage exact :

- `emitted_at(t, style=interjection)`
- `emitted_at(t, style=vqa, role=user, camera=observation.images.top)`
- `emitted_at(t, role=assistant, tool_name=say)`

La correspondance exacte des événements n’a pas de fenêtre de tolérance, les rédacteurs doivent donc horodater les lignes d’événements avec les horodatages des images issus des données parquet.

### Résolution dépendante de la vue

Pour les styles dépendants de la vue (`vqa` et `trace`), le résolveur gagne un filtre `camera=` parallèle à `role=` et `tool_name=`. Les jeux de données avec plusieurs caméras émettent typiquement une paire (`vqa`, `user`) + (`vqa`, `assistant`) par caméra au même horodatage ; sans `camera=`, ces résolveurs voient deux correspondances et lèvent une erreur d’ambiguïté. Les recettes consomment chaque caméra via sa propre liaison plus un bloc d’image correspondant, par exemple

```yaml
ask_vqa_top:
  route: vqa
  bindings:
    vqa_query: "emitted_at(t, style=vqa, role=user, camera=observation.images.top)"
    vqa: "emitted_at(t, style=vqa, role=assistant, camera=observation.images.top)"
  messages:
    - role: user
      stream: high_level
      if_present: vqa_query
      content:
        - { type: image, feature: observation.images.top }
        - { type: text, text: "${vqa_query}" }
    - {
        role: assistant,
        content: "${vqa}",
        stream: high_level,
        target: true,
        if_present: vqa,
      }
```

Ajoutez un tel sous-réceptacle pour chaque caméra enregistrée par le jeu de données. Le marqueur explicite `route: vqa` permet à une annotation VQA correspondante de prendre le pas sur la sélection pondérée standard ; les noms des composants sont purement descriptifs.

## Couche 3 — format d'entraînement

Les échantillons rendus utilisent des messages de chat au format HF ainsi que des sidecars LeRobot :

```python
sample["messages"]
sample["message_streams"]
sample["target_message_indices"]
```

Le renderer n’applique pas de modèle de chat du tokenizer. Les processeurs de politique décident comment sérialiser les messages pour leur backbone, ce qui permet de réutiliser le même jeu de données avec SmolVLA, Pi0.5 et tout futur VLM qui s’attend à des messages de chat au format OpenAI.

## Mélanges

Les recettes de mélange sélectionnent de manière déterministe une sous-recette pondérée à partir de l’index de l’échantillon. `recipes/subtask_mem.yaml` entraîne le mélange compact du cœur — prédiction de sous-tâches de haut niveau, exécution de bas niveau et mémoire. `recipes/subtask_mem_vqa_speech.yaml` est la variante plus complète qui ajoute également les réponses VQA et les interjections orales.

`recipes/subtask_joint.yaml` illustre un entraînement conjoint de séquences plutôt qu’un mélange pondéré. Pour un même échantillon, sa sous-tâche assistant est supervisée par l’entropie croisée de texte sur le flux `low_level` tandis que la prédiction d’action reste active, conformément à la configuration conjointe du papier π0.5. Pour l’instant, il est réservé à l’entraînement : le runtime ne reconstruit pas encore ce préfixe conjoint lors de l’inférence.

## Absence élégante

Si les deux colonnes de langue sont absentes, `None` ou vides, `RenderTrainingMessagesStep` utilise la chaîne de tâche comme supervision de bas niveau lorsqu’elle est disponible, et laisse sinon l’échantillon inchangé. Pour un échantillon annoté, si aucune branche de recette ne s’applique et qu’aucun repli sur la tâche n’existe, le rendu renvoie `None`, permettant à un chargeur de réessayer avec un autre échantillon.

