Your first AI app should be a chatbot, and you should ship it in a weekend. By the end of this lesson you'll have a deployed Claude-powered chatbot, a public URL, and the first project on your portfolio. In 2026, "I built a chatbot in 50 lines and deployed it" is the cheapest credibility you can buy yourself.
The most common AI engineer interview question in 2026 is "show me a project." The second most common is "walk me through how you'd build a chatbot." This lesson answers both — and gives you a deployable artifact you can put in your résumé this weekend.
Learning Objectives
After this lesson, you will be able to:
Explain why LLMs don't actually remember conversations — and how to fake it with conversation history
Build a complete chatbot with conversation memory in under 50 lines of Python using the Anthropic SDK
Add a system prompt to give your chatbot a custom personality and a domain specialty
Handle the token limit constraint when conversations get long, using sliding window and summary strategies
Add streaming responses so the chatbot feels fast (the single biggest UX upgrade)
Deploy the chatbot to a public URL using Streamlit Cloud or Railway in under 20 minutes
Build this → By the end of this lesson, you'll have a 50-line Python script that is a real, working chatbot with memory, personality, streaming, and a deployed Streamlit web UI on a public URL.
This single insight separates engineers from users. In every interview, when an interviewer asks "how does conversational memory work in your chatbot?" — saying "the LLM remembers" is wrong and immediately downgrades you. Saying "the LLM is stateless; we send the full history each turn and pay the input tokens" is correct and signals you have actually built one.
This code calls input(). Python here can't pause to ask you, so type the answers below before running — one value per line, in the order the program asks for them.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
"""
chatbot.py — a 50-line conversational chatbot with memory.
Run: python chatbot.py
Env: export ANTHROPIC_API_KEY=sk-ant-...
"""
import os
import anthropic
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
# This list is the entire "memory" of the chatbot.
conversation_history: list[dict] = []
SYSTEM_PROMPT = (
"You are a helpful AI tutor specializing in machine learning. "
"Be concise, friendly, and clear. If a question is ambiguous, ask "
"one clarifying question before answering. Use plain language."
)
def chat(user_message: str) -> str:
"""Send a message and get a response, maintaining conversation history."""
conversation_history.append({"role": "user", "content": user_message})
# Send the ENTIRE conversation history every time.
# Trim old messages if we get too long (keep last 20 exchanges = 40 messages).
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=conversation_history[-40:],
)
assistant_message = response.content[0].text
conversation_history.append({"role": "assistant", "content": assistant_message})
return assistant_message
if __name__ == "__main__":
print("Chatbot ready! Type 'quit' to exit, 'reset' to clear history.\n")
while True:
user_input = input("You: ").strip()
if not user_input:
continue
if user_input.lower() == "quit":
print("Goodbye!")
break
if user_input.lower() == "reset":
conversation_history.clear()
print("Conversation reset.\n")
continue
print(f"AI: {chat(user_input)}\n")
That's it. 50 lines. A working chatbot with memory, a custom persona, and a safety rail (sliding-window trimming).
First message ("What is Python?"):
You send: [
{"role": "user", "content": "What is Python?"}
]
AI replies: "Python is a programming language..."
history = [
user: "What is Python?",
assistant: "Python is a programming language..."
]
Second message ("How do I install it?"):
You send: [
{"role": "user", "content": "What is Python?"},
{"role": "assistant", "content": "Python is a programming language..."},
{"role": "user", "content": "How do I install it?"} <-- new message
]
AI replies: "To install Python..."
(The AI knows "it" means Python because the full history is in context.)
The AI sees the entire conversation every turn. You pay input tokens for the entire history every turn. This is the central cost trade-off in production chatbots and the first question senior interviewers will probe.
The system parameter sets the chatbot's persona. Try these.
pythonrunnable cell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Study buddy
SYSTEM_PROMPT = (
"You are a patient study buddy helping with ML. Always check the "
"user's understanding with one question before moving on to the next topic."
)
# Cooking assistant
SYSTEM_PROMPT = (
"You are an expert chef. Give practical cooking advice. Always suggest "
"substitutions for ingredients the user may not have."
)
# Senior code reviewer
SYSTEM_PROMPT = (
"You are a senior software engineer doing code review. Be direct, "
"specific, and constructive. Flag security, performance, and "
"readability issues separately."
)
# Interview coach
SYSTEM_PROMPT = (
"You are an AI interview coach for ML engineering roles. Ask ONE "
"technical question at a time. After the user answers, give specific "
"feedback (what was strong, what was missing) before the next question."
)
The system prompt is the cheapest differentiator. A "ChatGPT clone" with a generic system prompt is forgettable. A "calculus tutor that always uses the Socratic method" is memorable.
A non-streaming chatbot feels broken in 2026. Users expect tokens to appear word-by-word, ChatGPT-style. The fix is one method call away.
pythonrunnable cell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def chat_stream(user_message: str) -> str:
"""Send a message and stream the response token-by-token."""
conversation_history.append({"role": "user", "content": user_message})
full_response = ""
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=conversation_history[-40:],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
full_response += text
print() # newline at end
conversation_history.append({"role": "assistant", "content": full_response})
return full_response
That's the entire change. The user now sees "P-y-t-h-o-n i-s..." appearing live instead of waiting 4 seconds for the full response. Perceived latency drops 60-80%; user retention on free-tier products jumps 20-30%.
#Deploy in 20 Minutes: Streamlit + Streamlit Cloud
For your portfolio, you want a web UI on a public URL, not a CLI script. Streamlit is the fastest path.
pythonrunnable cell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# app.py — Streamlit web chatbot. Save next to chatbot.py.
import streamlit as st
import anthropic
import os
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
st.set_page_config(page_title="ML Tutor", page_icon="🤖")
st.title("ML Tutor — built on Claude")
if "messages" not in st.session_state:
st.session_state.messages = []
# Render history
for msg in st.session_state.messages:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
# Take a new message
if prompt := st.chat_input("Ask anything about ML..."):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
placeholder = st.empty()
full = ""
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=1024,
system="You are a helpful ML tutor. Be concise and clear.",
messages=st.session_state.messages[-40:],
) as stream:
for text in stream.text_stream:
full += text
placeholder.markdown(full + "▌")
placeholder.markdown(full)
st.session_state.messages.append({"role": "assistant", "content": full})
Deploy steps (literal, copy-pasteable):
bash
# 1) Save as app.py and add requirements.txt
echo "streamlit\nanthropic" > requirements.txt
# 2) Push to GitHub
git init && git add . && git commit -m "ML tutor chatbot v1"
gh repo create ml-tutor --public --source=. --push
# 3) Go to https://share.streamlit.io
# Connect GitHub. Pick the repo. Set ANTHROPIC_API_KEY in Secrets.
# Click Deploy.
# 4) Within 60 seconds you have a public URL like:
# https://yourhandle-ml-tutor.streamlit.app
Total time from git init to live URL: 15-20 minutes. The deploy is free on Streamlit Cloud (1 app, unlimited time, 1GB RAM). For higher-traffic, switch to Railway or Modal.
Once your basic chatbot works, these are the upgrades that turn it from "tutorial output" into "interview-worthy portfolio piece."
1. Add tool use (1 weekend). Let the chatbot actually do things — search the web, run Python, look up a database. Claude's tool use API is 30 lines on top of what you already have. Suddenly it's an agent, not a chatbot.
2. Add RAG over your own documents (1 weekend). Plug in 50 PDFs of your favorite topic (cookbook, study notes, company docs). Use BGE-small embeddings + cosine similarity (see the "Build Your First AI App" lesson). Now it's a domain expert.
3. Add cost telemetry (2 hours). Track tokens-in, tokens-out, cost-per-conversation, p95 latency. Display in the UI. This single addition signals production-engineering thinking to any interviewer.
4. Add evals (1 weekend). Pick 30 representative questions, hand-write the expected answers, run them through your chatbot weekly, track accuracy over time. This is what gets you the senior signal in interviews.
5. Ship to a real user (1 week). Find one person who would actually use it — a friend, a sibling, a coworker. Watch them use it. Watch what breaks. Fix that. Repeat. By week 3 you have a "shipped to real users" line on your resume.
Here is a realistic schedule for taking this lesson from a CLI script to something worth showing. It is a plan to follow, not a promise of any particular outcome:
Day 1-2: build the CLI version (this lesson).
Day 3-4: Streamlit and a deploy.
Week 2: add RAG over a document set you are allowed to use. If it belongs to your employer, get explicit sign-off first.
Week 3: demo it to a real audience, such as an internal hackathon or a team meeting.
Week 4: write it up publicly, with anything confidential stripped out.
Budget roughly 400 lines of code including the Streamlit app and the eval harness, and roughly 50 hours spread over eight weeks of nights and weekends. What makes this worth doing is not the traffic the write-up gets; it is that you end up with a working artifact you can open in an interview and explain line by line.
How does an LLM 'remember' the conversation from message #1 when it answers message #10?
Recap
Key Takeaways
1LLMs are stateless — they remember nothing between calls. 'Memory' is the trick of sending the full history each turn.
2Conversation memory is a list of messages sent with every API call. The list grows; you pay tokens for the full list each turn.
3The system prompt controls personality and specialty — your differentiator vs. a thousand generic chatbots.
4Use a sliding window (last 40 messages) and summarize older turns to control cost at scale.
5Streaming is mandatory in 2026 — 5 lines, 60-80% perceived-latency drop.
6Deploy to Streamlit Cloud in 20 minutes. The live URL multiplies the portfolio value 5x.
Build the chatbot today. Deploy it tomorrow. Share the link on LinkedIn the day after. Two weeks from now you have the foundation of a portfolio that gets interviews.