Окей, в прошлый раз мы собрали агента прямо в браузере — через Projects в Claude. Без кода, без терминала, за 10 минут. Но у такого агента есть потолок: он работает только с тем, что ты вручную закинул в контекст. Теперь снимаем это ограничение — пишем агента на Python, который сам решает, когда ему нужно позвонить в интернет, прочитать файл или сохранить заметку.
🎯 Tool use — зачем Claude нужны «руки»
Tool use (вызов функций) — это механизм, при котором Claude сам определяет, какой инструмент ему нужно использовать для ответа. Ты описываешь набор доступных функций — «вот тебе получение погоды, вот сохранение заметки, вот чтение файла» — и Claude, получив вопрос от пользователя, решает: ответить из своих знаний или вызвать один из инструментов.
Смотрите, проще всего это понять так. У сотрудника на столе три телефона: один для склада, один для бухгалтерии, один для клиентов. Приходит задача — он сам понимает, в какой телефон звонить. Не ты ему говоришь «позвони на склад», а он сам: «Так, тут вопрос про остатки — это склад». Tool use работает точно так же.
Зачем это нужно:
- Актуальные данные — Claude не знает погоду на сегодня, но может вызвать API погоды
- Работа с файлами — прочитать конфиг, лог, CSV-таблицу с диска
- Запись данных — сохранить результат в файл, базу, отправить уведомление
- Интеграции — дёрнуть любое API: CRM, Telegram, Notion, Яндекс.Метрика
🧰 Что понадобится
- Python 3.9+ (проверить: python3 --version)
- Библиотека anthropic — ставим командой pip install anthropic
- 5 минут и терминал
API-ключ (API key) — это уникальный «пароль» для программы, чтобы сервер Anthropic понимал, кто делает запрос и сколько за него списать. Хранить его нужно в переменных окружения, а не в коде.
Что происходит под капотом
Прежде чем писать код — разберём схему. Честно говоря, я сам первый раз смотрел на это и не мог понять, где заканчивается магия и начинается инженерия. Но всё оказалось логично.
- Ты отправляешь запрос — текст пользователя + описание доступных инструментов
- Claude анализирует — решает: хватит ли ему своих знаний или нужен инструмент
- Claude возвращает tool_use — «хочу вызвать функцию get_weather с параметром city=Москва»
- Твой код выполняет функцию — дёргает реальное API, читает файл, что угодно
- Ты отправляешь результат обратно — «вот данные от функции»
- Claude формирует финальный ответ — красивым человеческим языком, с учётом полученных данных
Ключевой момент: Claude не выполняет функции сам. Он только говорит «хочу вызвать вот это с такими параметрами». Выполнение — на твоей стороне. Это и есть безопасность: ты контролируешь, что реально происходит.
🚀 Рабочий код агента с тремя инструментами
Вот полный рабочий пример. Агент умеет три вещи: узнать погоду, сохранить заметку в файл и прочитать файл с диска.
import anthropic
import json
import os
from datetime import datetime
# Клиент берёт ключ из переменной окружения ANTHROPIC_API_KEY
client = anthropic.Anthropic()
# --- Реальные функции, которые выполняют работу ---
def get_weather(city: str) -> str:
"""Имитация получения погоды (замени на реальное API, например OpenWeatherMap)."""# В реальном проекте здесь будет requests.get(f"https://api.openweathermap.org/...")demo_weather = {"Москва": {"temp": 18, "condition": "облачно"},"Санкт-Петербург": {"temp": 14, "condition": "дождь"},"Сочи": {"temp": 27, "condition": "солнечно"},}data = demo_weather.get(city)if data:return f"{city}: {data['temp']}°C, {data['condition']}"return f"{city}: данных нет (добавьте реальное API для полного покрытия)"
def save_note(filename: str, content: str) -> str:
"""Сохраняет текст в файл."""filepath = os.path.join("notes", filename)os.makedirs("notes", exist_ok=True)with open(filepath, "w", encoding="utf-8") as f:f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M')}]\n{content}\n")return f"Заметка сохранена: {filepath}"
def read_file(filepath: str) -> str:
"""Читает содержимое файла."""if not os.path.exists(filepath):return f"Файл не найден: {filepath}"with open(filepath, "r", encoding="utf-8") as f:text = f.read(5000) # Ограничиваем размер — не тащим гигабайт в контекстreturn text
# --- Описание инструментов для Claude ---
tools = [{"name": "get_weather","description": "Получить текущую погоду в указанном городе","input_schema": {"type": "object","properties": {"city": {"type": "string","description": "Название города, например: Москва"}},"required": ["city"]}},{"name": "save_note","description": "Сохранить текстовую заметку в файл","input_schema": {"type": "object","properties": {"filename": {"type": "string","description": "Имя файла, например: ideas.txt"},"content": {"type": "string","description": "Текст заметки"}},"required": ["filename", "content"]}},{"name": "read_file","description": "Прочитать содержимое текстового файла с диска","input_schema": {"type": "object","properties": {"filepath": {"type": "string","description": "Путь к файлу, например: notes/ideas.txt"}},"required": ["filepath"]}}
]
# Маппинг имени инструмента -> реальная функция
tool_functions = {"get_weather": lambda inp: get_weather(inp["city"]),"save_note": lambda inp: save_note(inp["filename"], inp["content"]),"read_file": lambda inp: read_file(inp["filepath"]),
}
def run_agent(user_message: str) -> str:
"""Запускает агента: отправляет запрос, обрабатывает вызовы инструментов."""messages = [{"role": "user", "content": user_message}]
while True:response = client.messages.create(model="claude-sonnet-4-6",max_tokens=1024,system="Ты — полезный ассистент с доступом к инструментам. ""Используй их когда нужна актуальная информация или действие.",tools=tools,messages=messages,)
# Если Claude не вызвал ни одного инструмента — это финальный ответif response.stop_reason == "end_turn":return "".join(block.text for block in response.content if block.type == "text")
# Обрабатываем все вызовы инструментов в ответеtool_results = []for block in response.content:if block.type == "tool_use":func = tool_functions.get(block.name)if func:result = func(block.input)else:result = f"Неизвестный инструмент: {block.name}"
print(f" 🔧 Вызван инструмент: {block.name}({json.dumps(block.input, ensure_ascii=False)})")
tool_results.append({"type": "tool_result","tool_use_id": block.id,"content": result,})
# Добавляем ответ Claude и результаты инструментов в историюmessages.append({"role": "assistant", "content": response.content})messages.append({"role": "user", "content": tool_results})
# --- Пробуем ---
if __name__ == "__main__":queries = ["Какая сейчас погода в Москве и Сочи?","Сохрани заметку в файл todo.txt: купить молоко, написать статью, выгулять кота","Прочитай файл notes/todo.txt",]
for q in queries:print(f"\n{'='*50}")print(f"👤 {q}")answer = run_agent(q)print(f"🤖 {answer}")
Где тут что и почему именно так
Блок tools — это JSON-описание каждого инструмента. Claude читает description и input_schema, чтобы понять, когда и как вызывать функцию. Чем точнее ты опишешь инструмент, тем лучше Claude будет им пользоваться. Описание "Получить текущую погоду в указанном городе" — это не для тебя, это инструкция для модели.
Цикл while True — сердце агента. Claude может вызвать несколько инструментов за один ход (например, погоду в двух городах сразу). После каждого раунда вызовов мы отправляем результаты обратно — и Claude или вызывает ещё инструменты, или выдаёт финальный ответ. Цикл крутится, пока stop_reason не станет "end_turn".
Ну и тут я, кстати, сначала забыл про tool_use_id — полчаса ловил ошибку, пока до меня дошло. Каждый вызов инструмента получает уникальный идентификатор. Когда мы возвращаем результат, мы указываем этот ID — так Claude понимает, какой результат к какому вызову относится. Без этого всё ломается.
response.content — это список блоков. Claude может вернуть и текст, и вызовы инструментов в одном ответе. Поэтому мы перебираем все блоки и обрабатываем каждый по типу.
Где это сломается в продакшене
Безопасность. Слушайте, это важно. Агент с инструментом read_file может прочитать /etc/passwd, если ты ему позволишь. Всегда валидируй пути — ограничивай рабочую директорию, проверяй расширения файлов, не давай доступ к системным путям. То же касается записи: агент не должен мочь перезаписать твой .env с ключами.
Токены (tokens) и стоимость. Токен — это кусочек текста, примерно 3/4 слова. Каждый вызов API тратит токены: входные (твой запрос + описание инструментов + история) и выходные (ответ Claude). Описание трёх инструментов — это уже ~300-400 токенов к каждому запросу. С десятком инструментов будет ощутимо. Не добавляй инструменты «на всякий случай» — давай только те, что реально нужны для текущей задачи.
Обработка ошибок. В примере выше, если API погоды упадёт — Claude получит текст ошибки и объяснит пользователю ситуацию. Но в продакшене стоит добавить try/except в каждую функцию инструмента, ограничить количество итераций цикла (не больше 10 раундов) и логировать все вызовы. Не буду врать — я сам первый раз запустил без try/except, и когда демо-данные не нашлись, агент вернул такое, что было стыдно показывать.
Лимит контекста. Каждый раунд «вопрос → инструмент → ответ» добавляет сообщения в messages. На длинном диалоге можно упереться в лимит контекста модели (200K токенов у Sonnet). Для длинных сессий — обрезай старые сообщения или используй суммаризацию.
📋 Копируй и пробуй
Чтобы запустить пример:
- pip install anthropic
- export ANTHROPIC_API_KEY=sk-ant-... (свой ключ из console.anthropic.com)
- Скопируй код в файл agent.py
- python3 agent.py
Замени get_weather на реальное API (OpenWeatherMap бесплатный, 60 запросов в минуту) — и у тебя будет агент с актуальной погодой. Добавь инструмент для Telegram API — и агент сможет отправлять сообщения. Добавь поиск по базе данных — и получишь AI-помощника для службы поддержки. Короче, инструменты — это конструктор, собирай что хочешь.
В следующей статье пойдём ещё дальше: мульти-агентная система, где несколько агентов общаются между собой. Один ищет информацию, другой анализирует, третий пишет ответ. Как конвейер на заводе — каждый делает свою часть.
—
📱 Больше промтов, экспериментов и смешных фейлов нейросетей —
в моём Телеграме: @skazhi_ai
Подписывайся на «Скажи AI» здесь, если хочешь видеть такое регулярно →