Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🤖 Karlin AI — AI Chatbot with Groq

A conversational AI chatbot built with Streamlit and the Groq API, featuring persistent multi-turn memory, a custom persona, and production-style error handling.

This is Project 4 of a 12-month self-directed AI Engineering roadmap — built to understand why AI-powered apps are structured the way they are, not just to produce working code.


Features

  • 💬 Multi-turn conversation with real memory (the model recalls earlier context in the same session)
  • ⚡ Powered by Groq's llama-3.3-70b-versatile — fast, free-tier inference
  • 🧠 Custom system prompt defining a consistent assistant persona
  • 🛡️ Graceful error handling for auth failures, rate limits, and connection issues — no raw tracebacks shown to the user
  • 🧹 "Clear Chat" button that resets conversation while preserving the assistant's persona
  • 🏗️ Clean separation between UI logic and API logic

Tech Stack

Tool Purpose
Python Core language
Streamlit Web UI, no HTML/CSS/JS required
Groq API (groq SDK) LLM inference
python-dotenv Secure API key management

Architecture

User (browser)
      ↓
Streamlit UI (chat_input, chat_message)
      ↓
st.session_state.messages (persists across reruns)
      ↓
groq_client.py → Groq API (LLM)
      ↓
Response appended to session_state → UI redraws full history

Why this shape matters: Streamlit reruns the entire script on every user interaction. Without st.session_state, conversation history would reset on every message. The LLM API itself is stateless — it has no memory between calls — so the entire message history is resent on every request to simulate memory.


Project Structure

ai-chatbot/0-api-based/
├── app.py              # Streamlit UI — orchestration layer
├── groq_client.py       # Groq API logic — business logic layer
├── .env                 # GROQ_API_KEY (not committed)
├── .gitignore
├── requirements.txt
└── README.md

Setup & Run

  1. Clone the repo and navigate into the project folder:

    git clone <your-repo-url>
    cd ai-chatbot/0-api-based
  2. Create and activate a virtual environment:

    python -m venv venv
    venv\Scripts\activate      # Windows
  3. Install dependencies:

    pip install -r requirements.txt
  4. Create a .env file in the project root:

    GROQ_API_KEY=your_api_key_here
    
  5. Run the app:

    streamlit run app.py

Key Design Decisions

Why is groq_client.py separated from app.py? groq_client.py is the business logic layer — it has no idea whether it's being called from a Streamlit app, a CLI script, or a test suite. app.py is the orchestration layer that decides how to display things. This mirrors the same separation used in earlier projects (e.g., expense_manager.py vs main.py), and means swapping Groq for another provider later only requires touching one file.

Why lazy initialization for the Groq client? Creating the client at import time causes side effects (reading .env, instantiating a client) the moment the module is imported — even if it's never actually used. Recreating the client on every single call is wasteful. Lazy initialization creates the client once, on first use, and reuses it afterward — the same pattern Streamlit itself uses internally via @st.cache_resource.

Why are exceptions caught in app.py, not groq_client.py? groq_client.py doesn't know how it's being consumed, so it shouldn't decide how errors are presented. It lets exceptions propagate. app.py knows it's a Streamlit UI, so it's responsible for catching specific exceptions (AuthenticationError, RateLimitError, APIConnectionError) and showing a user-friendly message instead of a raw traceback.

Why isn't the error fallback message saved into session_state.messages? The entire message list is resent to the LLM on every call. If a fallback error string like "Unable to connect" were saved into that list, it would later be sent back to the model as if it were part of the real conversation — potentially confusing the model or causing it to reference an error that a human never actually said. Error messages are shown to the user but excluded from the API-facing conversation history.


What I Learned

  • How Streamlit's rerun model works, and why st.session_state is non-negotiable for any persistent UI
  • Why LLM APIs are stateless, and how conversation memory is actually simulated (resending full history)
  • The lazy initialization pattern, and where it's the right middle ground between "too eager" and "too wasteful"
  • Layered exception handling: catching specific exception types before generic ones, and deciding where in an app errors should be caught vs. where they should propagate
  • Why UI-facing error messages must be kept separate from the data a model actually sees

Roadmap

Future iterations planned in this project family:

  • 1-with-rag/ — document-grounded chatbot using retrieval-augmented generation
  • 2-with-agents/ — tool-calling and agentic behavior

Part of a 12-month AI Engineering self-development roadmap — Month 2, Project 4.

About

Karlin AI — a fast, streaming chatbot powered by Groq's LLaMA 3.3, built with Streamlit.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages