Многошаговые диалоги с инструментами: Когда Claude нужен не один инструмент
Разработка приложений с использованием больших языковых моделей (LLM), таких как Claude, часто включает интеграцию с внешними инструментами. Эти инструменты позволяют Claude выполнять действия в реальном мире, получать актуальную информацию или проводить сложные вычисления, выходящие за рамки его собственных знаний. В простых сценариях Claude может вызвать один инструмент, получить результат и сразу же ответить пользователю.
Однако что происходит, когда для ответа на один вопрос пользователя Claude требуется выполнить несколько последовательных действий, каждое из которых требует вызова отдельного инструмента? Например, если пользователь спрашивает: "Какой день будет через 103 дня от сегодняшней даты?", Claude не может ответить на этот вопрос напрямую. Ему сначала нужно узнать текущую дату, а затем прибавить к ней 103 дня. Это подразумевает два отдельных вызова инструмента: один для получения текущей даты, другой для выполнения арифметической операции с датами.
Такие ситуации создают так называемый многошаговый диалог с инструментами (Multi-Turn Tool Pattern). В этом паттерне Claude делает несколько запросов к инструментам, прежде чем предоставить окончательный ответ. Ваше приложение должно быть спроектировано таким образом, чтобы автоматически обрабатывать эту последовательность взаимодействий, создавая впечатление бесшовного и мгновенного ответа для конечного пользователя.
Как Claude использует несколько инструментов: Сценарий взаимодействия
Чтобы лучше понять, как работает многошаговый диалог, давайте рассмотрим пример с датой:
- Пользователь спрашивает: "Какой день будет через 103 дня от сегодняшней даты?"
- Claude анализирует запрос: Он понимает, что для ответа ему нужна текущая дата.
- Claude отвечает с блоком
tool_use: Он запрашивает вызов инструментаget_current_datetime. - Ваш сервер получает запрос: Он вызывает функцию
get_current_datetime(например, через API операционной системы или внешнего сервиса) и возвращает результат Claude (например, "2023-10-27"). - Claude обрабатывает результат: Теперь у него есть текущая дата. Он понимает, что следующим шагом является добавление 103 дней.
- Claude снова отвечает с блоком
tool_use: Он запрашивает вызов инструментаadd_duration_to_datetime, передавая ему текущую дату и "103 дня". - Ваш сервер получает второй запрос: Он вызывает функцию
add_duration_to_datetimeс предоставленными параметрами и возвращает результат (например, "2024-02-07"). - Claude получает окончательный результат: Теперь у него достаточно информации, чтобы сформулировать окончательный ответ.
- Claude предоставляет окончательный ответ: "Через 103 дня от сегодняшней даты будет 7 февраля 2024 года."
Ключевой момент здесь заключается в том, что ваше приложение выступает в роли оркестратора, управляя потоком сообщений между пользователем, Claude и внешними инструментами. Claude не выполняет инструменты сам; он лишь указывает, какие инструменты ему нужны и с какими параметрами. Ваша задача — выполнить эти вызовы и передать результаты обратно Claude.
Реализация цикла обработки диалога
Для обработки многошаговых диалогов с инструментами вам потребуется реализовать цикл на стороне вашего приложения, который будет продолжаться до тех пор, пока Claude не перестанет запрашивать вызовы инструментов. Этот цикл гарантирует, что все необходимые инструменты будут вызваны, а их результаты будут переданы Claude для получения окончательного ответа.
Концептуально такой цикл может выглядеть следующим образом:
def run_conversation(messages):
while True:
# 1. Отправляем текущую историю сообщений Claude и получаем ответ.
response = chat(messages)
# 2. Добавляем ответ Claude в историю сообщений.
add_assistant_message(messages, response)
# 3. Проверяем, запрашивает ли Claude вызов инструмента.
if not response_contains_tool_use(response):
# Если нет, значит, Claude готов дать окончательный ответ или уже дал его.
break
# 4. Если Claude запросил инструменты, извлекаем их.
tool_calls = extract_tool_calls(response)
# 5. Выполняем запрошенные инструменты на вашем сервере.
tool_results = run_tools(tool_calls)
# 6. Добавляем результаты выполнения инструментов в историю сообщений
# как сообщения от пользователя (имитируя, что пользователь "предоставил" результаты).
add_user_message(messages, tool_results)
# 7. После завершения цикла возвращаем полную историю сообщений.
return messages
Этот цикл позволяет Claude и вашему приложению обмениваться сообщениями до тех пор, пока Claude не соберет всю необходимую информацию для формирования окончательного ответа. Важно, чтобы функция chat возвращала полный объект сообщения, а не только текстовое содержимое, чтобы вы могли анализировать блоки tool_use.
Обновление вспомогательных функций для работы с инструментами
Для эффективной работы с многошаговыми диалогами и инструментами необходимо обновить некоторые вспомогательные функции, которые управляют историей сообщений и взаимодействием с API Claude.
Гибкая обработка сообщений (add_user_message и add_assistant_message)
Ваши функции для добавления сообщений в историю (например, add_user_message) должны стать более гибкими. Ранее они могли просто принимать строку текста. Теперь им нужно уметь обрабатывать различные типы контента, включая:
- Простые текстовые строки.
- Списки блоков контента (например, когда Claude возвращает текст и
tool_useблоки). - Полные объекты
Message, возвращаемые API Claude.
Это позволяет вам передавать результаты выполнения инструментов в виде структурированных блоков, которые Claude может легко интерпретировать. Например, функция add_user_message может быть изменена для проверки типа входных данных:
from anthropic.types import Message, ToolUseBlock, ToolResultBlock
def add_user_message(messages, content):
if isinstance(content, Message):
# Если это полный объект Message, берем его content
user_content = content.content
elif isinstance(content, list):
# Если это список блоков (например, ToolResultBlock), используем его напрямую
user_content = content
else:
# В противном случае, это просто строка текста
user_content = [{"type": "text", "text": str(content)}]
user_message = {
"role": "user",
"content": user_content
}
messages.append(user_message)
Аналогичные изменения потребуются и для add_assistant_message, чтобы она могла корректно добавлять ответы Claude, содержащие блоки tool_use.
Модификация функции chat
Ваша основная функция для взаимодействия с Claude (например, chat) также требует изменений:
- Прием схем инструментов: Она должна принимать список схем инструментов (
tools), которые вы хотите предоставить Claude. Это позволяет Claude знать, какие инструменты доступны и как их вызывать. - Возврат полного объекта
Message: Вместо того чтобы возвращать только текстовое содержимое ответа Claude, функцияchatтеперь должна возвращать полный объектMessage, который содержит все блоки контента, включаяtool_use.
def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=None):
params = {
"model": "claude-3-opus-20240229", # Пример модели
"max_tokens": 1000,
"messages": messages,
"temperature": temperature,
"stop_sequences": stop_sequences,
}
if tools:
params["tools"] = tools # Передаем схемы инструментов
if system:
params["system"] = system
# Вызываем API и возвращаем полный объект Message
message = client.messages.create(**params)
return message
Извлечение текстового контента (text_from_message)
Поскольку функция chat теперь возвращает полные объекты Message, которые могут содержать не только текст, но и другие блоки (например, tool_use), вам понадобится вспомогательная функция для извлечения только текстового содержимого, когда это необходимо (например, для отображения ответа пользователю).
def text_from_message(message):
# Ищем все блоки типа "text" и объединяем их содержимое
return "\n".join(
[block.text for block in message.content if block.type == "text"]
)
Эта функция позволяет легко получить читаемый текст из сложного объекта Message, игнорируя блоки, не предназначенные для прямого отображения пользователю.
Ключевые преимущества и подготовка к сложным сценариям
Эти шаги по рефакторингу и обновлению кода подготавливают ваше приложение к надежной и гибкой работе с инструментами:
- Гибкая обработка сообщений: Ваши вспомогательные функции теперь могут работать с различными форматами сообщений, что критически важно для передачи результатов инструментов.
- Поддержка инструментов в функции
chat: Функцияchatможет принимать и передавать схемы инструментов, позволяя Claude знать о доступных возможностях. - Возврат полных объектов
Message: Вы получаете полные объекты сообщений вместо простого текста, сохраняя весь контекст и все блоки, включая запросы на использование инструментов. - Утилита для извлечения текста: У вас есть простой способ получить читаемый текст из сложных сообщений для отображения пользователю.
С этими основами вы готовы реализовать полноценный цикл обработки диалога, который автоматически обрабатывает множественные вызовы инструментов. Это создает бесшовный опыт, при котором Claude может использовать столько инструментов, сколько необходимо, чтобы ответить на вопросы пользователя, а ваше приложение эффективно управляет этим сложным взаимодействием за кулисами.