# 도구

LeRobot는 정책에서 **도구 호출**을 지원합니다. 어시스턴트 메시지는 `say(text="OK, starting now")`와 같은 구조화된 호출을 출력할 수 있으며, 런타임은 이를 실제 구현(TTS, 컨트롤러, 로거 등)으로 전달합니다.

이 페이지에서 다루는 내용:

1. tool catalog이 저장되는 위치입니다.
2. How the annotation pipeline produces tool-call atoms.
3. 자신의 도구를 추가하는 방법

## 도구가 선언되는 위치

두 개의 레이어.

**카탈로그** — OpenAI 스타일의 함수 스키마 목록 — 은 각 데이터셋의 `meta/info.json["tools"]`에 있습니다. 예시:

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

dataset 메타데이터 접근자를 통해 읽으세요:

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

데이터셋의 `info.json`에 도구가 선언되어 있지 않으면, `meta.tools`은 `lerobot.datasets.language`의 `DEFAULT_TOOLS`를 반환합니다. 현재 이는 정형화된 `say` 스키마를 가진 단일 항목 목록입니다. 따라서 주석이 없는 데이터셋과 채팅 템플릿 사용자는 어떤 설정 없이도 계속 작동합니다:

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

**구현** — 실행 가능한 Python — 은 `src/lerobot/tools/` 아래에 위치하며, 도구별로 하나의 파일로 구성됩니다. 런타임 디스패처와 정형화된 `say` 구현(Kyutai의 pocket-tts를 래핑)은 여기서 설명하는 카탈로그 레이어의 일부가 아닙니다. 현재 이 레이어는 스키마 저장소와 `DEFAULT_TOOLS` 폴백 상수만 제공합니다.

## 행별 도구 _호출_

위 카탈로그는 _호출할 수 있는 것_을 설명합니다. 실제 _호출_ — 함수 이름과 인자 값 — 은 `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." } } }
  ]
}
```

Recipes는 `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,
      }
```

모델의 학습 대상은 계획 텍스트 _그리고_ `say` 도구 호출을 모두 포함하는 하나의 어시스턴트 턴입니다. 추론 시 런타임은 생성된 텍스트를 구조화된 `tool_calls`로 다시 파싱하고, 일치하는 구현으로 분기합니다.

## 자체 도구 추가 방법

> **참고:** 아래 단계 2와 3은 런타임 레이어(`src/lerobot/tools/`, `Tool` 프로토콜, `TOOL_REGISTRY`, `get_tools(meta)`)를 설명하며, 이는 현재 제공되는 카탈로그 레이어의 일부가 아닙니다. 해당 모듈은 아직 트리에 존재하지 않습니다. 단계 1만으로도 `meta.tools`를 통해 채팅 템플릿에서 도구를 볼 수 있게 되어 모델이 호출을 _생성_하도록 학습할 수 있습니다. 추론 시 호출을 실행하려면 런타임 레이어가 필요합니다.

세 단계입니다. 구체적인 예시: 정책이 호출하여 정규 제어 루프 외부에서 추가 관측을 캡처할 수 있는 `record_observation` 도구입니다.

### 1단계 — 스키마 선언

`meta/info.json["tools"]` 아래에 항목을 추가하려면, 어노테이션 파이프라인을 _실행하기 전에_ 파일을 디스크에서 직접 편집하세요(이 변경 사항은 유지됩니다).

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

schema는 OpenAI의 function-calling 규약을 정확히 따르므로, chat template가 이를 네이티브로 렌더링할 수 있습니다.

### 2단계 — 호출 구현

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

도구별로 파일을 분리하면 의존성이 격리됩니다. `record_observation`은 `pillow`를 가져올 수 있고, `say`는 `pocket-tts`를 가져옵니다. 필요한 도구만 설치하는 사용자는 무거운 전이 의존성을 피할 수 있습니다.

### 3단계 — 등록

`src/lerobot/tools/registry.py`에 추가:

```python
from .record_observation import RecordObservationTool

TOOL_REGISTRY["record_observation"] = RecordObservationTool
```

이상입니다. 런타임에 `get_tools(meta)`는 `meta.tools`에서 각 스키마를 조회하고, 일치하는 등록된 클래스를 인스턴스화한 후, 디스패처가 라우팅할 수 있는 이름 → 인스턴스 딕셔너리를 반환합니다.

구현을 작성_하지 않고_ 도구를 사용하려는 경우(예: 학습 시 chat-template 포맷팅만 필요한 경우) 1단계만으로도 충분합니다 — 모델은 여전히 호출을 _생성_하도록 학습됩니다. 2단계와 3단계는 추론 시 실제로 호출을 _실행_해야 할 때만 필요합니다.

