Ваш первый простой инструмент
Ваш первый простой инструмент
В предыдущем уроке мы рассмотрели рабочий процесс использования инструментов. Пришло время приступить к реализации простого примера использования инструмента. Напомним, процесс использования инструментов состоит из 4 шагов:
-
Предоставьте Claude инструменты и пользовательский prompt: (API запрос)
- Определите набор инструментов, к которым Claude должен иметь доступ, включая их имена, описания и входные схемы.
- Предоставьте пользовательский prompt, для ответа на который может потребоваться использование одного или нескольких из этих инструментов.
-
Claude использует инструмент: (API ответ)
- Claude оценивает пользовательский prompt и решает, поможет ли какой-либо из доступных инструментов ответить на запрос или выполнить задачу пользователя. Если да, он также решает, какой(ие) инструмент(ы) использовать и с каким(и) входным(и) данным(и).
- Claude выводит правильно отформатированный запрос на использование инструмента.
- API ответ будет содержать
stop_reasonсо значениемtool_use, указывая, что Claude хочет использовать внешний инструмент.
-
Извлеките входные данные инструмента, запустите код и верните результаты: (API запрос)
- На стороне клиента вам следует извлечь имя инструмента и входные данные из запроса на использование инструмента Claude.
- Запустите фактический код инструмента на стороне клиента.
- Верните результаты Claude, продолжив диалог новым пользовательским сообщением, содержащим блок контента
tool_result.
-
Claude использует результат работы инструмента для формулирования ответа: (API ответ)
- Получив результаты работы инструмента, Claude использует эту информацию для формулирования окончательного ответа на исходный пользовательский prompt.
Мы начнем с простой демонстрации, которая требует "общения" с Claude всего один раз (не волнуйтесь, скоро мы перейдем к более интересным примерам!). Это означает, что мы пока не будем заниматься шагом 4. Мы попросим Claude ответить на вопрос, Claude запросит использование инструмента для ответа, а затем мы извлечем входные данные инструмента, запустим код и вернем полученное значение.
Современные большие языковые модели испытывают трудности с математическими операциями, что подтверждается следующим кодом.
Мы просим Claude "Умножить 1984135 на 9343116":
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv()
client = Anthropic()
# Относительно простая математическая задача
response = client.messages.create(
model="claude-3-haiku-20240307",
messages=[{"role": "user", "content":"Multiply 1984135 by 9343116. Only respond with the result"}],
max_tokens=400
)
print(response.content[0].text)
18555375560
Вероятно, при многократном запуске приведенного выше кода мы получим другой ответ, но это один из ответов Claude:
18593367726060
Фактический правильный ответ:
18538003464660
Claude ошибся всего лишь на 55364261400!
Использование инструментов спешит на помощь!
Claude не очень хорошо справляется со сложной математикой, поэтому давайте расширим его возможности, предоставив доступ к инструменту-калькулятору.
Вот простая схема, объясняющая процесс:

Первый шаг — определить фактическую функцию калькулятора и убедиться, что она работает независимо от 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("Cannot divide by zero.")
return operand1 / operand2
else:
raise ValueError(f"Unsupported operation: {operation}")
Обратите внимание, что эта простая функция довольно ограничена в своей полезности, поскольку она может обрабатывать только простые выражения, такие как 234 + 213 или 3 * 9. Цель здесь — пройти процесс работы с инструментами на очень простом учебном примере.
Давайте протестируем нашу функцию и убедимся, что она работает.
calculator("add", 10, 3)
13
calculator("divide", 200, 25)
8.0
Следующий шаг — определить наш инструмент и сообщить о нем Claude. При определении инструмента мы следуем очень специфическому формату. Каждое определение инструмента включает:
name: Имя инструмента. Должно соответствовать регулярному выражению ^[a-zA-Z0-9_-]{1,64}$.description: Подробное текстовое описание того, что делает инструмент, когда его следует использовать и как он себя ведет.input_schema: Объект JSON Schema, определяющий ожидаемые параметры для инструмента.
Не знакомы с JSON Schema? Узнайте больше здесь.
Вот простой пример для гипотетического инструмента:
{
"name": "send_email",
"description": "Sends an email to the specified recipient with the given subject and body.",
"input_schema": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "The email address of the recipient"
},
"subject": {
"type": "string",
"description": "The subject line of the email"
},
"body": {
"type": "string",
"description": "The content of the email message"
}
},
"required": ["to", "subject", "body"]
}
}
Этот инструмент, названный send_email, ожидает следующие входные данные:
* to, который является строкой и обязателен
* subject, который является строкой и обязателен
* body, который является строкой и обязателен
Вот еще одно определение инструмента для инструмента под названием search_product:
{
"name": "search_product",
"description": "Search for a product by name or keyword and return its current price and availability.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The product name or search keyword, e.g. 'iPhone 13 Pro' or 'wireless headphones'"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "home", "toys", "sports"],
"description": "The product category to narrow down the search results"
},
"max_price": {
"type": "number",
"description": "The maximum price of the product, used to filter the search results"
}
},
"required": ["query"]
}
}
Этот инструмент имеет 3 входных параметра:
* Обязательную строку query, представляющую название продукта или ключевое слово для поиска
* Необязательную строку category, которая должна быть одним из предопределенных значений для сужения поиска. Обратите внимание на "enum" в определении.
* Необязательное число max_price для фильтрации результатов ниже определенной ценовой отметки
Определение нашего инструмента-калькулятора
Давайте определим соответствующий инструмент для нашей функции калькулятора, которую мы написали ранее. Мы знаем, что функция калькулятора имеет 3 обязательных аргумента:
* operation - который может быть только "add" (сложить), "subtract" (вычесть), "multiply" (умножить) или "divide" (разделить)
* operand1, который должен быть числом
* operand2, который также должен быть числом
Вот определение инструмента:
calculator_tool = {
"name": "calculator",
"description": "A simple calculator that performs basic arithmetic operations.",
"input_schema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "The arithmetic operation to perform."
},
"operand1": {
"type": "number",
"description": "The first operand."
},
"operand2": {
"type": "number",
"description": "The second operand."
}
},
"required": ["operation", "operand1", "operand2"]
}
}
Упражнение
Давайте попрактикуемся в написании правильно отформатированного определения инструмента, используя следующую функцию в качестве примера:
def inventory_lookup(product_name, max_results):
return "this function doesn't do anything"
#Вам не нужно трогать это или что-либо с этим делать!
Эту гипотетическую функцию inventory_lookup следует вызывать следующим образом:
inventory_lookup("AA batteries", 4)
inventory_lookup("birthday candle", 10)
Ваша задача — написать соответствующее, правильно отформатированное определение инструмента. Предположим, что оба аргумента обязательны в вашем определении инструмента.
Предоставляем Claude наш инструмент
Теперь вернемся к нашей функции калькулятора, которую мы написали ранее. В данный момент Claude ничего не знает об инструменте-калькуляторе! Это просто небольшой словарь Python. При отправке запроса Claude мы можем передать список инструментов, чтобы "сообщить" Claude о них. Давайте попробуем сейчас:
response = client.messages.create(
model="claude-3-haiku-20240307",
messages=[{"role": "user", "content": "Multiply 1984135 by 9343116. Only respond with the result"}],
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))
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.
Кроме того, мы можем проверить 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
print("The Tool Name Claude Wants To Call:", tool_name)
print("The Inputs Claude Wants To Call It With:", tool_inputs)
The Tool Name Claude Wants To Call: calculator
The Inputs Claude Wants To Call It With: {'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 IS", result)
RESULT IS 18538003464660
Мы получили правильный ответ 18538003464660!!! Вместо того чтобы полагаться на Claude в правильности математических вычислений, мы просто задаем ему вопрос и предоставляем доступ к инструменту, который он может использовать при необходимости.
Важное замечание
Если мы спросим Claude о чем-то, что не требует использования инструмента, в данном случае о чем-то, что не имеет отношения к математике или расчетам, мы, вероятно, хотим, чтобы он ответил как обычно. Claude обычно так и делает, но иногда он очень стремится использовать свои инструменты!
Вот пример, когда Claude иногда пытается использовать калькулятор, хотя это не имеет смысла. Давайте посмотрим, что произойдет, если мы спросим Claude: "Какого цвета изумруды?"
response = client.messages.create(
model="claude-3-haiku-20240307",
messages=[{"role": "user", "content":"What color are emeralds?"}],
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 дает нам такой ответ:
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 хочет, чтобы мы вызвали инструмент-калькулятор? Очень простое решение — скорректировать наш prompt или добавить системный prompt, который гласит примерно следующее: У вас есть доступ к инструментам, но используйте их только при необходимости. Если инструмент не требуется, отвечайте как обычно:
response = client.messages.create(
model="claude-3-haiku-20240307",
system="You have access to tools, but only use them when necessary. If a tool is not required, respond as normal",
messages=[{"role": "user", "content":"What color are emeralds?"}],
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 отвечает соответствующим контентом и не пытается "втиснуть" использование инструмента, когда это не имеет смысла. Вот новый ответ, который мы получаем:
'Emeralds are green in color.'
Мы также видим, что 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("Cannot divide by zero.")
return operand1 / operand2
else:
raise ValueError(f"Unsupported operation: {operation}")
calculator_tool = {
"name": "calculator",
"description": "A simple calculator that performs basic arithmetic operations.",
"input_schema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "The arithmetic operation to perform.",
},
"operand1": {"type": "number", "description": "The first operand."},
"operand2": {"type": "number", "description": "The second operand."},
},
"required": ["operation", "operand1", "operand2"],
},
}
def prompt_claude(prompt):
messages = [{"role": "user", "content": prompt}]
response = client.messages.create(
model="claude-3-haiku-20240307",
system="You have access to tools, but only use them when necessary. If a tool is not required, respond as normal",
messages=messages,
max_tokens=500,
tools=[calculator_tool],
)
if response.stop_reason == "tool_use":
tool_use = response.content[-1]
tool_name = tool_use.name
tool_input = tool_use.input
if tool_name == "calculator":
print("Claude wants to use the calculator tool")
operation = tool_input["operation"]
operand1 = tool_input["operand1"]
operand2 = tool_input["operand2"]
try:
result = calculator(operation, operand1, operand2)
print("Calculation result is:", result)
except ValueError as e:
print(f"Error: {str(e)}")
elif response.stop_reason == "end_turn":
print("Claude didn't want to use a tool")
print("Claude responded with:")
print(response.content[0].text)
prompt_claude("I had 23 chickens but 2 flew away. How many are left?")
Claude want to use the calculator tool
Calculation result is: 21
prompt_claude("What is 201 times 2")
Claude want to use the calculator tool
Calculation result is: 402
prompt_claude("Write me a haiku about the ocean")
Claude didn't want to use a tool
Claude responded with:
Here is a haiku about the ocean:
Vast blue expanse shines,
Waves crash upon sandy shores,
Ocean's soothing song.
Упражнение
Ваша задача — помочь создать ассистента для исследований с использованием Claude. Пользователь может ввести тему, которую он хочет исследовать, и получить список ссылок на статьи Википедии, сохраненных в файл Markdown для последующего чтения. Мы могли бы попробовать напрямую попросить Claude сгенерировать список URL-адресов статей, но Claude ненадежен в работе с URL-адресами и может галлюцинировать их. Кроме того, легальные статьи могли быть перемещены на новый URL-адрес после даты отсечения обучения Claude. Вместо этого мы будем использовать инструмент, который подключается к реальному Wikipedia API, чтобы это работало!
Мы предоставим Claude доступ к инструменту, который принимает список возможных заголовков статей Википедии, сгенерированных Claude, но которые могли быть сгаллюцинированы. Мы можем использовать этот инструмент для поиска в Википедии фактических заголовков статей и URL-адресов Википедии, чтобы убедиться, что окончательный список состоит из статей, которые действительно существуют. Затем мы сохраним эти URL-адреса статей в файл Markdown для последующего чтения.
Мы предоставили вам две вспомогательные функции:
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")
Первая функция, generate_wikipedia_reading_list, ожидает получения темы исследования, такой как "История Гавайев" или "Пираты по всему миру", и списка потенциальных названий статей Википедии, которые мы попросим Claude сгенерировать. Функция использует пакет wikipedia для поиска соответствующих РЕАЛЬНЫХ страниц Википедии и создает список словарей, содержащих заголовок и URL-адрес статьи.
Затем она вызывает add_to_research_reading_file, передавая список данных статей Википедии и общую тему исследования. Эта функция просто добавляет ссылки Markdown на каждую статью Википедии в файл output/research_reading.md. Имя файла пока жестко закодировано, и функция предполагает, что он существует. Он существует в этом репозитории, но вам потребуется создать его самостоятельно, если вы работаете в другом месте.
Идея заключается в том, что мы заставим 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 просматривает каждый из этих заголовков статей и собирает реальные заголовки статей и соответствующие URL-адреса для любых статей Википедии, которые действительно существуют. Затем она вызывает 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)
После этих 3 вызовов функций наш выходной файл research_reading.md выглядит следующим образом (проверьте его сами в output/research_reading.md):

Для достижения этой цели вам потребуется выполнить следующее:
- Напишите определение инструмента для функции
generate_wikipedia_reading_list - Реализуйте функцию
get_research_help- Напишите prompt для Claude, сообщающий ему, что вам нужна помощь в сборе информации по конкретной теме и сколько заголовков статей вы хотите, чтобы он сгенерировал
- Сообщите 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