Tool calling — контракт между вашим кодом и LLM: вы объявляете список инструментов с описанием и схемой аргументов, модель решает, какой вызвать, а ваш код выполняет реальную работу.
Модель не выполняет код. Она генерирует JSON-запрос — вы его исполняете.
Анатомия инструмента
tool = {
"name": "get_stock_price",
# Описание — ГЛАВНЫЙ сигнал для модели, когда звать инструмент
"description": (
"Get the current stock price for a ticker symbol. "
"Use when the user asks about stock prices or market data."
),
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Stock ticker, e.g. 'AAPL', 'MSFT'"
},
"currency": {
"type": "string",
"enum": ["USD", "EUR", "RUB"],
"description": "Currency for the price"
}
},
"required": ["ticker"]
}
}
Правила хорошего description:
- Начинайте с глагола: «Get», «Search», «Send», «Create».
- Укажите явное условие: «Use when the user asks about…».
- Опишите ограничения: «Only for public companies».
Полный цикл с Anthropic SDK
import anthropic
import json
client = anthropic.Anthropic()
def get_stock_price(ticker: str, currency: str = "USD") -> dict:
# Реальный вызов API — здесь мок
prices = {"AAPL": 189.5, "MSFT": 415.2, "GOOGL": 175.8}
price = prices.get(ticker.upper(), 0)
return {"ticker": ticker, "price": price, "currency": currency}
tools = [
{
"name": "get_stock_price",
"description": "Get current stock price. Use when user asks about stock prices.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {"type": "string"},
"currency": {"type": "string", "enum": ["USD", "EUR", "RUB"]}
},
"required": ["ticker"]
}
}
]
messages = [{"role": "user", "content": "Какая цена у акций Apple и Microsoft?"}]
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
# Модель вернула запрос на вызов инструмента
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
try:
result = get_stock_price(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
})
except Exception as e:
# Возвращаем ошибку в контекст — модель учтёт её
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": f"Error: {str(e)}",
"is_error": True,
})
messages.append({"role": "user", "content": tool_results})
final = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
print(final.content[0].text)
Параллельные вызовы
Когда вопрос о нескольких тикерах, Claude вернёт несколько блоков tool_use за один ответ. Обрабатывайте их все до следующего запроса:
for block in response.content:
if block.type == "tool_use":
# Здесь может быть два вызова: AAPL и MSFT одновременно
result = get_stock_price(**block.input)
tool_results.append({...})
Параллельные вызовы снижают задержку — вместо двух последовательных обращений к API вы делаете одно.
Обработка ошибок
| Ситуация | Что делать |
|----------|-----------|
| Инструмент упал с исключением | Вернуть is_error: true с текстом ошибки |
| Инструмент вернул пустой результат | Вернуть "No results found" — модель скорректирует ответ |
| Неверные аргументы от модели | Валидировать входные данные до вызова, вернуть ошибку валидации |
Никогда не игнорируйте ошибки инструментов — модель не узнает, что что-то пошло не так, и выдаст уверенный неправильный ответ.