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 — инструментами, которые выполняются на вашей стороне). Бот сможет находить информацию о клиенте, получать детали заказа и отменять заказы от имени клиента. Мы опишем нужные инструменты и сымитируем синтетические ответы, чтобы показать возможности бота.
Шаг 1. Настраиваем окружение
Заголовок раздела «Шаг 1. Настраиваем окружение»Сначала установим нужные библиотеки и настроим клиент Claude API.
%pip install anthropicfrom claude_local import client, MODEL_NAME # в оригинале: client = anthropic.Client(); MODEL_NAME = "claude-opus-4-8"Шаг 2. Описываем клиентские инструменты
Заголовок раздела «Шаг 2. Описываем клиентские инструменты»Теперь опишем инструменты, с помощью которых бот будет помогать клиентам. Их три: 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"], }, },]Шаг 3. Имитируем ответы инструментов
Заголовок раздела «Шаг 3. Имитируем ответы инструментов»Настоящих данных о клиентах и заказах у нас нет, поэтому сымитируем ответы инструментов. В реальном приложении эти функции обращались бы к вашей базе клиентов и системе управления заказами.
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"])Шаг 5. Общаемся с ботом
Заголовок раздела «Шаг 5. Общаемся с ботом»Теперь функция для общения с ботом. Мы отправляем сообщение пользователя, обрабатываем все вызовы инструментов, которые делает 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Шаг 6. Тестируем бота
Заголовок раздела «Шаг 6. Тестируем бота»Проверим бота на нескольких запросах.
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_useContent: [Текст: '<thinking>Функция get_customer_info возвращает имя, email и телефон клиента по ID. Для вызова нужен параметр customer_id. Пользователь указал ID «C1», так что всё нужное есть.</thinking>', ToolUse: get_customer_info(customer_id='C1')]
Tool Used: get_customer_infoTool Input:{ "customer_id": "C1"}
Tool Result:{ "name": "John Doe", "email": "john@example.com", "phone": "123-456-7890"}
Response:Stop Reason: end_turnContent: [Текст: '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_useContent: [Текст: '<thinking>Из доступных функций подходит get_order_details: она принимает order_id и возвращает детали заказа, включая статус. Пользователь указал ID заказа «O2» — параметр есть, можно вызывать.</thinking>', ToolUse: get_order_details(order_id='O2')]
Tool Used: get_order_detailsTool Input:{ "order_id": "O2"}
Tool Result:{ "id": "O2", "product": "Gadget B", "quantity": 1, "price": 49.99, "status": "Processing"}
Response:Stop Reason: end_turnContent: [Текст: 'По данным функции 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_useContent: [Текст: '<thinking>Для отмены заказа есть функция cancel_order, ей нужен order_id. Пользователь указал «O1» — можно вызывать.</thinking>', ToolUse: cancel_order(order_id='O1')]
Tool Used: cancel_orderTool Input:{ "order_id": "O1"}
Tool Result:true
Response:Stop Reason: end_turnContent: [Текст: 'Судя по полученному подтверждению, ваш заказ O1 успешно отменён. Если могу помочь чем-то ещё — дайте знать.']
Final Response: Судя по полученному подтверждению, ваш заказ O1 успешно отменён. Если могу помочь чем-то ещё — дайте знать.Вот и всё! Мы сделали чат-бота поддержки клиентов на моделях Claude 3 с клиентскими инструментами. Бот находит информацию о клиенте, получает детали заказа и отменяет заказы по запросам пользователя. Понятные описания и схемы инструментов позволяют Claude правильно понимать и использовать доступные инструменты.
Можете развить пример: подключить настоящую базу клиентов и систему управления заказами и добавить инструменты для более широкого круга задач поддержки.
Разбор от переводчика
Заголовок раздела «Разбор от переводчика»Что здесь хорошо
Заголовок раздела «Что здесь хорошо»- Цикл
while. В отличие от первых уроков курса Tool use, где после одного вызова инструмента код сразу заканчивался, здесь модель может вызывать инструменты сколько угодно раз, пока не ответит текстом (stop_reason == "end_turn"). Это и есть агентный цикл. - Поиск блока
tool_useчерезnext(...), а не «последний блок списка»: перед вызовом модель может написать текст, и такой код это переживёт.
Две ловушки
Заголовок раздела «Две ловушки»- История теряется. На каждом круге цикла список
messagesсобирается заново из трёх сообщений: исходный вопрос, последний ответ модели и результат. Если модели нужно два вызова подряд (например, «найди клиента C1 и покажи его заказ O2»), то при втором запросе она уже не увидит первый вызов и его результат. Правильно — дописывать сообщения в конец списка. - Один инструмент за раз. Модель может попросить несколько вызовов в одном ответе (параллельные вызовы). Код
берёт только первый блок
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» разобрано, как проектировать такие инструменты: проверять права в коде инструмента, а не надеяться на промпт, и возвращать модели понятные сообщения об ошибках.
Словарик
Заголовок раздела «Словарик»| EN | RU |
|---|---|
| client-side tools | клиентские инструменты (выполняются на вашей стороне) |
| synthetic responses | синтетические (сымитированные) ответы |
| order management system | система управления заказами |
| parallel tool calls | параллельные вызовы инструментов |
| agent loop | агентный цикл |