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

Claude Cookbooks: агент поддержки клиентов с клиентскими инструментами

Полный перевод рецепта «Creating a customer service agent with client-side tools» из Claude Cookbooks: три инструмента (данные клиента, детали заказа, отмена заказа), заглушки вместо базы и агентный цикл while stop_reason == tool_use. С разбором ошибки в цикле и исправленной версией.

Короткий рецепт из Claude Cookbooks (лицензия MIT): самая компактная полная реализация агентного цикла с инструментами. Если вы прошли курс Anthropic по tool use, здесь всё знакомо — но собрано в одну функцию с циклом while, который позволяет модели вызывать инструменты несколько раз подряд.

  • Когда. Сразу после урока 8.3.1 «Агентный цикл и ReAct»: это тот же цикл «думай → действуй → наблюдай», только думает и решает модель, а действует ваш код.
  • Дальше. Урок 8.3.2 «Инструменты и MCP» — как описывать инструменты и почему опасно давать агенту отмену заказов без проверки личности. Расширенная версия того же бота (база, системный промпт, теги ответа) — в уроке 6 второй части курса Tool use.
  • Сколько времени. 20–30 минут.

Создаём агента поддержки клиентов с клиентскими инструментами

Заголовок раздела «Создаём агента поддержки клиентов с клиентскими инструментами»

В этом рецепте мы покажем, как сделать чат-бота поддержки клиентов на Claude 3 с клиентскими инструментами (client-side tools — инструментами, которые выполняются на вашей стороне). Бот сможет находить информацию о клиенте, получать детали заказа и отменять заказы от имени клиента. Мы опишем нужные инструменты и сымитируем синтетические ответы, чтобы показать возможности бота.

Сначала установим нужные библиотеки и настроим клиент Claude API.

%pip install anthropic
from claude_local import client, MODEL_NAME # в оригинале: client = anthropic.Client(); MODEL_NAME = "claude-opus-4-8"

Теперь опишем инструменты, с помощью которых бот будет помогать клиентам. Их три: get_customer_info, get_order_details и cancel_order.

tools = [
{
"name": "get_customer_info",
"description": "Получает информацию о клиенте по его ID. Возвращает имя, email и номер телефона клиента.",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "Уникальный идентификатор клиента.",
}
},
"required": ["customer_id"],
},
},
{
"name": "get_order_details",
"description": "Получает детали заказа по его ID. Возвращает ID заказа, название товара, количество, цену и статус заказа.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Уникальный идентификатор заказа.",
}
},
"required": ["order_id"],
},
},
{
"name": "cancel_order",
"description": "Отменяет заказ по указанному ID. Если отмена прошла успешно, возвращает подтверждение.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Уникальный идентификатор заказа, который нужно отменить.",
}
},
"required": ["order_id"],
},
},
]

Настоящих данных о клиентах и заказах у нас нет, поэтому сымитируем ответы инструментов. В реальном приложении эти функции обращались бы к вашей базе клиентов и системе управления заказами.

def get_customer_info(customer_id):
# Имитация данных клиентов
customers = {
"C1": {"name": "John Doe", "email": "john@example.com", "phone": "123-456-7890"},
"C2": {"name": "Jane Smith", "email": "jane@example.com", "phone": "987-654-3210"},
}
return customers.get(customer_id, "Customer not found") # «Клиент не найден»
def get_order_details(order_id):
# Имитация данных заказов
orders = {
"O1": {
"id": "O1",
"product": "Widget A",
"quantity": 2,
"price": 19.99,
"status": "Shipped", # отправлен
},
"O2": {
"id": "O2",
"product": "Gadget B",
"quantity": 1,
"price": 49.99,
"status": "Processing", # в обработке
},
}
return orders.get(order_id, "Order not found") # «Заказ не найден»
def cancel_order(order_id):
# Имитация отмены заказа
if order_id in ["O1", "O2"]:
return True
else:
return False

Шаг 4. Обрабатываем вызовы инструментов и возвращаем результаты

Заголовок раздела «Шаг 4. Обрабатываем вызовы инструментов и возвращаем результаты»

Напишем функцию, которая обрабатывает вызовы инструментов от Claude и возвращает соответствующие результаты.

def process_tool_call(tool_name, tool_input):
if tool_name == "get_customer_info":
return get_customer_info(tool_input["customer_id"])
elif tool_name == "get_order_details":
return get_order_details(tool_input["order_id"])
elif tool_name == "cancel_order":
return cancel_order(tool_input["order_id"])

Теперь функция для общения с ботом. Мы отправляем сообщение пользователя, обрабатываем все вызовы инструментов, которые делает Claude, и возвращаем пользователю окончательный ответ.

import json
def chatbot_interaction(user_message):
print(f"\n{'=' * 50}\nUser Message: {user_message}\n{'=' * 50}")
messages = [{"role": "user", "content": user_message}]
response = client.messages.create(
model=MODEL_NAME, max_tokens=4096, tools=tools, messages=messages
)
print("\nInitial Response:") # первый ответ модели
print(f"Stop Reason: {response.stop_reason}")
print(f"Content: {response.content}")
# Пока модель просит инструмент — выполняем и отвечаем ей результатом
while response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use") # первый блок tool_use
tool_name = tool_use.name
tool_input = tool_use.input
print(f"\nTool Used: {tool_name}")
print("Tool Input:")
print(json.dumps(tool_input, indent=2))
tool_result = process_tool_call(tool_name, tool_input)
print("\nTool Result:")
print(json.dumps(tool_result, indent=2))
messages = [
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": str(tool_result),
}
],
},
]
response = client.messages.create(
model=MODEL_NAME, max_tokens=4096, tools=tools, messages=messages
)
print("\nResponse:")
print(f"Stop Reason: {response.stop_reason}")
print(f"Content: {response.content}")
# Окончательный ответ — первый текстовый блок
final_response = next(
(block.text for block in response.content if hasattr(block, "text")),
None,
)
print(f"\nFinal Response: {final_response}")
return final_response

Проверим бота на нескольких запросах.

chatbot_interaction("Can you tell me the email address for customer C1?")
chatbot_interaction("What is the status of order O2?")
chatbot_interaction("Please cancel order O1 for me.")

(Запросы: «Подскажите email клиента C1?», «Какой статус у заказа O2?», «Отмените, пожалуйста, мой заказ O1». Можно задавать их и по-русски.)

Вывод в оригинале (переведено, классы и id сокращены):

==================================================
User Message: Can you tell me the email address for customer C1?
==================================================
Initial Response:
Stop Reason: tool_use
Content: [Текст: '<thinking>Функция get_customer_info возвращает имя, email и телефон клиента по ID.
Для вызова нужен параметр customer_id. Пользователь указал ID «C1», так что всё нужное есть.</thinking>',
ToolUse: get_customer_info(customer_id='C1')]
Tool Used: get_customer_info
Tool Input:
{
"customer_id": "C1"
}
Tool Result:
{
"name": "John Doe",
"email": "john@example.com",
"phone": "123-456-7890"
}
Response:
Stop Reason: end_turn
Content: [Текст: 'Email клиента C1 (John Doe) — john@example.com.']
Final Response: Email клиента C1 (John Doe) — john@example.com.
==================================================
User Message: What is the status of order O2?
==================================================
Initial Response:
Stop Reason: tool_use
Content: [Текст: '<thinking>Из доступных функций подходит get_order_details: она принимает order_id и
возвращает детали заказа, включая статус. Пользователь указал ID заказа «O2» — параметр есть, можно вызывать.</thinking>',
ToolUse: get_order_details(order_id='O2')]
Tool Used: get_order_details
Tool Input:
{
"order_id": "O2"
}
Tool Result:
{
"id": "O2",
"product": "Gadget B",
"quantity": 1,
"price": 49.99,
"status": "Processing"
}
Response:
Stop Reason: end_turn
Content: [Текст: 'По данным функции get_order_details, статус заказа O2 — «Processing» (в обработке).']
Final Response: По данным функции get_order_details, статус заказа O2 — «Processing» (в обработке).
==================================================
User Message: Please cancel order O1 for me.
==================================================
Initial Response:
Stop Reason: tool_use
Content: [Текст: '<thinking>Для отмены заказа есть функция cancel_order, ей нужен order_id.
Пользователь указал «O1» — можно вызывать.</thinking>',
ToolUse: cancel_order(order_id='O1')]
Tool Used: cancel_order
Tool Input:
{
"order_id": "O1"
}
Tool Result:
true
Response:
Stop Reason: end_turn
Content: [Текст: 'Судя по полученному подтверждению, ваш заказ O1 успешно отменён. Если могу помочь чем-то ещё — дайте знать.']
Final Response: Судя по полученному подтверждению, ваш заказ O1 успешно отменён. Если могу помочь чем-то ещё — дайте знать.

Вот и всё! Мы сделали чат-бота поддержки клиентов на моделях Claude 3 с клиентскими инструментами. Бот находит информацию о клиенте, получает детали заказа и отменяет заказы по запросам пользователя. Понятные описания и схемы инструментов позволяют Claude правильно понимать и использовать доступные инструменты.

Можете развить пример: подключить настоящую базу клиентов и систему управления заказами и добавить инструменты для более широкого круга задач поддержки.

  • Цикл while. В отличие от первых уроков курса Tool use, где после одного вызова инструмента код сразу заканчивался, здесь модель может вызывать инструменты сколько угодно раз, пока не ответит текстом (stop_reason == "end_turn"). Это и есть агентный цикл.
  • Поиск блока tool_use через next(...), а не «последний блок списка»: перед вызовом модель может написать текст, и такой код это переживёт.
  1. История теряется. На каждом круге цикла список messages собирается заново из трёх сообщений: исходный вопрос, последний ответ модели и результат. Если модели нужно два вызова подряд (например, «найди клиента C1 и покажи его заказ O2»), то при втором запросе она уже не увидит первый вызов и его результат. Правильно — дописывать сообщения в конец списка.
  2. Один инструмент за раз. Модель может попросить несколько вызовов в одном ответе (параллельные вызовы). Код берёт только первый блок tool_use, а API требует вернуть tool_result на каждый tool_use — иначе следующий запрос вернёт ошибку.

Исправленный цикл:

def chatbot_interaction_fixed(user_message, max_steps=10):
messages = [{"role": "user", "content": user_message}]
for _ in range(max_steps): # предохранитель от бесконечного цикла
response = client.messages.create(model=MODEL_NAME, max_tokens=4096, tools=tools, messages=messages)
messages.append({"role": "assistant", "content": response.content}) # история накапливается
if response.stop_reason != "tool_use":
return next((b.text for b in response.content if b.type == "text"), None)
results = []
for block in response.content: # результат на КАЖДЫЙ вызов
if block.type == "tool_use":
output = process_tool_call(block.name, block.input)
results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)})
messages.append({"role": "user", "content": results})
return "Превышено число шагов"
print(chatbot_interaction_fixed("Найди email клиента C1 и скажи статус заказа O2"))

Бот отменяет любой заказ по одному слову пользователя: нет проверки, что заказ принадлежит этому клиенту, нет подтверждения перед необратимым действием. В уроке 8.3.2 (раздел о безопасности инструментов) и в статье «Writing effective tools for agents» разобрано, как проектировать такие инструменты: проверять права в коде инструмента, а не надеяться на промпт, и возвращать модели понятные сообщения об ошибках.

ENRU
client-side toolsклиентские инструменты (выполняются на вашей стороне)
synthetic responsesсинтетические (сымитированные) ответы
order management systemсистема управления заказами
parallel tool callsпараллельные вызовы инструментов
agent loopагентный цикл