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

  1. Why Streamlit for the first app
  2. Hello, chatbot: the 30-line MVP
  3. Adding streaming and a persona
  4. Session state and conversation memory
  5. File uploads and a starter "chat with my notes" feature
  6. Deploying to Streamlit Cloud
  7. 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

  1. Why is Streamlit good for first AI apps?
  2. What happens when a user clicks a button?
  3. When should you outgrow it?

Quiz Questions

  1. 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

  1. What does st.session_state do?
  2. What does the walrus operator := do here?
  3. How is memory maintained?

Quiz Questions

  1. The chatbot remembers context because: a) OpenAI stores session b) Streamlit stores it server-side c) We send the full messages list 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.markdown inside 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

  1. How does the placeholder pattern work?
  2. Why is persona in a system message?
  3. What happens if we stream but do not save the final text?

Quiz Questions

  1. 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:

  1. Save chat history to a file (json) keyed by user.
  2. Save to a database (Supabase, SQLite, Firebase).
  3. Use Streamlit's st.experimental_user for 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

  1. What is the scope of st.session_state?
  2. How would you scope memory per user?
  3. What is a rolling window?

Quiz Questions

  1. 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

  1. Why is "paste the whole file" only a small-file solution?
  2. What is the simplest way to inject document context?
  3. When would you switch to RAG?

Quiz Questions

  1. 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:

  1. Push your app to a public GitHub repo.
  2. Add a requirements.txt.
  3. Sign in at share.streamlit.io with GitHub.
  4. Click "Deploy", choose repo, branch, and file.
  5. 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

  1. What goes in requirements.txt?
  2. Where do you put your API key for the deployed app?
  3. Why use a small model in early deploys?

Quiz Questions

  1. The right place to put your API key for a Streamlit Cloud deploy is: a) In a committed .env b) 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:

  1. Model picker: let user choose GPT-4o-mini vs Gemini Flash vs Claude Haiku-class.
  2. Token usage display: show running cost.
  3. Export chat as Markdown.
  4. Voice input (st.audio_input).
  5. Image uploads (multimodal models).
  6. Themed UI: tweak theme in .streamlit/config.toml.
  7. Authentication: gate behind a simple password or Supabase auth.
  8. Streaming + cancellation.
  9. History persistence (Supabase, SQLite).
  10. 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

  1. What is the cost of shipping too many features at once?
  2. Which 2 features would you pick first and why?
  3. How do you decide what to ship next?

Quiz Questions

  1. 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)

Next Module

Module 7: Introduction to RAG