Tool use в Claude: полный цикл вызова инструментов на Python
Tool use позволяет Claude вызывать функции вашего кода. Разбираем цикл целиком: описание инструментов, параллельные вызовы, ошибки и ограничения.
4 мин · · разработка · агенты
Tool use (вызов инструментов, function calling) позволяет Claude обращаться к функциям вашего кода: модель решает, какую функцию вызвать и с какими аргументами, вы выполняете её и возвращаете результат, модель продолжает работу. На этом строится любой агент — от калькулятора до бота с доступом к базе данных. Ниже — полный рабочий пример tool use в Claude на Python через Anthropic SDK и Zukko, сверенный с документацией Anthropic.
Как устроен цикл tool use
- Вы передаёте в запросе список инструментов: имя, описание и JSON Schema аргументов.
- Если Claude решает вызвать инструмент, ответ приходит с
stop_reason: "tool_use"и одним или несколькими блокамиtool_use. - Вы добавляете ответ модели в историю целиком (
response.content, а не только текст). - Выполняете каждый блок
tool_useи отправляете результаты в одном сообщении пользователя — блокамиtool_resultсtool_use_idсоответствующего вызова. - Повторяете запрос. Цикл заканчивается, когда
stop_reasonперестаёт бытьtool_use.
Установка и клиент
pip install anthropicimport json
import anthropic
client = anthropic.Anthropic(
base_url="https://api.zukko.pro",
api_key="fetch_ваш_ключ",
)Адрес указан без /v1: Anthropic SDK добавляет путь сам. Подробнее — в руководстве по Python и справке по Messages.
Функции и их описание
Сначала обычные Python-функции. Калькулятор разбирает выражение через ast, а не через eval: аргументы приходят от модели, а значит, косвенно от пользователя, и выполнять их как код нельзя.
import ast
import operator
def get_weather(location: str, unit: str = "celsius") -> str:
data = {"London": 15, "Paris": 18, "Tokyo": 22}
if location not in data:
raise ValueError(f"Нет данных о погоде для {location}")
t = data[location]
return f"{location}: {t}°C" if unit == "celsius" else f"{location}: {round(t * 9 / 5 + 32)}°F"
OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg}
def _eval(node):Теперь описание для модели. Описание инструмента — главное, по чему Claude решает, когда его вызывать, поэтому пишите его подробно.
tools = [
{
"name": "get_weather",
"description": "Возвращает текущую температуру в городе. Используй, когда спрашивают о погоде.",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "Город на английском, например London"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
},
{Полный цикл
Ключевая деталь: в одном ответе Claude может запросить несколько инструментов сразу (например, погоду в двух городах). Нужно выполнить все вызовы и вернуть все результаты в одном сообщении пользователя. Если обработать только первый блок tool_use, API отклонит следующий запрос: у каждого вызова должен быть свой tool_result.
def run_agent(user_message: str, model: str = "claude-sonnet-5", max_steps: int = 10) -> str:
messages = [{"role": "user", "content": user_message}]
for _ in range(max_steps):
response = client.messages.create(
model=model,
max_tokens=4096,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
if response.stop_reason == "max_tokens":Что здесь важно:
- Ответ модели добавляется целиком. В
response.contentкромеtool_useмогут быть текст и служебные блоки; их нужно вернуть в историю без изменений. - Результаты идут первыми. В сообщении с результатами блоки
tool_resultдолжны стоять в начале; если хотите добавить текст, ставьте его после всех результатов. - Ошибки не теряются. Если функция упала, вернуть нужно
tool_resultсis_error: Trueи текстом ошибки. Модель увидит его и сможет исправить аргументы или ответить пользователю. - Есть предел шагов.
max_stepsзащищает от бесконечного цикла и неожиданного расхода.
Запуск
print(run_agent("Какая погода в London и Paris? И сколько будет (18 - 15) * 7?"))
print(run_agent("Сколько градусов в Tokyo по Фаренгейту?"))
print(run_agent("Какая погода в Berlin?"))Третий запрос вернёт ошибку от инструмента (нет данных), и модель сообщит об этом в ответе, вместо того чтобы падать.
Управление выбором инструмента
Параметр tool_choice по умолчанию равен {"type": "auto"} — модель сама решает, вызывать ли инструмент. Можно запретить вызовы ({"type": "none"}) или ограничить их одним за ход: {"type": "auto", "disable_parallel_tool_use": True}.
Принудительный вызов ({"type": "any"} или {"type": "tool", "name": "..."}) поддерживается не всеми моделями. По документации Anthropic, для Claude Opus 5.5 такие значения возвращают ошибку 400. Если с этой моделью нужен гарантированный вызов, используйте auto и явную инструкцию в промпте, например «используй инструмент get_weather».
Асинхронный вариант
import asyncio
from anthropic import AsyncAnthropic
aclient = AsyncAnthropic(base_url="https://api.zukko.pro", api_key="fetch_ваш_ключ")
async def run_agent_async(user_message: str, model: str = "claude-sonnet-5") -> str:
messages = [{"role": "user", "content": user_message}]
for _ in range(10):
response = await aclient.messages.create(
model=model, max_tokens=4096, tools=tools, messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if b.type == "text")Типичные ошибки
- Обработан только первый
tool_use. При параллельных вызовах следующий запрос получит ошибку 400. Перебирайте все блоки. - В историю добавлен только текст ответа. Добавляйте
response.contentцеликом. - Результаты разбиты на несколько сообщений. Все
tool_resultза один ход — в одном сообщении пользователя. - Нет
tool_use_id. Без него модель не сопоставит результат с вызовом. - Аргументы выполняются как код. Никакого
eval,execи подстановки в shell или SQL без проверки.
Каждый шаг цикла — отдельный запрос, и вся история оплачивается заново как входные токены. Для агентов с длинной историей помните: в Zukko кэшированные токены оплачиваются по обычной входной цене. Ключ для экспериментов можно создать в кабинете.
Частые вопросы
Может ли Claude не вызвать инструмент?
Да. Если инструмент не нужен, модель ответит сразу со stop_reason: "end_turn". Цикл выше это обрабатывает: он возвращает текст, как только stop_reason не равен tool_use.
Может ли Claude вызвать несколько инструментов за один ход?
Да, это поведение по умолчанию. Выполните все вызовы и верните результаты одним сообщением. Ограничить до одного вызова за ход можно через disable_parallel_tool_use.
Есть ли ограничение на размер результата инструмента?
Результат становится частью контекста и оплачивается как входные токены на каждом следующем шаге. Возвращайте только нужное: отфильтрованные строки, а не весь ответ внешнего API.
Можно ли сделать то же через OpenAI-формат?
Да, но описание инструментов и формат ответа там другие. Этот пример — для Anthropic SDK; про OpenAI-формат смотрите справку по Chat Completions.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Claude Code YOLO: режимы разрешений и работа без подтвержденийОтдельного режима YOLO в Claude Code нет, зато есть шесть режимов разрешений и гибкие правила. Разбираем, как перестать нажимать «Да» и не лишиться файлов.
- Claude Code: чек-лист лучших практик для ежедневной работыПривычки, которые делают работу с Claude Code предсказуемой: короткий CLAUDE.md, план перед правками, мелкие задачи, проверка результата и контроль расхода.
- MCP-сервер на Java: минимальный пример с одним инструментомПишем минимальный MCP-сервер на Java с одним инструментом, регистрируем его в Claude Code и учимся находить причину, если агент его не видит.