Spaces:
Running
Running
Upload 11 files
Browse files- .gitattributes +35 -35
- Dockerfile +16 -0
- README.md +11 -10
- app.py +156 -0
- chunker.py +33 -0
- embedder.py +31 -0
- loader.py +30 -0
- portfolio.pdf +0 -0
- requirements.txt +11 -0
- retriever.py +28 -0
- 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:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
| 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]
|