Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -7,6 +7,9 @@ import asyncio
|
|
| 7 |
from contextlib import asynccontextmanager
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
from operator import itemgetter
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
from fastapi import FastAPI, Depends, HTTPException, Header
|
| 12 |
from fastapi.responses import JSONResponse
|
|
@@ -49,7 +52,7 @@ load_dotenv()
|
|
| 49 |
|
| 50 |
vector_cache = {}
|
| 51 |
ml_models = {}
|
| 52 |
-
|
| 53 |
PRELOAD_URLS = [
|
| 54 |
"https://hackrx.blob.core.windows.net/assets/Arogya%20Sanjeevani%20Policy%20-%20CIN%20-%20U10200WB1906GOI001713%201.pdf?sv=2023-01-03&st=2025-07-21T08%3A29%3A02Z&se=2025-09-22T08%3A29%3A00Z&sr=b&sp=r&sig=nzrz1K9Iurt%2BBXom%2FB%2BMPTFMFP3PRnIvEsipAX10Ig4%3D",
|
| 55 |
"https://hackrx.blob.core.windows.net/assets/Super_Splendor_(Feb_2023).pdf?sv=2023-01-03&st=2025-07-21T08%3A10%3A00Z&se=2025-09-22T08%3A10%3A00Z&sr=b&sp=r&sig=vhHrl63YtrEOCsAy%2BpVKr20b3ZUo5HMz1lF9%2BJh6LQ0%3D",
|
|
@@ -173,6 +176,17 @@ Step 3 – **Final Output**:
|
|
| 173 |
# We pass the lifespan function to the FastAPI constructor
|
| 174 |
app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)
|
| 175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
# --- 3. API Key Verification ---
|
| 177 |
TEAM_API_KEY = os.getenv("TEAM_API_KEY")
|
| 178 |
TEAM_API_KEY2 = os.getenv("TEAM_API_KEY2")
|
|
@@ -181,7 +195,7 @@ def verify_api_key(authorization: str = Header(...)):
|
|
| 181 |
if not authorization.startswith("Bearer "):
|
| 182 |
raise HTTPException(status_code=401, detail="Invalid Authorization header format")
|
| 183 |
token = authorization.split("Bearer ")[1]
|
| 184 |
-
if token != TEAM_API_KEY and token != TEAM_API_KEY2:
|
| 185 |
raise HTTPException(status_code=403, detail="Invalid or missing API key")
|
| 186 |
|
| 187 |
|
|
@@ -214,72 +228,76 @@ def parse_llm_response(content: str) -> str:
|
|
| 214 |
# --- 5. Main API Endpoint ---
|
| 215 |
@app.post("/api/v1/hackrx/run", response_model=RunResponse, dependencies=[Depends(verify_api_key)])
|
| 216 |
async def run_hackrx(req: RunRequest):
|
| 217 |
-
doc_url = str(req.documents)
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
-
|
| 255 |
-
# Create retrievers using the pre-loaded models from our ml_models dictionary
|
| 256 |
-
keyword_retriever = BM25Retriever.from_documents(chunks)
|
| 257 |
-
keyword_retriever.k = 9 #prev 11
|
| 258 |
-
# dense_retriever = Chroma.from_documents(documents=chunks, embedding=ml_models["embedder"]).as_retriever()
|
| 259 |
-
ensemble_retriever = EnsembleRetriever(retrievers=[keyword_retriever, dense_retriever], weights=[0.3, 0.7],search_kwargs={"k": 14}) #prev 16
|
| 260 |
-
### to make it faster we are now using our built reranker thats why commenting the code below
|
| 261 |
-
# compression_retriever = ContextualCompressionRetriever(
|
| 262 |
-
# base_retriever=ensemble_retriever, base_compressor=ml_models["reranker_compressor"]
|
| 263 |
-
# )
|
| 264 |
-
|
| 265 |
-
# Define the RAG chain using pre-loaded components
|
| 266 |
-
hybrid_rag_chain = (
|
| 267 |
-
{"context": itemgetter("full_query") | ensemble_retriever, "full_query": itemgetter("full_query")}
|
| 268 |
-
| ml_models["prompt_template"]
|
| 269 |
-
| ml_models["llm"]
|
| 270 |
-
)
|
| 271 |
-
|
| 272 |
-
tasks = [hybrid_rag_chain.ainvoke({"full_query": q}) for q in req.questions]
|
| 273 |
-
results = await asyncio.gather(*tasks)
|
| 274 |
-
# answers = [parse_llm_response(result.content) for result in results]
|
| 275 |
-
answers = []
|
| 276 |
-
|
| 277 |
-
for msg in results:
|
| 278 |
-
# Safely access the content field
|
| 279 |
-
if hasattr(msg, "content"):
|
| 280 |
-
answers.append(msg.content.strip())
|
| 281 |
-
|
| 282 |
-
return JSONResponse({"answers": answers}, status_code=200)
|
| 283 |
|
| 284 |
@app.get("/", include_in_schema=False)
|
| 285 |
def root():
|
|
|
|
| 7 |
from contextlib import asynccontextmanager
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
from operator import itemgetter
|
| 10 |
+
import requests
|
| 11 |
+
from bs4 import BeautifulSoup
|
| 12 |
+
|
| 13 |
|
| 14 |
from fastapi import FastAPI, Depends, HTTPException, Header
|
| 15 |
from fastapi.responses import JSONResponse
|
|
|
|
| 52 |
|
| 53 |
vector_cache = {}
|
| 54 |
ml_models = {}
|
| 55 |
+
secret = ""
|
| 56 |
PRELOAD_URLS = [
|
| 57 |
"https://hackrx.blob.core.windows.net/assets/Arogya%20Sanjeevani%20Policy%20-%20CIN%20-%20U10200WB1906GOI001713%201.pdf?sv=2023-01-03&st=2025-07-21T08%3A29%3A02Z&se=2025-09-22T08%3A29%3A00Z&sr=b&sp=r&sig=nzrz1K9Iurt%2BBXom%2FB%2BMPTFMFP3PRnIvEsipAX10Ig4%3D",
|
| 58 |
"https://hackrx.blob.core.windows.net/assets/Super_Splendor_(Feb_2023).pdf?sv=2023-01-03&st=2025-07-21T08%3A10%3A00Z&se=2025-09-22T08%3A10%3A00Z&sr=b&sp=r&sig=vhHrl63YtrEOCsAy%2BpVKr20b3ZUo5HMz1lF9%2BJh6LQ0%3D",
|
|
|
|
| 176 |
# We pass the lifespan function to the FastAPI constructor
|
| 177 |
app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)
|
| 178 |
|
| 179 |
+
def store_secret(url: str):
|
| 180 |
+
url_c = url
|
| 181 |
+
r = requests.get(url_c)
|
| 182 |
+
r.raise_for_status()
|
| 183 |
+
soup = BeautifulSoup(r.text, "html.parser")
|
| 184 |
+
token = (soup.find(id="token") or soup).get_text(strip=True)
|
| 185 |
+
m = re.search(r"[0-9a-fA-F]{64}", token)
|
| 186 |
+
token = m.group(0) if m else token
|
| 187 |
+
secret = token
|
| 188 |
+
|
| 189 |
+
|
| 190 |
# --- 3. API Key Verification ---
|
| 191 |
TEAM_API_KEY = os.getenv("TEAM_API_KEY")
|
| 192 |
TEAM_API_KEY2 = os.getenv("TEAM_API_KEY2")
|
|
|
|
| 195 |
if not authorization.startswith("Bearer "):
|
| 196 |
raise HTTPException(status_code=401, detail="Invalid Authorization header format")
|
| 197 |
token = authorization.split("Bearer ")[1]
|
| 198 |
+
if token != TEAM_API_KEY and token != TEAM_API_KEY2 and token != secret:
|
| 199 |
raise HTTPException(status_code=403, detail="Invalid or missing API key")
|
| 200 |
|
| 201 |
|
|
|
|
| 228 |
# --- 5. Main API Endpoint ---
|
| 229 |
@app.post("/api/v1/hackrx/run", response_model=RunResponse, dependencies=[Depends(verify_api_key)])
|
| 230 |
async def run_hackrx(req: RunRequest):
|
| 231 |
+
doc_url = str(req.documents)
|
| 232 |
+
lower_url = doc_url.lower()
|
| 233 |
+
if "get-secret-token" in lower_url:
|
| 234 |
+
store_secret(doc_url)
|
| 235 |
+
else:
|
| 236 |
+
start_time = time.time()
|
| 237 |
+
# if(doc_url not in vector_cache):
|
| 238 |
+
chunks = load_and_chunk(str(req.documents))
|
| 239 |
+
if not chunks:
|
| 240 |
+
return JSONResponse({"error": "No documents could be processed."}, status_code=400)
|
| 241 |
+
end_time = time.time() - start_time
|
| 242 |
+
print(f"chunking done: {end_time}")
|
| 243 |
+
# if not chunks:
|
| 244 |
+
# return JSONResponse({"error": "No documents could be processed."}, status_code=400)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
start_time2 = time.time()
|
| 248 |
+
# ✅ Reuse vectorstore if already cached
|
| 249 |
+
if doc_url in vector_cache:
|
| 250 |
+
print(f"♻ Using cached vectorstore for: {doc_url}")
|
| 251 |
+
vectorstore = vector_cache[doc_url]
|
| 252 |
+
else:
|
| 253 |
+
print(f"📄 Processing new document: {doc_url}")
|
| 254 |
+
# Build vectorstore & save to cache
|
| 255 |
+
vectorstore = await FAISS.afrom_documents(documents=chunks, embedding=ml_models["embedder"])
|
| 256 |
+
vector_cache[doc_url] = vectorstore # store in memory cache
|
| 257 |
+
print(f"✅ Vectorstore cached for: {doc_url}")
|
| 258 |
+
end_time2 = time.time() - start_time2
|
| 259 |
+
print(f"vector done: {end_time2}")
|
| 260 |
+
|
| 261 |
+
# start_time2 = time.time()
|
| 262 |
+
# vectorstore = await FAISS.afrom_documents(
|
| 263 |
+
# documents=chunks,
|
| 264 |
+
# embedding=ml_models["embedder"]
|
| 265 |
+
# )
|
| 266 |
+
# end_time2 = time.time() - start_time2
|
| 267 |
+
# print(f"vector done: {end_time2}")
|
| 268 |
+
# dense_retriever = vectorstore.as_retriever(search_type="mmr",search_kwargs={"k": 8})
|
| 269 |
+
dense_retriever = vectorstore.as_retriever(search_type="mmr",search_kwargs={"k": 14 ,"lambda_mult": 0.7} ) # prev 16
|
| 270 |
+
# dense_retriever = vectorstore.as_retriever(search_type="similarity" ,search_kwargs={"k": 11} )
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
# Create retrievers using the pre-loaded models from our ml_models dictionary
|
| 274 |
+
keyword_retriever = BM25Retriever.from_documents(chunks)
|
| 275 |
+
keyword_retriever.k = 9 #prev 11
|
| 276 |
+
# dense_retriever = Chroma.from_documents(documents=chunks, embedding=ml_models["embedder"]).as_retriever()
|
| 277 |
+
ensemble_retriever = EnsembleRetriever(retrievers=[keyword_retriever, dense_retriever], weights=[0.3, 0.7],search_kwargs={"k": 14}) #prev 16
|
| 278 |
+
### to make it faster we are now using our built reranker thats why commenting the code below
|
| 279 |
+
# compression_retriever = ContextualCompressionRetriever(
|
| 280 |
+
# base_retriever=ensemble_retriever, base_compressor=ml_models["reranker_compressor"]
|
| 281 |
+
# )
|
| 282 |
+
|
| 283 |
+
# Define the RAG chain using pre-loaded components
|
| 284 |
+
hybrid_rag_chain = (
|
| 285 |
+
{"context": itemgetter("full_query") | ensemble_retriever, "full_query": itemgetter("full_query")}
|
| 286 |
+
| ml_models["prompt_template"]
|
| 287 |
+
| ml_models["llm"]
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
tasks = [hybrid_rag_chain.ainvoke({"full_query": q}) for q in req.questions]
|
| 291 |
+
results = await asyncio.gather(*tasks)
|
| 292 |
+
# answers = [parse_llm_response(result.content) for result in results]
|
| 293 |
+
answers = []
|
| 294 |
+
|
| 295 |
+
for msg in results:
|
| 296 |
+
# Safely access the content field
|
| 297 |
+
if hasattr(msg, "content"):
|
| 298 |
+
answers.append(msg.content.strip())
|
| 299 |
|
| 300 |
+
return JSONResponse({"answers": answers}, status_code=200)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
@app.get("/", include_in_schema=False)
|
| 303 |
def root():
|