Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions endee-rag-ai-project/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Python
__pycache__/
*.py[cod]
*$py.class

# Environment
.env
.venv
env/
venv/
ENV/

# Project specifics
chroma_data/
*.log
*.pdf
Vector_store.json

# IDE
.vscode/
151 changes: 151 additions & 0 deletions endee-rag-ai-project/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# 🤖 AI Workspace (RAG & Recommendations)

An AI-powered document assistant and dynamic e-commerce recommendation engine.
This project demonstrates advanced **Retrieval-Augmented Generation (RAG)**, **Semantic Search**, and **Agentic Routing Workflows** paired with a beautiful glassmorphism User Interface.

*Note: This project initially targeted the Endee vector database, but was adapted to use ChromaDB to support native execution on Windows without Docker environments while maintaining identical semantic search capabilities.*

---

## 🚀 Features

- 📄 **Upload PDF Documents**: Ingest documents on the fly into the vector knowledge base.
- 🔍 **Semantic Search**: Powered by `sentence-transformers`.
- 🧠 **Agentic AI Workflow**: A smart Gemini-powered agent that dynamically routes user intents to either RAG retrieval, Recommendation generation, or general conversation.
- 🛒 **Recommendation Engine**: Provides contextual item recommendations based on vector similarity.
- ⚡ **FastAPI Backend**: High performance API serving both the LLM logic and the static asset UI.
- 💬 **Interactive Premium UI**: A beautiful Vanilla HTML/CSS/JS frontend featuring dark-mode, glassmorphism, and markdown support.

---

## 🛠 Tech Stack

- **Python** (Backend Core)
- **FastAPI** & **Uvicorn** (Web framework and application server)
- **Google GenAI** (Gemini 2.5 Flash for LLM reasoning and routing)
- **ChromaDB** (Persistent native vector database)
- **Sentence Transformers** (`all-MiniLM-L6-v2` for embeddings)
- **PyPDF2** (Document parsing)
- **HTML5, CSS3, JS** (Frontend Interface)

---

## 🏗 System Architecture

```text
User Input (Chat / File Upload)
UI Frontend (Vanilla JS + CSS)
FastAPI Backend
├──> PDF -> PyPDF2 -> SentenceTransformer -> ChromaDB (Knowledge Base)
Agentic Router (Gemini)
├───> Intent: RAG -> Query ChromaDB (Knowledge DB) -> Gemini -> User
├───> Intent: RECOMMEND -> Query ChromaDB (Products DB) -> Gemini -> User
└───> Intent: CHAT -> Gemini -> User
```

---

## 📸 Project Screenshots & Demo

### AI Workspace Complete UI
![UI Application](images/ai-workspace-ui.png)

### RAG & Recommendations In-Action
![Demo Recording](images/demo-final.webp)

### RAG Document Upload feature
![RAG File Upload](images/rag-success.png)

---

## 📂 Project Structure

```text
endee-rag-ai-project
├── api/
│ ├── main.py # FastAPI application and endpoint routing
│ ├── agent.py # Gemini Agentic routing and response generation
│ └── db_client.py # ChromaDB integration and embedding logic
├── ui/
│ ├── index.html # Main application interface
│ ├── style.css # Premium Glassmorphism styling
│ └── app.js # Client-side API interactions and chat logic
├── chroma_data/ # Persistent vector database storage
├── requirements.txt # Python dependencies
└── README.md # Project documentation
```

---

## ⚙ Installation

### 1. Clone the repository
```bash
git clone https://github.com/ajaykumarhn/endee.git
cd endee/endee-rag-ai-project
```

### 2. Install dependencies
Ensure you have Python 3.9+ installed.
```bash
pip install fastapi uvicorn sentence-transformers chromadb google-genai pypdf python-multipart
```

### 3. Configure API Keys
You need a Google Gemini API key to run the generative AI routing. Get one from [Google AI Studio](https://aistudio.google.com/).

Set the environment variable in your terminal:

**Windows (PowerShell):**
```powershell
$env:GEMINI_API_KEY="your_api_key_here"
```

**Linux/macOS:**
```bash
export GEMINI_API_KEY="your_api_key_here"
```

---

## ▶ Running the Project

Start the unified API server and UI:

```bash
python -m uvicorn api.main:app --host 0.0.0.0 --port 8000
```

Open your browser and navigate to:
**http://localhost:8000**

---

## 💡 Usage Guide

Once the application is running in your browser:

1. **Test the Recommendation Engine**:
- Click the **"Seed Catalog"** button in the sidebar to populate the database with sample products.
- Ask the AI: *"I need a new monitor for coding."* The agent will query the database and generate a curated response.

2. **Test RAG (Document Q&A)**:
- Use the **"Upload a PDF to context"** section to upload a PDF file. (You can download and use the provided [sample_doc.pdf](sample_doc.pdf) for testing).
- Wait for the "Success" confirmation.
- Ask the AI a question about the contents of the document you uploaded, for example: *"What is the secret access code to the server room?"*

3. **General Chat**:
- Ask the AI any general question like *"What is retrieval augmented generation?"* or *"Hello there!"*

---

## 👨‍💻 Author

Ajay Kumar
GitHub: [https://github.com/ajaykumarhn](https://github.com/ajaykumarhn)
Binary file added endee-rag-ai-project/__pycache__/app.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions endee-rag-ai-project/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Exposes api as a proper python module
87 changes: 87 additions & 0 deletions endee-rag-ai-project/api/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import os
from google import genai
from .db_client import query_collection

# Configure API Key (we'll set this via env var or dynamically)
client = genai.Client()

def route_and_execute(user_message: str) -> str:
"""
A simple ReAct/Router agent.
Decides whether the user is asking for:
1. A search/question about knowledge base (RAG)
2. A recommendation
3. General chat
"""

routing_prompt = f"""
You are an intelligent router. Analyze the user's message and determine the intent.
Respond ONLY with one of the following exact words:
RAG - if the user is asking a factual question that might be in a document/knowledge base.
RECOMMEND - if the user is asking for a suggestion, recommendation, or similar items.
CHAT - if it's a general greeting or conversation.

User Message: "{user_message}"
"""

intent_response = client.models.generate_content(
model='gemini-2.5-flash',
contents=routing_prompt
).text.strip().upper()

if "RAG" in intent_response:
# 1. Retrieve Context
results = query_collection("knowledge_base", user_message, top_k=3)
context = ""
if results and results.get("documents") and results["documents"][0]:
context = "\n".join(results["documents"][0])

# 2. Generate Answer
rag_prompt = f"""
You are an AI Assistant answering questions based on the provided context.
If the answer is not in the context, politely say you don't know based on the documents.

Context:
{context}

Question: {user_message}
"""
answer = client.models.generate_content(
model='gemini-2.5-flash',
contents=rag_prompt
).text
return f"**(RAG Search)**\n\n{answer}"

elif "RECOMMEND" in intent_response:
# 1. Retrieve recommendations based on semantic similarity
results = query_collection("recommendations", user_message, top_k=3)
context = ""
if results and results.get("documents") and results["documents"][0]:
for doc, meta in zip(results["documents"][0], results["metadatas"][0]):
title = meta.get('title', 'Unknown Item') if meta else 'Unknown Item'
context += f"- **{title}**: {doc}\n"

rec_prompt = f"""
You are an AI Recommendation Engine. The user asked for a recommendation.
Below are the top matches from our database. Present them nicely to the user.

Matches:
{context}

User Request: {user_message}
"""
answer = client.models.generate_content(
model='gemini-2.5-flash',
contents=rec_prompt
).text
return f"**(Recommendation Engine)**\n\n{answer}"

else: # CHAT
chat_prompt = f"""
You are a helpful AI Assistant. Respond to the user's message kindly.
User: {user_message}
"""
return client.models.generate_content(
model='gemini-2.5-flash',
contents=chat_prompt
).text
42 changes: 42 additions & 0 deletions endee-rag-ai-project/api/db_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import chromadb
from sentence_transformers import SentenceTransformer

# We use ChromaDB as our local persistent datastore since Docker/Endee isn't available
client = chromadb.PersistentClient(path="./chroma_data")
model = SentenceTransformer("all-MiniLM-L6-v2")

def get_or_create_collection(name: str):
return client.get_or_create_collection(name=name)

def generate_embedding(text: str) -> list[float]:
"""Generates embedding for a single string."""
return model.encode(text).tolist()

def upsert_document(collection_name: str, doc_id: str, text: str, metadata: dict = None):
"""Embeds and upserts a document into the ChromaDB collection."""
collection = get_or_create_collection(collection_name)
embedding = generate_embedding(text)

collection.add(
ids=[doc_id],
embeddings=[embedding],
documents=[text],
metadatas=[metadata] if metadata else None
)

def query_collection(collection_name: str, query: str, top_k: int = 3):
"""Queries the collection using semantic similarity."""
collection = get_or_create_collection(collection_name)
query_embedding = generate_embedding(query)

results = collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)

return results

def get_all_documents(collection_name: str):
"""Retrieves all documents from a collection."""
collection = get_or_create_collection(collection_name)
return collection.get()
83 changes: 83 additions & 0 deletions endee-rag-ai-project/api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
import uuid
import os

from api.db_client import upsert_document
from api.agent import route_and_execute
import PyPDF2

app = FastAPI(title="AI Document & Recommendation Assistant")

# Mount UI static files
os.makedirs("ui", exist_ok=True)
app.mount("/ui", StaticFiles(directory="ui"), name="ui")

class ChatRequest(BaseModel):
message: str

@app.get("/", response_class=HTMLResponse)
async def serve_ui():
"""Serve the main UI page."""
with open("ui/index.html", "r", encoding="utf-8") as f:
return HTMLResponse(content=f.read())


@app.post("/api/chat")
async def chat_endpoint(req: ChatRequest):
"""Agentic Chat endpoint."""
try:
response = route_and_execute(req.message)
return {"response": response}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/upload")
async def upload_pdf(file: UploadFile = File(...)):
"""Ingest a PDF document into the knowledge base."""
if not file.filename.endswith(".pdf"):
raise HTTPException(status_code=400, detail="Only PDF files are supported.")

try:
pdf_reader = PyPDF2.PdfReader(file.file)
text = ""
for page in pdf_reader.pages:
text += page.extract_text() + "\n"

doc_id = str(uuid.uuid4())
# In a real app we'd chunk this text, but for demo we just store it
upsert_document(
collection_name="knowledge_base",
doc_id=doc_id,
text=text,
metadata={"source": file.filename}
)
return {"message": "PDF ingested successfully! You can now ask questions about it.", "id": doc_id}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to process PDF: {str(e)}")


@app.post("/api/seed_recommendations")
async def seed_recommendations():
"""Seeds some dummy items for the recommendation functionality."""
items = [
{"id": "rec_1", "title": "Ergonomic Office Chair", "desc": "A comfortable mesh chair with lumbar support for long working hours."},
{"id": "rec_2", "title": "Mechanical Keyboard", "desc": "Wireless mechanical keyboard with tactile brown switches, great for typing."},
{"id": "rec_3", "title": "Noise Cancelling Headphones", "desc": "Over-ear headphones with active noise cancellation and 30-hour battery life."},
{"id": "rec_4", "title": "4K Monitor", "desc": "27-inch 4K IPS monitor with vibrant colors, perfect for designers and coders."}
]

try:
for item in items:
upsert_document(
collection_name="recommendations",
doc_id=item["id"],
text=item["desc"],
metadata={"title": item["title"]}
)
return {"message": "Recommendation Engine seeded with sample products!"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

Loading