Глава 03 · Расширения

Tools — руки и глаза агента

Инструменты — это санкционированный прокол sandbox: модель просит harness что-то сделать в реальном мире и получает результат обратно как новое сообщение. Без них агент только пишет текст; с ними — читает файлы, ходит в API и переписывает код.

Что добавляют tools к обычному чату

Языковая модель сама по себе — это функция «текст → текст». Она не открывает файлы, не ходит в сеть, не выполняет shell-команды; без расширений она может только описать, как такое сделать. Tools — механизм, через который модель получает санкционированный доступ к окружению: вместо того чтобы написать «прочитай main.py», она формирует структурированный запрос, harness его исполняет и кладёт результат в историю. Anthropic формулирует это так: «Tool use lets Claude call functions you define or that Anthropic provides. Claude decides when to call a tool based on the user's request and the tool's description, then returns a structured call that your application executes».

Главное: модель никогда не «звонит» сама. Она возвращает блок tool_use — структурированную просьбу; всё, что происходит в реальном мире, делает harness и только он отвечает за побочные эффекты. Эта строгая граница и есть фундамент безопасности: позволенный набор инструментов плюс правила вокруг них (глава «Permissions») полностью описывают то, что агент способен сделать с системой.

Tool schema: name, description, input_schema

Чтобы модель «узнала» об инструменте, harness кладёт его описание в параметр tools при вызове /v1/messages. У каждого инструмента три обязательных поля: name (короткий идентификатор), description (что инструмент делает и когда его звать) и input_schema (JSON Schema аргументов). Минимальный пример:

{
  "name": "list_files",
  "description": "Возвращает список файлов и подкаталогов внутри указанного абсолютного пути. Используй, когда нужно понять структуру каталога перед чтением конкретного файла.",
  "input_schema": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Абсолютный путь к каталогу (например, /home/dev/projects)."
      }
    },
    "required": ["path"]
  }
}

Описание важнее имени. Модель «понимает» инструмент по description, не по имени переменной в коде. Anthropic в «Building Effective Agents» советует относиться к этому как к docstring для джуниора: примеры, edge-кейсы, формат ввода, явные границы между похожими инструментами. Они же приводят показательный пример из SWE-bench: на оптимизацию tools у них ушло больше времени, чем на основной промпт, а замена относительных путей на абсолютные в схеме FileEdit устойчиво улучшила качество — модель перестала путаться после cd.

Протокол: tool_use → tool_result

Поворот цикла с инструментом всегда выглядит одинаково. Сначала модель возвращает в ответе блок tool_use с собственным id, именем инструмента и аргументами. Поле stop_reason у такого ответа — "tool_use", это сигнал harness, что нужно действовать. Harness целиком копирует ответ модели (включая tool_use-блоки) в messages как сообщение assistant, исполняет вызов и формирует следующее сообщение с ролью user, внутри которого — блок tool_result:

{
  "role": "user",
  "content": [{
    "type": "tool_result",
    "tool_use_id": "toolu_01A...",
    "content": "main.py\nsrc/\ntests/\nREADME.md"
  }]
}

Ключевое поле — tool_use_id: оно ссылается на id блока tool_use, который мы закрываем. Отсюда центральный инвариант: каждый tool_use в assistant-сообщении обязан быть закрыт tool_result в следующем user-сообщении. Если хоть один tool_use_id не найдёт пары, API вернёт ошибку валидации и цикл оборвётся. Логика как у скобок: открыли — закрывай.

Содержимое tool_result.content может быть строкой или массивом блоков (включая image). Модель видит ровно то, что вы туда положили. Ошибку инструмента передают тем же блоком с полем is_error: true — это не исключение для цикла, а другой результат, на который модель сама решает, как реагировать (повторить, поправить, спросить пользователя).

Параллельные tool calls

Модель не обязана просить по одному инструменту за раз. В одном ответе может быть несколько tool_use-блоков подряд — например, «прочитай эти три файла». В таком случае harness может (и часто должен) исполнить их параллельно: для read-only-инструментов это очевидный выигрыш по латентности. Главное правило — симметрия: сколько tool_use ушло, столько tool_result должно вернуться в следующем user-сообщении, в одном content-массиве и с правильными tool_use_id. Порядок результатов может не совпадать с порядком запросов — связь идёт по id.

Параллельность — свойство harness, а не модели. claude-cli помечает каждый tool флагом isConcurrencySafe: read-only по умолчанию параллелится, write-операции — нет. Если хоть один вызов в пачке небезопасен, harness исполняет всю пачку последовательно. В 07-tool-surface это видно в дефолтах: «every tool starts assumed-unsafe; opt-in via override».

Built-in vs custom vs MCP

С точки зрения протокола все инструменты одинаковы — единый Tool-контракт: name, description, input_schema, исполняющая функция. Источник реализации различает три класса.

Built-in tools — базовый набор, который харнесс приносит с собой: в claude-cli это BashTool, FileReadTool, FileEditTool, GrepTool, WebFetchTool, TodoWriteTool и ещё десяток, собираемый функцией getAllBaseTools. Custom tools — инструменты, которые добавляет конкретное приложение через тот же параметр tools API: «найди пользователя по email», «сделай платёж». Снаружи модель не видит разницы — это одинаковые JSON-схемы. MCP tools приходят из внешних серверов по Model Context Protocol: harness держит клиента, MCP-сервер публикует свои инструменты, harness склеивает их с остальной поверхностью. Имя MCP-инструмента префиксуется (mcp__server__tool), чтобы permission-слой мог адресовать целый сервер.

Главное следствие единого контракта: permission gate, hooks, рендеринг и логика повторов работают одинаково для всех трёх классов. claude-cli формулирует это так: «Built-in tools, MCP tools, REPL inner tools, agent skills, and bundled-via-feature-flag tools all implement the same Tool interface». Цена нового инструмента — одна запись в каталоге и одна функция.

Что ВАЖНО не делать

Самые частые ошибки при работе с tools — не на уровне протокола, а на уровне дизайна. Не описывайте инструменты «слишком вежливо». Размытая фраза вроде «помогает работать с файлами» оставляет модели слишком большой простор для интерпретации, и она начинает путать ваш инструмент с соседним. Пишите конкретно: что возвращает, что не возвращает, в каком формате аргументы. OpenAI прямо предупреждает: «issue isn't solely the number of tools, but their similarity or overlap» — десять «похожих» инструментов работают хуже, чем пятнадцать с чёткими границами.

Не смешивайте слишком много в один tool. Соблазнительно сделать универсальный do_anything(action, params), но модель плохо выбирает действие «по строке», особенно когда вариантов больше трёх. Лучше разделить на send_email, create_user, cancel_order — каждый с минимальным input_schema. Не забывайте про идемпотентность. Модель может повторить вызов из-за recovery-итерации, частичного сбоя или просто потому, что «не уверена». Если tool кладёт деньги или пишет в БД, проектируйте его так, чтобы повторный вызов с теми же аргументами не дублировал эффект — и явно пишите это в description.

И последнее: tools без permissions — это дверь без замка. Контракт «модель просит, harness делает» элегантен ровно до тех пор, пока harness слепо исполняет всё подряд. Реальная безопасность собирается в главе «Permissions»: allow/deny-правила, две фазы валидации, hooks на PreToolUse/PostToolUse.

"

Tool access is one of the highest-leverage primitives you can give an agent. On benchmarks like LAB-Bench FigQA (scientific figure interpretation) and SWE-bench (real-world software engineering), adding even basic tools produces outsized capability gains, often surpassing human expert baselines.

— Anthropic Docs, «Tool use with Claude»

Источники главы