# Herramientas

LeRobot admite **llamadas a herramientas** en las políticas: los mensajes del asistente pueden emitir invocaciones estructuradas como `say(text="OK, starting now")` que el tiempo de ejecución despacha a una implementación real (TTS, controlador, registrador, …).

Esta página cubre:

1. Donde se encuentra el catálogo de herramientas.
2. Cómo el pipeline de anotación produce átomos de llamadas a herramientas.
3. Cómo agregar tu propia herramienta.

## Donde se declaran las herramientas

Dos capas.

**El catálogo**, una lista de esquemas de funciones estilo OpenAI, se encuentra en `meta/info.json["tools"]` de cada conjunto de datos. Ejemplo:

```json
{
  "features": { "...": "..." },
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "say",
        "description": "Speak a short utterance to the user via the TTS executor.",
        "parameters": {
          "type": "object",
          "properties": {
            "text": {
              "type": "string",
              "description": "The verbatim text to speak."
            }
          },
          "required": ["text"]
        }
      }
    }
  ]
}
```

Léelo a través del accesor de metadatos del conjunto de datos:

```python
from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata

meta = LeRobotDatasetMetadata(repo_id="pepijn/super_poulain_final_annotations")
tools = meta.tools     # list[dict] — OpenAI tool schemas
```

Si el `info.json` del conjunto de datos no declara ninguna herramienta, `meta.tools` devuelve `DEFAULT_TOOLS` de `lerobot.datasets.language`, que actualmente es una lista de una sola entrada con el esquema canónico `say`. Así, los conjuntos de datos sin anotaciones y los consumidores de plantillas de chat siguen funcionando sin ninguna configuración:

```python
prompt_str = tokenizer.apply_chat_template(
    sample["messages"],
    tools=meta.tools,                 # works either way
    add_generation_prompt=False,
    tokenize=False,
)
```

**Las implementaciones** — Python ejecutable — se ubicarán en `src/lerobot/tools/`, un archivo por herramienta. El despachador de tiempo de ejecución y la implementación canónica de `say` (que envuelve pocket-tts de Kyutai) no forman parte de la capa de catálogo descrita aquí; hoy en día, esta capa solo incluye el almacenamiento del esquema y la constante de respaldo `DEFAULT_TOOLS`.

## Invocaciones de _invocations_ por fila

El catálogo anterior describe _lo que se puede llamar_. La _llamada_ real, es decir, el nombre de la función más los valores de los argumentos, se almacena por fila, en los átomos de asistente de `language_events`:

```python
{
  "role": "assistant",
  "content": null,
  "style": null,
  "timestamp": 12.4,
  "camera": null,
  "tool_calls": [
    { "type": "function",
      "function": { "name": "say", "arguments": { "text": "On it." } } }
  ]
}
```

Las recetas insertan estos en los mensajes renderizados mediante `tool_calls_from`:

```yaml
user_interjection_response:
  bindings:
    speech: "emitted_at(t, role=assistant, tool_name=say)"
  messages:
    - { role: user, content: "${task}", stream: high_level }
    - {
        role: assistant,
        content: "${current_plan}",
        stream: high_level,
        target: true,
        tool_calls_from: speech,
      }
```

El objetivo de entrenamiento del modelo es un turno del asistente que incluye tanto el texto del plan _como_ la llamada a la herramienta `say`. En la inferencia, el tiempo de ejecución analiza el texto generado y lo convierte de nuevo en `tool_calls` estructurados y los despacha a la implementación correspondiente.

## Cómo agregar tu propia herramienta

> **Nota:** Los pasos 2 y 3 a continuación describen la capa de tiempo de ejecución (`src/lerobot/tools/`, el protocolo `Tool`, `TOOL_REGISTRY`, `get_tools(meta)`) que no forma parte de la capa de catálogo que se distribuye hoy; esos módulos aún no existen en el árbol. El paso 1 por sí solo es suficiente para hacer que la herramienta sea visible para la plantilla de chat a través de `meta.tools` para que el modelo pueda aprender a _generar_ la llamada; ejecutar la llamada en tiempo de inferencia requiere la capa de tiempo de ejecución.

Tres pasos. Ejemplo concreto: una herramienta `record_observation` que la política puede llamar para capturar una observación adicional fuera del bucle de control regular.

### Paso 1 — declarar el esquema

Agrega una entrada en `meta/info.json["tools"]` editando el archivo directamente en el disco _antes_ de ejecutar el pipeline de anotación (se conservará).

```json
{
  "tools": [
    { "type": "function", "function": { "name": "say", "...": "..." } },
    {
      "type": "function",
      "function": {
        "name": "record_observation",
        "description": "Capture a high-resolution still image for the user.",
        "parameters": {
          "type": "object",
          "properties": {
            "label": {
              "type": "string",
              "description": "Short label for the saved image."
            }
          },
          "required": ["label"]
        }
      }
    }
  ]
}
```

El esquema sigue exactamente la convención de llamada de funciones de OpenAI, por lo que la plantilla de chat puede renderizarla de forma nativa.

### Paso 2 — implementar la llamada

Crea `src/lerobot/tools/record_observation.py`:

```python
from .base import Tool
from typing import Any

RECORD_OBSERVATION_SCHEMA: dict[str, Any] = { "...": "..." }   # mirrors the JSON above

class RecordObservationTool:
    name = "record_observation"
    schema = RECORD_OBSERVATION_SCHEMA

    def __init__(self, schema: dict | None = None, output_dir: str = "."):
        self.output_dir = output_dir

    def call(self, arguments: dict) -> str:
        label = arguments["label"]
        # ... save the latest camera frame to <output_dir>/<label>.png ...
        return f"saved {label}.png"
```

Mantener un archivo por herramienta aísla las dependencias: `record_observation` podría requerir `pillow`, mientras que `say` requiere `pocket-tts`. Los usuarios que instalan solo las herramientas que necesitan evitan dependencias transitivas pesadas.

### Paso 3 — regístrelo

Agrega a `src/lerobot/tools/registry.py`:

```python
from .record_observation import RecordObservationTool

TOOL_REGISTRY["record_observation"] = RecordObservationTool
```

Eso es todo. En tiempo de ejecución, `get_tools(meta)` busca cada esquema en `meta.tools`, instancia la clase registrada correspondiente y devuelve un diccionario de nombre → instancia al que el despachador puede enrutar.

Si deseas usar una herramienta _sin_ escribir una implementación (por ejemplo, solo para el formato de plantilla de chat durante el entrenamiento), el paso 1 es suficiente: el modelo sigue aprendiendo a _generar_ la llamada. Los pasos 2 y 3 solo son necesarios para _ejecutarla_ realmente en la inferencia.

