← НАЗАД К КУРСУ Этап 3 · Урок 2 из 5

Урок 3.2 — Tools: руки агента

Tools (инструменты) — это функции, которые ты даёшь агенту. Без них он может только говорить. С ними — он может делать: проверять погоду, записывать в таблицу, отправлять сообщения, читать файлы. Инструменты — это руки агента.


Зачем агенту инструменты

В прошлом уроке мы увидели разницу: чат-бот отвечает текстом, а агент выполняет действия. Но как именно агент выполняет действия? Через tools — функции, которые он может вызывать.

Без tools: Claude может только генерировать текст
С tools:   Claude может проверять погоду, отправлять письма,
           записывать в таблицы, искать в интернете...

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


Как создать свой инструмент

Создание инструмента состоит из двух частей:

Часть 1: Описание для Claude (что он может делать)

Это JSON-схема, которую Claude читает и решает, когда вызвать.

tools = [
    {
        "name": "get_exchange_rate",
        # Имя инструмента — Claude будет ссылаться на него

        "description": "Получить текущий курс обмена валют",
        # Описание — Claude читает его, чтобы понять, когда использовать
        # Чем точнее описание, тем лучше Claude решает, когда вызвать

        "input_schema": {
            "type": "object",
            "properties": {
                "from_currency": {
                    "type": "string",
                    "description": "Валюта, из которой конвертируем (например USD)"
                },
                "to_currency": {
                    "type": "string",
                    "description": "Валюта, в которую конвертируем (например RUB)"
                }
            },
            "required": ["from_currency", "to_currency"]
            # required — обязательные параметры
        }
    }
]

Часть 2: Реальная функция (что ДЕЙСТВИТЕЛЬНО происходит)

def get_exchange_rate(from_currency, to_currency):
    # В реальности — запрос к API курсов валют
    # Для примера — хардкод
    rates = {
        ("USD", "RUB"): 92.5,
        ("EUR", "RUB"): 100.3,
        ("USD", "EUR"): 0.92,
    }
    rate = rates.get((from_currency, to_currency), "Курс не найден")
    return f"1 {from_currency} = {rate} {to_currency}"

Важно: Claude видит только описание (Часть 1). Он не видит код функции (Часть 2). Он не знает, как она работает внутри — он знает только что она делает и какие параметры принимает.


Полный пример: агент с тремя инструментами

Создадим агента, который умеет: - Проверять курс валют - Считать стоимость - Сохранять результат в файл

from anthropic import Anthropic
import json

client = Anthropic()

# === ОПИСАНИЯ ИНСТРУМЕНТОВ ===

tools = [
    {
        "name": "get_exchange_rate",
        "description": "Получить курс обмена валют",
        "input_schema": {
            "type": "object",
            "properties": {
                "from_currency": {"type": "string", "description": "Из какой валюты"},
                "to_currency": {"type": "string", "description": "В какую валюту"}
            },
            "required": ["from_currency", "to_currency"]
        }
    },
    {
        "name": "calculate",
        "description": "Выполнить математический расчёт",
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {"type": "string", "description": "Математическое выражение, например '100 * 92.5'"}
            },
            "required": ["expression"]
        }
    },
    {
        "name": "save_to_file",
        "description": "Сохранить текст в файл",
        "input_schema": {
            "type": "object",
            "properties": {
                "filename": {"type": "string", "description": "Имя файла"},
                "content": {"type": "string", "description": "Содержимое файла"}
            },
            "required": ["filename", "content"]
        }
    }
]

# === РЕАЛЬНЫЕ ФУНКЦИИ ===

def get_exchange_rate(from_currency, to_currency):
    rates = {
        ("USD", "RUB"): 92.5,
        ("EUR", "RUB"): 100.3,
        ("BTC", "USD"): 67000,
    }
    rate = rates.get((from_currency, to_currency))
    if rate:
        return f"1 {from_currency} = {rate} {to_currency}"
    return f"Курс {from_currency}/{to_currency} не найден"

def calculate(expression):
    try:
        result = eval(expression)
        # eval() — вычисляет строку как математическое выражение
        return str(result)
    except Exception as e:
        return f"Ошибка: {e}"

def save_to_file(filename, content):
    with open(filename, "w") as f:
        f.write(content)
    return f"Файл {filename} сохранён"

# === МАППИНГ: имя инструмента → функция ===

tool_functions = {
    "get_exchange_rate": lambda args: get_exchange_rate(args["from_currency"], args["to_currency"]),
    "calculate": lambda args: calculate(args["expression"]),
    "save_to_file": lambda args: save_to_file(args["filename"], args["content"]),
}
# lambda — маленькая функция, которая принимает args и вызывает нужную функцию
# args — словарь с параметрами, которые Claude передал

# === АГЕНТНЫЙ ЦИКЛ ===

def run_agent(task):
    messages = [{"role": "user", "content": task}]

    print(f"\nЗадача: {task}")
    print("-" * 50)

    while True:
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=tools,
            messages=messages
        )

        # Claude закончил
        if response.stop_reason == "end_turn":
            for block in response.content:
                if hasattr(block, "text"):
                    print(f"\nРезультат: {block.text}")
            break

        # Claude хочет вызвать инструмент
        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":
                    print(f"  → Вызов: {block.name}({json.dumps(block.input, ensure_ascii=False)})")

                    # Выполняем функцию
                    result = tool_functions[block.name](block.input)
                    print(f"  ← Результат: {result}")

                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })

            messages.append({"role": "user", "content": tool_results})

# === ЗАПУСК ===

run_agent("Сколько стоят 500 долларов в рублях? Сохрани результат в файл report.txt")

Что произойдёт:

Задача: Сколько стоят 500 долларов в рублях? Сохрани результат в файл report.txt
--------------------------------------------------
  → Вызов: get_exchange_rate({"from_currency": "USD", "to_currency": "RUB"})
  ← Результат: 1 USD = 92.5 RUB
  → Вызов: calculate({"expression": "500 * 92.5"})
  ← Результат: 46250.0
  → Вызов: save_to_file({"filename": "report.txt", "content": "500 USD = 46,250 RUB (курс: 1 USD = 92.5 RUB)"})
  ← Результат: Файл report.txt сохранён

Результат: 500 долларов = 46,250 рублей по курсу 92.5.
           Результат сохранён в файл report.txt.

Claude сам решил: 1. Сначала вызвать get_exchange_rate чтобы узнать курс 2. Потом calculate чтобы умножить 3. Потом save_to_file чтобы сохранить

Три шага, три инструмента, ноль ручного управления.


Как описывать инструменты правильно

Описание (description) — самое важное

Claude решает, какой инструмент вызвать, по description. Если описание плохое — он выберет не тот инструмент или не вызовет его вообще.

# Плохое описание — Claude не поймёт когда использовать
{"name": "func1", "description": "Делает что-то"}

# Хорошее описание — Claude точно знает когда вызвать
{"name": "get_weather", "description": "Получить текущую погоду (температуру, осадки, ветер) в указанном городе. Используй когда пользователь спрашивает про погоду."}

Параметры — что функция принимает

"input_schema": {
    "type": "object",
    "properties": {
        "city": {
            "type": "string",
            "description": "Название города на русском или английском"
            # Описание параметра помогает Claude правильно заполнить
        },
        "units": {
            "type": "string",
            "description": "Единицы измерения: 'celsius' или 'fahrenheit'",
            "enum": ["celsius", "fahrenheit"]
            # enum — ограничивает возможные значения
        }
    },
    "required": ["city"]
    # required — обязательные поля
    # units не в required — значит необязательный
}

Типы параметров

"type": "string"   # текст: "Москва", "USD", "hello@mail.com"
"type": "number"   # число: 42, 3.14, -10
"type": "integer"  # целое число: 1, 2, 100
"type": "boolean"  # да/нет: true, false
"type": "array"    # список: ["Москва", "Питер"]
"type": "object"   # словарь: {"name": "Иван", "age": 25}

Какие инструменты можно создать

Инструментом может быть любая функция. Вот примеры по категориям:

Работа с данными

{"name": "search_database", "description": "Поиск информации в базе данных по запросу"}
{"name": "add_row_to_sheets", "description": "Добавить строку в Google Таблицу"}
{"name": "read_csv", "description": "Прочитать CSV-файл и вернуть данные"}

Коммуникации

{"name": "send_telegram", "description": "Отправить сообщение в Telegram"}
{"name": "send_email", "description": "Отправить email"}
{"name": "post_to_instagram", "description": "Опубликовать пост в Instagram"}

Внешние API

{"name": "get_weather", "description": "Получить погоду"}
{"name": "get_exchange_rate", "description": "Получить курс валют"}
{"name": "search_web", "description": "Поиск в интернете"}

Работа с файлами

{"name": "read_file", "description": "Прочитать содержимое файла"}
{"name": "save_to_file", "description": "Сохранить текст в файл"}
{"name": "list_files", "description": "Список файлов в папке"}

Вычисления

{"name": "calculate", "description": "Математический расчёт"}
{"name": "convert_currency", "description": "Конвертация валют с расчётом"}
{"name": "generate_report", "description": "Сгенерировать отчёт из данных"}

Реальный пример: как OpenClaw использует tools

OpenClaw — это бесплатный open-source AI-агент, созданный Peter Steinberger. Он работает локально на компьютере и подключается к 40+ мессенджерам (Telegram, WhatsApp, Discord, Slack, Signal, iMessage и др.). Внутри OpenClaw — ровно тот же принцип, который ты только что изучил: описания инструментов + реальные функции + агентный цикл.

Архитектура OpenClaw

┌──────────────────────────────────────────────────┐
│              OpenClaw — как устроен               │
│                                                   │
│  1. Channel Adapters                              │
│     Принимает сообщения из Telegram, WhatsApp,    │
│     Discord и 40+ других мессенджеров.            │
│     Приводит всё к единому формату.               │
│                                                   │
│  2. Gateway Server                                │
│     Центральный координатор. Распределяет         │
│     сообщения, управляет сессиями.                │
│     Сам НЕ думает — только маршрутизирует.        │
│                                                   │
│  3. Agent Runner                                  │
│     Собирает контекст перед отправкой в LLM:      │
│     - выбирает модель                             │
│     - собирает system prompt + tools              │
│     - загружает историю разговора                 │
│                                                   │
│  4. Agentic Loop (мозг)                           │
│     Цикл думай-действуй из урока 3.1:            │
│     LLM отвечает → tool call? → выполни →         │
│     → верни результат → повтори                   │
│                                                   │
│  5. Response Path                                 │
│     Отправляет финальный ответ обратно             │
│     в мессенджер через Channel Adapter            │
│                                                   │
└──────────────────────────────────────────────────┘

Инструменты OpenClaw (~49 встроенных)

OpenClaw поставляется с примерно 49 готовыми инструментами (skills). Вот основные категории:

Категория Что делают Примеры
Браузер Открывают сайты, заполняют формы Автоматический вход, сбор данных
Файлы Читают/пишут файлы, обрабатывают PDF Создание отчётов, анализ документов
Команды Выполняют команды в терминале Запуск скриптов, управление сервером
Веб-поиск Ищут информацию в интернете Ответы на вопросы с актуальными данными
Почта Читают и отправляют email (Gmail) Автоматические ответы, уведомления
Календарь Управляют расписанием Создание встреч, напоминания
Мессенджеры Отправляют/получают сообщения Ответы в чатах, мониторинг
Cron / Webhooks Выполняют задачи по расписанию Ежедневные отчёты, мониторинг

Сообщество добавляет свои инструменты через ClawHub — библиотеку расширений. Есть интеграции с GitHub, Spotify, умным домом (Philips Hue), фондовым рынком и другими сервисами.

Как OpenClaw обрабатывает сообщение

Допустим, в Telegram-чате появляется сообщение. Вот что происходит:

1. Channel Adapter: Telegram → единый формат
   Сообщение: "Ищу поставщика ASIC, контакт @mining_pro"

2. Agent Runner: собирает контекст
   → Модель: Claude / GPT-4 / локальная (на выбор)
   → System prompt + список tools + память

3. Agentic Loop (ReAct-паттерн):
   → LLM думает: "Это потенциальный клиент. Нужно записать."
   → Tool call: add_row_to_sheets(name="@mining_pro", request="поставщик ASIC")
   → Результат: "Строка добавлена"
   → LLM думает: "Задача выполнена."
   → Финальный ответ (или тишина — зависит от настройки)

4. Response Path: ответ → Telegram

Внутри — ровно тот же agentic loop, который мы писали в уроке 3.1. Разница только в масштабе: 49 инструментов вместо 2, десятки мессенджеров вместо одного терминала.

Память OpenClaw

OpenClaw хранит информацию в Markdown-файлах (да, обычные .md файлы):

SOUL.md      — личность агента, стиль поведения
TOOLS.md     — документация по инструментам
user.md      — предпочтения пользователя
memory.md    — что агент запомнил между разговорами
HEARTBEAT.md — расписание автоматических задач

Эти файлы загружаются в контекст LLM при каждом сообщении. Благодаря этому агент помнит предыдущие разговоры и учитывает предпочтения. Подробнее о памяти — в следующем уроке.

Стоимость работы OpenClaw

Сам OpenClaw бесплатный и open-source. Платить нужно только за API модели:

Типичные расходы: $2-75/день в зависимости от активности

Оптимизация: OpenClaw может маршрутизировать запросы —
простые задачи → дешёвая модель (Haiku),
сложные → мощная (Claude Sonnet / Opus).

При таком подходе расходы ~$2.50/день.

Это применение стратегий из урока 2.5 — выбор модели по задаче, а не "самой умной" для всего.


Безопасность: что агент может и чего нельзя

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

# Ты даёшь:
tools = [get_weather, calculate]
# Claude может: проверить погоду, посчитать
# Claude НЕ может: удалить файлы, отправить деньги, читать пароли

# Ты контролируешь:
# - Какие инструменты доступны
# - Что они делают внутри
# - Какие данные они возвращают

Правила безопасности:

  1. Давай только нужные инструменты — не давай delete_everything если не нужно
  2. Проверяй входные данные — валидируй параметры внутри функций
  3. Логируй вызовы — записывай что агент делал (мы делали это в print(f"→ Вызов: ..."))
  4. Опасные действия — с подтверждением — перед отправкой денег или удалением данных спрашивай пользователя

OpenClaw, например, имеет встроенные approval gates — перед выполнением опасных команд он запрашивает разрешение у пользователя.


Практика

Задание 1: Создай свой инструмент

Напиши описание инструмента search_contacts — поиск контакта по имени в списке.

# Заполни:
tool = {
    "name": "search_contacts",
    "description": "???",
    "input_schema": {
        "type": "object",
        "properties": {
            # ???
        },
        "required": [???]
    }
}
Ответ
tool = {
    "name": "search_contacts",
    "description": "Найти контакт по имени в списке контактов. Возвращает имя, email и телефон.",
    "input_schema": {
        "type": "object",
        "properties": {
            "name": {
                "type": "string",
                "description": "Имя или часть имени для поиска"
            }
        },
        "required": ["name"]
    }
}

Задание 2: Запусти агента

Скопируй полный пример с тремя инструментами (курс, калькулятор, файл) и запусти. Попробуй разные задачи: - "Сколько стоят 1000 EUR в рублях?" - "Посчитай 15% от 46250 и сохрани в файл tax.txt"

Задание 3: Добавь новый инструмент

Добавь к агенту инструмент get_time — возвращает текущее время:

from datetime import datetime

def get_time():
    return datetime.now().strftime("%H:%M:%S")

Опиши его в tools и добавь в tool_functions. Проверь, что работает: "Который сейчас час?"


Задачки на закрепление

Задача 1: Зачем инструменту нужно описание (description)?

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

Задача 2: Claude видит код функции?

Ответ Нет. Claude видит только описание инструмента (name, description, input_schema). Он не знает, как функция работает внутри — только что она принимает и что делает.

Задача 3: Можно ли дать агенту 10 инструментов одновременно?

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

Задача 4: OpenClaw — это чат-бот или агент? Почему?

Ответ Агент. Потому что он имеет ~49 инструментов (tools), работает в агентном цикле (agentic loop), может выполнять действия (читать чаты, писать в таблицы, отправлять сообщения, искать в интернете), а не просто генерировать текст.

Главное


Глоссарий

Термин Что значит
Tool (инструмент) Функция, которую ты описываешь для Claude, чтобы он мог выполнять действия во внешнем мире.
tool_use Тип блока в ответе Claude, означающий, что модель хочет вызвать конкретный инструмент с определёнными параметрами.
tool_result Тип сообщения, которое ты отправляешь обратно Claude с результатом выполнения запрошенного инструмента.
input_schema Описание параметров инструмента в формате JSON Schema, чтобы Claude знал, какие данные передавать при вызове.
JSON Schema Стандартный формат описания структуры данных, используемый для определения параметров инструментов.
Function calling Механизм, при котором LLM выбирает нужную функцию и формирует параметры для её вызова на основе контекста разговора.
Name (имя инструмента) Уникальное название инструмента, по которому Claude обращается к нему при вызове.
Description (описание) Текстовое пояснение для Claude, объясняющее что делает инструмент и когда его стоит использовать.
Параметры инструмента Входные данные, которые Claude передаёт при вызове инструмента — описываются через input_schema.
MCP (Model Context Protocol) Открытый протокол от Anthropic, который позволяет подключать внешние инструменты и данные к LLM через единый стандарт.

Что дальше?

В следующем уроке — Память агента. Как агент запоминает информацию между разговорами, чтобы не начинать каждый раз с чистого листа. На примере OpenClaw мы уже увидели, что он хранит память в Markdown-файлах — в следующем уроке разберём этот подход подробнее и реализуем его в коде.

← ПРЕДЫДУЩИЙ СЛЕДУЮЩИЙ →