AI-Powered Campus Information Assistant for NIT Durgapur
| Component | Technology |
|---|---|
| Backend API | FastAPI + Uvicorn |
| Database | SQLite3 |
| Vector Storage | ChromaDB (internally using SQLite3) |
| Embeddings | Sentence Transformers β 384-dim vectors |
| Frontend | Streamlit |
| PDF Loading | PyPDF Loader |
| Classification | Keyword β Logistic Regression (ML) β LLM |
| LLM | Open Source Model from HuggingFace: Mistral-7B-Instruct |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CAMPUS COMPANION SYSTEM β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β USER QUERY β FastAPI Backend β 3-Level Classification β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β INTENT CLASSIFIER (core/classifier.py) β β
β β β β
β β Level 1: Keyword Matching (β‘ 0.001s) - 70% queries β β
β β Level 2: ML Classifier (β‘β‘ 0.01s) - 25% queries β β
β β Level 3: LLM (Mistral-7B) (β‘β‘β‘ 1-2s) - 5% queries β β
β ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββΌββββββββββββββββ β
β βΌ βΌ βΌ β
β ββββββββββββββ ββββββββββββββ ββββββββββββββ β
β β DATABASE β β RAG SYSTEM β β AI FALLBACKβ β
β β β β β β β β
β β β’ Canteen β β β’ ChromaDB β β Mistral-7B β β
β β β’ Faculty β β β’ 384-dim β β Generates β β
β β β’ Rooms β β Vectors β β Responses β β
β β β’ Wardens β β β’ Cosine β β β β
β β (SQLite) β β Search β β β β
β βββββββ¬βββββββ βββββββ¬βββββββ βββββββ¬βββββββ β
β β β β β
β βββββββββββββββββΌββββββββββββββββ β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β AI RESPONSE FORMATTER (core/response.py) β β
β β Raw Data β Natural Language (Mistral-7B, Temp: 0.5) β β
β ββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β JSON RESPONSE β β
β β {"answer": "...", "intent": "...", "confidence": 0.85} β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CAMPUS_COMPANION/
βββ api/
β βββ main.py # FastAPI app initialization
β βββ routers/
β βββ chat.py # β Main chat endpoint (600+ lines)
βββ core/
β βββ classifier.py # β 3-level intent classification (800+ lines)
β βββ rag.py # β RAG system with ChromaDB (190+ lines)
β βββ response.py # π€ AI response formatter
β βββ fallback_message.py # π‘οΈ AI fallback handler
β βββ embeddings.py # Document chunking & embeddings
βββ db/
β βββ models.py # β Database schema (10 tables)
β βββ session.py # DB connection
βββ scripts/
β βββ ingest_pdfs.py # PDF β ChromaDB pipeline
β βββ pdf_processor.py # Text extraction (PyPDF2 + Tesseract)
β βββ chunking.py # Text chunking logic
βββ data/
β βββ pdfs/ # Source PDF documents
β βββ rag_docs/ # ChromaDB storage
βββ frontend.py # Streamlit chat UI
βββ app.py # Database initializer
βββ testdb.py # Sample data loader
βββ requirements.txt # Python dependencies
βββ .env # Environment variables
βββ campus_companion.db # SQLite database
Verify the following are installed:
python3 --version
pip --version
git --version1. Clone and Navigate
git clone <your-repo-url>
cd CAMPUS_COMPANION2. Create Virtual Environment
# Create virtual environment
python3 -m venv .venv
# Activate (Linux/Mac)
source .venv/bin/activate
# Activate (Windows)
.venv\Scripts\activate3. Install Dependencies
pip install -r requirements.txt4. Set Up HuggingFace Token
- Visit: https://huggingface.co/settings/tokens
- Create token (Read access)
- Copy token
- Create
.envfile:
echo "HUGGINGFACEHUB_API_TOKEN=hf_paste_your_token_here" > .env5. Initialize Database
python3 app.py
python3 testdb.py6. Set Up PDF Documents
mkdir -p data/pdfs
# Add your PDF documents to data/pdfs/
# Then run:
python3 scripts/ingest_pdfs.py7. Start Backend
uvicorn api.main:app --reload8. Test API (in new terminal)
curl -X POST http://localhost:8000/api/chat \
-H "Content-Type: application/json" \
-d '{"text":"hello"}'9. Start Frontend (in another terminal with .venv activated)
streamlit run frontend.pyπ― Learning Objectives:
- Understand the 3-level intent classification system
- Learn database structure and queries
- Implement AI fallback for graceful error handling
How does the system know what type of question was asked?
Solution: Progressive complexity with fallback
- Handles: 70% of queries
- Method: Simple word detection
- Function:
classify_keyword()inclassifier.py
Examples:
- β
"Roy canteen phone" β Keywords found β
db_contact - β
"Where is AB-301?" β Keywords found β
db_location - β "I need to contact the mess" β No exact keywords
- Handles: 25% of queries
- Method: TF-IDF + Logistic Regression
- Training: Pre-trained on 200+ example queries
- Function:
ml_classify()inclassifier.py
How it works:
- Converts text to numerical features (word importance)
- Trained model predicts intent
- Example: "mess contact" β ML recognizes as contact query
When it works:
- β Variations of known patterns
- β Synonyms and paraphrases
When it fails:
- β Completely novel phrasing
- β Ambiguous questions
Hints:
- Sends query to Mistral-7B with instructions
- "Classify this as: contact/location/rag/small_talk/fallback"
- Returns intent with reasoning
Example:
- "Can you help me reach the person in charge of food services?" β LLM understands context β
db_contact
How to create the DB:
- Create table models in
models.py - Use
session.pyto connect - Populate db by forming
testdb.py
What's in the Database?
- 8-10 tables in SQLite: Faculty, Canteen, Warden, Room, Building, etc.
- Fixed schema (columns known in advance)
- Fast exact matches
Key Functions:
try_get_contact(text, session)- Search people/placestry_get_location(text, session)- Search rooms/buildingsextract_entity_names()- Parse query for names
Concept: Graceful handling of out-of-scope queries
When Fallback Triggers:
- Intent classified as "ai_fallback"
- Database search returns nothing
- RAG search finds no relevant documents
- Confidence too low (< 0.3)
Response Generation:
- Send query + system prompt to Mistral-7B
- Temperature: 0.7 (more creative for deflection)
- AI generates polite refusal + redirection + organized response
Key Function: fallback_ai_response(query) in fallback_message.py
Edit testdb.py and add/populate data of your choice
Functions Involved:
session.add()- Add to databasesession.commit()- Save changestry_get_contact()- Search function
π― Learning Objectives:
- Understand RAG (Retrieval Augmented Generation) concept
- Learn PDF processing pipeline (extraction β chunking β embedding)
- Understand semantic search and cosine similarity
Student asks: "How to calculate CGPA?"
Why Database Won't Work:
- β CGPA calculation is a multi-step explanation (not a single data point)
- β Rules are in PDF documents (Academic Handbook, 50+ pages)
- β Manual data entry = tedious + error-prone
- β Rules change β Need to update database every time
Why not raw AI?
- β LLMs hallucinate
- β No existing knowledge of the campus rules
- β CGPA varies from campus to campus
Document Processing:
PDFs β Extract Text β Split into Chunks β Convert to Vectors β Store in Database
Query Processing:
User Question β Convert to Vector β Find Similar Vectors β Get Text Chunks
Question + Context Chunks β LLM β Natural Answer
Process:
- Open PDF file
- Iterate through each page
- Extract text layer (embedded text data)
- Concatenate all pages
Key Function: extract_text_pypdf2() in pdf_processor.py
Removes Noise:
- Page numbers
- URLs
- Headers/footers
- Extra whitespaces
Key Function: clean_text() in pdf_processor.py
Check the length of answer and whether it is in readable format
Key Function: validate_extracted_text() in pdf_processor.py
Why Chunking?
- Embedding models have token limits for each query
- Semantic search less accurate with more tokens
- Higher API cost with larger inputs
Solution: Split into smaller, semantically meaningful pieces
Chunking Parameters:
chunk_sizechunk_overlapmin_chunk_size
Key Function: chunk_text() in chunking.py
Model Used: all-MiniLM-L6-v2 (Sentence Transformers)
Concept: Convert text into numbers that capture meaning
Example:
Text: "How to calculate CGPA?"
Embedding: [0.234, -0.112, 0.567, ..., 0.891] (384 numbers)
"CGPA calculation" β [0.12, 0.45, -0.23, ...]
"Grade point average" β [0.15, 0.43, -0.20, ...] (CLOSE! β
)
"Pizza recipe" β [0.87, -0.32, 0.61, ...] (FAR! β)
Key Functions:
get_embeddings()inembeddings.pygenerate_embeddings()inembeddings.py
Stores all the embeddings for semantic search
# 1. Add PDF to data folder
cp ~/hostel_rules.pdf data/pdfs/
# 2. Run ingestion
python3 scripts/ingest_pdfs.py
# 3. Check ChromaDB
python3 -c "from core.rag import collection; print(f'{collection.count()} documents')"
# 4. Test query
curl -X POST http://localhost:8000/api/chat \
-d '{"text":"What are hostel visiting hours?"}'π― Learning Objectives:
- Master the unified classification system
- Understand the priority-based keyword matching
- Learn ML-based intent prediction
- Explore result aggregation strategies
When a user asks "Roy canteen phone", how does the system know they want contact information and not location or rules?
Solution: Intent Classification - categorizing user queries into predefined intents
| Intent | Description |
|---|---|
db_contact |
Contact information (phone, email) |
db_location |
Location queries (rooms, buildings) |
rag |
Document-based questions (CGPA rules, policies) |
ai_fallback |
General questions / greetings |
small_talk |
[HW] Conversational queries |
Keyword Matching (Fast) β Machine Learning (Accurate) β LLM (Slow but most Accurate) [HW]
Function: classify_keywords(text: str) -> IntentResult
Purpose: Fast rule-based classification using keyword matching
Priority Order (Matters!):
- β Check for RAG keywords β "CGPA", "rules", "policy"
- β Check for contact keywords β "phone", "email", "canteen"
- β Check for location keywords β "where", "room", "building"
- β
Default β
ai_fallback
Why this order?
- RAG first because academic queries are most specific
- Contact/Location second because they have clear entities
- Fallback last as catch-all
Key Class: MLClassifier
Purpose: Learn patterns from training examples using Machine Learning
Components:
- TF-IDF Vectorizer - Converts text to numerical features
- Logistic Regression - Predicts intent based on learned patterns
Function: UnifiedClassifier.classify()
Purpose: Combine all three classifiers and make final decision
Classification Pipeline:
Step 1: Run keyword classifier (always)
β
Step 2: Run ML classifier (if trained)
β
Step 3: Run LLM classifier (if requested AND confidence < 0.7)
β
Step 4: Aggregate results by taking MAX confidence per intent
β
Step 5: Detect multi-intent queries
β
Step 6: Determine if AI fallback needed
β
Return ClassificationResult
Why MAX (not AVG)?
- If one classifier is very confident, it likely found a strong signal
- Average would dilute strong predictions
- Example: Keyword (0.90) + ML (0.60) β MAX = 0.90 (better than AVG = 0.75)
[HW] Multi-intent Discussion
π― Learning Objectives:
- Understand how raw data is converted to natural language responses
- Learn the role of AI in response formatting
- Understand the frontend-backend connection
Database returns raw data for "Roy canteen phone":
Raw Output:
name: Roy Canteen
phone: +91-8012345678
email: roy@campus.edu
location: Ground Floor
- User Experience: β Boring, mechanical, not conversational
- What Users Expect: β Natural, helpful, human-like response
Output:
π½οΈ Roy Canteen
You can reach Roy Canteen at +91-8012345678 or email them at
roy@campus.edu. They're located on the Ground Floor!
RAW DATA (from DB/RAG)
β
AI FORMATTER (response.py)
β
NATURAL LANGUAGE RESPONSE
β
FRONTEND (frontend.py)
β
USER SEES POLISHED ANSWER
Key Class: ResponseGenerator in response.py
1. __init__() - Initialization
- Purpose: Set up LLM (Mistral-7B) and RAG system
2. refine_query(query: str) -> str
- Purpose: Improve search queries before RAG lookup
3. format_response(query: str, data: str) -> str
- Purpose: Convert raw data to natural language
4. generate_rag_response(query: str) -> Dict
- Purpose: Complete RAG pipeline - search docs + generate answer
_build_context(documents, max_length)
- Combines document chunks into one string
- Stops at 2000 chars (LLM context limit)
- Labels each source: [Source 1], [Source 2], etc.
_generate_llm_answer(query, context)
- Sends context + query to Mistral-7B
- Prompt engineering: "Answer using ONLY context"
- Prevents hallucination (AI making up facts)
_calculate_confidence(documents)
- Average relevance score of top 3 chunks
- Example: (0.92 + 0.87 + 0.81) / 3 = 0.87
_format_sources(documents)
- Extract metadata: filename, relevance score
- Show users where answer came from (transparency)
5. _generate_contact_response(query: str) -> Dict
- Purpose: Format database contact results
6. _generate_location_response(query: str) -> Dict
- Purpose: Format database location results
7. _generate_ai_fallback_response(query: str) -> Dict
- Purpose: Handle out-of-scope queries gracefully
8. generate_response(query: str, intent: str) -> Dict
- Purpose: Main entry point - routes to correct handler
What is Streamlit?
- Streamlit = Python web framework for data apps
Why Streamlit?
- β Write web UI in pure Python (no HTML/CSS/JavaScript)
- β Auto-refreshes on code changes
- β
Built-in chat components (
st.chat_message,st.chat_input) - β Fast prototyping (build UI in 50 lines!)
- Page Configuration:
st.set_page_config - Sidebar:
st.sidebar - Session State: Conversation memory and Chat History [HW]
- Chat Input & API Call:
st.chat_input
# Start backend
uvicorn api.main:app --reload
# Start frontend (on another terminal with .venv activated)
streamlit run frontend.pyQ: Why separate frontend and backend?
- A: Scalability. Backend can serve multiple frontends (web, mobile, API users).
Q: Can we use React instead of Streamlit?
- A: Yes! Backend API is framework-agnostic. Just POST to
/api/chat.
Q: Why not format in chat.py directly?
- A: Separation of concerns.
response.pyis reusable across different endpoints.
Q: How to deploy to production?
- A: Backend β Railway/Render. Frontend β Streamlit Cloud (free tier).
π― Learning Objectives:
- Understand FastAPI application structure
- Learn request/response flow
- Master the chat endpoint orchestration
Purpose: Entry point of the backend
Key Idea:
- Nothing intelligent happens here
- It does not answer questions
- It sets up everything needed so other files can work
1. app = FastAPI(...)
- App is the control center of the backend
- Every endpoint, rule, and config is attached to it
2. CORS Middleware Block
- Frontend and backend usually run on different ports
- Browsers block such requests by default
CORS Configuration:
allow_originsβ Who can access the backendallow_methodsβ What HTTP actions are allowedallow_headersβ What headers are acceptedallow_credentialsβ Whether cookies/auth can pass
3. init_db()
- Database tables exist before any request
- Backend never crashes due to missing tables
- Reads database models
- Creates tables if missing
- Skips if already present
4. app.include_router(...)
- A router is a group of related endpoints
- Example: chat routes live in
chat.py - Connects
/api/chatβ logic inchat.py - Adds structure and modularity
5. Root Endpoint /
- Helpful for debugging, deployment checks, dev sanity checks
6. Health Check /health
- Every production backend has a health endpoint
- It answers only one thing: "Am I alive?"
Purpose: Where user input becomes an intelligent response
Responsibilities:
- Receiving user queries
- Validating input
- Classifying intent
- Fetching data (DB / RAG)
- Using AI when needed
- Returning a structured response
Role:
- Single entry point for all user queries
Handles:
- Simple greetings
- Database lookups
- Document-based questions
- AI fallback responses
Why one endpoint?
- Simplifies frontend
- Centralizes logic
- Easier to debug and extend
Explanation:
- This function is the orchestrator β it doesn't do everything itself, but controls everything
ChatRequest
Purpose:
- Guarantees valid input
- Prevents malformed data
- Makes API predictable
ChatResponse
Purpose:
- Standardizes backend output
- Makes frontend rendering easy
Fields:
answerβ Final messageintentβ What the system understoodconfidenceβ How sure the system isused_fallbackβ Whether AI was usedis_multi_intentβ Multiple meanings detectedall_intentsβ Ranked intent candidates
Key Function: classify_detailed
Purpose:
- The system decides what the user wants, not how to answer yet
Types of intents:
db_contactdb_locationfaculty_inforagsmall_talk[HW][greetings]ai_fallback
Why classification first?
- Avoids unnecessary DB calls
- Prevents wrong answers
Important classification outputs:
primary_intentconfidenceneeds_fallbackis_multi_intentall_intents
Main routing decision: Based on primary_intent
Important Handler Functions:
try_get_contact()- Search for contact informationtry_get_location()- Search for locationstry_get_faculty()- Search for faculty informationtry_get_rag()- Retrieve from RAG systemfallback_ai_response()- Handle unknown queries
Final step before your query is sent, processed through a number of steps and ready to be printed in JSON format β formatting is required to return in user-friendly form
Over the past 6 days, we built Campus Companion, an AI-powered chatbot that helps students find contact information, locations, and academic policies through a beautiful Streamlit interface.
The system uses a 3-layer architecture:
- Frontend (Streamlit for UI)
- Backend (FastAPI for API server)
- Core Intelligence (classification, database handlers, RAG, and AI formatting)
When a user asks "Roy canteen phone", the request flows through:
- Pydantic validation
- 3-level intent classification (keywords/ML/LLM)
- Routing to appropriate handler (
try_get_contactsearches the database with fuzzy matching) - AI formatting (Mistral-7B converts raw data to natural language)
- Structured JSON response displayed in the frontend
Modern Stack:
- FastAPI (REST API)
- SQLAlchemy (database ORM)
- Scikit-learn (ML classification)
- ChromaDB (vector database for RAG)
- HuggingFace (embeddings and LLM)
Production-Grade Principles:
- β Separation of concerns
- β Graceful degradation (fallback mechanisms)
- β Comprehensive error handling
- β Type safety with Pydantic
- β Extensive logging
Our hybrid approach combines:
- Structured database queries for contacts/locations
- RAG (Retrieval-Augmented Generation) for document-based questions like "How to calculate CGPA?"
- Uses semantic search to find relevant PDF chunks
- Generates contextual answers
- Full-stack development (frontend + backend + database)
- AI/ML integration (classification, embeddings, LLMs)
- Software engineering (clean architecture, error handling, API design)
- Real-world application that solves actual campus problems
This same architecture can be adapted for:
- π₯ Hospital assistants
- π’ Corporate helpdesks
- π E-commerce support
- π Any domain requiring intelligent information retrieval
You're now ready to:
- Extend this system (add new intents, multilingual support)
- Improve accuracy (fine-tune classifiers, better RAG strategies)
- Deploy to production (Railway/Render/AWS)
- Add advanced features (voice input, analytics dashboards)
You didn't just learn to code, you learned to think like a software engineer, understanding:
- Why each component exists
- How they communicate
- When to use different approaches
These are skills that companies actively seek in full-stack AI developers.
FastAPI + Streamlit + SQLAlchemy + ChromaDB + HuggingFace + RAG + Clean Architecture
Keep coding, keep learning, keep building! π
For questions or issues, please refer to the implementation guide above or contact the development team.
Made with β€οΈ for NIT Durgapur