# Ferramentas

O LeRobot suporta **chamadas de ferramentas** em políticas — mensagens de assistente podem emitir invocações estruturadas como `say(text="OK, starting now")` que o tempo de execução despacha para uma implementação real (TTS, controlador, logger, …).

Esta página aborda:

1. Onde o catálogo de ferramentas fica.
2. Como o pipeline de anotação produz átomos de chamada de ferramenta.
3. Como adicionar sua própria ferramenta.

## Onde as ferramentas são declaradas

Duas camadas.

**O catálogo** — uma lista de esquemas de funções no estilo OpenAI — está em `meta/info.json["tools"]` em cada conjunto de dados. Exemplo:

```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"]
        }
      }
    }
  ]
}
```

Leia-o por meio do acessor de metadados do conjunto de dados:

```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
```

Se o `info.json` do conjunto de dados não declarar nenhuma ferramenta, `meta.tools` retorna `DEFAULT_TOOLS` de `lerobot.datasets.language` — atualmente, uma lista com uma única entrada contendo o esquema canônico `say`. Assim, conjuntos de dados sem anotações e consumidores de modelos de chat continuam funcionando sem nenhuma configuração:

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

**As implementações** — Python executável — ficarão em `src/lerobot/tools/`, um arquivo por ferramenta. O despachador de runtime e a implementação canônica de `say` (que encapsula o pocket-tts da Kyutai) não fazem parte da camada de catálogo descrita aqui; hoje, esta camada fornece apenas o armazenamento do esquema e a constante de fallback `DEFAULT_TOOLS`.

## Per-row tool _invocations_

O catálogo acima descreve _o que pode ser chamado_. A _chamada_ real — o nome da função mais os valores dos argumentos — é armazenada por linha, nos átomos de assistente em `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." } } }
  ]
}
```

As receitas inserem esses dados nas mensagens renderizadas por meio de `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,
      }
```

O alvo de treinamento do modelo é um turno do assistente que carrega tanto o texto do plano _quanto_ a chamada da ferramenta `say`. Na inferência, o tempo de execução analisa o texto gerado de volta em `tool_calls` estruturados e despacha para a implementação correspondente.

## Como adicionar sua própria ferramenta

> **Observação:** As etapas 2 e 3 abaixo descrevem a camada de tempo de execução (`src/lerobot/tools/`, o protocolo `Tool`, `TOOL_REGISTRY`, `get_tools(meta)`), que não faz parte da camada de catálogo distribuída atualmente — esses módulos ainda não existem na árvore. A etapa 1, por si só, é suficiente para tornar a ferramenta visível para o modelo de chat via `meta.tools`, permitindo que o modelo aprenda a _gerar_ a chamada; a execução da chamada durante a inferência requer a camada de tempo de execução.

Três etapas. Exemplo concreto: uma ferramenta `record_observation` que a política pode chamar para capturar uma observação extra fora do loop de controle regular.

### Etapa 1 — declare o esquema

Adicione uma entrada em `meta/info.json["tools"]` editando o arquivo diretamente no disco _antes_ de executar o pipeline de anotação (ela será preservada).

```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"]
        }
      }
    }
  ]
}
```

O esquema segue exatamente a convenção de chamada de função da OpenAI, de modo que o modelo de chat pode renderizá-lo nativamente.

### Etapa 2 — implemente a chamada

Crie `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"
```

Um arquivo por ferramenta mantém as dependências isoladas — `record_observation` pode exigir `pillow`, enquanto `say` exige `pocket-tts`. Usuários que instalam apenas as ferramentas de que precisam evitam dependências transitivas pesadas.

### Etapa 3 — registre-o

Adicione em `src/lerobot/tools/registry.py`:

```python
from .record_observation import RecordObservationTool

TOOL_REGISTRY["record_observation"] = RecordObservationTool
```

É isso. Em tempo de execução, `get_tools(meta)` consulta cada schema em `meta.tools`, instancia a classe registrada correspondente e retorna um dicionário nome → instância para o dispatcher rotear.

Se você deseja usar uma ferramenta _sem_ escrever uma implementação (por exemplo, apenas para o formatação do chat-template durante o treinamento), a etapa 1 é suficiente — o modelo ainda aprende a _gerar_ a chamada. As etapas 2 e 3 são necessárias apenas para realmente _executá-la_ na inferência.

