Aaravkumar commited on
Commit
d2aa2a5
·
verified ·
1 Parent(s): da193c2

Upload 11 files

Browse files
Files changed (11) hide show
  1. .gitattributes +35 -35
  2. Dockerfile +16 -0
  3. README.md +11 -10
  4. app.py +156 -0
  5. chunker.py +33 -0
  6. embedder.py +31 -0
  7. loader.py +30 -0
  8. portfolio.pdf +0 -0
  9. requirements.txt +11 -0
  10. retriever.py +28 -0
  11. vector.py +26 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ COPY . .
13
+
14
+ EXPOSE 7860
15
+
16
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,11 @@
1
- ---
2
- title: Portfolio AI
3
- emoji: 🐠
4
- colorFrom: blue
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
+ ---
2
+ title: Documind
3
+ emoji: 📚
4
+ colorFrom: yellow
5
+ colorTo: gray
6
+ sdk: docker
7
+ pinned: false
8
+ license: apache-2.0
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import StreamingResponse
4
+ from pydantic import BaseModel
5
+ import os, logging, time, threading
6
+
7
+ from loader import Loader
8
+ from chunker import Chunker
9
+ from embedder import Embedder
10
+ from vector import VectorStorage
11
+ from retriever import Retriever
12
+
13
+ app = FastAPI()
14
+
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ app.add_middleware(
19
+ CORSMiddleware,
20
+ allow_origins=["*"],
21
+ allow_methods=["*"],
22
+ allow_headers=["*"],
23
+ )
24
+
25
+ MODELS = [
26
+ "Qwen/Qwen2.5-72B-Instruct",
27
+ "meta-llama/Llama-3.2-3B-Instruct",
28
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
29
+ "mistralai/Mistral-7B-Instruct-v0.3",
30
+ "HuggingFaceH4/zephyr-7b-beta",
31
+ ]
32
+
33
+ SESSION_TIMEOUT = 3 * 60 * 60
34
+ sessions: dict = {}
35
+
36
+ def auto_cleanup():
37
+ while True:
38
+ time.sleep(SESSION_TIMEOUT)
39
+
40
+ current_time = time.time()
41
+ expired = [
42
+ sid for sid, data in sessions.items()
43
+ if current_time - data.get('created_at', current_time) > SESSION_TIMEOUT
44
+ ]
45
+ for sid in expired:
46
+ del sessions[sid]
47
+
48
+ if expired:
49
+ logger.info(f"Auto-cleaned {len(expired)} expired sessions to free RAM.")
50
+
51
+
52
+ threading.Thread(target=auto_cleanup, daemon=True).start()
53
+
54
+ # --- Global RAG Components ---
55
+ text = Loader("portfolio.pdf").load()
56
+ chunks = Chunker().chunker(text)
57
+ embedder = Embedder()
58
+ vectors = embedder.embed(chunks)
59
+ store = VectorStorage(dimension=len(vectors[0]))
60
+ store.add(vectors, chunks)
61
+
62
+ class ChatRequest(BaseModel):
63
+ session_id: str
64
+ message: str
65
+
66
+ @app.post("/")
67
+ def chat(req: ChatRequest):
68
+ if req.session_id not in sessions:
69
+
70
+ sessions[req.session_id] = {"history": [], "created_at": time.time()}
71
+
72
+ session = sessions[req.session_id]
73
+
74
+ retriever = Retriever(store, embedder, k=3)
75
+ context_chunks = retriever.retrieve(req.message)
76
+
77
+ if not context_chunks:
78
+ return {"response": "I only answer questions about Aarav and his work."}
79
+
80
+ context_text = "\n\n".join(context_chunks)
81
+ system_prompt = (
82
+ "You are Aarav's AI assistant.\n"
83
+ "Your name is Zooba\n"
84
+ "Your job is to answer questions about Aarav Kumar Ranjan, his projects, skills, and interests using the provided context.\n"
85
+ "Rules:\n"
86
+ "- Only answer using the given context. Do not make up information.\n"
87
+ "- If the answer is not in the context, say: I only answer questions about Aarav and his work.\n"
88
+ "- Keep answers clear, simple, and confident.\n"
89
+ "- Do not use complex jargon unless necessary.\n"
90
+ "- Prefer explaining things in a way a beginner can understand.\n"
91
+ "Style:\n"
92
+ "- Speak in a calm, intelligent, and slightly friendly tone.\n"
93
+ "- Be concise but informative.\n"
94
+ "- When explaining projects, include:\n"
95
+ " • what it does\n"
96
+ " • how it works (simple explanation)\n"
97
+ " • why it is useful\n"
98
+ "Do not generate fake achievements, skills, or experiences.\n"
99
+ "Do not pretend to be Aarav himself.\n"
100
+ "If asked about projects, mention their names clearly.\n"
101
+ "Make Aarav appear as a thoughtful, skilled, and curious machine learning enthusiast who focuses on understanding and building real systems.\n"
102
+ )
103
+
104
+ messages = [{"role": "system", "content": system_prompt}]
105
+
106
+
107
+ recent_history = session["history"][-10:]
108
+ messages.extend(recent_history)
109
+
110
+ messages.append({"role": "user", "content": f"Context:\n{context_text}\n\nQuestion: {req.message}"})
111
+
112
+ full_response = ""
113
+
114
+ def token_stream():
115
+ nonlocal full_response
116
+
117
+ for model in MODELS:
118
+ try:
119
+ client = InferenceClient(model, token=os.environ["HF_TOKEN"])
120
+ logger.info(f"Streaming with: {model}")
121
+ success = False
122
+
123
+ for token in client.chat_completion(messages, max_tokens=512, stream=True):
124
+ text = token.choices[0].delta.content
125
+ if text:
126
+ success = True
127
+ full_response += text
128
+ yield f"data: {text}\n\n"
129
+
130
+ yield "data: [DONE]\n\n"
131
+
132
+
133
+ session["history"].append({"role": "user", "content": req.message})
134
+ session["history"].append({"role": "assistant", "content": full_response})
135
+ return
136
+
137
+ except Exception as e:
138
+ if success:
139
+
140
+ session["history"].append({"role": "user", "content": req.message})
141
+ session["history"].append({"role": "assistant", "content": full_response})
142
+ yield "data: [DONE]\n\n"
143
+ return
144
+
145
+ logger.warning(f"Streaming failed for {model}: {e}")
146
+ full_response = ""
147
+ continue
148
+
149
+ yield "data: Sorry, we are currently unavailable. Try again later.\n\n"
150
+ yield "data: [DONE]\n\n"
151
+
152
+ return StreamingResponse(token_stream(), media_type="text/event-stream")
153
+
154
+ if __name__ == "__main__":
155
+ import uvicorn
156
+ uvicorn.run(app, host="0.0.0.0", port=7600)
chunker.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Chunker:
2
+ def __init__(self, chunk_size=500, overlap=100):
3
+ self.chunk_size = chunk_size
4
+ self.overlap = overlap
5
+
6
+ def chunker(self, text):
7
+ if self.overlap >= self.chunk_size:
8
+ raise ValueError("Overlap must be smaller than chunk size.")
9
+
10
+ chunks = []
11
+ start = 0
12
+ text_size = len(text)
13
+
14
+ while start < text_size:
15
+ end = start + self.chunk_size
16
+
17
+
18
+ if end < text_size:
19
+ last_space = text.rfind(' ', start, end)
20
+ if last_space != -1:
21
+ end = last_space
22
+
23
+
24
+ chunk = text[start:end].strip()
25
+ if chunk:
26
+ chunks.append(chunk)
27
+
28
+
29
+ start = end - self.overlap
30
+
31
+ return chunks
32
+
33
+
embedder.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sentence_transformers import SentenceTransformer
2
+ import numpy as np
3
+ import time
4
+
5
+ class Embedder:
6
+ def __init__(self):
7
+ print("Loading embedding model...")
8
+ self.model = SentenceTransformer('sentence-transformers/paraphrase-MiniLM-L6-v2')
9
+ print("Embedder loaded: paraphrase-MiniLM-L6-v")
10
+
11
+ def embed(self, chunks, batch_size=32):
12
+ try:
13
+ vectors = self.model.encode(
14
+ chunks,
15
+ batch_size=batch_size,
16
+ show_progress_bar=False,
17
+ convert_to_numpy=True
18
+ )
19
+ return vectors.tolist()
20
+ except Exception as e:
21
+ print(f"Embedding failed: {e}")
22
+ raise e
23
+
24
+ def embed_q(self, query):
25
+
26
+ try:
27
+ v = self.model.encode(query, convert_to_numpy=True)
28
+ return v.tolist()
29
+ except Exception as e:
30
+ print(f"Query embedding failed: {e}")
31
+ raise e
loader.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fitz
2
+
3
+
4
+ class Loader:
5
+ """
6
+ loads the text from the pdf files
7
+ """
8
+ def __init__(self,file_path):
9
+ self.file = file_path
10
+
11
+ def load(self):
12
+ text_chunks = []
13
+
14
+
15
+ doc = fitz.open(self.file)
16
+ display_number = 1
17
+ for page in doc:
18
+ print(f"Loading for {display_number} ")
19
+ display_number += 1
20
+ page_text = page.get_text("text")
21
+
22
+
23
+ page_text = "\n".join([line.strip() for line in page_text.split("\n") if line.strip()])
24
+
25
+ text_chunks.append(page_text)
26
+
27
+ doc.close()
28
+ return "\n\n".join(text_chunks)
29
+
30
+ return text
portfolio.pdf ADDED
Binary file (39.8 kB). View file
 
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart
4
+ requests
5
+ numpy
6
+ PyMuPDF
7
+ faiss-cpu
8
+ pydantic
9
+ python-dotenv
10
+
11
+ sentence-transformers
retriever.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Retriever:
2
+ def __init__(self, vector_store, embedder, k=5):
3
+ self.vector_store = vector_store
4
+ self.embedder = embedder
5
+ self.k = k
6
+
7
+ def retrieve(self, query):
8
+
9
+ vquery = self.embedder.embed_q(query)
10
+
11
+
12
+ scores, indices = self.vector_store.search(vquery, self.k)
13
+
14
+
15
+ if scores[0] < 0.5:
16
+ print(f"Evidence too low: {scores[0]}")
17
+ return []
18
+
19
+
20
+ results = [
21
+ self.vector_store.chunks[i]
22
+ for i in indices if i != -1
23
+ ]
24
+ return results
25
+
26
+
27
+
28
+
vector.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import faiss
2
+ import numpy as np
3
+
4
+ class VectorStorage:
5
+ def __init__(self, dimension=384):
6
+
7
+ self.index = faiss.IndexFlatIP(dimension)
8
+ self.chunks = []
9
+
10
+ def add(self, vectors, chunks):
11
+
12
+ v_array = np.array(vectors).astype('float32')
13
+
14
+ faiss.normalize_L2(v_array)
15
+
16
+ self.index.add(v_array)
17
+ self.chunks.extend(chunks)
18
+
19
+ def search(self, query_vector, k=5):
20
+
21
+ q_array = np.array([query_vector]).astype('float32')
22
+ faiss.normalize_L2(q_array)
23
+
24
+
25
+ scores, indices = self.index.search(q_array, k)
26
+ return scores[0], indices[0]