Перейти к содержимому
ПереводыAnthropic

Курс Anthropic: Tool use, часть 1 — что такое инструменты, первый инструмент, JSON через инструменты

Перевод уроков 1–3 курса Tool use из anthropics/courses: как устроен вызов инструментов (function calling), калькулятор как первый инструмент, описание инструмента JSON-схемой и приём «инструмент как форма для JSON» — анализ тональности, извлечение сущностей, классификация статьи.

Tool use — пятый, последний курс в репозитории anthropics/courses. Он учит давать модели инструменты (tools): функции на вашей стороне, которые модель может «попросить» вызвать. В курсе шесть уроков; перевод разбит на две части:

  1. Основы (эта страница): уроки 1–3 — как устроен вызов инструментов, первый инструмент-калькулятор и приём, при котором инструмент служит формой для структурированного JSON.
  2. Полный цикл и чат-бот: уроки 4–6 — возврат результата модели (tool_result), параметр tool_choice и чат-бот поддержки с несколькими инструментами.
  • Когда. После урока 8.3.1 «Агентный цикл и ReAct»: там раздел «Tool calling» объясняет ту же идею в формате OpenAI-совместимых API. Этот курс показывает формат Anthropic: блоки tool_use и tool_result внутри списка content. Дальше — урок 8.3.2 «Инструменты и MCP»: как описывать инструменты хорошо и как подключать их через протокол MCP.
  • Что нужно знать. Формат сообщений Anthropic (уроки 1–2 курса основ API) и словари Python.
  • Сколько времени. Полтора-два часа на три урока вместе с упражнениями.
  • Что можно пропустить. Длинный список отраслевых примеров в уроке 1 — его можно пролистать.
  • Понять, что такое tool use.
  • Разобраться, где он применяется.
  • Понять в общих чертах, как проходит работа с инструментами в Claude.

Tool use, или вызов функций (function calling), — способность расширять возможности Claude: мы описываем внешние инструменты или функции, и модель может их вызывать. Мы даём Claude доступ к набору заранее определённых инструментов, к которым он может обратиться в любой момент. Инструменты позволяют написать код, который выполняет конкретные задачи или вычисления, недоступные Claude самому по себе. Коротко: tool use — способ расширить функциональность Claude.

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

Tool use меняет правила игры: он раскрывает настоящий потенциал Claude и открывает множество новых возможностей. Это ключевая часть, которая может заметно повысить ценность ваших приложений.

  • Расширить возможности Claude. Инструменты выводят Claude за пределы встроенных умений. Описывая и вызывая внешние инструменты, вы позволяете модели делать то, чего иначе она не смогла бы. Значит, можно строить более мощные и гибкие приложения, которые закрывают больше потребностей пользователей.
  • Встроиться в существующие системы. Инструменты связывают Claude с вашими системами, базами данных и API. Можно описать инструменты, которые обращаются к бэкенду, достают данные из базы или запускают действия в приложении. Так вы используете силу Claude, не меняя свою инфраструктуру и данные.
  • Автоматизировать сложные задачи. С инструментами можно автоматизировать многошаговые процессы и специализированные вычисления. Упаковав такие задачи в инструменты, вы упрощаете процессы, сокращаете ручную работу и повышаете эффективность. Claude сам вызывает нужные инструменты в нужный момент — по запросу пользователя или по заданным условиям.
  • Улучшить опыт пользователя. Получив доступ к подходящим инструментам, Claude даёт более точные, учитывающие контекст и персональные ответы. Пользователь говорит с приложением обычным языком, а Claude с помощью инструментов находит нужные сведения или выполняет действия от его имени.
  • Масштабировать и настраивать. Когда пользователей становится больше или меняются требования, можно легко добавить новые инструменты или изменить старые. Такая гибкость позволяет быстро развивать приложение.

Общие темы, которые мы видим у клиентов, использующих инструменты:

  • Получение информации. Инструменты достают данные из внешних источников: баз данных, API, веб-сервисов. Например, инструмент может узнавать погоду, курсы акций или новости по запросу пользователя.
  • Вычисления. Инструменты выполняют сложные расчёты, которые могут быть не по силам самому Claude: финансовые, научные, статистические.
  • Обработка данных. Инструменты преобразуют данные разных форматов: форматирование, извлечение, конвертация.
  • Взаимодействие с внешними системами. Инструменты отправляют письма и уведомления, управляют устройствами интернета вещей.
  • Создание контента. Инструменты помогают делать картинки, графики, оформленные документы по запросу или по шаблону.

Более конкретные варианты:

  • Интеграция с корпоративными данными: подключить Claude к CRM, ERP, ITSM-системам, чтобы получать данные клиентов, автоматизировать процессы и персонально поддерживать пользователей.
  • Финансовый анализ и отчётность: анализ финансовых данных, инвестиционные отчёты, оценка рисков, соблюдение регуляторных требований.
  • Медицинская диагностика и планы лечения: связать Claude с электронными медкартами и медицинскими базами знаний, чтобы помогать врачам принимать решения и составлять индивидуальные планы лечения.
  • Обучение и учебные материалы: персональное репетиторство, генерация учебных материалов, подстройка под стиль обучения.
  • Анализ юридических документов: извлечение ключевых сведений, поиск возможных проблем, краткие выжимки.
  • Автоматизация поддержки: связать Claude с базами знаний и системой заявок, чтобы отвечать клиентам, разбираться с проблемами и сокращать время ответа.
  • Продажи и маркетинг: анализ данных клиентов, персональные рекламные тексты, оценка лидов.
  • Помощь в разработке: подключить Claude к IDE, системе контроля версий и трекеру задач, чтобы помогать писать код, искать ошибки и вести проекты.
  • Исследования и инновации: анализ рынка и патентов, генерация идей.
  • Создание и оптимизация контента для сайтов, соцсетей и рекламных кампаний.

Посмотрим, как вызов инструментов устроен на самом деле. Самое важное, что нужно понять сразу: Claude сам не запускает никакой код. Мы рассказываем Claude о наборе инструментов, которые он может попросить нас вызвать, а запустить код инструмента и сообщить Claude результат — наша работа.

Заметьте: у Claude нет встроенных инструментов на стороне сервера. Все инструменты вы явно передаёте в каждом запросе к API. То есть вы описываете доступные инструменты — с понятными описаниями и схемами входных данных — и вы же реализуете и выполняете их логику: запускаете функцию или обращаетесь к API, когда Claude об этом просит. Так у вас остаётся полный контроль над тем, какими инструментами пользуется Claude.

(В оригинале здесь схема: Claude запрашивает инструмент «поиск в базе данных», ваш код выполняет запрос к базе и возвращает результат.)

Работа с инструментами в Claude состоит из таких шагов:

  1. Дать Claude инструменты и запрос пользователя (запрос к API).

    • Опишите набор инструментов, к которым у Claude будет доступ: названия, описания и схемы входных данных.
    • Передайте запрос пользователя, для ответа на который может понадобиться один или несколько инструментов, например: «Сколько акций General Motors я могу купить на 500 долларов?»
  2. Claude вызывает инструмент (ответ API).

    • Claude оценивает запрос и решает, поможет ли какой-то из инструментов. Если да — решает, какой инструмент (или какие) использовать и с какими входными данными.
    • Claude выдаёт правильно оформленный запрос на вызов инструмента.
    • У ответа API будет stop_reason, равный tool_use: это значит, что Claude хочет воспользоваться внешним инструментом.
  3. Извлечь входные данные, запустить код и вернуть результат (запрос к API).

    • На стороне клиента достаньте из запроса Claude название инструмента и входные данные.
    • Запустите код инструмента у себя.
    • Верните результат Claude: продолжите диалог новым сообщением пользователя с блоком tool_result.
  4. Claude использует результат, чтобы ответить (ответ API).

    • Получив результаты инструментов, Claude составляет окончательный ответ на исходный запрос.

Шаги 3 и 4 необязательны: в некоторых сценариях нам достаточно самого факта, что Claude вызвал инструмент, и возвращать ему результат не нужно. Об этом — в следующих уроках.

Чтобы лучше понять поток работы, представим, что мы делаем чат-приложение, где пользователь может поговорить с Claude о фондовом рынке и узнать текущие цены акций.

Конечно, Claude не знает, что происходит на рынке в данный момент, поэтому дадим ему инструмент get_stock_price, который узнаёт текущую цену акций компании.

Вот как движется информация между нашим приложением и Claude:

sequenceDiagram
participant P as Пользователь
participant A as Наше приложение
participant C as Claude
P->>A: Сколько акций GM я куплю на 500 долларов?
A->>C: запрос + описание get_stock_price
C-->>A: tool_use: get_stock_price(company='General Motors')
A->>A: вызываем API биржи: GM = 43.09
A->>C: tool_result: price 43.09
C-->>A: Примерно 11 акций
A->>P: ответ
Схема из урока: приложение, Claude и инструмент get_stock_price (перерисовано)

Прежде чем рассказывать Claude об инструменте, нужно написать его функциональность. Для get_stock_price мы, вероятно, напишем функцию, которая принимает название компании или биржевой тикер и отправляет запрос к API биржи за живыми данными. Примерно так:

def get_stock_price(company):
# Отправить запрос к API биржи и узнать текущую цену акций компании
# Вернуть словарь с информацией о текущей цене акций указанной компании
...

Если бы функция была реализована полностью и мы вызвали бы get_stock_price("General Motors"), то могли бы получить такое значение:

{
"symbol": "GM",
"price": 43.09
}
Шаг 1: дать Claude инструменты и запрос (запрос к API)
Заголовок раздела «Шаг 1: дать Claude инструменты и запрос (запрос к API)»

Теперь опишем инструмент get_stock_price: имя, описание и схему входных данных. Подробнее об описании инструментов поговорим позже, а пока вот возможное описание:

tool_definition = {
"name": "get_stock_price",
"description": "Возвращает текущую цену акций указанной компании",
"input_schema": {
"type": "object",
"properties": {
"company": {
"type": "string",
"description": "Название компании, для которой нужны данные об акциях"
}
},
"required": ["company"]
}
}

Затем сообщаем Claude, что инструмент существует, и отправляем запрос, для которого он может понадобиться:

response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-opus-20240229"
messages=[{"role": "user", "content": "Сколько акций General Motors я могу купить на 500 долларов?"}],
max_tokens=500,
tools=[tool_definition] # список инструментов, о которых знает модель
)

Claude получает запрос и оценивает его. Он решает, что инструмент get_stock_price поможет ответить на вопрос о покупке акций General Motors.

Claude отвечает правильно оформленным запросом на вызов инструмента. Подробнее разберём позже, а пока — упрощённый пример ответа, где Claude «хочет» воспользоваться нашим инструментом:

{
"stop_reason": "tool_use",
"tool_use": {
"name": "get_stock_price",
"input": {
"company": "General Motors"
}
}
}
Шаг 3: извлечь входные данные, запустить код и вернуть результат (запрос к API)
Заголовок раздела «Шаг 3: извлечь входные данные, запустить код и вернуть результат (запрос к API)»

На стороне клиента достаём из запроса Claude имя инструмента (get_stock_price) и входные данные (название компании General Motors).

Запускаем настоящую функцию get_stock_price, которая получает живые биржевые данные из API, с извлечёнными аргументами. Функция может вернуть что-то такое:

{
"symbol": "GM",
"price": 43.09
}

Затем сообщаем Claude результат: продолжаем диалог новым сообщением пользователя, в котором лежит блок tool_result (подробнее — дальше).

Шаг 4: Claude использует результат, чтобы ответить (ответ API)
Заголовок раздела «Шаг 4: Claude использует результат, чтобы ответить (ответ API)»

Claude получает цену акций и включает живые данные в окончательный ответ на исходный вопрос — например:

При текущей цене 43,09 доллара за акцию на ваши 500 долларов можно купить примерно 11 акций General Motors.

Какие из этих задач хорошо подходят для tool use?

  • (a) Чат-бот поддержки, которому нужно доставать информацию о заказе из базы данных по номеру заказа.
  • (b) Помощник по письму, который помогает исправлять грамматику и стиль.
  • (c) Финансовый советник, который даёт персональные рекомендации по инвестициям с учётом готовности к риску и финансовых целей пользователя.
  • (d) Чат-бот для путешествий, который бронирует билеты и отели по предпочтениям и наличию мест.
Ответ на вопрос 1

a, c и d

  • (a) Чат-бот поддержки, которому нужно доставать информацию о заказе из базы данных по номеру заказа.
  • (c) Финансовый советник, который даёт персональные рекомендации по инвестициям.
  • (d) Чат-бот для путешествий, который бронирует билеты и отели.

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

Задаче (b) инструменты, скорее всего, не нужны: с ней справляются встроенные способности Claude понимать и порождать текст.

Расставьте шаги работы с инструментом в правильном порядке:

  • (a) Claude использует результат инструмента, чтобы ответить на исходный запрос.
  • (b) Код на стороне клиента извлекает имя инструмента и входные данные из запроса Claude.
  • (c) Claude оценивает запрос пользователя, решает воспользоваться инструментом и выдаёт запрос на вызов.
  • (d) Код на стороне клиента выполняет инструмент и возвращает результат Claude.
  • (e) Код на стороне клиента передаёт Claude набор доступных инструментов и запрос пользователя.
Ответ на вопрос 2

Правильный порядок: e, c, b, d, a

  1. (e) Код на стороне клиента передаёт Claude набор доступных инструментов и запрос пользователя.
  2. (c) Claude оценивает запрос, решает воспользоваться инструментом и выдаёт запрос на вызов.
  3. (b) Код на стороне клиента извлекает имя инструмента и входные данные из запроса Claude.
  4. (d) Код на стороне клиента выполняет инструмент и возвращает результат Claude.
  5. (a) Claude использует результат инструмента, чтобы ответить на исходный запрос.

В прошлом уроке мы прошли весь процесс работы с инструментами. Пора взяться за дело и реализовать простой пример. Напомним: в процессе до четырёх шагов.

  1. Дать Claude инструменты и запрос пользователя (запрос к API): описать инструменты — имена, описания, схемы входных данных — и передать запрос, для ответа на который они могут понадобиться.
  2. Claude вызывает инструмент (ответ API): Claude решает, нужен ли инструмент и с какими входными данными, и выдаёт правильно оформленный запрос на вызов; stop_reason в ответе равен tool_use.
  3. Извлечь входные данные, запустить код и вернуть результат (запрос к API): достать имя инструмента и входные данные, запустить код у себя, вернуть результат новым сообщением пользователя с блоком tool_result.
  4. Claude использует результат, чтобы ответить (ответ API).

Начнём с простой демонстрации, где с Claude нужно «поговорить» только один раз (не волнуйтесь, интересные примеры скоро будут!). Значит, шаг 4 пока пропускаем. Мы зададим Claude вопрос, Claude попросит вызвать инструмент, а мы извлечём входные данные, запустим код и получим значение.

Современные большие языковые модели плохо справляются с арифметикой, и следующий код это показывает. Попросим Claude перемножить 1984135 и 9343116:

from claude_local import client, MODEL_NAME # Claude или локальная модель — см. «Подготовку» курса основ API
# Сравнительно простая задача на умножение
response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-haiku-20240307"
messages=[{"role": "user", "content": "Перемножь 1984135 и 9343116. В ответе — только результат"}],
max_tokens=400
)
print(response.content[0].text)

Вывод в оригинале:

18555375560

Если запустить код несколько раз, ответы, скорее всего, будут разными. Вот ещё один ответ Claude:

18593367726060

Правильный ответ:

18538003464660

Claude ошибся «всего лишь» на 55364261400!

Claude плохо справляется со сложной арифметикой, так что расширим его возможности: дадим ему калькулятор.

(В оригинале здесь схема: пользователь спрашивает «У меня было 23 курицы, 2 улетели — сколько осталось?», Claude просит вызвать калькулятор с операцией «вычесть» и числами 23 и 2, наш код считает и получает 21.)

Первый шаг — написать саму функцию калькулятора и убедиться, что она работает, независимо от Claude. Напишем ОЧЕНЬ простую функцию с тремя аргументами:

  • операция, например "add" (сложить) или "multiply" (умножить);
  • два операнда (числа).

Простейшая реализация:

def calculator(operation, operand1, operand2):
if operation == "add": # сложение
return operand1 + operand2
elif operation == "subtract": # вычитание
return operand1 - operand2
elif operation == "multiply": # умножение
return operand1 * operand2
elif operation == "divide": # деление
if operand2 == 0:
raise ValueError("Делить на ноль нельзя.")
return operand1 / operand2
else:
raise ValueError(f"Неподдерживаемая операция: {operation}")

Эта функция очень ограничена: она умеет только простые выражения вроде 234 + 213 или 3 * 9. Смысл в том, чтобы пройти весь процесс работы с инструментами на очень простом учебном примере.

Проверим, что функция работает:

calculator("add", 10, 3)
13
calculator("divide", 200, 25)
8.0

Следующий шаг — описать инструмент и рассказать о нём Claude. Описание инструмента подчиняется строгому формату. В каждом описании есть:

  • name — имя инструмента. Должно соответствовать регулярному выражению ^[a-zA-Z0-9_-]{1,64}$ (латинские буквы, цифры, _ и -, от 1 до 64 символов).
  • description — подробное текстовое описание: что делает инструмент, когда его использовать и как он себя ведёт.
  • input_schema — объект JSON Schema («JSON-схема»), который описывает ожидаемые параметры инструмента.

Вот простой пример для воображаемого инструмента:

{
"name": "send_email",
"description": "Отправляет письмо указанному адресату с заданной темой и текстом.",
"input_schema": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Адрес электронной почты получателя"
},
"subject": {
"type": "string",
"description": "Тема письма"
},
"body": {
"type": "string",
"description": "Текст письма"
}
},
"required": ["to", "subject", "body"]
}
}

Этот инструмент, send_email, ожидает такие входные данные:

  • to — строка, обязательный параметр;
  • subject — строка, обязательный;
  • body — строка, обязательный.

Вот описание другого инструмента, search_product:

{
"name": "search_product",
"description": "Ищет товар по названию или ключевому слову и возвращает его текущую цену и наличие.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Название товара или ключевое слово, например 'iPhone 13 Pro' или 'беспроводные наушники'"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "home", "toys", "sports"],
"description": "Категория товара, чтобы сузить поиск"
},
"max_price": {
"type": "number",
"description": "Максимальная цена товара для фильтрации результатов"
}
},
"required": ["query"]
}
}

У этого инструмента три входа:

  • обязательная строка query — название товара или ключевое слово;
  • необязательная строка category, которая должна быть одним из заранее заданных значений. Обратите внимание на "enum" (перечисление допустимых значений) в описании;
  • необязательное число max_price, чтобы отсеять товары дороже заданной цены.

Опишем инструмент для функции калькулятора, которую мы написали. У неё три обязательных аргумента:

  • operation — может быть только "add", "subtract", "multiply" или "divide";
  • operand1 — число;
  • operand2 — тоже число.

Вот описание:

calculator_tool = {
"name": "calculator",
"description": "Простой калькулятор для базовых арифметических операций.",
"input_schema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "Арифметическая операция."
},
"operand1": {
"type": "number",
"description": "Первый операнд."
},
"operand2": {
"type": "number",
"description": "Второй операнд."
}
},
"required": ["operation", "operand1", "operand2"]
}
}

Потренируемся писать правильно оформленное описание инструмента на примере такой функции:

def inventory_lookup(product_name, max_results):
return "эта функция ничего не делает"
# Её не нужно трогать и вообще что-то с ней делать!

Воображаемая функция inventory_lookup (поиск по складу) вызывается так:

inventory_lookup("AA batteries", 4)
inventory_lookup("birthday candle", 10)

Задача: написать соответствующее правильно оформленное описание инструмента. Считайте, что оба аргумента обязательны.

Один из возможных ответов (от переводчика; в оригинале решения нет)
inventory_lookup_tool = {
"name": "inventory_lookup",
"description": "Ищет товар на складе по названию и возвращает не больше max_results подходящих позиций.",
"input_schema": {
"type": "object",
"properties": {
"product_name": {"type": "string", "description": "Название товара, например 'AA batteries'."},
"max_results": {"type": "integer", "description": "Сколько результатов вернуть, не больше."}
},
"required": ["product_name", "max_results"]
}
}

Вернёмся к калькулятору. Сейчас Claude ничего о нём не знает: это просто маленький словарь Python. В запросе к Claude можно передать список инструментов, чтобы «рассказать» о них. Попробуем:

response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-haiku-20240307"
messages=[{"role": "user", "content": "Перемножь 1984135 и 9343116. В ответе — только результат"}],
max_tokens=300,
# Рассказываем Claude о нашем инструменте
tools=[calculator_tool]
)

Посмотрим, что вернул Claude:

response
ToolsBetaMessage(id='msg_01UfKwdmEsgTh99wfpgW4NJ7', content=[ToolUseBlock(id='toolu_015wQ7Wipo589yT9B3YTwjF1', input={'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}, name='calculator', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=420, output_tokens=93))

Ответ выглядит не так, как обычно! Вместо простого Message мы получили ToolsMessage (примечание переводчика: в выводе — ToolsBetaMessage, имя из бета-версии 2024 года; в современном SDK это обычный Message).

Кроме того, response.stop_reason показывает, что Claude остановился, потому что решил вызвать инструмент:

response.stop_reason
'tool_use'

response.content — список, в котором лежит ToolUseBlock (блок вызова инструмента) с именем инструмента и входными данными:

response.content
[ToolUseBlock(id='toolu_015wQ7Wipo589yT9B3YTwjF1', input={'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}, name='calculator', type='tool_use')]
tool_name = response.content[0].name # имя инструмента
tool_inputs = response.content[0].input # входные данные — обычный словарь Python
print("Инструмент, который Claude хочет вызвать:", tool_name)
print("Входные данные, с которыми он хочет его вызвать:", tool_inputs)
Инструмент, который Claude хочет вызвать: calculator
Входные данные, с которыми он хочет его вызвать: {'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}

Осталось взять имя инструмента и входные данные, которые дал Claude, и вызвать с ними нашу функцию калькулятора. Тогда у нас будет окончательный ответ!

operation = tool_inputs["operation"]
operand1 = tool_inputs["operand1"]
operand2 = tool_inputs["operand2"]
result = calculator(operation, operand1, operand2)
print("РЕЗУЛЬТАТ:", result)
РЕЗУЛЬТАТ: 18538003464660

Мы получили правильный ответ 18538003464660!!! Вместо того чтобы надеяться, что Claude правильно посчитает, мы задаём вопрос и даём ему инструмент, которым он может воспользоваться, если нужно.

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

Вот пример, где Claude иногда пытается применить калькулятор, хотя это бессмысленно. Спросим: «Какого цвета изумруды?»

response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-haiku-20240307"
messages=[{"role": "user", "content": "Какого цвета изумруды?"}],
max_tokens=400,
tools=[calculator_tool]
)
response
ToolsBetaMessage(id='msg_01Dj82HdyrxGJpi8XVtqEYvs', content=[ToolUseBlock(id='toolu_01Xo7x3dV1FVoBSGntHNAX4Q', input={'operand1': 0, 'operand2': 0, 'operation': 'add'}, name='calculator', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=409, output_tokens=89))

Claude хочет, чтобы мы вызвали калькулятор (и сложили 0 и 0)? Простейшее исправление — поправить промпт или добавить системный промпт примерно такого содержания: «У тебя есть инструменты, но пользуйся ими, только когда это нужно. Если инструмент не нужен, отвечай как обычно»:

response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-haiku-20240307"
system="У тебя есть инструменты, но пользуйся ими, только когда это нужно. Если инструмент не нужен, отвечай как обычно",
messages=[{"role": "user", "content": "Какого цвета изумруды?"}],
max_tokens=400,
tools=[calculator_tool]
)
response
ToolsBetaMessage(id='msg_01YRRfnUUhP1u5ojr9iWZGGu', content=[TextBlock(text='Emeralds are green in color.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(input_tokens=434, output_tokens=12))

Теперь Claude отвечает по существу и не пытается впихнуть инструмент туда, где он не нужен. Новый ответ: «Изумруды зелёного цвета». Заметьте, что stop_reason теперь end_turn, а не tool_use:

response.stop_reason
'end_turn'
def calculator(operation, operand1, operand2):
if operation == "add":
return operand1 + operand2
elif operation == "subtract":
return operand1 - operand2
elif operation == "multiply":
return operand1 * operand2
elif operation == "divide":
if operand2 == 0:
raise ValueError("Делить на ноль нельзя.")
return operand1 / operand2
else:
raise ValueError(f"Неподдерживаемая операция: {operation}")
calculator_tool = {
"name": "calculator",
"description": "Простой калькулятор для базовых арифметических операций.",
"input_schema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "Арифметическая операция.",
},
"operand1": {"type": "number", "description": "Первый операнд."},
"operand2": {"type": "number", "description": "Второй операнд."},
},
"required": ["operation", "operand1", "operand2"],
},
}
def prompt_claude(prompt):
messages = [{"role": "user", "content": prompt}]
response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-haiku-20240307"
system="У тебя есть инструменты, но пользуйся ими, только когда это нужно. Если инструмент не нужен, отвечай как обычно",
messages=messages,
max_tokens=500,
tools=[calculator_tool],
)
if response.stop_reason == "tool_use":
tool_use = response.content[-1] # блок tool_use обычно последний в списке
tool_name = tool_use.name
tool_input = tool_use.input
if tool_name == "calculator":
print("Claude хочет воспользоваться калькулятором")
operation = tool_input["operation"]
operand1 = tool_input["operand1"]
operand2 = tool_input["operand2"]
try:
result = calculator(operation, operand1, operand2)
print("Результат вычисления:", result)
except ValueError as e:
print(f"Ошибка: {str(e)}")
elif response.stop_reason == "end_turn":
print("Claude не стал вызывать инструмент")
print("Ответ Claude:")
print(response.content[0].text)
prompt_claude("У меня было 23 курицы, но 2 улетели. Сколько осталось?")
Claude хочет воспользоваться калькулятором
Результат вычисления: 21
prompt_claude("Сколько будет 201 умножить на 2")
Claude хочет воспользоваться калькулятором
Результат вычисления: 402
prompt_claude("Напиши хокку об океане")
Claude не стал вызывать инструмент
Ответ Claude:
Вот хокку об океане:
Синий простор блестит,
Волны бьются о песок,
Песнь океана.

(Хокку — в переводе; в оригинале Claude ответил по-английски.)

Задача — сделать с помощью Claude помощника для исследований. Пользователь вводит тему, которую хочет изучить, и получает список ссылок на статьи Википедии, сохранённый в markdown-файл, чтобы прочитать позже. Можно было бы просто попросить Claude сгенерировать список адресов статей, но Claude ненадёжен с URL и может выдумать несуществующие адреса. К тому же настоящие статьи могли переехать по новому адресу после даты среза обучающих данных Claude. Вместо этого воспользуемся инструментом, который обращается к настоящему API Википедии!

Мы дадим Claude инструмент, который принимает список возможных названий статей, придуманных Claude (среди них могут быть и выдуманные). Инструмент ищет эти названия в Википедии, находит настоящие названия статей и их адреса, так что в итоговом списке будут только существующие статьи. Затем адреса сохраняются в markdown-файл для чтения.

Вам даны две вспомогательные функции:

import wikipedia # pip install wikipedia
def generate_wikipedia_reading_list(research_topic, article_titles):
wikipedia_articles = []
for t in article_titles:
results = wikipedia.search(t) # ищем статьи по названию
try:
page = wikipedia.page(results[0]) # берём первую найденную
title = page.title
url = page.url
wikipedia_articles.append({"title": title, "url": url})
except:
continue # ничего не нашлось — пропускаем
add_to_research_reading_file(wikipedia_articles, research_topic)
def add_to_research_reading_file(articles, topic):
with open("output/research_reading.md", "a", encoding="utf-8") as file:
file.write(f"## {topic} \n")
for article in articles:
title = article["title"]
url = article["url"]
file.write(f"* [{title}]({url}) \n")
file.write(f"\n\n")

Первая функция, generate_wikipedia_reading_list, получает тему исследования, например «История Гавайев» или «Пираты по всему миру», и список возможных названий статей, который сгенерирует Claude. С помощью пакета wikipedia она ищет соответствующие НАСТОЯЩИЕ страницы Википедии и собирает список словарей с названием и адресом статьи.

Затем она вызывает add_to_research_reading_file, передавая список статей и тему. Эта функция просто дописывает markdown-ссылки на статьи в файл output/research_reading.md. Имя файла пока зашито в код, и функция предполагает, что папка output существует. В репозитории курса она есть, а если работаете в другом месте — создайте её сами.

Идея такая: Claude «вызывает» generate_wikipedia_reading_list со списком возможных названий статей, которые могут существовать, а могут и нет. Например, Claude может передать такой список — часть названий настоящие, часть нет:

["Piracy", "Famous Pirate Ships", "Golden Age Of Piracy", "List of Pirates", "Pirates and Parrots", "Piracy in the 21st Century"]

Функция generate_wikipedia_reading_list проходит по названиям и собирает настоящие названия и адреса для статей, которые существуют. Потом вызывает add_to_research_reading_file, чтобы записать их в markdown-файл.

Нужно реализовать функцию get_research_help, которая принимает тему исследования и желаемое число статей. Функция должна с помощью Claude сгенерировать список возможных статей Википедии и вызвать generate_wikipedia_reading_list. Примеры вызовов:

get_research_help("Pirates Across The World", 7)
get_research_help("History of Hawaii", 3)
get_research_help("are animals conscious?", 3)

После этих трёх вызовов файл research_reading.md выглядит так: три заголовка второго уровня по темам, под каждым — список ссылок на найденные статьи Википедии (в оригинале — скриншот файла).

Для этого нужно:

  • написать описание инструмента для функции generate_wikipedia_reading_list;
  • реализовать функцию get_research_help:
    • написать промпт, в котором вы просите Claude помочь собрать материалы по теме и говорите, сколько названий статей сгенерировать;
    • рассказать Claude о доступном инструменте;
    • отправить запрос;
    • проверить, вызвал ли Claude инструмент. Если да — передать сгенерированные названия и тему в функцию generate_wikipedia_reading_list. Она соберёт настоящие ссылки и вызовет add_to_research_reading_file, чтобы записать их в output/research_reading.md;
    • открыть output/research_reading.md и проверить, что всё получилось!

Заготовка кода:

# Вот ваша заготовка!
import wikipedia
def generate_wikipedia_reading_list(research_topic, article_titles):
wikipedia_articles = []
for t in article_titles:
results = wikipedia.search(t)
try:
page = wikipedia.page(results[0])
title = page.title
url = page.url
wikipedia_articles.append({"title": title, "url": url})
except:
continue
add_to_research_reading_file(wikipedia_articles, research_topic)
def add_to_research_reading_file(articles, topic):
with open("output/research_reading.md", "a", encoding="utf-8") as file:
file.write(f"## {topic} \n")
for article in articles:
title = article["title"]
url = article["url"]
file.write(f"* [{title}]({url}) \n")
file.write(f"\n\n")
def get_research_help(topic, num_articles=3):
# Реализуйте эту функцию!
pass
  • Понять, как инструменты помогают получить ответ строго заданной структуры.
  • Использовать этот «трюк», чтобы генерировать структурированный JSON.

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

Можно просто попросить Claude ответить в JSON. Но тогда придётся дополнительно вытаскивать JSON из длинной строки ответа и проверять, что он точно соответствует нужному формату.

Хорошая новость: когда Claude хочет вызвать инструмент, он уже отвечает в точно том формате, который мы задали в описании инструмента.

В прошлом уроке мы дали Claude калькулятор. Когда Claude хотел им воспользоваться, он отвечал примерно так:

{
'operand1': 1984135,
'operand2': 9343116,
'operation': 'multiply'
}

Подозрительно похоже на JSON!

Если нам нужен структурированный JSON от Claude, этим можно воспользоваться. Достаточно описать инструмент, входные данные которого и есть нужная структура JSON, и рассказать о нём Claude. Всё. Claude ответит, «думая», что вызывает инструмент, а нам нужен только структурированный ответ.

Чем это отличается от прошлого урока? Там Claude получил доступ к инструменту, захотел его вызвать, и мы действительно вызывали функцию инструмента.

В этом уроке мы «перехитрим» Claude: расскажем ему об инструменте, но вызывать функцию не будем. Инструмент служит способом задать структуру ответа:

flowchart LR
subgraph L2["Урок 2: настоящий инструмент"]
  A1["Запрос + описание калькулятора"] --> B1["Claude: tool_use с аргументами"] --> C1["Наш код вызывает calculator()"]
end
subgraph L3["Урок 3: инструмент как форма"]
  A2["Текст + описание print_sentiment_scores"] --> B2["Claude: tool_use с аргументами"] --> C2["Берём аргументы как готовый JSON"]
end
Два способа использовать вызов инструмента (по схемам из оригинала)

Начнём с простого примера. Пусть Claude анализирует тональность текста и отвечает JSON-объектом такого вида:

{
"negative_score": 0.6,
"neutral_score": 0.3,
"positive_score": 0.1
}

Нужно лишь описать инструмент, который задаёт эту форму через JSON Schema. Возможный вариант:

tools = [
{
"name": "print_sentiment_scores",
"description": "Печатает оценки тональности заданного текста.",
"input_schema": {
"type": "object",
"properties": {
"positive_score": {"type": "number", "description": "Оценка положительной тональности, от 0.0 до 1.0."},
"negative_score": {"type": "number", "description": "Оценка отрицательной тональности, от 0.0 до 1.0."},
"neutral_score": {"type": "number", "description": "Оценка нейтральной тональности, от 0.0 до 1.0."}
},
"required": ["positive_score", "negative_score", "neutral_score"]
}
}
]

Теперь расскажем Claude об этом инструменте и явно попросим им воспользоваться, чтобы он точно это сделал. Мы должны получить ответ, в котором Claude хочет вызвать инструмент, а в запросе на вызов будут все данные ровно в нужном формате.

import json
from claude_local import client, MODEL_NAME
tweet = "Я ОГРОМНЫЙ ненавистник солёных огурцов. Я их просто презираю. Это мусор."
query = f"""
<text>
{tweet}
</text>
Используй только инструмент print_sentiment_scores.
"""
response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-sonnet-20240229"
max_tokens=4096,
tools=tools,
messages=[{"role": "user", "content": query}]
)
response
ToolsBetaMessage(id='msg_01BhF4TkK8vDM6z5m4FNGRnB', content=[TextBlock(text='Here is the sentiment analysis for the given text:', type='text'), ToolUseBlock(id='toolu_01Mt1an3KHEz5RduZRUUuTWz', input={'positive_score': 0.0, 'negative_score': 0.791, 'neutral_score': 0.209}, name='print_sentiment_scores', type='tool_use')], model='claude-3-sonnet-20240229', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=374, output_tokens=112))

Посмотрим на ответ внимательнее. Самое важное в нём — вот эта часть блока ToolUseBlock:

input={‘positive_score’: 0.0, ‘negative_score’: 0.791, ‘neutral_score’: 0.209}

Перед ним идёт текстовый блок «Вот анализ тональности данного текста:». Claude «думает», что вызывает инструмент, который как-то использует эти данные, а мы просто извлечём их и превратим в JSON:

import json
json_sentiment = None
for content in response.content: # ответ — список блоков; ищем нужный tool_use
if content.type == "tool_use" and content.name == "print_sentiment_scores":
json_sentiment = content.input
break
if json_sentiment:
print("Анализ тональности (JSON):")
print(json.dumps(json_sentiment, indent=2))
else:
print("В ответе нет анализа тональности.")
Анализ тональности (JSON):
{
"positive_score": 0.0,
"negative_score": 0.791,
"neutral_score": 0.209
}

Работает! Превратим это в функцию, которая принимает твит или статью и печатает (или возвращает) анализ тональности в JSON.

def analyze_sentiment(content):
query = f"""
<text>
{content}
</text>
Используй только инструмент print_sentiment_scores.
"""
response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-sonnet-20240229"
max_tokens=4096,
tools=tools,
messages=[{"role": "user", "content": query}]
)
json_sentiment = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_sentiment_scores":
json_sentiment = content.input
break
if json_sentiment:
print("Анализ тональности (JSON):")
print(json.dumps(json_sentiment, indent=2))
else:
print("В ответе нет анализа тональности.")
analyze_sentiment("БОЖЕ, я просто обожаю пенные ванны!!!!")
Анализ тональности (JSON):
{
"positive_score": 0.8,
"negative_score": 0.0,
"neutral_score": 0.2
}
analyze_sentiment("Честно говоря, у меня нет мнения о ваннах")
Анализ тональности (JSON):
{
"positive_score": 0.056,
"negative_score": 0.065,
"neutral_score": 0.879
}

Сейчас мы «заставляем» Claude вызвать print_sentiment_scores промптом: пишем «Используй только инструмент print_sentiment_scores». Обычно это работает, но есть способ лучше! Параметр tool_choice позволяет потребовать, чтобы Claude вызвал конкретный инструмент:

tool_choice={"type": "tool", "name": "print_sentiment_scores"}

Этот код говорит Claude, что он обязан ответить вызовом инструмента print_sentiment_scores. Обновим функцию:

def analyze_sentiment(content):
query = f"""
<text>
{content}
</text>
Используй только инструмент print_sentiment_scores.
"""
response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-sonnet-20240229"
max_tokens=4096,
tools=tools,
tool_choice={"type": "tool", "name": "print_sentiment_scores"}, # обязательный вызов этого инструмента
messages=[{"role": "user", "content": query}]
)
json_sentiment = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_sentiment_scores":
json_sentiment = content.input
break
if json_sentiment:
print("Анализ тональности (JSON):")
print(json.dumps(json_sentiment, indent=2))
else:
print("В ответе нет анализа тональности.")

Подробнее tool_choice разберём в одном из следующих уроков (урок 5).

Тем же приёмом заставим Claude выдать аккуратный JSON с сущностями — людьми, организациями и местами, — извлечёнными из текста:

tools = [
{
"name": "print_entities",
"description": "Печатает извлечённые именованные сущности.",
"input_schema": {
"type": "object",
"properties": {
"entities": {
"type": "array", # массив...
"items": { # ...из объектов такого вида
"type": "object",
"properties": {
"name": {"type": "string", "description": "Название извлечённой сущности."},
"type": {"type": "string", "description": "Тип сущности (например, PERSON, ORGANIZATION, LOCATION)."},
"context": {"type": "string", "description": "Контекст, в котором сущность встречается в тексте."}
},
"required": ["name", "type", "context"]
}
}
},
"required": ["entities"]
}
}
]
text = "Джон работает в Google в Нью-Йорке. На прошлой неделе в Сан-Франциско он встретился с Сарой, генеральным директором Acme Inc."
query = f"""
<document>
{text}
</document>
Используй инструмент print_entities.
"""
response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-sonnet-20240229"
max_tokens=4096,
tools=tools,
messages=[{"role": "user", "content": query}]
)
json_entities = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_entities":
json_entities = content.input
break
if json_entities:
print("Извлечённые сущности (JSON):")
print(json.dumps(json_entities, ensure_ascii=False, indent=2)) # ensure_ascii=False — чтобы кириллица печаталась как есть
else:
print("В ответе нет сущностей.")

Вывод (из оригинала, текст переведён):

Извлечённые сущности (JSON):
{
"entities": [
{"name": "Джон", "type": "PERSON", "context": "Джон работает в Google в Нью-Йорке."},
{"name": "Google", "type": "ORGANIZATION", "context": "Джон работает в Google в Нью-Йорке."},
{"name": "Нью-Йорк", "type": "LOCATION", "context": "Джон работает в Google в Нью-Йорке."},
{"name": "Сара", "type": "PERSON", "context": "...он встретился с Сарой, генеральным директором Acme Inc."},
{"name": "Acme Inc.", "type": "ORGANIZATION", "context": "...он встретился с Сарой, генеральным директором Acme Inc."},
{"name": "Сан-Франциско", "type": "LOCATION", "context": "На прошлой неделе в Сан-Франциско он встретился..."}
]
}

Здесь тот же «трюк»: мы говорим Claude, что у него есть инструмент, чтобы получить ответ в нужном формате. Потом извлекаем отформатированные данные — и готово.

Помните, что в этом сценарии полезно явно сказать Claude, что нужно воспользоваться инструментом:

Используй инструмент print_entities.

Пример посложнее: сводка статьи Википедии

Заголовок раздела «Пример посложнее: сводка статьи Википедии»

Возьмём пример сложнее. Пакетом Python wikipedia загрузим целые статьи Википедии и передадим их Claude. Попросим Claude сгенерировать ответ, в котором есть:

  • главная тема статьи;
  • краткое содержание статьи;
  • список ключевых слов и тем, упомянутых в статье;
  • список категорий для статьи (развлечения, политика, бизнес и т. д.) с оценкой классификации — насколько сильно тема относится к категории.

Если передать Claude статью об Уолте Диснее, можно ожидать примерно такой результат:

{
"subject": "Уолт Дисней",
"summary": "Уолтер Элайас Дисней — американский аниматор, кинопродюсер и предприниматель. Он был пионером американской анимационной индустрии и ввёл ряд новшеств в производство мультфильмов. Ему принадлежит рекорд по числу премий «Оскар» и номинаций на неё у одного человека. Он также участвовал в создании Диснейленда и других тематических парков и телепрограмм.",
"keywords": ["Уолт Дисней", "анимация", "кинопродюсер", "предприниматель", "Диснейленд", "тематические парки", "телевидение"],
"categories": [
{"name": "Entertainment", "score": 0.9},
{"name": "Business", "score": 0.7},
{"name": "Technology", "score": 0.6}
]
}

Вот пример функции, которая получает тему статьи Википедии, находит статью, загружает её текст, передаёт Claude и печатает полученный JSON. Стратегия та же: описать инструмент, чтобы «подсказать» форму ответа.

Примечание: если пакета wikipedia нет, поставьте его командой pip install wikipedia!

import wikipedia
# Описание инструмента
tools = [
{
"name": "print_article_classification",
"description": "Печатает результаты классификации.",
"input_schema": {
"type": "object",
"properties": {
"subject": {
"type": "string",
"description": "Общая тема статьи",
},
"summary": {
"type": "string",
"description": "Краткое содержание статьи в один абзац"
},
"keywords": {
"type": "array",
"items": {
"type": "string",
"description": "Список ключевых слов и тем статьи"
}
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Название категории."},
"score": {"type": "number", "description": "Оценка классификации для категории, от 0.0 до 1.0."}
},
"required": ["name", "score"]
}
}
},
"required": ["subject", "summary", "keywords", "categories"]
}
}
]
# Функция, которая генерирует JSON для статьи на заданную тему
def generate_json_for_article(subject):
page = wikipedia.page(subject, auto_suggest=True)
query = f"""
<document>
{page.content}
</document>
Используй инструмент print_article_classification. Примеры категорий: Politics, Sports, Technology, Entertainment, Business.
"""
response = client.messages.create(
model=MODEL_NAME, # в оригинале — "claude-3-haiku-20240307"
max_tokens=4096,
tools=tools,
messages=[{"role": "user", "content": query}]
)
json_classification = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_article_classification":
json_classification = content.input
break
if json_classification:
print("Классификация текста (JSON):")
print(json.dumps(json_classification, ensure_ascii=False, indent=2))
else:
print("В ответе нет классификации текста.")
generate_json_for_article("Jeff Goldblum")

Вывод в оригинале (переведён): тема — «Джефф Голдблюм»; краткое содержание — американский актёр и музыкант, снимался в самых кассовых фильмах вроде «Парка юрского периода» и «Дня независимости», долгая карьера в кино и на телевидении, джазовый музыкант со своим оркестром The Mildred Snitzer Orchestra; ключевые слова — «актёр», «музыкант», «Парк юрского периода», «День независимости», «кино», «телевидение», «джаз»; категории — Entertainment с оценкой 0.9.

generate_json_for_article("Octopus")

Вывод в оригинале: тема — «Осьминог»; краткое содержание — обзор анатомии, физиологии, поведения, экологии и эволюции осьминогов, их сложной нервной системы, маскировки и смены цвета, интеллекта и отношений с людьми; ключевые слова — «осьминог», «головоногие», «моллюски», «морская биология», «поведение животных», «эволюция»; категории — Science (0.9) и Nature (0.8).

generate_json_for_article("Herbert Hoover")

Вывод в оригинале: тема — «Герберт Гувер»; краткое содержание — биография 31-го президента США: ранние годы, карьера горного инженера и гуманитарного деятеля, президентство во время Великой депрессии и жизнь после него; десять ключевых слов, от «Великой депрессии» и «Республиканской партии» до «тарифа Смута — Хоули» и «Нового курса»; категории — Politics (0.9), History (0.8), Business (0.7).

Обратите внимание: модель сама добавила категории Science, Nature и History, которых не было в примере из промпта, — список в промпте лишь подсказка.

С помощью этой стратегии напишите функцию translate, которая принимает слово или фразу и генерирует структурированный JSON: исходная фраза по-английски и её перевод на испанский, французский, японский и арабский.

Пример работы. Если вызвать:

translate("how much does this cost")

ожидаем такой вывод:

{
"english": "how much does this cost",
"spanish": "¿cuánto cuesta esto?",
"french": "combien ça coûte?",
"japanese": "これはいくらですか",
"arabic": "كم تكلفة هذا؟"
}

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

print(json.dumps(translations_from_claude, ensure_ascii=False, indent=2))
Один из возможных ответов (от переводчика)
translate_tool = {
"name": "print_translations",
"description": "Печатает фразу на английском и её переводы на испанский, французский, японский и арабский.",
"input_schema": {
"type": "object",
"properties": {
"english": {"type": "string", "description": "Исходная фраза на английском"},
"spanish": {"type": "string", "description": "Перевод на испанский"},
"french": {"type": "string", "description": "Перевод на французский"},
"japanese": {"type": "string", "description": "Перевод на японский"},
"arabic": {"type": "string", "description": "Перевод на арабский"},
},
"required": ["english", "spanish", "french", "japanese", "arabic"],
},
}
def translate(phrase):
response = client.messages.create(
model=MODEL_NAME,
max_tokens=1000,
tools=[translate_tool],
tool_choice={"type": "tool", "name": "print_translations"}, # Ollama его проигнорирует — выручит промпт
messages=[{"role": "user", "content": f"Переведи фразу: {phrase}. Используй инструмент print_translations."}],
)
for block in response.content:
if block.type == "tool_use":
print(json.dumps(block.input, ensure_ascii=False, indent=2))
return block.input

Во второй части — полный цикл: результат инструмента возвращается модели блоком tool_result, модель пишет окончательный ответ; затем три режима tool_choice и чат-бот интернет-магазина с четырьмя инструментами. Как писать описания инструментов так, чтобы модель реже ошибалась, — в конспекте статьи Anthropic «Writing effective tools for agents».

ENRU
tool use, function callingвызов инструментов, вызов функций
tool definitionописание инструмента
input schema, JSON Schemaсхема входных данных, JSON-схема
tool_use blockблок вызова инструмента
tool_result blockблок результата инструмента
stop reasonпричина остановки
structured outputструктурированный вывод
entity extractionизвлечение (именованных) сущностей
sentiment analysisанализ тональности