Hands-On with AI

Building a Simple Chatbot

From API call to interactive conversation

14 min read · Lesson 17 of 18

Beyond Single Messages

In the previous lesson, you made a single API call and got a single response. But real AI applications are conversational — they maintain context across multiple exchanges. In this lesson, you'll build a working chatbot that remembers what was said earlier in the conversation.


The Key Insight: Conversation History

Here's something that surprises many beginners: the AI model itself doesn't remember previous messages. Every API call is independent. The model doesn't know what you said two messages ago unless you tell it.

So how do chat applications work? They send the entire conversation history with every request. Each time you send a new message, your application includes all previous messages (both yours and the assistant's) in the API call. The model reads the full history and generates a contextually appropriate response.

This means conversation memory is managed by your code, not by the model. You maintain an array of messages and keep appending to it.


A Minimal Chatbot

Here's a complete, working chatbot in Python:

import anthropic

client = anthropic.Anthropic()

system_prompt = (
    "You are a friendly, knowledgeable assistant. "
    "Be concise but helpful. If you don't know something, say so."
)

conversation = []

print("Chatbot ready! Type 'quit' to exit.\n")

while True:
    user_input = input("You: ")

    if user_input.lower() in ("quit", "exit", "q"):
        print("Goodbye!")
        break

    conversation.append({
        "role": "user",
        "content": user_input
    })

    response = client.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=1024,
        system=system_prompt,
        messages=conversation
    )

    assistant_message = response.content[0].text

    conversation.append({
        "role": "assistant",
        "content": assistant_message
    })

    print(f"\nAssistant: {assistant_message}\n")

Save this as chatbot.py and run it with python chatbot.py. You now have a working chatbot that maintains conversation context.


How It Works

Let's trace through a conversation:

  1. User types "My name is Alice." → conversation = [{user: "My name is Alice."}] → API call with 1 message.
  2. Model responds "Nice to meet you, Alice!" → conversation = [{user: "My name is Alice."}, {assistant: "Nice to meet you, Alice!"}]
  3. User types "What's my name?" → conversation = [...all previous..., {user: "What's my name?"}] → API call with 3 messages.
  4. Model can answer "Alice!" because the full history is included.

Each API call sends the growing conversation. The model reads it all and responds appropriately.


Customizing with System Prompts

The system prompt shapes the chatbot's personality. Here are some examples:

A cooking assistant:

system_prompt = (
    "You are a friendly chef who helps with cooking questions. "
    "Suggest recipes based on available ingredients, explain techniques "
    "clearly, and offer substitutions when possible."
)

A Socratic tutor:

system_prompt = (
    "You are a Socratic tutor. Never give direct answers. Instead, "
    "ask guiding questions that help the student discover the answer "
    "themselves. Be encouraging and patient."
)

A code reviewer:

system_prompt = (
    "You are a senior software engineer doing code reviews. "
    "Be constructive but direct. Point out bugs, suggest improvements, "
    "and explain why. Focus on correctness and readability."
)

Handling Long Conversations

As conversations grow, they consume more tokens — and eventually hit the context window limit. In production chatbots, you'd handle this by:

  • Trimming old messages: Remove the earliest messages when the conversation gets too long, keeping the system prompt and recent context.
  • Summarizing: Periodically summarize the conversation and replace the history with the summary.
  • Token counting: Track token usage and manage the context window proactively.

For learning purposes, don't worry about this — modern models have large enough context windows for extended conversations.


Streaming Responses

The chatbot above waits for the complete response before displaying it. For a more natural feel, you can stream the response token by token:

import anthropic

client = anthropic.Anthropic()
conversation = []
system_prompt = "You are a helpful assistant."

print("Chatbot ready! Type 'quit' to exit.\n")

while True:
    user_input = input("You: ")
    if user_input.lower() in ("quit", "exit", "q"):
        break

    conversation.append({"role": "user", "content": user_input})

    print("Assistant: ", end="", flush=True)
    full_response = ""

    with client.messages.stream(
        model="claude-sonnet-4-5-20250929",
        max_tokens=1024,
        system=system_prompt,
        messages=conversation,
    ) as stream:
        for text in stream.text_stream:
            print(text, end="", flush=True)
            full_response += text

    print("\n")
    conversation.append({"role": "assistant", "content": full_response})

Now responses appear word by word, just like the ChatGPT or Claude interface.


What You've Built

This simple chatbot demonstrates the core architecture of every AI chat application — from ChatGPT to customer service bots to coding assistants. They all work the same way: maintain conversation history, send it with each request, append the response. Everything else is UI and features built on top of this foundation.


Key Takeaways

  • The model doesn't remember — your code manages conversation history by sending all messages with each request.
  • A working chatbot is surprisingly simple: a loop, a list of messages, and API calls.
  • System prompts customize the chatbot's personality and behavior.
  • Streaming provides a natural, word-by-word response experience.
  • Every AI chat product is built on this same basic pattern — conversation history management + API calls.
Ask about this lesson