Главная / Статьи

Инструмент Claude tool_use «от начала до конца»: цикл агентности в трёх итерациях

Инструмент Claude use end to end: один запрос к API Messages через цикл агентности, с правилами определения инструментов, выбора инструмента, параллельных вызовов и получения результатов инструмента.

Издатель: REACTAPP.TOP19 мин чтения4379 слов

Источники: Part III | ‘tool_use’, End to End: The Agentic Loop in Three Iterations

Почему цикл нужно писать самостоятельно

Работа с tool_use непосредственно через API Messages (anthropic.Anthropic() с последующим вызовом client.messages.create(...)) позволяет вам контролировать процесс выполнения цикла. Ваш код отвечает за объявление инструментов, анализ причин остановки, парсинг запросов от Claude, их выполнение и возвращение результатов. Ручное написание такого цикла является основой любой системы, использующей инструменты, и это стандартная тема в учебном плане курса Claude Certified Architect.

Сначала небольшое напоминание: API Messages не имеет состояния. Ничего не сохраняется между вызовами, поэтому каждый запрос содержит всю информацию о диалоге, а выполнение действий с использованием инструментов обычно включает несколько запросов, повторяющихся до тех пор, пока Claude не получит необходимое. Остальная часть процесса выполняется с помощью одного запроса в рамках этого полного цикла.

Приведённый пример — это небольшой ассистент для планирования. Пользователь спрашивает, стоит ли взять зонт на встречу в Лондоне завтра, и во сколько нужно выйти из дома, чтобы прибыть вовремя. Для ответа требуются три отдельных факта: детали встречи, прогноз погоды и время в пути. Посмотрите, как Claude разделяет вопрос на три этапа и сколько работы выполняется вашим кодом, а не моделью.

1. Структура цикла

Каждый обмен с использованием инструментов проходит по одному четырёхэтапному циклу:

  1. Объявляются инструменты в запросе.
  2. Claude запрашивает один или несколько из них, что обозначается кодом stop_reason: "tool_use".
  3. Ваш код запускает эти инструменты и собирает результаты.
  4. Вы отправляете обратно результаты и снова вызываете API.

Этапы с двух по четыре повторяются, причем каждый проход считается одной итерацией. Цикл завершается, когда Claude перестает запрашивать инструменты и возвращает обычный результат завершения (end_turn) или другую причину остановки. Поскольку API не имеет состояния, вы сами формируете диалог и пересылаете его каждый раз. Ничего не движется дальше, если только ваш код этого не сделает.

2. Объявление инструментов

Claude может использовать только те инструменты, которые вы указали. Они находятся в параметре верхнего уровня запроса tools, который содержит массив их определений. Каждый раз, когда параметр tools присутствует, API формирует скрытый системный промпт на основе этих определений перед сообщением пользователя. За этот скрытый промпт взимается плата: каждое имя, описание и схема потребляют токены входных данных при каждом запросе, даже если Claude никогда не использует соответствующий инструмент.

Каждому клиентскому инструменту требуются три поля:

  • name — это уникальный идентификатор, который должен соответствовать формату ^[a-zA-Z0-9_-]{1,64}$
"tools": [
  {
    "name": "get_calendar_event",
    "description": "Look up a calendar event by a natural-language query. Returns the event's start time and location.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "query": { "type": "string", "description": "What to search for, e.g. 'meeting in London tomorrow'" }
      },
      "required": ["query"],
      "additionalProperties": false
    }
  },
  {
    "name": "get_weather",
    "description": "Get the forecast for a city on a given date. Returns condition and temperature in Celsius.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "city": { "type": "string", "description": "City name, e.g. 'London'" },
        "date": { "type": "string", "description": "ISO date, e.g. '2026-07-14'" }
      },
      "required": ["city", "date"],
      "additionalProperties": false
    }
  },
  {
    "name": "get_travel_time",
    "description": "Estimate door-to-door travel time between two places. Returns minutes.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "origin": { "type": "string", "description": "Starting location" },
        "destination": { "type": "string", "description": "Ending location" }
      },
      "required": ["origin", "destination"],
      "additionalProperties": false
    }
  }
]

Определяйте их тщательно. Рекомендации Anthropic по эффективным инструментам сводятся к нескольким привычкам:

  • Используйте точные названия инструментов. get_current_weather дает Claude больше информации, чем weather.
  • Называйте и описывайте параметры однозначно. Лучше использовать user_id вместо user, чтобы передавалось правильное значение, и описывайте каждый параметр, поскольку даже хорошо названное поле с нечетким описанием может привести к некорректному вводу.
  • Сохраняйте небольшой набор инструментов. Чрезмерное количество инструментов засоряет генерируемый системный промпт и затрудняет выбор модели.
  • Проверяйте всё. Схема регулирует структуру, а не цель, поэтому проверяйте каждый входной данный, который получает ваш код.

Два момента в этих определениях заслуживают более внимательного рассмотрения.

  1. strict: true имеет наибольшее значение. Он находится на верхнем уровне каждого инструмента рядом с name, description и input_schema и включает грамматически ограниченную выборку для аргументов инструмента, так что модель физически не может сгенерировать input, нарушающий вашу схему. Если его не включить, Claude в основном будет генерировать корректные аргументы, но может допускать ошибки, например "2" вместо необходимого числа 2, значение вне диапазона enum или отсутствие обязательного поля. Каждая такая ошибка приводит к сбою в коде, использующем вызов. При включении strict: true input в любом блоке tool_use обязательно будет соответствовать input_schema, а имя гарантированно будет принадлежать одному из ваших собственных инструментов.
  2. Одно из правил схемы гарантирует, что каждый объект должен иметь значение additionalProperties: false, поэтому оно присутствует во всех вышеупомянутых инструментах. Строгое соблюдение этих правил является частью функции структурированного вывода от Anthropic. На момент написания этого текста она была доступна для современных моделей, таких как Opus 4.8, а более старые модели, например Sonnet 4.5, требовали заголовка бета-версии structured-outputs-2025-11-13. Скомпилированные схемы хранятся в кэше в течение 24 часов, поэтому наличие «теплого» кэша исключает дополнительные затраты на компиляцию.

Вы также можете добавить параметр cache_control к определениям инструментов, чтобы кэшировать их между запросами и избежать платы за повторную обработку, что оправдывает себя, когда набор инструментов становится большим и стабильным.

Определения описывают только входные данные

Определение инструмента включает только параметры входных данных, то есть то, что Claude вам отправляет. Это удивляет тех, кто привык к фреймворкам, где указываются типы возвращаемых значений. Проще говоря:

  • В определении обязательными являются поля name, description и input_schema, а strict, input_examples и cache_control — факультативными дополнениями. Полей output_schema и error_schema не существует.
  • Значения возврата определяются вашим кодом во время выполнения, после чего передаются обратно внутри блока tool_result, описанного в разделе 6. Поле content может содержать либо строку, либо список блоков, причем API не проверяет его ни на что, поскольку отсутствует схема для проверки. Одно практическое замечание: инструмент может вернуть объект DTO, а стандартный парсер преобразует его в обычную строку с помощью метода toString. Использование такой преобразованной формы работает, но не является идеальным решением. Лучше преобразовывать DTO в структурированный JSON.
  • Ошибки также являются вопросом времени выполнения. Для отчета о неудачном вызове укажите is_error: true в поле tool_result и включите информативное сообщение в поле content; в определении отсутствует декларативный тип ошибки. По умолчанию SDK передает возникшее исключение в Claude в качестве результата ошибки, после чего Claude вносит соответствующие коррективы.
  • Неравномерность является намеренной. Определения предназначены для того, чтобы показать Claude, как вызывать инструмент. Результат — это просто дополнительный контекст, возвращающийся в цикл; поэтому, кроме tool_use_id, связывающего его с запросом, никаких договорённостей не требуется.
  • Хотите гарантированный формат вывода? Проверьте его сами в коде перед отправкой любых данных или примените структурированный вывод (output_config.format) к заключительному ответу, который Claude формирует на основе этих результатов. Инструменты MCP имеют собственный outputSchema, но это особенность слоя MCP, а не нативных определений.

Контроль над тем, вызывает ли Claude инструмент

tool_choice определяет, могут ли инструменты быть вызваны в туре и какие именно.

  • Это поле находится на верхнем уровне запроса рядом с tools и messages и не принадлежит ни одному конкретному инструменту.
  • Оно ограничивает тот вывод, который может сгенерировать Claude. Процесс рассуждений остается нетронутым, поскольку Claude в любом случае читает весь запрос.
  • Его действие охватывает только один запрос, никогда не является глобальным. API не имеет состояния, поэтому параметр tool_choice влияет только на ответ, который он отправляет, то есть на одну итерацию. Чтобы сохранить его между итерациями, необходимо пересылать его каждый раз. Изменение этого параметра в каждой итерации допустимо: можно заранее указать инструмент, позже использовать auto, а для окончательного текстового ответа — none.

Объект выглядит следующим образом:

{
  "type": "auto" | "any" | "tool" | "none",
  "name": "get_weather",              // required ONLY when type is "tool"
  "disable_parallel_tool_use": false  // optional; default false
}

type обязателен и может принимать одно из четырех значений. name требуется только тогда, когда type равен "tool". disable_parallel_tool_use является необязательным параметром и ограничивает количество инструментов, которые могут быть вызваны за один раз.

auto. Claude принимает решение: ноль, один или несколько инструментов, либо текстовый ответ. Разрешены параллельные вызовы. Допускается ответ в виде обычного текста, причем Claude может дать пояснения перед вызовами. Это значение является предустановленным, если указан параметр tools. Установка disable_parallel_tool_use: true ограничивает использование только одним инструментом, хотя возможен и вариант нуля. Этот режим подходит для чат-ассистентов и агентных циклов, где в разных этапах требуются разные действия. Это единственный режим, позволяющий Claude говорить вслух перед выполнением действий, а также единственный режим, кроме none, который работает с расширенным мышлением.

any. Claude должен вызвать какой-либо инструмент, но сам выбирает, какой именно. Осуществляется как минимум один вызов, и поскольку параллельное использование включено по умолчанию, могут вернуться несколько блоков tool_use, а не обязательно только один. Ровно один вызов происходит только при настройке disable_parallel_tool_use: true. Простой текст не допускается, предварительные пояснения отсутствуют, и это никогда не является значением по умолчанию. Используйте этот режим, когда результат, полученный с помощью инструмента, является обязательным, но сам инструмент может различаться, как, например, у роутеров и классификаторов.

tool. Claude должен вызывать именно тот инструмент, который вы указали, поэтому параметр name обязателен. Должен быть совершён хотя бы один вызов этого инструмента (он может вызывать его несколько раз одновременно), причём ровно один из них должен содержать параметр disable_parallel_tool_use: true. Текста в простом виде и вступительных пояснений не допускается. Этот формат подходит для однократной структурированной выгрузки данных, обязательного первого шага или для использования JSON через инструмент, например {"type": "tool", "name": "get_weather"}. Запрос с указанием инструмента, отсутствующего в списке tools, возвращает ошибку 400, а принудительное использование инструмента не ограничивает количество его вызовов, если только параллельное использование не отключено.

none. В этом ходу Claude не может вызывать никаких инструментов, даже если они определены, а значение stop_reason не может быть tool_use. Разрешается использовать обычный текст, а также пояснения к действиям. Это значение является значением по умолчанию, когда параметр tools не указан. Используйте его для принудительного перехода к ходу с текстом, например для финальной синтезации, оставляя массив tools без изменений. Это отключает использование инструментов без редактирования массива, однако переход на это значение всё равно делает недействительными сохранённые блоки сообщений.

Есть несколько важных деталей, которые легко упустить:

  • В режимах принудительного использования преамбула опускается. При выборе режимов any или tool API автоматически заполняет часть сообщения, отведённую ассистенту, поэтому блок tool_use поступает без каких-либо пояснений или обоснований, независимо от ваших запросов. Для отображения пояснений необходимо выбрать режим auto.
  • Для расширенного мышления требуются значения auto или none. Использование инструмента принудительно невозможно в сочетании с ним, и API отклоняет такую комбинацию.
  • Правило подсчёта для параметра disable_parallel_tool_use. Значение auto означает не более одного инструмента, any и tool — ровно один, а значение по умолчанию false разрешает параллельные вызовы у моделей Claude 4.
  • Затраты на кэширование. Изменение значения tool_choice между запросами делает недействительными заложенные в кэш блоки сообщений. Определения инструментов и системный промпт остаются в кэше, но разговор перепроцессируется заново.
  • Сочетайте с параметром strict. tool_choice обеспечивает запуск определённого инструмента, а strict — соответствие его аргументов заданной схеме. Вместе они гарантируют получение типизированного, структурированного результата после каждого хода.
  • В запросе:

    response = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=1024,
        tools=tools,
        tool_choice={"type": "any", "disable_parallel_tool_use": True},  # must call exactly one tool
        messages=messages,
    )
    

    Из этого следуют два правила дизайна. Резервируйте использование специальных инструментов для тех случаев, когда действительно необходим результат от конкретного инструмента, а при приемлемом ответе в виде простого текста оставайтесь на режиме auto. Кроме того, когда используется принудительный инструмент классификации, добавьте в его перечень возможностей опцию "other", чтобы Claude мог отклонить определенную категорию вместо того, чтобы неправильно помечать входные данные, которые он не может отнести к какой-либо категории.

    3. Первая итерация: Claude запрашивает инструменты

    Запрос включает в себя массив инструментов, выбранный вариант tool_choice и вопрос пользователя:

    messages = [{"role": "user",
                 "content": "I have a meeting in London tomorrow
                             — should I bring an umbrella, and
                             when should I leave home to be on time?"}]
    
    response = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=1024,
        tools=tools,                    # the three strict definitions from Section 2
        tool_choice={"type": "auto"},   # let Claude decide whether, and what, to call
        messages=messages,
    )
    

    Клод анализирует задачу и замечает, что две из трех необходимых для неё операций не зависят друг от друга: поиск в календаре и прогноз могут выполняться одновременно. Поэтому он запрашивает оба инструмента в одном ответе. Одновременное использование независимых запросов называется параллельным использованием инструментов, и оно проявляется в виде нескольких блоков tool_use в одном ходе.

    {
      "id": "msg_01...",
      "role": "assistant",
      "stop_reason": "tool_use",
      "content": [
        { "type": "text", "text": "Let me check your meeting details and the London forecast." },
        { "type": "tool_use", "id": "toolu_01Cal", "name": "get_calendar_event",
          "input": { "query": "meeting in London tomorrow" } },
        { "type": "tool_use", "id": "toolu_01Wx", "name": "get_weather",
          "input": { "city": "London", "date": "2026-07-14" } }
      ]
    }
    

    В этом ответе выделяются три аспекта.

    1. stop_reason равен tool_use, что означает, что ход ещё не завершён и Claude ожидает от вас действий.
    2. Массив content содержит смешанный набор элементов. Блоки с текстом описания часто предшествуют блокам tool_use, поэтому вызовы инструментов не находятся в фиксированном месте.
    3. Каждый блок tool_use имеет id (с префиксом toolu_), name и объект input, который уже соответствует вашей схеме.

    Чтобы найти эти вызовы, перебирайте элементы массива content и фильтруйте их по типу, а не по индексу:

    tool_calls = [block for block in response.content if block.type == "tool_use"]
    

    Это приводит к двум вызовам здесь. Каждый id служит идентификатором для получения результата соответствующего вызова, и именно сочетание id и результата позволяет Claude сопоставлять ответы с вопросами, когда есть несколько нерешенных вопросов.

    Примечание для моделей Claude 4: процесс расширенного мышления может происходить попеременно, поэтому логические рассуждения могут осуществляться как между вызовами инструментов, так и до них. При включенном режиме расширенного мышления необходимо передавать блоки с рассуждениями вместе с остальной частью ответа ассистента, иначе продолжение ответа забудет об этих рассуждениях.

    4. Запуск инструментов

    Теперь задача переходит к вашему коду. Claude запросил два инструмента, но ни один из них не был запущен. Необходимо выполнить все запрошенные действия, собрать все результаты и отправить единый ответ, в котором они будут объединены. Claude будет ждать, пока у каждого сделанного им вызова появится соответствующий результат.

    Решение о том, будет ли выполнение параллельным или последовательным, принимаете вы, поскольку Claude этого не указывает. В ответе указывается, что вызывать, а не как это запланировать. Независимые запросы, такие как поиск в календаре или прогноз погоды, могут выполняться одновременно для снижения задержек. Если один запрос зависит от результата другого, выполняйте их по порядку. Модель намекает на зависимость, объединяя независимые задачи в параллельные блоки и откладывая зависимые задачи на более поздний этап, что и происходит на второй итерации. Использование потоков, асинхронности или очереди задач — это выбор вашего цикла, а не API.

    Считайте выполнение в производственной среде ненадежным вводом-выводом, который может сбоить:

    • Установите время ожидания для каждого инструмента, чтобы один медленный запрос не заблокировал всю операцию.
    • По возможности предпочитайте идемпотентные операции, поскольку повторные попытки и перезапуск неизбежны в агенте.
    • Обрабатывайте сбои по отдельности. Когда один из двух работающих параллельно инструментов выдает ошибку, всё равно отправляйте для него результат (подробнее о is_error ниже), вместо того чтобы прерывать выполнение.
    • Измеряйте работу цикла. Задержка работы каждого инструмента, количество вызовов, использование токенов и уровень ошибок позволяют определить, находится ли агент в нормальном состоянии или его работа постепенно ухудшается.

    5. Шаги до и после запуска инструмента

    Ответственность за цикл подразумевает управление двумя этапами: периодом между моментом, когда «Claude запросил инструмент», и моментом его запуска, а также периодом между завершением работы инструмента и возвратом результата. Эти этапы нуждаются в названиях, поскольку качество их проектирования во многом определяет разницу между безопасным агентом и рискованным.

    Шаг до использования инструмента выполняется до самого запуска. Он анализирует запрошенный запрос и решает выполнить его без изменений, изменить или отклонить. Именно здесь происходит контроль: Claude предлагает варианты, а ваш код принимает решение.

    Шаг после использования инструмента выполняется после завершения работы инструмента и до того, как результат вернётся к Claude. Он обрабатывает сырой вывод путём удаления части информации, обрезки, изменения формата или записи в журнал.

    Внутри цикла Client SDK это просто функции, обёртывающие процесс выполнения:

    for block in tool_calls:
        if not pre_tool_use(block):                       # validate / guard
            results.append(error_result(block.id, "Blocked by policy."))
            continue
        output = execute(block.name, block.input)         # run the tool
        output = post_tool_use(block, output)             # redact / log / reshape
        results.append(tool_result(block.id, output))
    

    На этапе до использования инструмента находятся реальные ограничения, и их реализация — задача пользователя, а не модели. Распространённые паттерны в производстве:

    • Проверяйте аргументы согласно собственной политике, а также схеме.
    • Категорически отклоняйте разрушительные операции: rm -rf для инструментов оболочки, DROP TABLE для инструментов баз данных.
    • Запрашивайте у человека одобрение для любых действий с высоким влиянием, например, записи в производственную среду.
    • Удаляйте конфиденциальные данные из аргументов перед их отправкой во внешний сервис.
    • Фиксируйте каждую попытку вызова, чтобы при аудите и проверке соответствия была возможность отслеживания.

    После этого на этапе после использования инструмента обрабатываются результаты: удаляются учётные данные или личная информация перед тем, как результаты попадут в контекст, удаляются чрезмерно большие объёмы данных и фиксируются итоги.

    6. Возврат результатов

    Ответьте на ход tool_use сообщением от user, содержащим список блоков tool_result — по одному блоку на каждый блок tool_use в ответе, причем каждый из них должен соответствовать своему tool_use_id. Сначала добавьте ход ассистента в текущий список сообщений, затем результаты в качестве нового хода от пользователя. Пример первой итерации выглядит следующим образом:

    {
      "role": "user",
      "content": [
        {
            "type": "tool_result",
            "tool_use_id": "toolu_01Cal",
            "content": "{\"start\": \"2026-07-14T15:00\", \"location\": \"Canary Wharf, London\"}"
        },
        {
            "type": "tool_result",
            "tool_use_id": "toolu_01Wx",
            "content": "{\"condition\": \"rain\", \"temp_c\": 12}"
        }
      ]
    }
    

    Два вида содержимого. Поле content результата может представлять собой обычную строку, сериализованное значение, как показано в примере, или список блоков содержимого. Блоки содержат не только текст — в них могут сосуществовать поля text и image, что позволяет инструментам для генерации диаграмм или скриншотов передавать Claude настоящее изображение. Строки подходят для скалярных и JSON-данных, а списки блоков — для мультимодального вывода.

    Четко сообщайте, когда что-то пошло не так. Означьте неудачный вызов как таковой с помощью is_error: true и пояснительным сообщением вроде «Ошибка: не найдено событие календаря, соответствующее этому запросу», никогда не используйте пустое значение или просто «Не удалось». Из всех привычек, связанных с результатами, именно эта имеет наибольшее значение. Четкая ошибка позволяет Claude попробовать снова с другими параметрами, выбрать другой инструмент или сообщить пользователю, что произошло, тогда как скрытая ошибка заставляет цикл застрять. Большой результат также может содержать параметр cache_control, что позволяет избежать повторной обработки позже.

    Контракт продолжения

    Три строгих правила, каждое из которых является распространенной причиной сбоев:

    • Один tool_result на каждый tool_use. Если пропустить один из них, запрос завершится с ошибкой 400, поскольку модели ожидают один-на-один ответ для каждого вызова инструмента, который они генерируют.
    • Переслать тот же массив tools. Любой запрос, содержащий блоки tool_use или tool_result, должен указывать инструменты, на которые они ссылаются, в противном случае он будет отклонён.
    • Результаты составляют отдельный этап обработки. Блоки tool_result следует передавать в качестве сообщения от пользователя, размещая их в начале его содержимого. Дополнительные инструкции в рамках этого этапа недопустимы. Любые новые инструкции должны быть отправлены в отдельном сообщении от пользователя после завершения обработки инструмента.

    Каждый результат считается ненадёжным входным данным

    tool_result — это не нейтральные данные. Это текст, который попадает в контекст модели и влияет на её дальнейшие действия. Если он происходит из открытого Интернета, из документа, предоставленного пользователем, или из любой системы, находящейся вне вашего полного контроля, он может содержать внедрённые инструкции, предназначенные для захвата управления агентом, например, команду игнорировать предыдущие указания и отправить базу данных. Модель может подчиниться этим инструкциям. Это и есть внедрение промпта, и в цикле агентов это реальная производственная угроза, а не гипотетическая.

    Защитные меры являются структурными и находятся внутри написанного вами цикла:

    • Предоставьте агенту минимальные необходимые привилегии. Инструмент, которого у него нет, — это действие, в которое его невозможно обмануть.
    • Запускайте рискованные инструменты в сандбоксе.
  • Очистка — это преобразование вывода в действия, например путем разрешения путей к файлам относительно фиксированной директории для предотвращения проскальзывания.
  • Пусть шаг после использования инструмента удаляет всё, что выглядит как инструкция, а не как данные.
  • Всё, что имеет реальные последствия, должно находиться под человеческим утверждением.
  • Результат работы инструмента никогда не должен иметь право разрешать то, что сам вызов инструмента сделать не может.

    Рассматривайте результаты также как контекст, который вы формируете. Каждый токен результата остаётся в окне на протяжении всего диалога, поэтому старайтесь создавать краткий и хорошо структурированный вывод: последовательный JSON или маркированные поля лучше, чем проза, которую модели приходится декодировать. Циклы, генерирующие множество результатов, могут использовать функцию очистки результатов инструмента — бета-функцию управления контекстом, которая удаляет устаревшие результаты по мере приближения к лимиту. Используйте её до того, как цикл станет слишком большим и заполнит всё окно.

    7. Вторая итерация: зависимый вызов

    Оба результата уже получены, поэтому API вызывается ещё раз с передачей всего списка сообщений и определений инструментов. Календарь указывает время 15:00 в Canary Wharf, а прогноз — дождь, однако он всё равно не может сказать, когда нужно выезжать, поскольку время в пути зависит от местоположения, которое он только что узнал. Именно эта зависимость стала причиной того, что вызов нельзя было выполнить параллельно во время первой итерации; его пришлось выполнить позже.

    {
      "role": "assistant",
      "stop_reason": "tool_use",
      "content": [
        {
          "type": "tool_use",
          "id": "toolu_02Tt",
          "name": "get_travel_time",
          "input": {
            "origin": "home",
            "destination": "Canary Wharf, London"
          }
        }
      ]
    }
    

    Вы запускаете инструмент по тому же пути проверки перед использованием, выполнения и обработки после использования, и возвращаете единый результат:

    {
      "role": "user",
      "content": [
        { "type": "tool_result", "tool_use_id": "toolu_02Tt", "content": "{\"minutes\": 45}" }
      ]
    }
    

    Структура цикла совпадает с первой итерацией. Различаются лишь количество вызовов и их взаимозависимости. Именно этот принцип следует запомнить: форма цикла не меняется с каждой итерации, и он продолжается до тех пор, пока Claude не прекратит задавать вопросы.

    8. Третья итерация: синтез и end_turn

    При третьем вызове API Claude уже имеет в своем распоряжении все необходимые факты. Дополнительных инструментов не требуется, поэтому он записывает ответ. end_turn заменяет прежнюю причину остановки, и цикл завершается.

    {
      "role": "assistant",
      "stop_reason": "end_turn",
      "content": [
        {
          "type": "text",
          "text": "Yes, bring an umbrella — rain is forecast in London tomorrow, around 12°C. Your meeting is at 3:00 PM in Canary Wharf, roughly 45 minutes away, so leave home by about 2:00 PM to arrive with a buffer."
        }
      ]
    }
    

    Всего это три итерации: два параллельных вызова, зависимый вызов и синтез. Модель решает, что и когда загружать, а ваш код определяет, как происходит каждый вызов, разрешен ли он и как возвращаются результаты. Контекст увеличивается с каждым шагом, поскольку вся история пересылается заново, но три шага легко вмещаются в заданные рамки.

    9. Весь цикл в коде

    Всё, что было рассказано до сих пор, сводится к одному циклу в Client SDK. Он проверяет значение stop_reason, направляет каждый запрошенный инструмент через предварительные и последующие действия, отвечает на каждый вызов одним результатом и продолжает работу до завершения работы Claude.

    import anthropic
    client = anthropic.Anthropic()
    
    messages = [{"role": "user", "content": user_request}]
    MAX_ITERATIONS = 10
    for _ in range(MAX_ITERATIONS):
        response = client.messages.create(
            model="claude-opus-4-8", max_tokens=1024, tools=tools, messages=messages,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            break   # end_turn (or another reason): done
        results = []
        for block in (b for b in response.content if b.type == "tool_use"):
            if not pre_tool_use(block):   # your guard - may block the call
                results.append({"type": "tool_result", "tool_use_id": block.id,
                                "content": "Blocked by policy.", "is_error": True})
                continue
            try:
                output = execute(block.name, block.input)   # run it (concurrently if independent)
                output = post_tool_use(block, output)   # redact / log / reshape
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
            except Exception as e:
                results.append({"type": "tool_result", "tool_use_id": block.id,
                                "content": f"Error: {e}", "is_error": True})
        messages.append({"role": "user", "content": results})   # one result per tool_use
    print(response.content[-1].text)
    

    В цикле описаны некоторые моменты. Вы сами храните массив messages, поскольку API не сохраняет состояние. Любой значимый параметр stop_reason, отличный от tool_use, приводит к завершению работы. Жесткий лимит количества итераций предотвращает неуправляемое поведение агента, поэтому никогда не используйте бесконечный цикл в производственной среде. Все виды сбоев, включая заблокированные запросы и исключения, возвращаются в виде значений is_error, вместо того чтобы привести к сбою текущей итерации. В готовом коде также учтены параметры max_tokens (если итерация обрывается, необходимо либо увеличить лимит, либо продолжить работу) и pause_turn (итерация, связанная с серверным инструментом, которую нужно отправить заново). Каждый из этих параметров может возникнуть в рамках одного и того же цикла.

    Список проверки для запоминания

    • Инструменты объявляются в массиве tools с указанием name, description и input_schema, причем каждый запрос оплачивает использование токенов входных данных в соответствии с этими определениями.
    • tool_choice определяет, будет ли вызываться инструмент: auto, any, tool или none. Режим расширенного обработки работает только при значении auto и none, а параметр strict: true обязывает аргументы полностью соответствовать схеме.
    • Значение stop_reason в виде "tool_use" передаёт управление вам. Обнаруживайте такие вызовы путём фильтрации content по условию type == "tool_use", а не по позиции.
    • Параллельное использование инструментов означает наличие нескольких блоков tool_use в одном ответе. Порядок выполнения — будь то параллельно или последовательно — выбираете вы, а не модель. Зависимые вызовы поступают в последующих этапах.
    • Запустите все запрошенные инструменты и ответьте один раз: один ход пользователя, в котором сохраняется tool_result для каждого tool_use, связанного по tool_use_id, при этом массив tools остается неизменным. Отсутствие результата приводит к коду ошибки 400.
    • Вы сами отвечаете за промежутки времени между выполнениями. Проводите проверки и защиту перед запуском, а также обрабатывайте данные и удаляйте лишнее после этого. Это шаги, которые вы пишете сами, а не функции, которые настраиваются.
    • tool_result.content может быть строкой или списком. В случае сбоя установите is_error: true вместе с понятным сообщением, а также сохраняйте результаты краткими и структурированными.
    • Рассматривайте результаты работы инструментов как ненадежный контекст. Принцип минимальных привилегий, использование песочниц, очистка данных и подтверждение действий человеком — вот способы защиты от внедрения вредоносного кода.
    • Цикл выполняется до момента, указанного в end_turn, сохраняет собственное состояние и требует жесткого ограничения количества итераций.

    Связанные статьи

  • Практические заметки: Внутри ARD: Как на самом деле работает спецификация агентного поиска ресурсов — Подробное руководство по Практическим заметкам: Внутри ARD: Как на самом деле работает спецификация агентного поиска ресурсов: контракты, проверки и готовые блоки кода для команд, использующих эту модель.