File size: 14,125 Bytes
eb9ec17
9a1a8d3
 
0a7f715
1f2b26b
9a1a8d3
 
6824985
b54e384
 
 
9a1a8d3
 
8ca2283
c6f2a8c
 
ccf254d
6824985
8ca2283
9a1a8d3
ccf254d
 
6824985
 
5225cff
8ca2283
 
 
ccf254d
8ca2283
cdbf2f9
569b7b7
 
8ca2283
0fb8c62
 
b38767f
6e0eba1
0fb8c62
 
 
d128292
0fb8c62
 
ccf254d
0fb8c62
ccf254d
9a1a8d3
 
8532f92
9a1a8d3
b54e384
b38767f
35e1354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b38767f
4f4ee5f
 
9a1a8d3
 
6824985
e616961
 
e5e735f
8ca2283
 
6824985
 
84ee692
 
fcd02f3
84ee692
 
4f4ee5f
 
b38767f
569b7b7
b38767f
ca17f53
b38767f
f87f3b0
 
 
 
569b7b7
ccf254d
 
8ca2283
b38767f
874a60a
b38767f
55f4347
06c501c
ccf254d
 
b38767f
 
cdbf2f9
f22caa3
6036aa5
a4649e2
d3e01cd
 
f22caa3
 
 
 
 
 
 
 
40a7274
f22caa3
d3e01cd
f22caa3
0cb056b
6036aa5
d3e01cd
 
abd3cce
d3e01cd
6036aa5
d3e01cd
 
a4649e2
 
4e2ad8d
a4649e2
 
 
 
4e2ad8d
8ca2283
cdbf2f9
4f4ee5f
 
6824985
57b098a
8ca2283
57b098a
 
c6f2a8c
57b098a
c6f2a8c
ccf254d
c6f2a8c
 
b38767f
b54e384
85754bc
b54e384
 
 
 
 
 
 
 
 
 
ccf254d
c6f2a8c
557efda
c6f2a8c
 
 
 
ccf254d
b38767f
 
 
ccf254d
8ca2283
ce4334f
b38767f
c6f2a8c
 
b38767f
b54e384
 
b38767f
 
b54e384
b228dc0
b54e384
3c0a8ca
 
b38767f
 
 
abd3cce
b228dc0
7b17c7b
 
35e1354
 
 
 
 
 
 
 
 
 
 
b38767f
 
 
 
 
 
 
 
 
 
 
2858db1
 
71d7b29
2858db1
 
71d7b29
2858db1
 
71d7b29
2858db1
 
71d7b29
2858db1
 
b38767f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35e1354
 
 
 
 
 
b38767f
35e1354
b54e384
 
 
b38767f
b54e384
 
b38767f
b54e384
b38767f
b54e384
b38767f
b54e384
b38767f
b54e384
 
 
b38767f
b54e384
b38767f
 
b54e384
 
b38767f
 
b54e384
 
 
b38767f
 
b54e384
 
 
 
 
b38767f
b54e384
b38767f
b54e384
b38767f
 
 
 
b54e384
 
 
 
b38767f
b54e384
 
 
 
 
 
 
 
b38767f
b54e384
 
 
 
ccf254d
b54e384
ce4334f
c6f2a8c
 
b38767f
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
206
207
208
209
210
211
212
213
214
215
216
217
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
import os
import json
import re
import time
import asyncio
from contextlib import asynccontextmanager
from dotenv import load_dotenv
from operator import itemgetter
import requests
from bs4 import BeautifulSoup

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
# from langchain_nvidia_ai_endpoints.embeddings import NVIDIAEmbeddings
# from langchain_nvidia_ai_endpoints.reranking import  NVIDIARerank

from sentence_transformers import SentenceTransformer

#loading the model for SentenceTransformersTokenTextSplitter
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()

vector_cache = {}
ml_models = {}
secret = ""

landmark_data = {
    "Delhi": "Gateway of India", "Mumbai": "India Gate", "Chennai": "Charminar",
    "Hyderabad": "Taj Mahal", "Ahmedabad": "Howrah Bridge", "Mysuru": "Golconda Fort",
    "Kochi": "Qutub Minar", "Pune": "Golden Temple", "Nagpur": "Lotus Temple",
    "Chandigarh": "Mysore Palace", "Kerala": "Rock Garden", "Bhopal": "Victoria Memorial",
    "Varanasi": "Vidhana Soudha", "Jaisalmer": "Sun Temple", "New York": "Eiffel Tower",
    "London": "Sydney Opera House", "Tokyo": "Big Ben", "Beijing": "Colosseum",
    "Bangkok": "Christ the Redeemer", "Toronto": "Burj Khalifa", "Dubai": "CN Tower",
    "Amsterdam": "Petronas Towers", "Cairo": "Leaning Tower of Pisa",
    "San Francisco": "Mount Fuji", "Berlin": "Niagara Falls", "Barcelona": "Louvre Museum",
    "Moscow": "Stonehenge", "Seoul": "Times Square", "Cape Town": "Acropolis",
    "Istanbul": "Big Ben", "Riyadh": "Machu Picchu", "Paris": "Taj Mahal",
    "Dubai Airport": "Moai Statues", "Singapore": "Christchurch Cathedral",
    "Jakarta": "The Shard", "Vienna": "Blue Mosque", "Kathmandu": "Neuschwanstein Castle",
    "Los Angeles": "Buckingham Palace"
}



@asynccontextmanager
async def lifespan(app: FastAPI):
    print("🚀 Initializing models and prompt template...")

    try:
        GOOGLE_API_KEY = os.getenv("gemini_api_key3")
        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!")
        # nvidia_api_key = os.getenv("nvidia_api_key")
        # print("🔑 nvidia:", "FOUND" if nvidia_api_key else "NOT FOUND")

        # if not nvidia_api_key:
        #     raise RuntimeError("CRITICAL: Missing nvidia  api key in environment secrets!")

        
        # Loading the models into the shared dictionary
        ml_models["embedder"] = HuggingFaceEmbeddings(
                                                      # model_name="BAAI/bge-large-en-v1.5", #better but lil more slower
                                                      model_name="BAAI/bge-base-en-v1.5", #better but lil slower
                                                      # model_name="intfloat/e5-large-v2",
                                                      # encode_kwargs={
                                                      #     "batch_size": 64,
                                                      #     # "normalize_embeddings": True
                                                      # }
        # ml_models["embedder"] = NVIDIAEmbeddings(model="nvidia/nv-embedqa-e5-v5", nvidia_api_key=nvidia_api_key)
                                                    
    )
        cross_encoder_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base")
        
        ml_models["reranker_compressor"] = CrossEncoderReranker(model=cross_encoder_model, top_n=9)
        
        ml_models["llm"] = ChatGoogleGenerativeAI(
                               model="gemini-2.5-flash", #using flash 2.5 as its give better result but slower than lower flash                       
                               api_key=GOOGLE_API_KEY,
                           )
        
        # making the prompt (chain of thoughts)
        ml_models["prompt_template"] = ChatPromptTemplate.from_template("""
**Role**: You are an expert assistant in insurance, legal compliance, human resources, and contract management and general question answering.

**Instructions**:

Step 1 – **Initial Draft**:
- If the query contains multiple questions, split them into  perfect sub-questions.
- Use ONLY the provided context to answer.
- Provide one concise, complete sentence per sub-question.
- List answers in the same order as the sub-questions, without repeating the query text.
- Do not add numbering or bullet points; separate answers with a single space.
- Make the answer well structured and with proper starting like a human is answering it.
- Make grammatically correct sentence, improving phrasing and spelling.
- Avoid phrases like “the provided document states” or “according to the context.”
- Do NOT use line breakers ("/n" ,"\" and "/") in between the answers.
- Summarize relevant parts of the context without losing meaning.
- Avoid boilerplate phrases like “the document states” or “according to the context.”
- If the answer is not in the context for some subqueries, respond exactly with: " I do not know the answer of "subquery",Please ask query related to the Document only." for that subquery.
- Make sure that the You answer  the query in the same language in which the query is asked.

Step 2 – **Critique & Revise**:
- Review the initial answers for any missing or underused context.
- Revise responses to improve accuracy, completeness, grammar and clarity based on the context.
- Maintain a professional and domain-appropriate tone.

Step 3 – **Final Output**:
- Present the revised and cohesive set of responses.
---
**Context**:
{context}
---
**Query**:
{full_query}
---
**Response**:
"""
)

        
        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 ---
app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)

# for realtime authorization
def store_secret(url: str):
    global secret
    url_c = url
    r = requests.get(url_c)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")
    token = (soup.find(id="token") or soup).get_text(strip=True)
    m = re.search(r"[0-9a-fA-F]{64}", token)
    token = m.group(0) if m else token
    secret = token


# --- 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]

    # 1st for initial team token , 2nd for real time token
    if token != TEAM_API_KEY and token != secret:
        raise HTTPException(status_code=403, detail="Invalid or missing API key")



# --- 4. Main API Endpoint ---
@app.post("/api/v1/hackrx/run", response_model=RunResponse, dependencies=[Depends(verify_api_key)])
async def run_hackrx(req: RunRequest):
    
    doc_url = str(req.documents)
    lower_url = doc_url.lower()

    # for setting the secret-token in realtime
    if "get-secret-token" in lower_url:
        print(doc_url)
        store_secret(doc_url)
        answers = []
        return JSONResponse({"answers": answers}, status_code=200)
   
    # for flight problem (trying to bring Sachin ji back to the real world ... he should not have slept!!! because now we are not able to sleep )
    
    elif "FinalRound4SubmissionPDF.pdf" in doc_url:
        print(doc_url)
        for q in req.questions:
            print(q)
        try:
            city_url = "https://register.hackrx.in/submissions/myFavouriteCity"
            city_response = requests.get(city_url)
            city_response.raise_for_status()
            
            # Parse city from nested JSON
            data = city_response.json()
            assigned_city = data.get("data", {}).get("city")
            if not assigned_city:
                raise HTTPException(status_code=500, detail="City not found in response")
        
            print(f"Assigned city is: {assigned_city}")
            
            landmark = landmark_data.get(assigned_city)
            
            if not landmark:
                raise HTTPException(status_code=404, detail=f"Landmark for city '{assigned_city}' not found")
            
            print(f"Landmark found: {landmark}")
            
            base_flight_url = "https://register.hackrx.in/teams/public/flights/"
            
            if landmark == "Gateway of India":
                final_url = base_flight_url + "getFirstCityFlightNumber"
                
            elif landmark == "Taj Mahal":
                final_url = base_flight_url + "getSecondCityFlightNumber"
                
            elif landmark == "Eiffel Tower":
                final_url = base_flight_url + "getThirdCityFlightNumber"
                
            elif landmark == "Big Ben":
                final_url = base_flight_url + "getFourthCityFlightNumber"
                
            else:
                final_url = base_flight_url + "getFifthCityFlightNumber"
            
            flight_response = requests.get(final_url)
            flight_response.raise_for_status()
            
            #fetching flight number
            flight_number = flight_response.json().get("data", {}).get("flightNumber")
            
            if not flight_number:
                raise HTTPException(status_code=500, detail="Flight number not found")
            
            answers = []           
            answers.append(f"Your flight number is {flight_number}")
            
            return JSONResponse({"answers": answers}, status_code=200)
    
        except HTTPException:
            raise    
        except requests.exceptions.RequestException as e:
            raise HTTPException(status_code=500, detail=f"An API call failed: {e}")
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {e}")

    
    else:       
        start_time = time.time()
        chunks = load_and_chunk(str(req.documents))
        
        if not chunks:
            return JSONResponse({"error": "No documents could be processed."}, status_code=400)    
            
        end_time = time.time() - start_time
        
        print(f"chunking done: {end_time}")
             
        start_time2 = time.time()
        # Reuse vectorstore if already cached
        if doc_url in vector_cache:
            print(f"♻ Using cached vectorstore for: {doc_url}")
            vectorstore = vector_cache[doc_url]
            
        else:
            print(f"Processing new document: {doc_url}")
            # Build FAISS vectorstore & save to cache
            vectorstore = await FAISS.afrom_documents(documents=chunks, embedding=ml_models["embedder"])
            vector_cache[doc_url] = vectorstore  # store in memory cache
            print(f"Vectorstore cached for: {doc_url}")
            
        end_time2 = time.time() - start_time2
        print(f"vector done: {end_time2}")

        dense_retriever = vectorstore.as_retriever(search_type="mmr",search_kwargs={"k": 14 ,"lambda_mult": 0.7} ) # prev  used 16
        # dense_retriever = vectorstore.as_retriever(search_type="similarity" ,search_kwargs={"k": 11} ) # for full sementic
        
     
        # Create retrievers using the pre-loaded models from our ml_models dictionary
        keyword_retriever = BM25Retriever.from_documents(chunks)
        keyword_retriever.k = 9 #prev 11
        
        # dense_retriever = Chroma.from_documents(documents=chunks, embedding=ml_models["embedder"]).as_retriever()
        
        ensemble_retriever = EnsembleRetriever(retrievers=[keyword_retriever, dense_retriever], weights=[0.3, 0.7],search_kwargs={"k": 14}) #prev 16

        # sadly commenting reranker as it take larger time in cpu but is using GPU make use of it
        #Also if using GPU chnage the ensemble retriver in rag chain to compression_retriever
        
        # compression_retriever = ContextualCompressionRetriever(
        #     base_retriever=ensemble_retriever, base_compressor=ml_models["reranker_compressor"]
        # )
     
        # RAG chain 
        hybrid_rag_chain = (
            {"context": itemgetter("full_query") | ensemble_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 = []
        for msg in results:
            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."}