Реализация цикла

Чему научитесь

Видео 16:25 и текст урока

К концу урока вы сможете:

Видео и текст урока.

Смотреть урок на Skilljar Implementing multiple turns · 16:25

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

Как понять, что нужен инструмент

Ключ к тому, хочет ли Claude вызвать инструмент, — поле stop_reason ответа. Когда Claude решает, что ему нужен инструмент, это поле равно "tool_use". Это даёт простой способ понять, нужно ли продолжать цикл:

if response.stop_reason != "tool_use":
    break  # Claude is done, no more tools needed

Цикл разговора

Основная функция разговора устроена просто:

def run_conversation(messages):
    while True:
        response = chat(messages, tools=[get_current_datetime_schema])
        add_assistant_message(messages, response)
        print(text_from_message(response))

        if response.stop_reason != "tool_use":
            break

        tool_results = run_tools(response)
        add_user_message(messages, tool_results)

    return messages

Цикл продолжается, пока Claude не даст итоговый ответ без запроса инструментов.

Несколько вызовов в одном ответе

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

message.content содержит TextBlock и ToolUseBlock для 10 + 10 и 30 + 30; каждый выполняется и возвращается как ToolResultBlock.
message.content: блок текста и два блока tool_use (10 + 10 и 30 + 30); на каждый отвечает свой блок tool_result.

Функция run_tools отбирает блоки вызова инструментов и обрабатывает каждый:

def run_tools(message):
    tool_requests = [
        block for block in message.content if block.type == "tool_use"
    ]
    tool_result_blocks = []

    for tool_request in tool_requests:
        # Process each tool request...

Блоки результатов

На каждый блок вызова инструмента нужно ответить своим блоком результата. Связь между ними держится на совпадающих ID:

Ячейка ноутбука: run_tools вызывает get_current_datetime при совпадении имени инструмента и собирает блок результата.
run_tools вызывает get_current_datetime, если имя совпало, и собирает блок результата.

Структура блока результата:

tool_result_block = {
    "type": "tool_result",
    "tool_use_id": tool_request.id,
    "content": json.dumps(tool_output),
    "is_error": False
}

Обработка ошибок

Надёжное выполнение инструментов требует обработки возможных ошибок. Даже если инструмент упал, Claude всё равно нужно вернуть блок результата:

try:
    tool_output = run_tool(tool_request.name, tool_request.input)
    tool_result_block = {
        "type": "tool_result",
        "tool_use_id": tool_request.id,
        "content": json.dumps(tool_output),
        "is_error": False
    }
except Exception as e:
    tool_result_block = {
        "type": "tool_result",
        "tool_use_id": tool_request.id,
        "content": f"Error: {e}",
        "is_error": True
    }

Маршрутизация инструментов

Чтобы поддерживать несколько инструментов, сделайте функцию-маршрутизатор, которая сопоставляет имена инструментов с их реализациями:

def run_tool(tool_name, tool_input):
    if tool_name == "get_current_datetime":
        return get_current_datetime(**tool_input)
    elif tool_name == "another_tool":
        return another_tool(**tool_input)
    # Add more tools as needed

Так легко добавлять новые инструменты, не трогая основную логику разговора.

Полный цикл

Многоходовый разговор целиком работает так:

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

Дальше

Инфраструктура готова — следующий урок подключает остальные инструменты проекта.

Оригинал урока: Implementing multiple turns.