How to Build a Chatbot with Complex Multi-Turn Prompts Using Claude: A Complete Guide

You can build a multi-turn chatbot with Claude by maintaining a single conversation list that includes a system prompt, appending each user message and assistant response, and passing the entire history to the client.messages.create method with every new turn.

The anthropics/prompt-eng-interactive-tutorial repository provides a comprehensive blueprint for assembling complex multi-turn chatbots using Claude's Messages API. By structuring prompts as modular components and managing conversation state through a simple Python list, you can create sophisticated conversational agents that maintain context across dozens of turns.

Setting Up the Anthropic Client

Installation and Initialization

Before building the chatbot loop, you must install the Anthropic SDK and initialize the client with your API credentials. As demonstrated in 09_Complex_Prompts_from_Scratch.ipynb, the setup involves loading environment variables and creating an Anthropic client instance.

!pip install anthropic

import anthropic

# Initialize with your API key and chosen model

API_KEY = "YOUR_API_KEY"
MODEL_NAME = "claude-3-haiku-20240307"

client = anthropic.Anthropic(api_key=API_KEY)

Creating the Conversation Loop

The get_completion Helper Function

The tutorial provides a reusable get_completion function that wraps client.messages.create. This helper accepts a system prompt, a user prompt, and an optional prefill (the assistant's first reply), allowing you to control the model's behavior and output format consistently.

def get_completion(prompt: str, system_prompt: str = "", prefill: str = "") -> str:
    message = client.messages.create(
        model=MODEL_NAME,
        max_tokens=2000,
        temperature=0.0,
        system=system_prompt,
        messages=[
            {"role": "user", "content": prompt},
            {"role": "assistant", "content": prefill},
        ],
    )
    return message.content[0].text

Managing Conversation State

For true multi-turn capabilities, you must maintain a conversation state as a Python list of message dictionaries. Each dictionary contains "role" and "content" keys. Before each API call, append the newest user message; after receiving the response, append Claude's reply to the list. This list becomes the messages argument for client.messages.create.

conversation = [{"role": "system", "content": SYSTEM_PROMPT}]

while True:
    user_msg = input("You: ")
    conversation.append({"role": "user", "content": user_msg})
    
    reply = client.messages.create(
        model=MODEL_NAME,
        max_tokens=1000,
        temperature=0.0,
        messages=conversation
    ).content[0].text
    
    print("Claude:", reply)
    conversation.append({"role": "assistant", "content": reply})

Structuring Complex Multi-Turn Prompts

Modular Prompt Components

Complex chatbots require prompts that combine multiple elements without becoming unwieldy. The tutorial demonstrates breaking prompts into reusable blocks: system description, task instructions, example interactions, and data context. This modular approach allows you to swap task-specific sections while maintaining a consistent system persona.

Combining Elements for Each Turn

The 09_Complex_Prompts_from_Scratch.ipynb notebook includes a "Combine Elements" section that demonstrates concatenating these blocks in a deterministic order before feeding them to Claude. This ensures that every turn receives the full context required for coherent responses while keeping the overall prompt size within the model's token limits.

Advanced Patterns for Complex Chatbots

Chaining Prompts for Retrieval-Augmented Generation

For chatbots that require external knowledge, you can chain multiple Claude calls. As shown in AmazonBedrock/10.1_Appendix_Chaining Prompts.ipynb, a first call performs a retrieval step (searching a knowledge base), and a second call reasons over the retrieved data to generate the final response.


# Step 1 – Retrieve relevant docs (illustrative; actual retrieval logic lives in Appendix 10.4)

def retrieve(query: str) -> str:
    # Placeholder – in a real notebook you would call a vector store or search API

    return "Relevant excerpt about tax elections..."

# Step 2 – Build a prompt that includes retrieved context

def build_chat_prompt(history: str, user_msg: str, retrieved: str) -> str:
    return (
        f"{history}\n"
        f"Context: {retrieved}\n"
        f"User: {user_msg}\n"
        f"Assistant:"
    )

conversation_history = ""
while True:
    user_msg = input("\nYou: ")
    if user_msg.lower() in {"exit", "quit"}:
        break
    ctx = retrieve(user_msg)
    prompt = build_chat_prompt(conversation_history, user_msg, ctx)
    reply = get_completion(prompt, system_prompt="You are a tax‑law chatbot.")
    print("\nClaude:", reply)
    conversation_history = f"{prompt} {reply}"

Integrating Tool Use

The AmazonBedrock/10.2_Appendix_Tool Use.ipynb notebook demonstrates Claude's native tool-use capability, allowing your chatbot to call external functions (such as Python code execution or API lookups) during a conversation. This enables smarter bots that can execute code or retrieve real-time data on the fly.

Key Files in the Tutorial Repository

File What you’ll find Why it matters
Anthropic 1P/09_Complex_Prompts_from_Scratch.ipynb Full walkthrough of building a complex multi‑turn chatbot, including the get_completion helper and the “Example Playground”. Primary source for the chatbot pattern.
Anthropic 1P/hints.py Small utility functions (e.g., token‑count helpers, pretty‑printing) used throughout the notebooks. Helpful when scaling the chatbot to respect token limits.
AmazonBedrock/10.1_Appendix_Chaining Prompts.ipynb Demonstrates how to chain multiple Claude calls (search → reasoning). Extends the basic chatbot to include tool use or retrieval.
AmazonBedrock/10.2_Appendix_Tool Use.ipynb Shows Claude’s native tool‑use capability (e.g., calling a Python function). Enables smarter bots that can execute code or look up data on the fly.
[AmazonBedrock/requirements.txt](https://github.com/anthropics/prompt-eng-interactive-tutorial/blob/master/AmazonBedrock/requirements.txt) Lists required Python packages (anthropic, boto3, etc.). Quick way to set up a development environment for the examples.

Summary

  • Maintain conversation state as a Python list of message dictionaries with "role" and "content" keys, passing the entire list to client.messages.create on every turn.
  • Use the get_completion helper from 09_Complex_Prompts_from_Scratch.ipynb to standardize API calls with system prompts, user prompts, and optional prefills.
  • Structure complex prompts by breaking them into modular blocks (system description, instructions, examples) that you combine deterministically before each API call.
  • Chain prompts for retrieval-augmented generation by making an initial Claude call to fetch context, then a second call to generate the response based on that context.
  • Integrate tool use by leveraging Claude's native function-calling capabilities demonstrated in the Appendix notebooks to execute code or query external APIs during conversations.

Frequently Asked Questions

How do I maintain conversation history in a Claude chatbot?

You maintain conversation history by storing each interaction in a Python list of dictionaries, where each dictionary contains "role" (either "user", "assistant", or "system") and "content" keys. Before every API call to client.messages.create, append the new user message to this list; after receiving Claude's response, append the assistant's reply. Pass the entire list as the messages parameter so Claude sees the full context of the conversation.

What is the purpose of the system prompt in multi-turn conversations?

The system prompt sets the global behavior, persona, and constraints for Claude across all turns of the conversation. By including it as the first message with "role": "system" (or via the system parameter in the API call), you ensure that Claude maintains a consistent tone, follows specific formatting rules, and adheres to safety guidelines throughout the entire chat session, regardless of how many turns the conversation lasts.

How can I add external knowledge retrieval to my Claude chatbot?

You can add external knowledge by chaining prompts, as demonstrated in AmazonBedrock/10.1_Appendix_Chaining Prompts.ipynb. First, make a Claude call (or use a traditional search method) to retrieve relevant documents based on the user's query. Then, construct a second prompt that includes both the conversation history and the retrieved context, and pass this to Claude for the final response. This retrieval-augmented generation pattern ensures your chatbot answers based on up-to-date or proprietary information not contained in the model's training data.

What is the difference between using prefill and appending assistant messages?

Prefill is a technique where you include an initial assistant message in the messages array (with "role": "assistant") when calling the API. This primes Claude to continue from that specific starting point, ensuring it adopts a desired format or tone immediately. In contrast, appending assistant messages refers to adding Claude's actual generated responses to the conversation history after each API call, which builds the ongoing context for subsequent turns. Prefill happens before generation to steer the output; appending happens after generation to preserve history.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →