DEV Community

Oswin Heman-Ackah
Oswin Heman-Ackah

Posted on

Building a Chatbot with Python (Frontend)

Documentation: Frontend (frontend.py)

This file defines the Streamlit-based frontend for the Indaba Retrieval-Augmented Generation (RAG) chatbot.
It provides the user interface, manages queries, retrieves relevant chunks from the FAISS index, and generates answers using the Groq LLM API.
It also applies a custom CSS theme for a modern UI.

Key Responsibilities

  1. Load the FAISS index and pre-processed text chunks.
  2. Take user input (questions) through a chat-like form.
  3. Retrieve the most relevant chunks using semantic search.
  4. Pass retrieved chunks into the Groq-powered LLM to generate answers.
  5. Display responses in a styled Streamlit interface.
  6. Provide a button to clear chat history.

Step-by-Step Breakdown
1. Imports and Setup


import streamlit as st
import pickle
import faiss
from sentence_transformers import SentenceTransformer
from groq import Groq
import os
# from dotenv import load_dotenv

# load_dotenv()
Enter fullscreen mode Exit fullscreen mode

What is happening:

  • streamlit→ UI framework.
  • pickle→ loads pre-saved chunks.
  • faiss → similarity search engine for embeddings.
  • SentenceTransformer → embedding model (all-MiniLM-L6-v2).
  • Groq → client to access LLM API.
  • os → (optional) environment variable access.

Unlike local.env use, API keys are pulled from st.secrets for deployment safety. And that is why the dotenv variable is commented out.

2. Load FAISS Index and Chunks


INDEX_FILE = "faiss_index.bin"
CHUNKS_FILE = "chunks.pkl"
embedder = SentenceTransformer("all-MiniLM-L6-v2", device="cpu")

index = faiss.read_index(INDEX_FILE)
with open(CHUNKS_FILE, "rb") as f:
    chunks = pickle.load(f)

Enter fullscreen mode Exit fullscreen mode
  • Loads FAISS index (vector database).
  • Loads preprocessed document chunks (chunks.pkl).
  • Ensures embeddings match the index by using the same model.
  • Forces CPU for compatibility on Streamlit Cloud.

3. Initialize Groq Client
client = Groq(api_key=st.secrets["grok"]["api_key"])

  • Retrieves API key from Streamlit secrets.
  • Sets up Groq client for LLM queries.

4. Semantic Search Function

def search_index(query, k=10):
    q_vec = embedder.encode([query])
    D, I = index.search(q_vec, k)
    return [chunks[i] for i in I[0]]
Enter fullscreen mode Exit fullscreen mode
  • Encodes the query into a vector.
  • Searches FAISS for the top k most relevant chunks.
  • Returns those chunks for answer generation.

5. LLM Answer Generation

def generate_answer(question, context_chunks):
    context = "\n\n".join(context_chunks)
    prompt = (
        f"Answer the question based on the context provided. "
        "If the question is not related to the context in any way, do NOT attempt to answer. "
        "Instead, strictly reply: 'My knowledge base does not have information about this. Please contact the technical team.'\n\n"
        f"Context: {context}\n\nQuestion: {question}\nAnswer:"
    )
    response = client.chat.completions.create(
        messages=[{"role": "user", "content": prompt}],
        model="llama-3.3-70b-versatile",
    )
    return response.choices[0].message.content.strip()
Enter fullscreen mode Exit fullscreen mode

Builds a RAG prompt with:

  • Retrieved context.
  • Question.

Sends to Groq’s LLaMA-3.3-70B model.
Returns a clean answer.
Enforces that if no context existschatbot says:
“My knowledge base does not have information about this. Please contact the technical team.”

  1. Custom Chat UI (CSS)
st.markdown(
    """
<style>
...
</style>
""",
    unsafe_allow_html=True,
)
Enter fullscreen mode Exit fullscreen mode
  • Dark theme with neon-blue highlights.
  • Styles input box, buttons, and retrieved chunks.
  • Enhances readability and adds a polished UI feel.

7. Streamlit UI: Title & Instructions

st.title("🤖 Indaba")
st.write("Ask questions based on Discrete Mathematics.")
Enter fullscreen mode Exit fullscreen mode
  • Displays app title.
  • Provides short instructions to user.

8. Question Input Form

with st.form(key="chat_form", clear_on_submit=True):
    question = st.text_input("Your question:", key="question_input")
    submit_button = st.form_submit_button("Send")

Enter fullscreen mode Exit fullscreen mode
  • Input form for user questions.
  • Clears text box on submit.
  • Controlled by a Send button.

9. Main Chat Logic

if submit_button and question:
    retrieved = search_index(question)
    answer = generate_answer(question, retrieved)

    st.markdown("### 🤖 Answer")
    st.write(answer)

Enter fullscreen mode Exit fullscreen mode

On submit:

  • Retrieves relevant chunks.
  • Generates answer from Groq LLM.
  • Displays result under “🤖 Answer”.

# Clear Chat Button

if st.button("Clear Chat"):
    st.session_state.messages = []
    st.rerun()

Enter fullscreen mode Exit fullscreen mode
  • Resets chat state.
  • Reruns app for a fresh session.

Workflow (Frontend)

  • User submits a question.
  • Query is embedded and searched in FAISS.
  • Top chunks are retrieved.
  • Chunks + question are passed into LLM.
  • LLM generates an answer based only on retrieved knowledge.
  • Answer is displayed in a styled interface.
  • User can clear chat anytime.

Other Notes

  1. Frontend relies on the backend (index_docs.py) having run beforehand. It doesn’t rebuild the index.
  2. Chat memory is session-based only (clears on refresh).
  3. Environment variable GROQ_API_KEY must be set in when running locally in the:
  4. Local .env file. Otherwise, it is set in streamlit secrets as:
  5. Streamlit Secrets ["grok"]["api_key"] during deployment.

Top comments (0)