Claude Platform Docs
MessagesИнструменты

Исполнитель инструментов (SDK)

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

«Tool runner» (исполнитель инструментов) берёт на себя агентный цикл, обёртывание ошибок и типобезопасность, чтобы вам не приходилось делать это самостоятельно. Если вам нужно одобрение с участием человека (human-in-the-loop), пользовательское логирование или условное выполнение, используйте вместо него ручной цикл.

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

  • Запускает инструменты, когда Claude их вызывает
  • Обрабатывает цикл запрос/ответ
  • Управляет состоянием диалога
  • Обеспечивает типобезопасность и валидацию

Базовое использование

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

В зависимости от сигнатуры инструмента в SDK инструмент возвращает свой результат в виде строки или в виде блоков содержимого (текстовых блоков, блоков изображений или документов), поэтому инструмент может возвращать мультимодальные результаты. Возвращённая строка становится одним текстовым блоком содержимого. Чтобы вернуть структурированные данные, например объект JSON или число, сначала закодируйте их в строку.

Используйте декоратор @beta_tool для определения инструментов с аннотациями типов и строками документации (docstrings).

import json
from anthropic import Anthropic, beta_tool

client = Anthropic()


@beta_tool
def get_weather(location: str, unit: str = "fahrenheit") -> str:
    """Get the current weather in a given location.

    Args:
        location: The city and state, e.g. San Francisco, CA
        unit: Temperature unit, either 'celsius' or 'fahrenheit'
    """
    return json.dumps({"temperature": "20°C", "condition": "Sunny"})


@beta_tool
def calculate_sum(a: int, b: int) -> str:
    """Add two numbers together.

    Args:
        a: First number
        b: Second number
    """
    return str(a + b)


runner = client.beta.messages.tool_runner(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=[get_weather, calculate_sum],
    messages=[
        {
            "role": "user",
            "content": "What's the weather like in Paris? Also, what's 15 + 27?",
        }
    ],
)
for message in runner:
    print(message)

Декоратор @beta_tool анализирует аргументы функции и строку документации, чтобы вывести JSON-схему за вас.

Итерация по исполнителю инструментов

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

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

Если вам не нужны промежуточные сообщения, вы можете получить финальное сообщение напрямую:

Используйте runner.until_done(), чтобы получить финальное сообщение.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=[get_weather, calculate_sum],
    messages=[
        {
            "role": "user",
            "content": "What's the weather like in Paris? Also, what's 15 + 27?",
        }
    ],
)
final_message = runner.until_done()
for block in final_message.content:
    if block.type == "text":
        print(block.text)

Расширенное использование

Внутри цикла вы можете читать каждое ответное сообщение и изменять состояние исполнителя перед следующим вызовом API. Каждая итерация следует такому жизненному циклу:

  1. Исполнитель отправляет запрос к Messages API со своим текущим состоянием.
  2. Исполнитель выдаёт ответное сообщение в тело вашего цикла.
  3. Выполняется тело вашего цикла. Вы можете прочитать сообщение и при необходимости изменить состояние исполнителя.
  4. Когда тело вашего цикла завершается, исполнитель проверяет, изменили ли вы его историю сообщений.
    • Если вы не изменяли историю сообщений: если сообщение содержит вызовы инструментов, исполнитель добавляет сообщение ассистента и результаты инструментов, затем продолжает. Если вызовов инструментов нет, цикл завершается.
    • Если вы изменили историю сообщений: исполнитель пропускает автоматическое добавление и использует ваше состояние без изменений. См. Взятие управления историей сообщений на себя.

Взятие управления историей сообщений на себя

По умолчанию исполнитель управляет состоянием диалога за вас: после каждого хода с вызовом инструмента он добавляет сообщение ассистента и все результаты инструментов в собственную историю сообщений. Вы берёте управление историей сообщений на себя, когда хотите повторить ход (отбросить ответ и отправить заново), вставить последующее сообщение или сформировать результат инструмента самостоятельно.

Вы берёте управление на себя, изменяя сообщения исполнителя изнутри тела цикла. Конкретный метод зависит от SDK. См. вкладки для каждого языка ниже.

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

Используйте generate_tool_call_response(), чтобы проверить или вычислить результат инструмента. Вызов append_messages() внутри цикла сообщает исполнителю, что вы управляете историей самостоятельно, поэтому включите сообщение ассистента и результат инструмента в то, что вы добавляете.

runner = client.beta.messages.tool_runner(
    model="claude-opus-5-5",
    max_tokens=1024,
    max_iterations=10,
    tools=[get_weather],
    messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()
    if tool_response is not None:
        # append_messages() помечает состояние как изменённое, поэтому runner пропускает
        # автоматическое добавление на этой итерации. Добавьте сообщение ассистента и
        # результат инструмента самостоятельно, а также любые последующие сообщения.
        runner.append_messages(
            message,
            tool_response,
            {"role": "user", "content": "Please be concise."},
        )
    # Если вызова инструмента нет, не трогайте состояние, чтобы цикл завершился.

Чтобы изменить параметры запроса, такие как max_tokens, не беря на себя управление историей сообщений, используйте set_messages_params(). Исполнитель по-прежнему автоматически добавляет сообщение ассистента и результат инструмента.

for message in runner:
    runner.set_messages_params(lambda params: {**params, "max_tokens": 2048})

Автоматическое управление контекстом

Для длительных агентных задач исполнители инструментов TypeScript и Ruby поддерживают автоматическое «compaction» (сжатие), которое генерирует сводки, когда использование токенов превышает пороговое значение, чтобы диалог мог продолжаться за пределами ограничений «context window» (контекстного окна). Оба SDK объявили этот клиентский вариант устаревшим в пользу серверного сжатия, которое работает с исполнителем инструментов любого SDK через параметр запроса context_management. Python SDK (v1.0 и новее), а также исполнители инструментов Go, Java, C# и PHP не включают клиентское сжатие. Исполнители инструментов Python, TypeScript, C#, Go, Java, PHP и Ruby имеют вспомогательный метод compact_before_next_turn() для сжатия по запросу. См. раздел Сжатие в цикле. Используйте для исполнителя либо его, либо правку сжатия context_management, но не оба варианта одновременно.

Отладка выполнения инструментов

Когда инструмент выбрасывает исключение, исполнитель инструментов перехватывает его и возвращает ошибку Claude в виде результата инструмента с is_error: true. Результат инструмента содержит сообщение исключения (в Python — его тип и сообщение), а не полную трассировку стека.

То, что логирует SDK, зависит от языка. Python SDK логирует полное исключение, включая трассировку стека, через стандартный модуль logging всякий раз, когда инструмент выбрасывает необработанное исключение. SDK для Python, TypeScript и Java читают переменную окружения ANTHROPIC_LOG для включения логирования SDK, которое включает детали запросов и ответов:

# Логирование на уровне info
export ANTHROPIC_LOG=info

# Логирование на уровне debug для более подробного вывода
export ANTHROPIC_LOG=debug

SDK для Go, Ruby, C# и PHP не читают ANTHROPIC_LOG. За пределами Python ни один SDK не логирует сбой инструмента: чтобы увидеть, почему инструмент завершился с ошибкой, перехватите и залогируйте исключение внутри функции инструмента перед возвратом или повторным выбрасыванием.

Перехват ошибок инструментов

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

В SDK для Python и TypeScript используйте метод ответа инструмента (generate_tool_call_response() в Python, generateToolResponse() в TypeScript), чтобы перехватывать результаты инструментов и проверять их на ошибки перед отправкой Claude. Другие SDK не предоставляют такой хук. В их вкладках описана ближайшая альтернатива:

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=[my_tool],
    messages=[{"role": "user", "content": "Run my_tool with the query 'hello'."}],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()

    if tool_response is not None:
        # tool_response — это словарь: {"role": "user", "content": [...]}
        # Проверяем, содержит ли какой-либо результат инструмента ошибку
        for block in tool_response["content"]:
            if block.get("is_error"):
                # Вариант 1: выбросить исключение, чтобы остановить цикл
                raise RuntimeError(f"Tool failed: {json.dumps(block['content'])}")

                # Вариант 2: записать в журнал и продолжить (пусть Claude обработает это)
                # logger.error(f"Tool error: {json.dumps(block['content'])}")

    # Обрабатываем сообщение как обычно
    print(message.content)

Изменение результатов инструментов

Вы можете изменять результаты инструментов перед их отправкой обратно Claude. Это полезно для добавления метаданных, таких как cache_control, чтобы включить «prompt caching» (кэширование подсказок) для результатов инструментов, или для преобразования вывода инструмента.

В SDK для Python и TypeScript используйте метод ответа инструмента, чтобы получить результат инструмента, затем измените его до того, как исполнитель продолжит работу. Нужно ли явно добавлять изменённый результат или изменять его на месте, зависит от SDK. См. комментарии в коде на каждой вкладке.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=[search_documents],
    messages=[
        {
            "role": "user",
            "content": "Search for information about the climate of San Francisco",
        }
    ],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()

    if tool_response is not None:
        # tool_response — это словарь: {"role": "user", "content": [...]}
        # Изменяем результат инструмента, чтобы добавить управление кэшированием
        for block in tool_response["content"]:
            if block["type"] == "tool_result":
                # Добавляем cache_control, чтобы кэшировать этот результат инструмента
                block["cache_control"] = {"type": "ephemeral"}

        # Добавляем изменённый ответ (это предотвращает автодобавление исходного)
        runner.append_messages(message, tool_response)

    print(message.content)

Потоковая передача

Включите «streaming» (потоковую передачу), чтобы обрабатывать ответ каждого хода инкрементально. Каждая итерация выдаёт объект потока, по которому можно итерироваться для получения событий.

Установите stream=True и используйте get_final_message(), чтобы получить накопленное сообщение.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=[calculate_sum],
    messages=[{"role": "user", "content": "What is 15 + 27?"}],
    stream=True,
)

# При потоковой передаче runner возвращает BetaMessageStream
for message_stream in runner:
    for event in message_stream:
        print("event:", event)
    print("message:", message_stream.get_final_message())

print(runner.until_done())

Следующие шаги

Обеспечьте соответствие входных данных инструментов Claude схеме JSON Schema с помощью сэмплирования с грамматическими ограничениями.

Разбирайте блоки tool_use, форматируйте ответы tool_result и обрабатывайте ошибки с помощью is_error.

Включайте, форматируйте и отключайте параллельные вызовы инструментов, с рекомендациями по истории сообщений и устранению неполадок.

Задавайте схемы инструментов, пишите эффективные описания и управляйте тем, когда Claude вызывает ваши инструменты.

Was this page helpful?