Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -10,6 +10,7 @@ from dotenv import load_dotenv
|
|
| 10 |
from operator import itemgetter
|
| 11 |
from fastapi import FastAPI, Depends, HTTPException, Header
|
| 12 |
from fastapi.responses import JSONResponse
|
|
|
|
| 13 |
from utils.DocsLoader import load_and_chunk
|
| 14 |
from utils.Schemas import RunRequest, RunResponse
|
| 15 |
from langchain_community.vectorstores import FAISS
|
|
@@ -19,6 +20,7 @@ from langchain_community.retrievers import BM25Retriever
|
|
| 19 |
from langchain.retrievers import EnsembleRetriever
|
| 20 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 21 |
from langchain.prompts import PromptTemplate
|
|
|
|
| 22 |
|
| 23 |
# Load environment variables
|
| 24 |
load_dotenv()
|
|
@@ -39,7 +41,6 @@ async def lifespan(app: FastAPI):
|
|
| 39 |
if not GOOGLE_API_KEY:
|
| 40 |
raise RuntimeError("CRITICAL: Missing GOOGLE_API_KEY in environment secrets!")
|
| 41 |
|
| 42 |
-
# Optimized embedding model with batching
|
| 43 |
ml_models["embedder"] = HuggingFaceEmbeddings(
|
| 44 |
model_name="BAAI/bge-base-en-v1.5",
|
| 45 |
encode_kwargs={
|
|
@@ -48,7 +49,6 @@ async def lifespan(app: FastAPI):
|
|
| 48 |
}
|
| 49 |
)
|
| 50 |
|
| 51 |
-
# Faster LLM with constrained output
|
| 52 |
ml_models["llm"] = ChatGoogleGenerativeAI(
|
| 53 |
model="gemini-1.5-pro",
|
| 54 |
api_key=GOOGLE_API_KEY,
|
|
@@ -56,7 +56,6 @@ async def lifespan(app: FastAPI):
|
|
| 56 |
max_output_tokens=300
|
| 57 |
)
|
| 58 |
|
| 59 |
-
# Improved prompt template
|
| 60 |
ml_models["prompt_template"] = PromptTemplate.from_template("""
|
| 61 |
**Role**: Insurance Policy Expert
|
| 62 |
**Context**:
|
|
@@ -85,6 +84,7 @@ async def lifespan(app: FastAPI):
|
|
| 85 |
# --- 2. FastAPI App Instance ---
|
| 86 |
app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)
|
| 87 |
|
|
|
|
| 88 |
# --- 3. API Key Verification ---
|
| 89 |
TEAM_API_KEY = os.getenv("TEAM_API_KEY")
|
| 90 |
|
|
@@ -98,7 +98,6 @@ def verify_api_key(authorization: str = Header(...)):
|
|
| 98 |
# --- 4. Parsing Helper ---
|
| 99 |
def parse_llm_response(content: str) -> str:
|
| 100 |
try:
|
| 101 |
-
# Clean JSON response
|
| 102 |
content_cleaned = re.sub(r"^```json|```$", "", content.strip(), flags=re.IGNORECASE).strip()
|
| 103 |
data = json.loads(content_cleaned)
|
| 104 |
|
|
@@ -118,7 +117,6 @@ def parse_llm_response(content: str) -> str:
|
|
| 118 |
|
| 119 |
# --- 5. Retrieval Optimization ---
|
| 120 |
async def get_relevant_docs(question: str, vectorstore: FAISS, keyword_retriever: BM25Retriever):
|
| 121 |
-
# Parallel retrieval
|
| 122 |
dense_docs, sparse_docs = await asyncio.gather(
|
| 123 |
vectorstore.asimilarity_search(question, k=6),
|
| 124 |
asyncio.get_event_loop().run_in_executor(
|
|
@@ -128,11 +126,9 @@ async def get_relevant_docs(question: str, vectorstore: FAISS, keyword_retriever
|
|
| 128 |
)
|
| 129 |
)
|
| 130 |
|
| 131 |
-
# Combine and deduplicate
|
| 132 |
all_docs = dense_docs + sparse_docs
|
| 133 |
unique_docs = {doc.page_content: doc for doc in all_docs}.values()
|
| 134 |
|
| 135 |
-
# Fast reranking
|
| 136 |
query_embedding = ml_models["embedder"].embed_query(question)
|
| 137 |
doc_texts = [doc.page_content for doc in unique_docs]
|
| 138 |
doc_embeddings = ml_models["embedder"].embed_documents(doc_texts)
|
|
@@ -147,14 +143,16 @@ async def get_relevant_docs(question: str, vectorstore: FAISS, keyword_retriever
|
|
| 147 |
async def run_hackrx(req: RunRequest):
|
| 148 |
start_time = time.time()
|
| 149 |
|
| 150 |
-
# Cache document processing
|
| 151 |
async with cache_lock:
|
| 152 |
-
|
|
|
|
| 153 |
print("♻️ Using cached document")
|
| 154 |
-
chunks = document_cache[
|
| 155 |
else:
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
| 158 |
|
| 159 |
if not chunks:
|
| 160 |
return JSONResponse({"error": "No documents processed"}, status_code=400)
|
|
@@ -169,7 +167,6 @@ async def run_hackrx(req: RunRequest):
|
|
| 169 |
relevant_docs = await get_relevant_docs(q, vectorstore, keyword_retriever)
|
| 170 |
context = "\n".join([d.page_content for d in relevant_docs])
|
| 171 |
|
| 172 |
-
# Generate response
|
| 173 |
prompt = ml_models["prompt_template"].format_prompt(
|
| 174 |
full_query=q,
|
| 175 |
context=context
|
|
@@ -179,7 +176,6 @@ async def run_hackrx(req: RunRequest):
|
|
| 179 |
|
| 180 |
answers = await asyncio.gather(*(process_question(q) for q in req.questions))
|
| 181 |
|
| 182 |
-
# Performance logging
|
| 183 |
proc_time = time.time() - start_time
|
| 184 |
print(f"⏱️ Processed {len(req.questions)} questions in {proc_time:.2f}s")
|
| 185 |
|
|
@@ -187,4 +183,4 @@ async def run_hackrx(req: RunRequest):
|
|
| 187 |
|
| 188 |
@app.get("/", include_in_schema=False)
|
| 189 |
def root():
|
| 190 |
-
return {"message": "HackRX API operational. Use /api/v1/hackrx/run"}
|
|
|
|
| 10 |
from operator import itemgetter
|
| 11 |
from fastapi import FastAPI, Depends, HTTPException, Header
|
| 12 |
from fastapi.responses import JSONResponse
|
| 13 |
+
from langchain_core.documents import Document
|
| 14 |
from utils.DocsLoader import load_and_chunk
|
| 15 |
from utils.Schemas import RunRequest, RunResponse
|
| 16 |
from langchain_community.vectorstores import FAISS
|
|
|
|
| 20 |
from langchain.retrievers import EnsembleRetriever
|
| 21 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 22 |
from langchain.prompts import PromptTemplate
|
| 23 |
+
from fastapi.middleware.cors import CORSMiddleware # Optional, for external frontend
|
| 24 |
|
| 25 |
# Load environment variables
|
| 26 |
load_dotenv()
|
|
|
|
| 41 |
if not GOOGLE_API_KEY:
|
| 42 |
raise RuntimeError("CRITICAL: Missing GOOGLE_API_KEY in environment secrets!")
|
| 43 |
|
|
|
|
| 44 |
ml_models["embedder"] = HuggingFaceEmbeddings(
|
| 45 |
model_name="BAAI/bge-base-en-v1.5",
|
| 46 |
encode_kwargs={
|
|
|
|
| 49 |
}
|
| 50 |
)
|
| 51 |
|
|
|
|
| 52 |
ml_models["llm"] = ChatGoogleGenerativeAI(
|
| 53 |
model="gemini-1.5-pro",
|
| 54 |
api_key=GOOGLE_API_KEY,
|
|
|
|
| 56 |
max_output_tokens=300
|
| 57 |
)
|
| 58 |
|
|
|
|
| 59 |
ml_models["prompt_template"] = PromptTemplate.from_template("""
|
| 60 |
**Role**: Insurance Policy Expert
|
| 61 |
**Context**:
|
|
|
|
| 84 |
# --- 2. FastAPI App Instance ---
|
| 85 |
app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)
|
| 86 |
|
| 87 |
+
|
| 88 |
# --- 3. API Key Verification ---
|
| 89 |
TEAM_API_KEY = os.getenv("TEAM_API_KEY")
|
| 90 |
|
|
|
|
| 98 |
# --- 4. Parsing Helper ---
|
| 99 |
def parse_llm_response(content: str) -> str:
|
| 100 |
try:
|
|
|
|
| 101 |
content_cleaned = re.sub(r"^```json|```$", "", content.strip(), flags=re.IGNORECASE).strip()
|
| 102 |
data = json.loads(content_cleaned)
|
| 103 |
|
|
|
|
| 117 |
|
| 118 |
# --- 5. Retrieval Optimization ---
|
| 119 |
async def get_relevant_docs(question: str, vectorstore: FAISS, keyword_retriever: BM25Retriever):
|
|
|
|
| 120 |
dense_docs, sparse_docs = await asyncio.gather(
|
| 121 |
vectorstore.asimilarity_search(question, k=6),
|
| 122 |
asyncio.get_event_loop().run_in_executor(
|
|
|
|
| 126 |
)
|
| 127 |
)
|
| 128 |
|
|
|
|
| 129 |
all_docs = dense_docs + sparse_docs
|
| 130 |
unique_docs = {doc.page_content: doc for doc in all_docs}.values()
|
| 131 |
|
|
|
|
| 132 |
query_embedding = ml_models["embedder"].embed_query(question)
|
| 133 |
doc_texts = [doc.page_content for doc in unique_docs]
|
| 134 |
doc_embeddings = ml_models["embedder"].embed_documents(doc_texts)
|
|
|
|
| 143 |
async def run_hackrx(req: RunRequest):
|
| 144 |
start_time = time.time()
|
| 145 |
|
|
|
|
| 146 |
async with cache_lock:
|
| 147 |
+
doc_key = hash(str(req.documents)) # Hash key for safety
|
| 148 |
+
if doc_key in document_cache:
|
| 149 |
print("♻️ Using cached document")
|
| 150 |
+
chunks = document_cache[doc_key]
|
| 151 |
else:
|
| 152 |
+
raw_chunks = load_and_chunk(str(req.documents))
|
| 153 |
+
# 🔧 FIXED: Convert string chunks to Document objects
|
| 154 |
+
chunks = [Document(page_content=chunk) for chunk in raw_chunks]
|
| 155 |
+
document_cache[doc_key] = chunks
|
| 156 |
|
| 157 |
if not chunks:
|
| 158 |
return JSONResponse({"error": "No documents processed"}, status_code=400)
|
|
|
|
| 167 |
relevant_docs = await get_relevant_docs(q, vectorstore, keyword_retriever)
|
| 168 |
context = "\n".join([d.page_content for d in relevant_docs])
|
| 169 |
|
|
|
|
| 170 |
prompt = ml_models["prompt_template"].format_prompt(
|
| 171 |
full_query=q,
|
| 172 |
context=context
|
|
|
|
| 176 |
|
| 177 |
answers = await asyncio.gather(*(process_question(q) for q in req.questions))
|
| 178 |
|
|
|
|
| 179 |
proc_time = time.time() - start_time
|
| 180 |
print(f"⏱️ Processed {len(req.questions)} questions in {proc_time:.2f}s")
|
| 181 |
|
|
|
|
| 183 |
|
| 184 |
@app.get("/", include_in_schema=False)
|
| 185 |
def root():
|
| 186 |
+
return {"message": "HackRX API operational. Use /api/v1/hackrx/run"}
|