singhankur01 commited on
Commit
9a1a8d3
·
verified ·
1 Parent(s): 40bb5df

Update app.py

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