Module 6: Building Your First AI Chat App
Module Goal
Take everything from Modules 1-5 and ship a real, deployable chat app. By the end, you have a public URL you can share, a GitHub repo, and the engineering pattern you will reuse for every AI product in your career.
Estimated Duration
4 to 5 hours.
Skills Learned
- Building a chat UI with Streamlit in 50 lines
- Session state and memory in a web app
- Streaming responses to a real UI
- Adding a persona, file upload, and reset button
- Deploying free to Streamlit Cloud
Real-world Importance
Every interview, every job application, every "what have you built" conversation: a deployed AI app on your resume changes everything. This module gives you that.
Lessons in this module
- Why Streamlit for the first app
- Hello, chatbot: the 30-line MVP
- Adding streaming and a persona
- Session state and conversation memory
- File uploads and a starter "chat with my notes" feature
- Deploying to Streamlit Cloud
- Stretch features and what to ship next
Lesson 6.1: Why Streamlit for the first app
Hook / Why This Matters
You could spend a week setting up Next.js, Tailwind, and Vercel for your first chat app. Or you could spend 4 hours with Streamlit and have something live tonight. The choice is obvious for beginners.
Beginner Analogy
Streamlit is the IKEA furniture of AI UIs. Not luxurious, but you assemble it in an afternoon and it works.
Concept Explanation
Streamlit is a Python library that turns scripts into web apps. It auto-handles UI components, reactivity, and hosting. Perfect for AI demos, data apps, and your first chatbot.
For a real production product you would graduate to Next.js or FastAPI + React. For learning and portfolio, Streamlit is unbeatable.
Technical Breakdown
Streamlit hello world:
import streamlit as st
st.title("My first AI app")
name = st.text_input("Your name?")
if name:
st.write(f"Hello, {name}!")
Run with: streamlit run app.py. A browser tab opens. That is the entire workflow.
Visual Learning Suggestion
Screenshot of a 5-line Streamlit app rendered next to its code. Side by side, "code -> UI" is the whole story.
Interactive Element
Install Streamlit. Run the hello world. Edit the title. Save. Watch the browser auto-refresh.
Hands-on Lab
Create a new project folder. Set up .venv, install streamlit, write hello world, run it. 10 minutes max.
Mini Exercise
When would you choose Streamlit over Next.js?
Common Mistakes
- Trying to do complex multi-page state in Streamlit (it can do it, but Next.js wins past a point)
- Forgetting that Streamlit re-runs the whole script on every interaction
- Building a production product on it (use Next.js + FastAPI when you outgrow it)
Debugging Tips
If your app behaves strangely after a click, remember Streamlit re-runs top to bottom. State must live in st.session_state.
Knowledge Check Questions
- Why is Streamlit good for first AI apps?
- What happens when a user clicks a button?
- When should you outgrow it?
Quiz Questions
- Streamlit is best described as: a) A frontend framework like React b) A Python library that turns scripts into web apps c) A backend ORM d) A model deployment platform Answer: b
Challenge Task
Build a 3-input "BMI calculator" in Streamlit. 20 lines max.
Real-world Use Cases
- AI demos and internal tools
- Data dashboards
- ML prototypes
- Portfolio projects for jobs
Industry Insight
Many AI engineers ship Streamlit demos for internal stakeholders before any real frontend exists. It is the universal "show, don't tell" tool of AI teams.
Interview Questions
- Why Streamlit for AI demos?
- What is its biggest limitation?
- Compare Streamlit, Gradio, and Next.js for AI apps.
Summary
Streamlit gets you live in hours. Perfect for module 6. Outgrow it later.
Lesson 6.2: Hello, chatbot: the 30-line MVP
Hook / Why This Matters
This is the lesson where you become someone who has built an AI app. After today, you can never go back to "I am learning AI". You are doing AI.
Beginner Analogy
Hanging your first painting on your own wall. The bar is just "exists and works". Polish comes later.
Concept Explanation
The MVP has:
- A title and intro line
- A chat input box
- A chat history display
- A function that calls OpenAI and appends the reply
- Session state to remember history
That is it.
Technical Breakdown
import streamlit as st
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI()
st.title("GeekBot: my first AI chat")
if "messages" not in st.session_state:
st.session_state.messages = []
for msg in st.session_state.messages:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
if prompt := st.chat_input("Ask me anything"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=st.session_state.messages,
)
reply = response.choices[0].message.content
st.session_state.messages.append({"role": "assistant", "content": reply})
with st.chat_message("assistant"):
st.markdown(reply)
That is a working chatbot. About 30 lines.
Visual Learning Suggestion
Annotated screenshot: each Streamlit element labeled with the line that creates it.
Interactive Element
Run it. Have a 5-turn conversation. Notice the memory works because we re-send st.session_state.messages every turn.
Hands-on Lab
Build the chatbot exactly as above. Commit to GitHub. Take a screenshot.
Mini Exercise
What would happen if we forgot the if "messages" not in st.session_state: line?
Common Mistakes
- Forgetting to append the assistant reply (chatbot has no memory)
- Calling the API without sending
st.session_state.messages(no context) - Hardcoding the API key (we have warned you, do not)
Debugging Tips
If the bot acts like every turn is the first, you forgot to append or you forgot to send full history.
Knowledge Check Questions
- What does
st.session_statedo? - What does the walrus operator
:=do here? - How is memory maintained?
Quiz Questions
- The chatbot remembers context because:
a) OpenAI stores session
b) Streamlit stores it server-side
c) We send the full
messageslist each call d) Magic Answer: c
Challenge Task
Add a "Clear chat" button that resets st.session_state.messages.
Real-world Use Cases
- Prototype chat assistants
- Internal AI demos
- Resume and portfolio projects
Industry Insight
This 30-line app is the skeleton of most LLM demos shown at hackathons in 2026. Memorize it.
Interview Questions
- How does memory work in a Streamlit chatbot?
- Walk me through the code line by line.
- How would you persist memory across browser refreshes?
Summary
30 lines. Working chatbot. Welcome to the club.
Lesson 6.3: Adding streaming and a persona
Hook / Why This Matters
Two upgrades that take the app from "okay" to "feels like ChatGPT". Both are 5 minutes of work.
Beginner Analogy
Adding wheels to a chair. Tiny change, totally different experience.
Concept Explanation
Streaming: convert the OpenAI call to stream=True and update the UI as tokens arrive.
Persona: prepend a system message to st.session_state.messages.
Technical Breakdown
Replace the API call section with:
SYSTEM = (
"You are GeekBot, a friendly tech tutor for beginners. "
"Reply in short, clear paragraphs. Use simple analogies."
)
# Ensure system message is first
if not st.session_state.messages or st.session_state.messages[0]["role"] != "system":
st.session_state.messages.insert(0, {"role": "system", "content": SYSTEM})
if prompt := st.chat_input("Ask me anything"):
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()
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=st.session_state.messages,
stream=True,
)
text = ""
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
text += delta
placeholder.markdown(text + "_")
placeholder.markdown(text)
st.session_state.messages.append({"role": "assistant", "content": text})
Done. Live streaming and a custom persona.
Visual Learning Suggestion
A GIF idea: side-by-side recording, before and after streaming. Before: blank for 3 seconds then full text. After: text flows in.
Interactive Element
Edit the system prompt to make GeekBot rude. Re-run. Note how the entire feel of the app changes from one variable.
Hands-on Lab
Add streaming and persona to your app. Push to GitHub.
Mini Exercise
Why is the system message inserted at index 0 and not appended?
Common Mistakes
- Forgetting to update
placeholder.markdowninside the loop (UI does not stream) - Adding multiple system messages over time (clutters context)
- Putting the persona in the user message instead of system
Debugging Tips
If streaming flickers, you may be re-rendering the chat history too aggressively. Streamlit handles this well in chat_message blocks but watch for nested expensive components.
Knowledge Check Questions
- How does the placeholder pattern work?
- Why is persona in a system message?
- What happens if we stream but do not save the final text?
Quiz Questions
- The "_" suffix on
placeholder.markdown(text + "_")is for: a) Error indication b) A typing-cursor effect c) A required suffix d) Cost optimization Answer: b
Challenge Task
Add a sidebar dropdown that lets the user pick from 4 personas. Switch live.
Real-world Use Cases
- Personality-driven chatbots
- Branded support assistants
- Multi-tone copywriting tools
Industry Insight
The two-feature combo of streaming + strong persona is the single biggest "feels like a real product" upgrade in any LLM app.
Interview Questions
- How does streaming work in Streamlit?
- How would you allow the user to switch personas?
- How is the system message handled in your code?
Summary
Streaming for speed, system prompt for soul. Five minutes. Twice the app.
Lesson 6.4: Session state and conversation memory
Hook / Why This Matters
Real apps need memory that survives reloads, multiple users, and budget caps. Streamlit gives you the first tools to learn this properly.
Beginner Analogy
A diary on your nightstand vs a journal app that syncs. Both are memory. One is fragile, one is durable.
Concept Explanation
st.session_state lives in memory per browser tab. Refresh = gone. For real persistence:
- Save chat history to a file (
json) keyed by user. - Save to a database (Supabase, SQLite, Firebase).
- Use Streamlit's
st.experimental_userfor SSO-style auth.
Memory strategies from Module 3.4 (full, window, summary, retrieval) all apply here.
Technical Breakdown
Add a 10-message rolling window:
MAX_HISTORY = 20 # 10 user + 10 assistant
if len(st.session_state.messages) > MAX_HISTORY + 1: # +1 for system
st.session_state.messages = (
[st.session_state.messages[0]] # keep system
+ st.session_state.messages[-MAX_HISTORY:]
)
Add reset and export:
col1, col2 = st.sidebar.columns(2)
if col1.button("Clear"):
st.session_state.messages = []
st.rerun()
if col2.download_button("Export", data=str(st.session_state.messages), file_name="chat.json"):
pass
Visual Learning Suggestion
A diagram with three layers: browser session (tab) -> server memory (Streamlit session) -> persistent storage (file or DB).
Interactive Element
Have a 30-turn conversation. Note when older messages get truncated. Add a banner that warns when truncation happens.
Hands-on Lab
Add rolling window, reset, and export to your app.
Mini Exercise
How would you scope memory per user when multiple people use the same deployed app?
Common Mistakes
- Sharing state across users (Streamlit isolates per session but logged-in apps need explicit user keys)
- Letting memory grow forever and crashing on context limit
- Persisting raw chat including sensitive info without encryption
Debugging Tips
If users see each other's chats, you forgot to scope by user id. Always key memory by an explicit user identifier in any multi-user deployment.
Knowledge Check Questions
- What is the scope of
st.session_state? - How would you scope memory per user?
- What is a rolling window?
Quiz Questions
- To persist chat across refreshes you need: a) Larger session state b) External storage (file or DB) c) Cookies d) A larger context window Answer: b
Challenge Task
Save and load chat history to/from a local JSON file. On refresh, the conversation resumes.
Real-world Use Cases
- Tutor apps with returning students
- Customer support apps with ticket-level memory
- Personal assistants with cross-session memory
Industry Insight
The 2026 winning pattern: store conversation in Supabase or Firebase, key by user_id, and load on session start. This single change moves your app from "demo" to "product".
Interview Questions
- Compare session vs persistent memory.
- How would you isolate memory between users?
- How do you handle a 10,000-turn conversation?
Summary
Session state is the start. Persistent storage is the finish. Scope by user, cap memory size, save what matters.
Lesson 6.5: File uploads and a starter "chat with my notes" feature
Hook / Why This Matters
The feature that turns a chatbot into a personal assistant: "chat with my notes". This lesson is the smallest possible step toward RAG (Module 7 and 9).
Beginner Analogy
The chatbot is a friend who can read whatever you hand them. Today we hand them a single note. In Module 9, we hand them a whole library.
Concept Explanation
For a small file, we can just paste its text into the prompt. This is the simplest possible "ask my document" feature. We will replace it with proper retrieval in Module 9.
Technical Breakdown
Add a sidebar uploader:
uploaded = st.sidebar.file_uploader("Upload a .txt or .md note", type=["txt", "md"])
note_text = uploaded.read().decode("utf-8") if uploaded else ""
# Inject note into system context
if note_text:
system_with_note = SYSTEM + f"\n\nUser's note:\n{note_text[:8000]}"
st.session_state.messages[0] = {"role": "system", "content": system_with_note}
Now every reply is informed by the note. For PDFs, large files, or many files, jump to Module 9 (RAG).
Visual Learning Suggestion
Before/after screenshots: chatbot answering without and with an uploaded note. Show how grounded the second answer is.
Interactive Element
Upload your own resume (as .txt). Ask "What roles am I best suited for?" Observe the answer ground itself in your data.
Hands-on Lab
Add the uploader to your app. Test with a 1-page note. Note what happens when you upload a 100-page document. (Hint: token limit. We will fix it in Module 9.)
Mini Exercise
Why does this approach fail past a certain file size?
Common Mistakes
- Pasting entire huge files into the prompt (context limits, cost)
- Forgetting to truncate (
note_text[:8000]) - Not handling non-text file types yet
Debugging Tips
If the bot ignores the uploaded note, check that you updated the system message and that the note text actually loaded.
Knowledge Check Questions
- Why is "paste the whole file" only a small-file solution?
- What is the simplest way to inject document context?
- When would you switch to RAG?
Quiz Questions
- The simplest "chat with my doc" pattern is: a) Fine-tuning b) RAG c) Paste content into system prompt for small files d) Use a different model Answer: c
Challenge Task
Allow uploading multiple notes and let the user pick which one to "chat with" via a dropdown.
Real-world Use Cases
- Personal note assistants
- Reading helpers
- Simple resume reviewers
Industry Insight
Most "chat with X" features start exactly like this and graduate to RAG when files get large or numerous. Knowing the stepping stones helps you scope projects honestly.
Interview Questions
- Why is "paste everything" not a scalable RAG alternative?
- When does this approach fail?
- How would you upgrade to RAG?
Summary
Single small note via system prompt is the starter. Module 9 turns it into a real PDF chatbot.
Lesson 6.6: Deploying to Streamlit Cloud
Hook / Why This Matters
A project that is not deployed does not count. Recruiters cannot click "I built this on my laptop". Today we make it clickable.
Beginner Analogy
You wrote a song. Now it is time to upload it to Spotify. Same song. Suddenly real.
Concept Explanation
Streamlit Cloud is free, GitHub-integrated, and deploys in 3 clicks. Steps:
- Push your app to a public GitHub repo.
- Add a
requirements.txt. - Sign in at
share.streamlit.iowith GitHub. - Click "Deploy", choose repo, branch, and file.
- Add your API key as a secret in the Streamlit settings (not in code).
You get a public URL like your-app.streamlit.app.
Technical Breakdown
requirements.txt:
streamlit
openai
python-dotenv
In Streamlit Cloud settings, add:
OPENAI_API_KEY = "sk-..."
Load it in your code via st.secrets or os.environ (Streamlit injects secrets as env vars). Make sure your code never tries to read .env in production.
Visual Learning Suggestion
Annotated screenshots of the deploy flow: GitHub commit -> Streamlit dashboard -> click deploy -> live URL.
Interactive Element
Deploy your chatbot. Share the URL in the GeekHub community. Real feedback is the gift.
Hands-on Lab
Deploy. Test from your phone. Have a friend test. Document any bugs.
Mini Exercise
Why must API keys never be in your committed code, even after deployment?
Common Mistakes
- Forgetting
requirements.txt - Hardcoding the key (free GitHub scanning bots will find it)
- Public deploy with no spending cap (rate-limit-friendly model = safer first deploy)
Debugging Tips
If deploy fails, check the build log on Streamlit Cloud. Almost always a missing dependency.
Knowledge Check Questions
- What goes in
requirements.txt? - Where do you put your API key for the deployed app?
- Why use a small model in early deploys?
Quiz Questions
- The right place to put your API key for a Streamlit Cloud deploy is:
a) In a committed
.envb) In the code as a string c) In Streamlit Cloud secrets d) In README Answer: c
Challenge Task
Add a "Star this project on GitHub" link in the sidebar. Hit 5 stars from real humans.
Real-world Use Cases
- Portfolio demos
- Internal team tools
- Public AI utilities
Industry Insight
A deployed Streamlit demo on your resume statistically increases interview callbacks. Recruiters click. Make sure they have something to click.
Interview Questions
- Walk me through deploying a Streamlit app.
- How do you manage secrets in production?
- What is the typical cost of running a small Streamlit demo?
Summary
Push, deploy, share. Your project is now real.
Lesson 6.7: Stretch features and what to ship next
Hook / Why This Matters
You have a working app. Now what makes it stand out? This lesson is the menu of upgrades that turn a demo into a product worth building a brand around.
Beginner Analogy
You have a working car. Stretch features are the radio, the AC, the leather seats. Pick the ones your audience cares about.
Concept Explanation
Top stretch features for a beginner AI chat app:
- Model picker: let user choose GPT-4o-mini vs Gemini Flash vs Claude Haiku-class.
- Token usage display: show running cost.
- Export chat as Markdown.
- Voice input (
st.audio_input). - Image uploads (multimodal models).
- Themed UI: tweak
themein.streamlit/config.toml. - Authentication: gate behind a simple password or Supabase auth.
- Streaming + cancellation.
- History persistence (Supabase, SQLite).
- Per-conversation save and resume.
Pick 2 and ship. Do not chase them all on the first version.
Technical Breakdown
Token counter:
total_tokens = sum(msg.get("tokens", 0) for msg in st.session_state.messages)
st.sidebar.metric("Tokens used", total_tokens)
Where msg["tokens"] is appended from response.usage per call.
Visual Learning Suggestion
A grid of 9 product screenshots showing each upgrade. Visual menu.
Interactive Element
Pick your top 2 upgrades. Plan them in writing before you code.
Hands-on Lab
Ship 2 upgrades within one weekend. Update the README. Push.
Mini Exercise
Why not ship all 10 upgrades at once?
Common Mistakes
- Stacking 10 features before any user has tried 1
- Adding auth when there is no user list yet
- Forgetting to update the README after each ship
Debugging Tips
Use a "Coming soon" toggle in the sidebar for half-finished features. Real users will tell you which to finish first.
Knowledge Check Questions
- What is the cost of shipping too many features at once?
- Which 2 features would you pick first and why?
- How do you decide what to ship next?
Quiz Questions
- The best feature priority signal is: a) Your gut b) What competitors have c) What real users ask for after using v1 d) What sounds cool in interviews Answer: c
Challenge Task
Pick 1 feature and ship it in 2 hours. Post the new demo URL with #ai-llms-beginners.
Real-world Use Cases
- Personal portfolio depth
- Open-source contribution opportunity
- Early-stage product validation
Industry Insight
The best AI engineers build narrow, ship fast, listen, then expand. Not the other way round.
Interview Questions
- How would you prioritize new features?
- Walk me through a feature you shipped solo from idea to deploy.
- What would you build next on this app?
Summary
Pick two upgrades. Ship them this weekend. Resist the urge to do ten.
Module 6 Recap
You built and deployed a streaming AI chat app with persona, memory, and file upload. You have a public URL, a GitHub repo, and the engineering pattern of every modern AI app.
SEO Notes
- Primary keyword: "build AI chat app for beginners"
- Long-tail targets: "Streamlit ChatGPT clone", "deploy chatbot free", "Python streaming chatbot"
- Schema: HowTo for the project end-to-end
- Internal links: Module 5 (API setup), Module 7 (RAG), Module 10 (deploy in depth)