---
title: Инструменты
sidebar:
  order: 9
---

OCR поставляется с **шестью встроенными инструментами**, которые LLM может
вызывать во время ревью. На этой странице описаны назначение каждого
инструмента, его схема входных данных и примеры ввода/вывода. Полные
машиночитаемые определения находятся в файле
[`internal/config/toolsconfig/tools.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/toolsconfig/tools.json).

## Доступность инструментов на разных этапах

Каждый инструмент объявляет, доступен ли он на **этапе планирования**, в
**основной задаче** или на обоих этапах:

| Инструмент | Планирование | Основная задача | Назначение |
|---|---|---|---|
| `task_done` | ✗ | ✓ | Сообщает «я закончил» и завершает цикл. |
| `code_comment` | ✗ | ✓ | Создаёт комментарий ревью с диапазоном строк и предложением. |
| `file_read` | ✗ | ✓ | Читает часть файла из состояния после изменения. |
| `file_read_diff` | ✓ | ✓ | Читает diff другого файла, чтобы проверить межфайловое предположение. |
| `file_find` | ✓ | ✓ | Ищет файлы по ключевому слову в имени. |
| `code_search` | ✓ | ✓ | Выполняет поиск по репозиторию (обычный текст или регулярное выражение). |

`task_done` и `code_comment` намеренно **недоступны** на этапе планирования:
планирование выполняется только для чтения.

> **Контекстные инструменты предоставляют контекст только для чтения, а не
> цели комментариев.** Промпт `main_task` прямо запрещает комментировать
> замечания в *других* файлах. `file_read`, `file_read_diff`, `file_find` и
> `code_search` помогают модели лучше понять diff текущего файла; любые
> проблемы, замеченные при сборе такого контекста, намеренно игнорируются.
> Межфайловые проблемы становятся комментариями только тогда, когда они
> видны из **diff текущего файла**.

Чтобы переопределить реестр инструментов, передайте флагу `--tools <path>`
JSON-файл той же структуры, что и встроенный. Так можно отключить инструмент,
изменить его описание или добавить новый инструмент на основе существующего
провайдера.

## `task_done`

Завершает основной цикл.

```json
{
  "name": "task_done",
  "input": { "state": "DONE" }
}
```

| Поле | Обязательно | Значение |
|---|---|---|
| `state` | да | `DONE` (по умолчанию) или `FAILED`. `FAILED` означает «я буквально не могу выполнить задачу доступными инструментами» и почти никогда не является правильным выбором. |

Когда агент получает `task_done`, он прекращает вызовы LLM и начинает
обрабатывать накопленные вызовы `code_comment`. `task_done` возвращается
немедленно (до записи результата в журнал сессии), поэтому значение `state`
принимается, но **не сохраняется** и также не влияет на коды завершения.

## `code_comment`

Создаёт один или несколько комментариев ревью. Каждый комментарий привязывается
к фрагменту кода (`existing_code`), чтобы OCR мог автоматически вычислить
номера строк.

### Схема

```json
{
  "name": "code_comment",
  "input": {
    "path": "string — optional, override the file path for this comment",
    "comments": [
      {
        "content": "string — the comment in the configured language",
        "existing_code": "string — snippet from the diff to anchor on",
        "suggestion_code": "string — optional fix snippet",
        "thinking": "string — optional, the model's reasoning for this comment"
      }
    ]
  }
}
```

`comments` — это массив, поэтому модель может создать несколько комментариев
за один вызов инструмента. Поля `content` и `existing_code` обязательны;
`suggestion_code` необязательно, но рекомендуется. `path` — необязательное
переопределение верхнего уровня; если оно отсутствует, агент подставляет
проверяемый в данный момент файл. Агент также автоматически добавляет `path`,
если модель его не указала, поэтому модели редко требуется задавать это поле
явно. Поле `thinking` для каждого комментария содержит рассуждение модели и
сохраняется вместе с комментарием; OCR заполняет его из содержимого
рассуждений (reasoning content), которое модель выводит в текущем ходе
(поле остаётся пустым, если модель ничего не выводит), и включает его в
JSON-вывод (в терминальном выводе оно не отображается).

> **`thinking` существует только во время выполнения.** OCR разбирает и
> сохраняет его, однако намеренно **не** включает в схему `code_comment`,
> которая объявляется модели в `tools.json` (там присутствуют только `content`,
> `existing_code` и `suggestion_code`). Более сильные модели, которые всё же
> выводят блок `thinking`, сохранят его; большинство его не отправляет, и это
> нормально.

### Алгоритм привязки

OCR ищет текст из `existing_code` в diff с помощью **динамического скользящего
окна**. Совпадения проверяются в следующем порядке:

1. **Новая сторона фрагмента** — последовательность смежных строк **контекста
   и добавлений** (не только удалённых или неизменённых строк), которая даёт
   номера строк нового файла. Если совпадение не найдено, OCR повторяет поиск
   по **старой стороне фрагмента** — строкам контекста и удалениям — и получает
   номера строк старого файла.
2. **Полное сканирование нового файла** — если совпадения во фрагменте нет,
   OCR построчно ищет последовательные совпадения во всём содержимом файла
   после изменения (`resolveFromFileContent`).
3. **Задача повторного позиционирования** — если сопоставление текста всё ещё
   не удалось для нетривиального diff, OCR запускает промпт `RE_LOCATION_TASK`,
   прося модель заново привязать фрагмент.

Сопоставление выполняется **без учёта пробельных символов**: строки обрезаются,
а маркеры diff `+`/`-` удаляются перед сравнением, поэтому точного совпадения
отступов не требуется. В крайнем случае комментарий выдаётся со значением
`start_line=0`, сообщая пользователю: «проблема реальна, но место придётся
найти самостоятельно».

### Пример

```json
{
  "comments": [
    {
      "content": "`tx.Rollback()` is never deferred — early returns leak the transaction.",
      "existing_code": "tx, err := db.Begin()\nif err != nil {\n    return err\n}",
      "suggestion_code": "tx, err := db.Begin()\nif err != nil {\n    return err\n}\ndefer tx.Rollback()"
    }
  ]
}
```

## `file_read`

Читает диапазон строк файла в состоянии **после изменения**.

### Схема

```json
{
  "name": "file_read",
  "input": {
    "file_path": "src/foo.go",
    "start_line": 10,
    "end_line": 80
  }
}
```

| Поле | Обязательно | По умолчанию | Примечания |
|---|---|---|---|
| `file_path` | да | — | Путь относительно корня репозитория. |
| `start_line` | нет | `1` | Нумерация начинается с 1. |
| `end_line` | нет | конец файла | Включительно. |

### Вывод

```
File: src/foo.go (Total lines: 220)
IS_TRUNCATED: false
LINE_RANGE: 10-80
10|package foo
11|
12|import (
13|    "fmt"
…
```

К каждой строке содержимого добавляется её номер с отсчётом от 1 и разделитель
`|`, чтобы модель могла точно указывать номера строк в последующих вызовах
`code_comment`.

### Ограничения

- **Не более 500 строк за вызов.** Более длинные диапазоны обрезаются,
  устанавливается `IS_TRUNCATED: true`, а в конец добавляется сообщение
  `Note: Results truncated to 500 lines. Please narrow your line range.`.
- Читается только **изменённая версия** файла. Чтобы увидеть старую версию,
  используйте `file_read_diff`.

Когда модели требуется окружающий контекст (например, для комментария к
функции, видимой в diff лишь частично), ей следует вычислить диапазон из
заголовка фрагмента diff `@@ -x,y +m,n @@` — обычно от `m-50` до `m+n+50`.

## `file_read_diff`

Читает diff одного или нескольких *других* файлов в том же наборе изменений.
Это полезно, когда комментарий зависит от того, был ли обновлён связанный файл.

### Схема

```json
{
  "name": "file_read_diff",
  "input": {
    "path_array": ["src/api/handler.go", "src/db/queries.go"]
  }
}
```

### Вывод

```
==== FILE: src/api/handler.go ====
--- a/src/api/handler.go
+++ b/src/api/handler.go
@@ -10,1 +10,2 @@
- old line
+ new line 1
+ new line 2

==== FILE: src/db/queries.go ====
@@ -5,1 +5,1 @@
- query := "SELECT *"
+ query := "SELECT id"
```

Если путь отсутствует в наборе изменений, соответствующая запись незаметно
пропускается. Если **ни одного** из запрошенных путей нет в наборе изменений,
инструмент возвращает `Error: diff not found for the requested paths`; пустой
`path_array` возвращает `Error: no files found`.

## `file_find`

Ищет файлы в репозитории по ключевому слову в имени (совпадение подстроки).

### Схема

```json
{
  "name": "file_find",
  "input": {
    "query_name": "UserService",
    "case_sensitive": false
  }
}
```

| Поле | Обязательно | По умолчанию | Примечания |
|---|---|---|---|
| `query_name` | да | — | Подстрока сопоставляется с **базовым именем** каждого файла (частью после последнего `/`), а не с полным путём. |
| `case_sensitive` | нет | `false` | Установите `true` для сопоставления с учётом регистра. |

Набор кандидатов формируется командой `git ls-files --cached --others
--exclude-standard` в режиме рабочей области или командой
`git ls-tree -r --name-only <ref>` в режиме диапазона / коммита. Файлы без
расширения пропускаются, кроме `Makefile`, `Dockerfile`, `LICENSE`, `Vagrantfile`
и `Containerfile`.

### Вывод

Список путей, разделённых переводами строк:

```
src/main/java/com/example/UserService.java
src/test/java/com/example/UserServiceTest.java
src/main/java/com/example/internal/UserServiceImpl.java
```

Если ни один файл не совпадает (или `query_name` пуст), инструмент возвращает
буквальную строку `// The file was not found`.

### Ограничения

Возвращается не более **100** совпадений; лишние незаметно отбрасываются. Если
модели нужен более широкий поиск, ей следует перейти к `code_search`.

## `code_search`

Полнотекстовый поиск по репозиторию. Основан на `git grep`, поэтому понимает
синтаксис `pathspec` и учитывает `.gitignore`.

### Схема

```json
{
  "name": "code_search",
  "input": {
    "search_text": "TODO|FIXME",
    "file_patterns": ["*.go", ":(exclude)vendor/"],
    "case_sensitive": false,
    "use_perl_regexp": true
  }
}
```

| Поле | Обязательно | По умолчанию | Примечания |
|---|---|---|---|
| `search_text` | да | — | Обычная строка или шаблон PCRE (см. `use_perl_regexp`). |
| `file_patterns` | нет | весь репозиторий | Массив элементов pathspec. Используйте `:(exclude)pat` для исключения. |
| `case_sensitive` | нет | `false` | — |
| `use_perl_regexp` | нет | `false` | При значении `true` параметр `search_text` интерпретируется как регулярное выражение. |

### Вывод

Результаты группируются по файлам. Каждая группа начинается со строк
`File: <path>` и `Match lines: <n>`, после чего для каждого совпадения выводится
строка `line|content`:

```
File: path/to/example.java
Match lines: 2
433|      String name = toolRequest.get().getName();
438|      logToolRequest(newPath, tool, toolRequest.get());

File: path/to/other.java
Match lines: 1
22|      var req = new ToolRequest();
```

Если совпадений нет, инструмент возвращает буквальную строку
`No matches found`.

### Рецепты pathspec

| Цель | `file_patterns` |
|---|---|
| Один файл | `["src/main.go"]` |
| Все файлы Go | `["*.go"]` |
| Все файлы Go, кроме тестов | `["*.go", ":(exclude)*_test.go"]` |
| Только один каталог | `["src/api/"]` |
| Несколько типов без vendor | `["*.go", "*.ts", ":(exclude)vendor/", ":(exclude)node_modules/"]` |

### Ограничения

- Число совпадений ограничено **100 для каждого файла** с помощью
  `git grep --max-count 100`, поэтому общий вывод для нескольких файлов может
  превышать 100 результатов. При достижении лимита для файла перед выводом
  добавляется `Note: The results have been truncated. Only showing first 100
  results.`.
- Пустое или состоящее только из пробелов значение `search_text` возвращает
  `Error: search_text is blank`, а не разворачивается во все строки.
- Поиск выполняется в **текущем рабочем дереве** в режиме рабочей области или
  в выбранной целевой ссылке в режиме диапазона / коммита (`FileReader.Ref`
  передаётся `git grep` как позиционный аргумент).

## Выполнение инструментов и ошибки

Инструменты выполняются синхронно внутри цикла агента, за двумя исключениями:

- `code_comment` передаётся в **CommentWorkerPool**, чтобы цикл не блокировался
  на определении строк и рефлексии.
- `task_done` немедленно завершает обработку и возвращается без вызова
  провайдера.

Если инструмент завершается с ошибкой (сбой сети, неверные аргументы, файл не
найден), результат передаётся модели как обычный результат инструмента с
текстом вроде `"Error: file not found: src/missing.go"`. Затем модель решает,
повторить ли попытку, запросить другой файл или вызвать `task_done`.

Если имени инструмента нет в реестре, OCR возвращает константу
`tool.NotAvailableMsg`, а не аварийно завершается. Благодаря этому инструменты
можно безопасно отключать во время выполнения через `--tools`.

## Настройка инструментов

Есть два способа расширения:

### 1. Отключение инструмента

Скопируйте `tools.json`, удалите ненужную запись и выполните:

```bash
ocr review --tools ./my-tools.json
```

Например, для «ревьюера только с комментариями», который не читает
дополнительный контекст, оставьте только `code_comment` и `task_done`.

### 2. Изменение описания инструмента

Сохраните `name` (провайдеры находятся внутри системы по имени), но измените
`description`, чтобы направить модель. Это самый простой способ добавить
рекомендации для конкретного проекта, например: «При использовании
`file_read` всегда читай не менее 30 строк вокруг изменения».

> Добавление **новых** *имён* инструментов требует подключения на стороне Go;
> см. `internal/tool/definitions.go` и провайдеры в `internal/tool/`. Одного
> JSON-файла недостаточно для добавления нового поведения.

## См. также

- [Архитектура](../architecture/) — как цикл агента вызывает инструменты.
- [Правила ревью](../review-rules/) — на чём LLM предлагается сосредоточиться.
- [Просмотр сессий](../viewer/) — какие именно инструменты вызывались в прошлых ревью.
