Site

App: Chat Loop — Console Chat

Tutorial A1.0  •  AI / Learn / Applications

A1.0 What This Teaches

The tutorials so far have all made single API calls and exited. A real chat application runs in a loop, reads user input repeatedly, and builds up a conversation history across turns. This tutorial builds that application from scratch. Topics covered:

A1.1 Application Design

The chat loop follows eight steps on every iteration:
  1. Print the You: prompt and wait for input
  2. Read the user's text with input()
  3. If the input is /quit, print a goodbye message and break
  4. Append a {"role": "user", "content": ...} dict to the history list
  5. Call client.messages.create with the full history
  6. Extract the reply text from response.content[0].text
  7. Print the reply
  8. Append a {"role": "assistant", "content": reply} dict to the history list
Steps 4 and 8 are where beginners commonly make mistakes - forgetting either one breaks the conversation context on the next turn.

A1.2 Complete Implementation

# chat_loop.py - interactive console chat with conversation history.
import anthropic

client = anthropic.Anthropic()
messages = []

print("Claude Chat - type /quit to exit")
print("-" * 40)

while True:
    user_input = input("You: ").strip()
    if not user_input:
        continue
    if user_input.lower() == "/quit":
        print("Goodbye.")
        break

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

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=messages
    )

    reply = response.content[0].text
    messages.append({"role": "assistant", "content": reply})

    print(f"Claude: {reply}")
    print()
This is a complete, runnable script. Save it as chat_loop.py and run it. Each call passes the full messages list, so the model always sees the complete conversation.

A1.3 How Conversation History Works

Every call to messages.create includes every prior turn. After 10 exchanges the list has 20 entries; after 20 exchanges it has 40. All of that text is sent to the API on each call and counts against the model's context window. When the history grows very long, the API will eventually return an error because the combined prompt exceeds the context limit. A simple guard warns the user before that happens:
# Add this check inside the loop, after appending the user message.
if len(messages) > 40:
    print("[Warning: conversation is getting long. Consider /clear to reset history.]")
For a more sophisticated app, you could trim the oldest messages automatically. For this tutorial, a warning is sufficient.

A1.4 Adding a System Prompt

A system prompt sets the model's persona and constraints for the entire session. Add it to the create call as the system parameter:
# chat_loop_system.py - specialize the assistant with a system prompt.
import anthropic

client = anthropic.Anthropic()
messages = []

SYSTEM = "You are a Python coding assistant. Keep answers concise and include code examples."

print("Python Coding Assistant - type /quit to exit")
print("-" * 40)

while True:
    user_input = input("You: ").strip()
    if not user_input:
        continue
    if user_input.lower() == "/quit":
        print("Goodbye.")
        break

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

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system=SYSTEM,        # applied to every turn automatically
        messages=messages
    )

    reply = response.content[0].text
    messages.append({"role": "assistant", "content": reply})

    print(f"Claude: {reply}")
    print()
The system string is not part of the messages list. Pass it as a separate keyword argument. It applies to the entire conversation.

A1.5 Adding a /clear Command

Let users reset the conversation without restarting the script. Add one check inside the loop before the API call:
# Inside the while loop, before appending the user message:
if user_input.lower() == "/clear":
    messages = []
    print("[History cleared. Starting fresh.]")
    continue
After /clear, messages is an empty list. The next user turn starts a brand-new conversation with no prior context.

A1.6 Running the Application

python chat_loop.py
A sample session:
Claude Chat - type /quit to exit
----------------------------------------
You: What is a Python list?
Claude: A Python list is an ordered, mutable collection of items. You create one
with square brackets: my_list = [1, 2, 3]. Items can be any type and the list
grows or shrinks as you add or remove elements.

You: How do I add an item?
Claude: Use .append() to add to the end: my_list.append(4). Use .insert(index, item)
to add at a specific position.

You: /quit
Goodbye.
Notice the second question uses "an item" without specifying what kind - the model knows from context it means a list item because it saw the first exchange.

A1.7 Exercise

Exercise Extend chat_loop.py with two new commands:
  • /history - print every message in the messages list. For each entry, print the role and the first 60 characters of the content on one line, for example: user: What is a Python list?
  • /tokens - print an estimated total token count for the current history. Use len(m["content"]) // 4 as a rough estimate per message, and sum across all messages.
Handle both commands before the API call so they do not add anything to the messages list.

A1.8 Common Mistakes

Not appending the assistant reply to messages

# Bad - the loop appends user messages but not assistant replies.
while True:
    user_input = input("You: ").strip()
    messages.append({"role": "user", "content": user_input})
    response = client.messages.create(model="claude-sonnet-4-6",
        max_tokens=1024, messages=messages)
    reply = response.content[0].text
    # Missing: messages.append({"role": "assistant", "content": reply})
    print(f"Claude: {reply}")
Without the assistant append, the next user turn sends two consecutive user messages, which is an API error. Always append both sides of each exchange.

Growing history without bound

Very long sessions accumulate hundreds of messages. Each call sends the full list, so cost and latency grow with session length. The API will eventually reject the call when the total tokens exceed the context window. Add a length check or implement message trimming for long-running sessions.

Calling input() without .strip()

# Bad - "  /quit  " with surrounding spaces will not match "/quit".
user_input = input("You: ")
if user_input == "/quit":   # fails if user types with spaces

# Good - strip whitespace before comparing.
user_input = input("You: ").strip()
if user_input.lower() == "/quit":   # matches regardless of spacing or case
Users often hit space before or after a command. Always call .strip() immediately after input().

A1.9 Key Terms

TermMeaning
chat loopA while True loop that repeatedly reads user input and calls the API
conversation historyThe growing list of user and assistant messages passed on every API call
/quit commandA special input string the loop checks to break out cleanly
context limitThe maximum total tokens (prompt + response) a model can process in one call
system prompt for chatA fixed instruction string passed via the system parameter that shapes the model's behavior across all turns
interactive consoleA terminal application that reads from stdin and writes to stdout in a loop