Loading

Multi-turn conversations with tools

Lesson 2610 min

When building applications with multiple tools, you need to handle scenarios where Claude might need to call several tools in sequence to answer a single user question. For example, if a user asks "What day is 103 days from today?", Claude needs to first get the current date, then add 103 days to it.

Four messages between our server and Claude, with the full message history omitted for clarity, described below.

This creates a multi-turn conversation pattern where Claude makes multiple tool requests before providing a final answer. Your application needs to handle this automatically.

The Multi-Turn Tool Pattern

Here's what happens behind the scenes when Claude needs multiple tools:

  1. User asks: "What day is 103 days from today?"
  2. Claude responds with a tool use block requesting get_current_datetime
  3. Your server calls the function and returns the result
  4. Claude realizes it needs more information and requests add_duration_to_datetime
  5. Your server calls that function and returns the result
  6. Claude now has enough information to provide the final answer

Diagram of the steps above as five messages between your server and Claude, using ToolUse and ToolResult blocks.

Building a Conversation Loop

To handle this pattern, you need a conversation loop that continues until Claude stops requesting tools:

python
def run_conversation(messages):
    while True:
        response = chat(messages)

        add_assistant_message(messages, response)

        # Pseudo code
        if response isn't asking for a tool:
            break

        tool_result_blocks = run_tools(response)
        add_user_message(messages, tool_result_blocks)
        
    return messages

The loop in the code above: if Claude isn't asking for a tool we have the final answer, otherwise run the tool and repeat.

Refactoring Helper Functions

Before implementing the conversation loop, you need to update your helper functions to handle multiple message blocks properly.

Updating Message Handlers

Your add_user_message and add_assistant_message functions currently assume you're always working with plain text. Update them to handle full message objects:

python
from anthropic.types import Message

def add_user_message(messages, message):
    user_message = {
        "role": "user",
        "content": message.content if isinstance(message, Message) else message
    }
    messages.append(user_message)

This allows you to pass in either a string, a list of blocks, or a complete message object.

Updating the Chat Function

Modify your chat function to accept a list of tools and return the full message instead of just text:

python
def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=None):
    params = {
        "model": model,
        "max_tokens": 1000,
        "messages": messages,
        "temperature": temperature,
        "stop_sequences": stop_sequences,
    }
    
    if tools:
        params["tools"] = tools
        
    if system:
        params["system"] = system
        
    message = client.messages.create(**params)
    return message

Extracting Text from Messages

Since you're now returning full message objects, create a helper to extract text when needed:

python
def text_from_message(message):
    return "\n".join(
        [block.text for block in message.content if block.type == "text"]
    )

This function finds all text blocks in a message and joins them together, which is useful when you need to display the final response to users.

Key Improvements

These refactoring steps prepare your code for robust tool handling:

  • Flexible message handling - Your helper functions can now work with different message formats
  • Tool support in chat - The chat function can receive and pass through tool schemas
  • Full message returns - You get complete message objects instead of just text, preserving all blocks
  • Text extraction utility - Easy way to get readable text from complex messages

With these foundations in place, you're ready to implement the conversation loop that handles multiple tool calls automatically, creating a seamless experience where Claude can use as many tools as needed to answer user questions.