Build a Chatbot in Python with an AI API Step by Step - Python with AI tutorial chapter 44
Python

Build a Chatbot in Python with an AI API Step by Step

Python with AI Tutorial · Chapter 44 of 48

A chatbot in Python is a loop that reads what the user types, sends the whole conversation so far to an AI API, prints the reply and repeats. The model itself remembers nothing between calls, so your code keeps the history. This chapter builds a terminal chatbot with a system prompt, then moves the same logic into a small Streamlit web page.

How a chatbot actually works

An AI API call is stateless: it sees only the messages you send in that request. To make the model "remember" that the customer asked about gluten-free bread two turns ago, you resend that turn every time. A chatbot is therefore three parts: a system prompt that fixes the role, a growing list of user and assistant messages, and a loop. Everything else is decoration.

The message list

Messages are dictionaries with a role and content. Roles alternate between user and assistant; the system role carries instructions. This snippet needs no API key.

history = [
    {"role": "system", "content": "You are the assistant for Crumb & Co bakery."},
    {"role": "user", "content": "Do you sell gluten-free bread?"},
    {"role": "assistant", "content": "Yes, we bake a gluten-free loaf every morning."},
    {"role": "user", "content": "How much is it?"},
]
for m in history:
    print(f"{m['role']:9} | {m['content']}")
Output:
system    | You are the assistant for Crumb & Co bakery.
user      | Do you sell gluten-free bread?
assistant | Yes, we bake a gluten-free loaf every morning.
user      | How much is it?

One turn with a system prompt

The system prompt should contain the facts the bot may quote, because a model without them will invent prices. The Chat Completions endpoint accepts the list directly.

from openai import OpenAI

SYSTEM = (
    "You are the assistant for Crumb & Co bakery. Prices: sourdough 5.50, "
    "gluten-free loaf 6.50, croissant 2.20. Open 7am to 3pm, closed Monday. "
    "Answer in one or two short sentences. If you do not know, say so."
)
history = [
    {"role": "system", "content": SYSTEM},
    {"role": "user", "content": "Do you sell gluten-free bread?"},
    {"role": "assistant", "content": "Yes, we bake a gluten-free loaf every morning."},
    {"role": "user", "content": "How much is it?"},
]
client = OpenAI()  # reads OPENAI_API_KEY
completion = client.chat.completions.create(model="gpt-5", messages=history)
print(completion.choices[0].message.content)
Output:
The gluten-free loaf is 6.50.

The terminal chatbot

Now wrap it in a loop. This version uses the Anthropic SDK, which takes the system prompt as a separate argument and the turns as messages. Save it as bakery_bot.py and run it with python bakery_bot.py.

import anthropic

SYSTEM = (
    "You are the assistant for Crumb & Co bakery. Prices: sourdough 5.50, "
    "gluten-free loaf 6.50, croissant 2.20. Open 7am to 3pm, closed Monday. "
    "Answer in one or two short sentences. If you do not know, say so."
)
client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY
history = []

print("Bakery bot ready. Type quit to exit.")
while True:
    user_text = input("You: ").strip()
    if user_text.lower() in {"quit", "exit"}:
        print("Bot: Bye!")
        break
    if not user_text:
        continue
    history.append({"role": "user", "content": user_text})
    reply = client.messages.create(
        model="claude-sonnet-5", max_tokens=400, system=SYSTEM, messages=history
    )
    answer = reply.content[0].text
    history.append({"role": "assistant", "content": answer})
    print("Bot:", answer)
Output:
Bakery bot ready. Type quit to exit.
You: Are you open on Monday?
Bot: No, we are closed on Mondays. We open Tuesday to Sunday, 7am to 3pm.
You: And what does a croissant cost?
Bot: A croissant is 2.20.
You: quit
Bot: Bye!

Notice that the second question never mentions the bakery, yet the answer is correct, because the first exchange travelled along in history.

Stop the history from growing forever

Every turn resends the whole list, so a long chat costs more and eventually hits the context limit. Keep the last N turns. This helper is pure Python and testable without a key.

def trim(history: list[dict], max_turns: int = 10) -> list[dict]:
    """Keep only the most recent user/assistant pairs."""
    return history[-2 * max_turns:]

fake = [{"role": "user" if i % 2 == 0 else "assistant", "content": f"msg {i}"} for i in range(30)]
short = trim(fake, max_turns=4)
print(len(fake), "->", len(short), "| first kept:", short[0]["content"])
Output:
30 -> 8 | first kept: msg 22

Call history = trim(history) right before the API call. The system prompt is passed separately, so it is never trimmed away.

Handle a failed call

If the network drops or you hit a rate limit, the user message is already in the list but no answer follows, which breaks the alternating pattern. Remove it on failure and let the user try again.

import anthropic

def ask(client, system, history):
    try:
        reply = client.messages.create(
            model="claude-sonnet-5", max_tokens=400, system=system, messages=history
        )
        return reply.content[0].text
    except anthropic.RateLimitError:
        history.pop()
        return "Too many requests right now, please try again in a moment."
    except anthropic.APIStatusError as e:
        history.pop()
        return f"Service error {e.status_code}, your message was not sent."

# in the loop:  answer = ask(client, SYSTEM, history)
Output:
(no output on its own - drop the function into the loop above)
Warning: the bot will answer confidently about anything you did not tell it. Put every fact it may quote in the system prompt, tell it to say "I do not know" otherwise, and never log customers’ personal details or your API key to a file you share.

Save the transcript

Writing the history to JSON lets you review conversations and resume them later.

import json
from datetime import datetime

history = [
    {"role": "user", "content": "Are you open on Monday?"},
    {"role": "assistant", "content": "No, we are closed on Mondays."},
]
name = f"chat_{datetime.now():%Y%m%d_%H%M}.json"
with open(name, "w", encoding="utf-8") as f:
    json.dump(history, f, indent=2, ensure_ascii=False)
print("saved", name, "with", len(history), "messages")
Output:
saved chat_20260905_1030.json with 2 messages
Try it with AI

Extend the terminal bot with two features and let the assistant explain each change.

Here is my Python terminal chatbot using the anthropic SDK: [paste bakery_bot.py]. Add a /reset command that clears the history, and print the running total of usage.input_tokens plus usage.output_tokens after each reply. Keep the code under 40 lines and explain each change briefly.

Move it to a web UI with Streamlit

Streamlit turns a script into a web page with no HTML. Install it with pip install streamlit, save the code below as app.py and run streamlit run app.py. The browser opens on localhost. Because Streamlit reruns the script on every interaction, the history lives in st.session_state.

import streamlit as st
from openai import OpenAI

SYSTEM = ("You are the assistant for Crumb & Co bakery. Prices: sourdough 5.50, "
          "gluten-free loaf 6.50, croissant 2.20. Answer briefly.")
client = OpenAI()
st.title("Crumb & Co assistant")

if "history" not in st.session_state:
    st.session_state.history = [{"role": "system", "content": SYSTEM}]

for m in st.session_state.history[1:]:
    st.chat_message(m["role"]).write(m["content"])

if prompt := st.chat_input("Ask about bread, prices or opening hours"):
    st.session_state.history.append({"role": "user", "content": prompt})
    st.chat_message("user").write(prompt)
    completion = client.chat.completions.create(
        model="gpt-5", messages=st.session_state.history
    )
    answer = completion.choices[0].message.content
    st.session_state.history.append({"role": "assistant", "content": answer})
    st.chat_message("assistant").write(answer)
Output:
  You can now view your Streamlit app in your browser.
  Local URL: http://localhost:8501

The page shows chat bubbles, an input box at the bottom and the full conversation. Share it on your network with streamlit run app.py --server.address 0.0.0.0, or deploy it to Streamlit Community Cloud with the API key stored as a secret, never in the code.

Try it with AI

Get a system prompt written for your own business before you paste in real facts.

Write a system prompt for a customer-service chatbot for a small accounting firm. It must: only answer from the facts I provide, refuse to give tax advice, offer to book a call for anything complex, and stay under 120 words. Leave placeholders for opening hours, prices and contact email.

Common mistakes

  • Sending only the latest message and wondering why the bot forgets everything.
  • Putting the system prompt inside the Anthropic messages list; it belongs in the system argument.
  • Leaving the user message in the history after a failed call, which produces two user turns in a row.
  • Letting the history grow without limit in a long-running bot.
  • Hard-coding the API key in app.py and deploying it.

Exercise

Add a /summary command to the terminal bot. When the user types it, the bot should send the conversation to the model with the instruction "Summarise this conversation in two sentences" and print the result, without adding the summary to the history.

Show answer
    if user_text == "/summary":
        transcript = "\n".join(f"{m['role']}: {m['content']}" for m in history)
        s = client.messages.create(
            model="claude-sonnet-5", max_tokens=200,
            messages=[{"role": "user", "content": "Summarise this conversation in two sentences:\n" + transcript}],
        )
        print("Summary:", s.content[0].text)
        continue
Output:
You: /summary
Summary: The customer asked about Monday opening hours and was told the bakery is closed that day. They then asked the croissant price, which is 2.20.

Place the block inside the loop after the empty-input check and before the message is appended to history.

Related chapters

FAQ

How does a Python chatbot remember the conversation?

It does not; your code does. Every turn appends the user message and the reply to a list, and the whole list is sent with the next request. Trim it to the last few turns to control cost.

Can I build a chatbot in Python without an API key?

You can run a small open model locally with tools such as Ollama, which exposes an OpenAI-compatible endpoint, so the same code works with a different base URL and no cloud key.

Is Streamlit good enough for a real chatbot?

For internal tools, demos and small teams, yes. For a public product you will eventually want authentication, a database for transcripts and a proper web framework.

Working with spreadsheets too? Ready-made Excel, Google Sheets and Power BI templates are at NextGenTemplates.com.

Chapter 44 of 48 · Python with AI: all 48 chapters

PK
Meet PK, the founder of NeotechNavigators.com! With over 15 years of experience in Data Visualization, Excel Automation, and dashboard creation. PK is a Microsoft Certified Professional who has a passion for all things in Excel. PK loves to explore new and innovative ways to use Excel and is always eager to share his knowledge with others. With an eye for detail and a commitment to excellence, PK has become a go-to expert in the world of Excel. Whether you're looking to create stunning visualizations or streamline your workflow with automation, PK has the skills and expertise to help you succeed. Join the many satisfied clients who have benefited from PK's services and see how he can take your data analysis skills to the next level!
https://neotechnavigators.com