singhankur01 commited on
Commit
eb9ec17
·
verified ·
1 Parent(s): 4cc7474

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +167 -0
  2. requirements (1).txt +18 -0
app.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import re
4
+ from contextlib import asynccontextmanager
5
+ from dotenv import load_dotenv
6
+ from operator import itemgetter
7
+
8
+ import gradio as gr
9
+ from fastapi import FastAPI, Depends, HTTPException, Header
10
+ from fastapi.responses import JSONResponse
11
+
12
+ # Make sure you have these files in a 'utils' folder
13
+ from utils.DocsLoader import load_and_chunk
14
+ from utils.Schemas import RunRequest, RunResponse
15
+
16
+ from langchain_google_genai import ChatGoogleGenerativeAI
17
+ from langchain_huggingface import HuggingFaceEmbeddings # Correct new import
18
+ from langchain_chroma import Chroma
19
+ from langchain_community.retrievers import BM25Retriever
20
+ from langchain.retrievers import EnsembleRetriever, ContextualCompressionRetriever
21
+ from langchain.retrievers.document_compressors import CrossEncoderReranker
22
+ from langchain_community.cross_encoders import HuggingFaceCrossEncoder
23
+ from langchain.prompts import PromptTemplate
24
+
25
+
26
+ # Load environment variables
27
+ load_dotenv()
28
+
29
+ # --- 1. Lifespan Event Handler (The New, Correct Way) ---
30
+ # This dictionary will hold our loaded models
31
+ ml_models = {}
32
+
33
+ @asynccontextmanager
34
+ async def lifespan(app: FastAPI):
35
+ # This code runs ONCE when the application starts up
36
+ print("🚀 Initializing models and prompt template...")
37
+
38
+ # Use a consistent key name that you set in Hugging Face Secrets
39
+ GOOGLE_API_KEY = os.getenv("gemini_api_key")
40
+ if not GOOGLE_API_KEY:
41
+ raise RuntimeError("CRITICAL: Missing GOOGLE_API_KEY in environment secrets!")
42
+
43
+ # Load models into the shared dictionary
44
+ ml_models["embedder"] = HuggingFaceEmbeddings(model_name="BAAI/bge-base-en-v1.5")
45
+ cross_encoder_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-large")
46
+ ml_models["reranker_compressor"] = CrossEncoderReranker(model=cross_encoder_model, top_n=5)
47
+ ml_models["llm"] = ChatGoogleGenerativeAI(model="gemini-1.5-flash", api_key=GOOGLE_API_KEY)
48
+ ml_models["prompt_template"] = PromptTemplate.from_template(
49
+ """
50
+ You are an expert decision maker Assistant in the domain such as insurance, legal compliance, human resources, and contract management.
51
+
52
+ The customer has submitted a query. If the query has details about age , gender ,procedure ,location , policy duration, then parse it and understand the query properly
53
+ If not, the raw question is provided instead:
54
+ - Query: {full_query}
55
+
56
+ We retrieved the following policy clauses and rules relevant to this case:
57
+ {context}
58
+
59
+ ### Task:
60
+ If the details about age , gender , procedure , location , policy duration are available, do all of the following:
61
+ 1. Decide whether the procedure is covered.
62
+ 2. Estimate the claimable amount.
63
+ 3. Justify with the relevant clause.
64
+ and answer the query precisely as insurance agent.
65
+
66
+ Otherwise, answer the question concisely and clearly using the retrieved context.
67
+
68
+ ### Output format:
69
+ If query involes age , gender , procedure , location , policy duration answer like below:
70
+ {{
71
+ "decision": "approved / rejected",
72
+ "amount": "INR amount or null",
73
+ "justification": "Refer to specific clause"
74
+ Make it a perfect and concise.
75
+ }}
76
+ Else:
77
+ {{
78
+ "response": "Concise natural language answer"
79
+ Make it a perfect and concise.
80
+ }}
81
+
82
+ ##NOTE : Do not mention document id or its page number just mention clauses if applicable .
83
+ """
84
+ )
85
+ print("✅ Models and prompt loaded successfully!")
86
+
87
+ yield
88
+
89
+ # Code below yield runs on shutdown (optional)
90
+ print("Shutting down and cleaning up.")
91
+ ml_models.clear()
92
+
93
+ # --- 2. FastAPI App Instance ---
94
+ # We pass the lifespan function to the FastAPI constructor
95
+ app = FastAPI(title="HackRX RAG Server", lifespan=lifespan)
96
+
97
+ # --- 3. API Key Verification ---
98
+ TEAM_API_KEY = os.getenv("TEAM_API_KEY")
99
+ def verify_api_key(authorization: str = Header(...)):
100
+ if not authorization.startswith("Bearer "):
101
+ raise HTTPException(status_code=401, detail="Invalid Authorization header format")
102
+ token = authorization.split("Bearer ")[1]
103
+ if token != TEAM_API_KEY:
104
+ raise HTTPException(status_code=403, detail="Invalid or missing API key")
105
+
106
+ # --- 4. Parsing Helper ---
107
+ def parse_llm_response(content: str) -> str:
108
+ try:
109
+ # Remove code fences and clean up
110
+ content_cleaned = re.sub(r"^```json|```$", "", content.strip(), flags=re.IGNORECASE).strip()
111
+ data = json.loads(content_cleaned)
112
+
113
+ if isinstance(data, dict):
114
+ if "decision" in data:
115
+ decision = data.get("decision", "N/A").upper()
116
+ amount = data.get("amount", "Not specified")
117
+ justification = data.get("justification", "No justification provided.")
118
+ return f"Decision: {decision}\nAmount: {amount}\nJustification: {justification}"
119
+
120
+ elif "response" in data:
121
+ return data["response"]
122
+
123
+ return "The response was parsed but didn't match expected structure."
124
+
125
+ except json.JSONDecodeError:
126
+ return f"Unstructured response:\n{content.strip()}"
127
+
128
+ except Exception as e:
129
+ return f"An error occurred while processing the response: {str(e)}"
130
+
131
+
132
+ # --- 5. Main API Endpoint ---
133
+ @app.post("/api/v1/hackrx/run", response_model=RunResponse, dependencies=[Depends(verify_api_key)])
134
+ async def run_hackrx(req: RunRequest):
135
+ chunks = load_and_chunk(str(req.documents))
136
+ if not chunks:
137
+ return JSONResponse({"error": "No documents could be processed."}, status_code=400)
138
+
139
+ # Create retrievers using the pre-loaded models from our ml_models dictionary
140
+ keyword_retriever = BM25Retriever.from_documents(chunks)
141
+ dense_retriever = Chroma.from_documents(documents=chunks, embedding=ml_models["embedder"]).as_retriever()
142
+ ensemble_retriever = EnsembleRetriever(retrievers=[keyword_retriever, dense_retriever], weights=[0.3, 0.7])
143
+
144
+ compression_retriever = ContextualCompressionRetriever(
145
+ base_retriever=ensemble_retriever, base_compressor=ml_models["reranker_compressor"]
146
+ )
147
+
148
+ # Define the RAG chain using pre-loaded components
149
+ hybrid_rag_chain = (
150
+ {"context": itemgetter("full_query") | compression_retriever, "full_query": itemgetter("full_query")}
151
+ | ml_models["prompt_template"]
152
+ | ml_models["llm"]
153
+ )
154
+
155
+ answers = []
156
+ for q in req.questions:
157
+ try:
158
+ result = await hybrid_rag_chain.ainvoke({"full_query": q})
159
+ parsed = parse_llm_response(result.content)
160
+ answers.append(parsed)
161
+ except Exception as e:
162
+ return JSONResponse({"error": str(e)}, status_code=500)
163
+
164
+ return JSONResponse({"answers": answers}, status_code=200)
165
+
166
+ # --- 6. Mount the FastAPI app using Gradio ---
167
+ demo = gr.mount_gradio_app(app, gr.Blocks(), path="/")
requirements (1).txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ huggingface_hub==0.25.2
2
+ fastapi
3
+ uvicorn
4
+ python-dotenv
5
+ requests
6
+ pydantic
7
+ pinecone
8
+ langchain
9
+ langchain-community
10
+ langchain-huggingface
11
+ sentence-transformers
12
+ docx2txt
13
+ pypdf
14
+ langchain-google-genai
15
+ chromadb
16
+ langchain_chroma
17
+ langchain_core
18
+ rank_bm25