singhankur01 commited on
Commit
ccf254d
·
verified ·
1 Parent(s): 5cc8abb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -47
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import json
3
  import re
@@ -6,32 +7,38 @@ from contextlib import asynccontextmanager
6
  from dotenv import load_dotenv
7
  from operator import itemgetter
8
 
 
9
  from fastapi import FastAPI, Depends, HTTPException, Header
10
  from fastapi.responses import JSONResponse
11
 
12
  from utils.DocsLoader import load_and_chunk
13
  from utils.Schemas import RunRequest, RunResponse
14
-
15
  from langchain_community.vectorstores import FAISS
16
  from langchain.schema import Document
17
  from langchain_google_genai import ChatGoogleGenerativeAI
18
- from langchain_huggingface import HuggingFaceEmbeddings
 
19
  from langchain_community.retrievers import BM25Retriever
20
  from langchain.retrievers import EnsembleRetriever
21
  from sklearn.metrics.pairwise import cosine_similarity
22
  import numpy as np
23
 
24
  from langchain.retrievers import ContextualCompressionRetriever
25
- from langchain.retrievers.document_compressors import CrossEncoderReranker
26
  from langchain_community.cross_encoders import HuggingFaceCrossEncoder
27
  from langchain.prompts import ChatPromptTemplate
28
 
 
 
29
  load_dotenv()
30
 
 
31
  ml_models = {}
32
 
33
  @asynccontextmanager
34
  async def lifespan(app: FastAPI):
 
35
  print("🚀 Initializing models and prompt template...")
36
 
37
  try:
@@ -40,21 +47,24 @@ async def lifespan(app: FastAPI):
40
 
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={"batch_size": 64}
47
- )
48
-
 
 
 
49
  cross_encoder_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base")
 
50
  ml_models["reranker_compressor"] = CrossEncoderReranker(model=cross_encoder_model, top_n=5)
51
  ml_models["llm"] = ChatGoogleGenerativeAI(
52
- model="gemini-1.5-pro",
53
- api_key=GOOGLE_API_KEY,
54
- temperature=0.1,
55
- max_output_tokens=300
56
- )
57
-
58
  ml_models["prompt_template"] = ChatPromptTemplate.from_template("""
59
  **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.
60
  Do not use your own knowledge.
@@ -80,7 +90,6 @@ Example how to answer for thr query:
80
 
81
  """
82
  )
83
-
84
  print("✅ Models and prompt loaded successfully!")
85
  except Exception as e:
86
  print("❌ Lifespan error:", str(e))
@@ -89,9 +98,11 @@ Example how to answer for thr query:
89
  yield
90
  print("🧹 Cleaning up.")
91
  ml_models.clear()
92
-
 
93
  app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)
94
 
 
95
  TEAM_API_KEY = os.getenv("TEAM_API_KEY")
96
 
97
  def verify_api_key(authorization: str = Header(...)):
@@ -101,8 +112,11 @@ def verify_api_key(authorization: str = Header(...)):
101
  if token != TEAM_API_KEY:
102
  raise HTTPException(status_code=403, detail="Invalid or missing API key")
103
 
 
 
104
  def parse_llm_response(content: str) -> str:
105
  try:
 
106
  content_cleaned = re.sub(r"^```json|```$", "", content.strip(), flags=re.IGNORECASE).strip()
107
  data = json.loads(content_cleaned)
108
 
@@ -112,54 +126,54 @@ def parse_llm_response(content: str) -> str:
112
  amount = data.get("amount", "Not specified")
113
  justification = data.get("justification", "No justification provided.")
114
  return f"Decision: {decision}\nAmount: {amount}\nJustification: {justification}"
 
115
  elif "response" in data:
116
  return data["response"]
117
 
118
  return "The response was parsed but didn't match expected structure."
 
119
  except json.JSONDecodeError:
120
  return f"Unstructured response:\n{content.strip()}"
 
121
  except Exception as e:
122
  return f"An error occurred while processing the response: {str(e)}"
123
 
124
- # --- Post-retrieval Filters ---
125
- def structure_filter(docs, min_length=30):
126
- return [doc for doc in docs if len(doc.page_content.strip()) >= min_length and not doc.page_content.isspace()]
127
-
128
- def semantic_filter(query, docs, embedder, threshold=0.45):
129
- query_emb = embedder.embed_query(query)
130
- doc_texts = [doc.page_content for doc in docs]
131
- doc_embs = embedder.embed_documents(doc_texts)
132
- sims = cosine_similarity([query_emb], doc_embs)[0]
133
- return [doc for doc, sim in zip(docs, sims) if sim >= threshold]
134
-
135
- def universal_filter(query, docs, embedder):
136
- docs = structure_filter(docs)
137
- docs = semantic_filter(query, docs, embedder)
138
- return docs if docs else docs # fallback to raw if filtering removes all
139
 
 
140
  @app.post("/api/v1/hackrx/run", response_model=RunResponse, dependencies=[Depends(verify_api_key)])
141
  async def run_hackrx(req: RunRequest):
142
  chunks = load_and_chunk(str(req.documents))
143
  if not chunks:
144
  return JSONResponse({"error": "No documents could be processed."}, status_code=400)
145
 
146
- vectorstore = await FAISS.afrom_documents(documents=chunks, embedding=ml_models["embedder"])
147
- dense_retriever = vectorstore.as_retriever(search_type="mmr", search_kwargs={"k": 8, "lambda_mult": 0.5})
 
 
 
 
 
 
 
 
148
  keyword_retriever = BM25Retriever.from_documents(chunks)
149
  keyword_retriever.k = 5
 
150
  ensemble_retriever = EnsembleRetriever(retrievers=[keyword_retriever, dense_retriever], weights=[0.35, 0.65])
151
-
152
- tasks = []
153
- for query in req.questions:
154
- raw_docs = await ensemble_retriever.ainvoke(query)
155
- filtered_docs = universal_filter(query, raw_docs, ml_models["embedder"])
156
- if not filtered_docs:
157
- filtered_docs = raw_docs # fallback
158
- reranked_docs = ml_models["reranker_compressor"].compress_documents(filtered_docs, query=query)
159
- context = "\n\n".join([doc.page_content for doc in reranked_docs])
160
- prompt_input = {"full_query": query, "context": context}
161
- tasks.append(ml_models["llm"].ainvoke(ml_models["prompt_template"].format_prompt(**prompt_input)))
162
-
 
 
163
  results = await asyncio.gather(*tasks)
164
  # answers = [parse_llm_response(result.content) for result in results]
165
  answers = []
@@ -167,7 +181,8 @@ async def run_hackrx(req: RunRequest):
167
  for msg in results:
168
  # Safely access the content field
169
  if hasattr(msg, "content"):
170
- answers.append(msg.content.strip())
 
171
  return JSONResponse({"answers": answers}, status_code=200)
172
 
173
  @app.get("/", include_in_schema=False)
 
1
+
2
  import os
3
  import json
4
  import re
 
7
  from dotenv import load_dotenv
8
  from operator import itemgetter
9
 
10
+ # import gradio as gr
11
  from fastapi import FastAPI, Depends, HTTPException, Header
12
  from fastapi.responses import JSONResponse
13
 
14
  from utils.DocsLoader import load_and_chunk
15
  from utils.Schemas import RunRequest, RunResponse
16
+ # from concurrent.futures import ThreadPoolExecutor
17
  from langchain_community.vectorstores import FAISS
18
  from langchain.schema import Document
19
  from langchain_google_genai import ChatGoogleGenerativeAI
20
+ from langchain_huggingface import HuggingFaceEmbeddings
21
+ # from langchain_chroma import Chroma
22
  from langchain_community.retrievers import BM25Retriever
23
  from langchain.retrievers import EnsembleRetriever
24
  from sklearn.metrics.pairwise import cosine_similarity
25
  import numpy as np
26
 
27
  from langchain.retrievers import ContextualCompressionRetriever
28
+ from langchain.retrievers.document_compressors import CrossEncoderReranker
29
  from langchain_community.cross_encoders import HuggingFaceCrossEncoder
30
  from langchain.prompts import ChatPromptTemplate
31
 
32
+
33
+ # Load environment variables
34
  load_dotenv()
35
 
36
+
37
  ml_models = {}
38
 
39
  @asynccontextmanager
40
  async def lifespan(app: FastAPI):
41
+ # This code runs ONCE when the application starts up
42
  print("🚀 Initializing models and prompt template...")
43
 
44
  try:
 
47
 
48
  if not GOOGLE_API_KEY:
49
  raise RuntimeError("CRITICAL: Missing GOOGLE_API_KEY in environment secrets!")
50
+
51
+ # Load models into the shared dictionary
52
+ ml_models["embedder"] = HuggingFaceEmbeddings(model_name="BAAI/bge-base-en-v1.5",
53
+ encode_kwargs={
54
+ "batch_size": 64,
55
+ # "normalize_embeddings": True
56
+ }
57
+
58
+ )
59
  cross_encoder_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base")
60
+ # cross_encoder_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-large")
61
  ml_models["reranker_compressor"] = CrossEncoderReranker(model=cross_encoder_model, top_n=5)
62
  ml_models["llm"] = ChatGoogleGenerativeAI(
63
+ model="gemini-1.5-pro",
64
+ api_key=GOOGLE_API_KEY,
65
+ temperature=0.1,
66
+ max_output_tokens=300
67
+ )
 
68
  ml_models["prompt_template"] = ChatPromptTemplate.from_template("""
69
  **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.
70
  Do not use your own knowledge.
 
90
 
91
  """
92
  )
 
93
  print("✅ Models and prompt loaded successfully!")
94
  except Exception as e:
95
  print("❌ Lifespan error:", str(e))
 
98
  yield
99
  print("🧹 Cleaning up.")
100
  ml_models.clear()
101
+ # --- 2. FastAPI App Instance ---
102
+ # We pass the lifespan function to the FastAPI constructor
103
  app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)
104
 
105
+ # --- 3. API Key Verification ---
106
  TEAM_API_KEY = os.getenv("TEAM_API_KEY")
107
 
108
  def verify_api_key(authorization: str = Header(...)):
 
112
  if token != TEAM_API_KEY:
113
  raise HTTPException(status_code=403, detail="Invalid or missing API key")
114
 
115
+
116
+ # --- 4. Parsing Helper ---
117
  def parse_llm_response(content: str) -> str:
118
  try:
119
+ # Remove code fences and clean up
120
  content_cleaned = re.sub(r"^```json|```$", "", content.strip(), flags=re.IGNORECASE).strip()
121
  data = json.loads(content_cleaned)
122
 
 
126
  amount = data.get("amount", "Not specified")
127
  justification = data.get("justification", "No justification provided.")
128
  return f"Decision: {decision}\nAmount: {amount}\nJustification: {justification}"
129
+
130
  elif "response" in data:
131
  return data["response"]
132
 
133
  return "The response was parsed but didn't match expected structure."
134
+
135
  except json.JSONDecodeError:
136
  return f"Unstructured response:\n{content.strip()}"
137
+
138
  except Exception as e:
139
  return f"An error occurred while processing the response: {str(e)}"
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ # --- 5. Main API Endpoint ---
143
  @app.post("/api/v1/hackrx/run", response_model=RunResponse, dependencies=[Depends(verify_api_key)])
144
  async def run_hackrx(req: RunRequest):
145
  chunks = load_and_chunk(str(req.documents))
146
  if not chunks:
147
  return JSONResponse({"error": "No documents could be processed."}, status_code=400)
148
 
149
+ vectorstore = await FAISS.afrom_documents(
150
+ documents=chunks,
151
+ embedding=ml_models["embedder"]
152
+ )
153
+
154
+ # dense_retriever = vectorstore.as_retriever(search_type="mmr",search_kwargs={"k": 8})
155
+ dense_retriever = vectorstore.as_retriever(search_type="mmr",search_kwargs={"k": 8 ,"lambda_mult": 0.5})
156
+
157
+
158
+ # Create retrievers using the pre-loaded models from our ml_models dictionary
159
  keyword_retriever = BM25Retriever.from_documents(chunks)
160
  keyword_retriever.k = 5
161
+ # dense_retriever = Chroma.from_documents(documents=chunks, embedding=ml_models["embedder"]).as_retriever()
162
  ensemble_retriever = EnsembleRetriever(retrievers=[keyword_retriever, dense_retriever], weights=[0.35, 0.65])
163
+ ### to make it faster we are now using our built reranker thats why commenting the code below
164
+ compression_retriever = ContextualCompressionRetriever(
165
+ base_retriever=ensemble_retriever, base_compressor=ml_models["reranker_compressor"]
166
+ )
167
+
168
+
169
+ # Define the RAG chain using pre-loaded components
170
+ hybrid_rag_chain = (
171
+ {"context": itemgetter("full_query") | compression_retriever, "full_query": itemgetter("full_query")}
172
+ | ml_models["prompt_template"]
173
+ | ml_models["llm"]
174
+ )
175
+
176
+ tasks = [hybrid_rag_chain.ainvoke({"full_query": q}) for q in req.questions]
177
  results = await asyncio.gather(*tasks)
178
  # answers = [parse_llm_response(result.content) for result in results]
179
  answers = []
 
181
  for msg in results:
182
  # Safely access the content field
183
  if hasattr(msg, "content"):
184
+ answers.append(msg.content.strip())
185
+
186
  return JSONResponse({"answers": answers}, status_code=200)
187
 
188
  @app.get("/", include_in_schema=False)