Spaces:
Sleeping
Sleeping
File size: 8,909 Bytes
ccf254d eb9ec17 9a1a8d3 1f2b26b 9a1a8d3 6824985 8ca2283 ccf254d 9a1a8d3 8ca2283 c6f2a8c ccf254d 6824985 8ca2283 9a1a8d3 ccf254d 6824985 5225cff 8ca2283 ccf254d 8ca2283 cdbf2f9 8ca2283 0fb8c62 0b0de3e 6e0eba1 0fb8c62 d128292 0fb8c62 ccf254d 0fb8c62 ccf254d 9a1a8d3 ccf254d 9a1a8d3 ccf254d 6824985 e616961 7ab6a0e 8ca2283 6824985 ccf254d d50d870 9ab78f8 7f7ca2c ccf254d 8ca2283 ccf254d ee2203f 55f4347 9ce6e11 cb0dae8 ccf254d cdbf2f9 7f7ca2c cdbf2f9 7f7ca2c cdbf2f9 7f7ca2c cdbf2f9 7f7ca2c cdbf2f9 7f7ca2c cdbf2f9 7f7ca2c 3c767f2 7f7ca2c 8ca2283 cdbf2f9 6824985 57b098a 8ca2283 57b098a c6f2a8c 57b098a c6f2a8c ccf254d c6f2a8c ccf254d c6f2a8c 557efda c6f2a8c ccf254d 6824985 c6f2a8c ccf254d 6824985 8ca2283 ccf254d 8ca2283 ccf254d 6824985 8ca2283 ccf254d c6f2a8c 8ca2283 ce4334f ccf254d c6f2a8c 8ca2283 55f4347 8ca2283 ccf254d 9ab78f8 ccf254d 55f4347 9ce6e11 ccf254d 8ca2283 ccf254d cdbf2f9 5cc8abb ccf254d c6f2a8c ce4334f c6f2a8c 8ca2283 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
import os
import json
import re
import asyncio
from contextlib import asynccontextmanager
from dotenv import load_dotenv
from operator import itemgetter
# import gradio as gr
from fastapi import FastAPI, Depends, HTTPException, Header
from fastapi.responses import JSONResponse
from utils.DocsLoader import load_and_chunk
from utils.Schemas import RunRequest, RunResponse
# from concurrent.futures import ThreadPoolExecutor
from langchain_community.vectorstores import FAISS
from langchain.schema import Document
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_huggingface import HuggingFaceEmbeddings
# from langchain_chroma import Chroma
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
from langchain.prompts import ChatPromptTemplate
import os
from sentence_transformers import SentenceTransformer
MODEL_DIR = os.path.join("/tmp", "e5-large-v2")
if not os.path.exists(MODEL_DIR):
print("π¦ Downloading SentenceTransformer model...")
model = SentenceTransformer("intfloat/e5-large-v2")
model.save(MODEL_DIR)
print("β
Model saved at", MODEL_DIR)
# Load environment variables
load_dotenv()
ml_models = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
# This code runs ONCE when the application starts up
print("π Initializing models and prompt template...")
try:
GOOGLE_API_KEY = os.getenv("gemini_api_key")
print("π gemini_api_key:", "FOUND" if GOOGLE_API_KEY else "NOT FOUND")
if not GOOGLE_API_KEY:
raise RuntimeError("CRITICAL: Missing GOOGLE_API_KEY in environment secrets!")
# Load models into the shared dictionary
ml_models["embedder"] = HuggingFaceEmbeddings(
model_name="BAAI/bge-base-en-v1.5", #better but lil slower
# model_name="intfloat/e5-large-v2", #lil faster but dont know response is slow
encode_kwargs={
"batch_size": 64,
# "normalize_embeddings": True
}
)
cross_encoder_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base")
# cross_encoder_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-large")
ml_models["reranker_compressor"] = CrossEncoderReranker(model=cross_encoder_model, top_n=6)
ml_models["llm"] = ChatGoogleGenerativeAI(
# model="gemini-1.5-pro",
model="gemini-2.0-flash",
api_key=GOOGLE_API_KEY,
temperature=0.1,
max_output_tokens=300
)
ml_models["prompt_template"] = ChatPromptTemplate.from_template("""
**Role**: You are an expert decision maker Assistant in the domain such as insurance, legal compliance, human resources, and contract management using ONLY the provided context.
Do not use your own knowledge.
**Context**:
{context}
**Query**: {full_query}
**Response**:
Example how to answer for thr query:
1. query:What is the grace period for premium payment under the National Parivar Mediclaim Plus Policy?
response: A grace period of thirty days is provided for premium payment after the due date to renew or continue the policy without losing continuity benefits.
2. query: What is the waiting period for pre-existing diseases (PED) to be covered?
response: There is a waiting period of thirty-six (36) months of continuous coverage from the first policy inception for pre-existing diseases and their direct complications to be covered.
3. query: Are the medical expenses for an organ donor covered under this policy?
response : Yes, the policy indemnifies the medical expenses for the organ donor's hospitalization for the purpose of harvesting the organ, provided the organ is for an insured person and the donation complies with the Transplantation of Human Organs Act, 1994.
4. query: Does this policy cover maternity expenses, and what are the conditions?
response: Yes, the policy covers maternity expenses, including childbirth and lawful medical termination of pregnancy. To be eligible, the female insured person must have been continuously covered for at least 24 months. The benefit is limited to two deliveries or terminations during the policy period.
"""
)
print("β
Models and prompt loaded successfully!")
except Exception as e:
print("β Lifespan error:", str(e))
raise e
yield
print("π§Ή Cleaning up.")
ml_models.clear()
# --- 2. FastAPI App Instance ---
# We pass the lifespan function to the FastAPI constructor
app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)
# --- 3. API Key Verification ---
TEAM_API_KEY = os.getenv("TEAM_API_KEY")
def verify_api_key(authorization: str = Header(...)):
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid Authorization header format")
token = authorization.split("Bearer ")[1]
if token != TEAM_API_KEY:
raise HTTPException(status_code=403, detail="Invalid or missing API key")
# --- 4. Parsing Helper ---
def parse_llm_response(content: str) -> str:
try:
# Remove code fences and clean up
content_cleaned = re.sub(r"^```json|```$", "", content.strip(), flags=re.IGNORECASE).strip()
data = json.loads(content_cleaned)
if isinstance(data, dict):
if "decision" in data:
decision = data.get("decision", "N/A").upper()
amount = data.get("amount", "Not specified")
justification = data.get("justification", "No justification provided.")
return f"Decision: {decision}\nAmount: {amount}\nJustification: {justification}"
elif "response" in data:
return data["response"]
return "The response was parsed but didn't match expected structure."
except json.JSONDecodeError:
return f"Unstructured response:\n{content.strip()}"
except Exception as e:
return f"An error occurred while processing the response: {str(e)}"
# --- 5. Main API Endpoint ---
@app.post("/api/v1/hackrx/run", response_model=RunResponse, dependencies=[Depends(verify_api_key)])
async def run_hackrx(req: RunRequest):
chunks = load_and_chunk(str(req.documents))
if not chunks:
return JSONResponse({"error": "No documents could be processed."}, status_code=400)
vectorstore = await FAISS.afrom_documents(
documents=chunks,
embedding=ml_models["embedder"]
)
# dense_retriever = vectorstore.as_retriever(search_type="mmr",search_kwargs={"k": 8})
dense_retriever = vectorstore.as_retriever(search_type="mmr",search_kwargs={"k": 8 ,"lambda_mult": 0.5})
# Create retrievers using the pre-loaded models from our ml_models dictionary
keyword_retriever = BM25Retriever.from_documents(chunks)
keyword_retriever.k = 5
# dense_retriever = Chroma.from_documents(documents=chunks, embedding=ml_models["embedder"]).as_retriever()
ensemble_retriever = EnsembleRetriever(retrievers=[keyword_retriever, dense_retriever], weights=[0.35, 0.65])
### to make it faster we are now using our built reranker thats why commenting the code below
compression_retriever = ContextualCompressionRetriever(
base_retriever=ensemble_retriever, base_compressor=ml_models["reranker_compressor"]
)
# Define the RAG chain using pre-loaded components
hybrid_rag_chain = (
{"context": itemgetter("full_query") | compression_retriever, "full_query": itemgetter("full_query")}
| ml_models["prompt_template"]
| ml_models["llm"]
)
tasks = [hybrid_rag_chain.ainvoke({"full_query": q}) for q in req.questions]
results = await asyncio.gather(*tasks)
# answers = [parse_llm_response(result.content) for result in results]
answers = []
for msg in results:
# Safely access the content field
if hasattr(msg, "content"):
answers.append(msg.content.strip())
return JSONResponse({"answers": answers}, status_code=200)
@app.get("/", include_in_schema=False)
def root():
return {"message": "API is running. Go to /docs for documentation."}
|