absiitr commited on
Commit
6663d87
Β·
verified Β·
1 Parent(s): fc6ccf0

Update backend.py

Browse files
Files changed (1) hide show
  1. backend.py +40 -81
backend.py CHANGED
@@ -5,201 +5,160 @@ import logging
5
  from fastapi import FastAPI, UploadFile, File, HTTPException
6
  from pydantic import BaseModel
7
  import torch
8
- from dotenv import load_dotenv # Used to load API key from .env file
9
 
10
- # ---------------- Groq API ----------------
11
  from groq import Groq, APIError
12
 
13
- # ---------------- LangChain ----------------
14
  from langchain_community.document_loaders import PyPDFLoader
15
  from langchain_text_splitters import RecursiveCharacterTextSplitter
16
  from langchain_community.embeddings import HuggingFaceEmbeddings
17
  from langchain_community.vectorstores import Chroma
18
 
19
- # --- Configuration & Setup ---
20
  logging.basicConfig(level=logging.INFO)
21
-
22
- # 1. Load environment variables from .env file
23
  load_dotenv()
24
 
25
- # 2. Load API Key from Environment Variable
26
- GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
27
  GROQ_MODEL = "llama-3.1-8b-instant"
28
 
29
- # 3. Initialize Groq Client
30
  client = None
31
- if not GROQ_API_KEY:
32
- logging.error(
33
- "❌ GROQ_API_KEY is not set in the environment or the .env file. The service will run but cannot answer questions.")
34
- else:
35
  try:
36
  client = Groq(api_key=GROQ_API_KEY)
37
- logging.info("βœ… Groq client initialized successfully.")
38
  except Exception as e:
39
- logging.error(f"❌ Failed to initialize Groq client: {e}")
40
- client = None
41
 
42
  app = FastAPI()
43
 
44
- # Global state for RAG components
45
  retriever = None
46
  vectorstore = None
47
 
48
 
49
- # ---------------- Input Schema ----------------
50
  class Query(BaseModel):
51
  question: str
52
 
53
 
54
  # ==================================================
55
- # PDF Upload β†’ Chunk β†’ Embed β†’ Vectorstore
56
  # ==================================================
57
- @app.post("/upload")
58
  async def upload_pdf(file: UploadFile = File(...)):
59
- """Handles PDF upload, processing, chunking, embedding, and vectorstore creation."""
60
  global retriever, vectorstore
61
 
62
  if not file.filename.endswith(".pdf"):
63
  raise HTTPException(400, "Only PDF files allowed")
64
 
65
  if not client:
66
- raise HTTPException(500, "Service not fully initialized. Groq API key is missing or invalid.")
67
 
68
  path = None
69
  try:
70
- # 1. Save file temporarily
71
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
72
  tmp.write(await file.read())
73
  path = tmp.name
74
 
75
- logging.info(f"Processing PDF: {path}")
76
-
77
- # 2. Load
78
  loader = PyPDFLoader(path)
79
  docs = loader.load()
80
 
81
- # 3. Split
82
  splitter = RecursiveCharacterTextSplitter(
83
  chunk_size=800,
84
  chunk_overlap=50
85
  )
86
  chunks = splitter.split_documents(docs)
87
 
88
- # 4. Embeddings (Using CPU-friendly model)
89
  embeddings = HuggingFaceEmbeddings(
90
  model_name="sentence-transformers/all-MiniLM-L6-v2",
91
  model_kwargs={"device": "cpu"},
92
  encode_kwargs={"normalize_embeddings": True}
93
  )
94
 
95
- # 5. Clear previous vectorstore to free memory
96
  if vectorstore:
97
  del vectorstore
98
  gc.collect()
99
 
100
- # 6. Create Vectorstore and Retriever
101
  vectorstore = Chroma.from_documents(chunks, embeddings)
102
- # Search for 3 most relevant chunks
103
  retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
104
 
105
- logging.info(f"PDF processed. Chunks created: {len(chunks)}")
106
-
107
  return {"message": "PDF processed", "chunks": len(chunks)}
108
 
109
  except Exception as e:
110
- logging.error(f"Error during PDF processing: {e}")
111
- raise HTTPException(500, f"Error: {str(e)}")
112
  finally:
113
- # 7. Cleanup temp file and memory
114
  if path and os.path.exists(path):
115
  os.unlink(path)
116
  gc.collect()
117
 
118
 
119
  # ==================================================
120
- # ASK β†’ RETRIEVE β†’ GROQ β†’ ANSWER
121
  # ==================================================
122
- @app.post("/ask")
123
  async def ask(req: Query):
124
- global retriever
125
-
126
- if client is None:
127
- raise HTTPException(500, "Groq client is not initialized. Check API key setup.")
128
-
129
- if retriever is None:
130
- raise HTTPException(400, "Upload PDF first to initialize the knowledge base.")
131
 
132
  try:
133
- # 1. Retrieve relevant chunks (NEW LangChain API)
134
  docs = retriever.invoke(req.question)
135
-
136
  context = "\n\n".join(d.page_content for d in docs)
137
 
138
- # 2. Build prompt
139
  prompt = f"""
140
- You are a strict RAG Q&A assistant.
141
- Use ONLY the context provided. If the answer is not found, reply:
142
- "I cannot find this in the PDF."
143
 
144
- ---------------- CONTEXT ----------------
145
  {context}
146
- -----------------------------------------
147
 
148
  QUESTION: {req.question}
149
 
150
- FINAL ANSWER:
151
  """
152
 
153
- # 3. Call Groq
154
  response = client.chat.completions.create(
155
  model=GROQ_MODEL,
156
  messages=[
157
- {"role": "system",
158
- "content": "Use only the PDF content. If answer not found, say: 'I cannot find this in the PDF.'"},
159
  {"role": "user", "content": prompt}
160
  ],
161
  temperature=0.0
162
  )
163
 
164
- answer = response.choices[0].message.content.strip()
165
- return {"answer": answer, "sources": len(docs)}
 
 
166
 
167
  except APIError as e:
168
- logging.error(f"Groq API Error: {e}")
169
- raise HTTPException(500, f"Groq API Error: {str(e)}")
170
-
171
- except Exception as e:
172
- logging.error(f"General error in /ask: {e}")
173
- raise HTTPException(500, f"General error: {str(e)}")
174
 
175
 
176
  # ==================================================
177
- # HEALTH & CLEAR
178
  # ==================================================
179
- @app.get("/health")
180
- async def health():
181
- """Endpoint for checking service status."""
182
- return {
183
- "status": "running",
184
- "pdf_loaded": retriever is not None,
185
- "groq_client_ok": client is not None
186
- }
187
-
188
-
189
- @app.post("/clear")
190
  async def clear():
191
- """Clears the current RAG components from memory."""
192
  global retriever, vectorstore
193
 
194
- # Explicitly clear objects
195
  if vectorstore:
196
  del vectorstore
197
  retriever = None
198
  vectorstore = None
199
 
200
  gc.collect()
201
- # Clear CUDA cache if running on a machine with a GPU (good practice)
202
  if torch.cuda.is_available():
203
  torch.cuda.empty_cache()
204
 
205
- return {"message": "Memory cleared. Upload a new PDF."}
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from fastapi import FastAPI, UploadFile, File, HTTPException
6
  from pydantic import BaseModel
7
  import torch
8
+ from dotenv import load_dotenv
9
 
 
10
  from groq import Groq, APIError
11
 
 
12
  from langchain_community.document_loaders import PyPDFLoader
13
  from langchain_text_splitters import RecursiveCharacterTextSplitter
14
  from langchain_community.embeddings import HuggingFaceEmbeddings
15
  from langchain_community.vectorstores import Chroma
16
 
17
+ # ---------------- Setup ----------------
18
  logging.basicConfig(level=logging.INFO)
 
 
19
  load_dotenv()
20
 
21
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
 
22
  GROQ_MODEL = "llama-3.1-8b-instant"
23
 
 
24
  client = None
25
+ if GROQ_API_KEY:
 
 
 
26
  try:
27
  client = Groq(api_key=GROQ_API_KEY)
28
+ logging.info("βœ… Groq client initialized")
29
  except Exception as e:
30
+ logging.error(f"Groq init failed: {e}")
 
31
 
32
  app = FastAPI()
33
 
 
34
  retriever = None
35
  vectorstore = None
36
 
37
 
 
38
  class Query(BaseModel):
39
  question: str
40
 
41
 
42
  # ==================================================
43
+ # PDF Upload
44
  # ==================================================
45
+ @app.post("/api/upload")
46
  async def upload_pdf(file: UploadFile = File(...)):
 
47
  global retriever, vectorstore
48
 
49
  if not file.filename.endswith(".pdf"):
50
  raise HTTPException(400, "Only PDF files allowed")
51
 
52
  if not client:
53
+ raise HTTPException(500, "Groq API key missing")
54
 
55
  path = None
56
  try:
 
57
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
58
  tmp.write(await file.read())
59
  path = tmp.name
60
 
 
 
 
61
  loader = PyPDFLoader(path)
62
  docs = loader.load()
63
 
 
64
  splitter = RecursiveCharacterTextSplitter(
65
  chunk_size=800,
66
  chunk_overlap=50
67
  )
68
  chunks = splitter.split_documents(docs)
69
 
 
70
  embeddings = HuggingFaceEmbeddings(
71
  model_name="sentence-transformers/all-MiniLM-L6-v2",
72
  model_kwargs={"device": "cpu"},
73
  encode_kwargs={"normalize_embeddings": True}
74
  )
75
 
 
76
  if vectorstore:
77
  del vectorstore
78
  gc.collect()
79
 
 
80
  vectorstore = Chroma.from_documents(chunks, embeddings)
 
81
  retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
82
 
 
 
83
  return {"message": "PDF processed", "chunks": len(chunks)}
84
 
85
  except Exception as e:
86
+ raise HTTPException(500, str(e))
87
+
88
  finally:
 
89
  if path and os.path.exists(path):
90
  os.unlink(path)
91
  gc.collect()
92
 
93
 
94
  # ==================================================
95
+ # Ask Question
96
  # ==================================================
97
+ @app.post("/api/ask")
98
  async def ask(req: Query):
99
+ if not retriever:
100
+ raise HTTPException(400, "Upload PDF first")
 
 
 
 
 
101
 
102
  try:
 
103
  docs = retriever.invoke(req.question)
 
104
  context = "\n\n".join(d.page_content for d in docs)
105
 
 
106
  prompt = f"""
107
+ Use ONLY the context below.
108
+ If answer not found, say: "I cannot find this in the PDF."
 
109
 
110
+ CONTEXT:
111
  {context}
 
112
 
113
  QUESTION: {req.question}
114
 
115
+ ANSWER:
116
  """
117
 
 
118
  response = client.chat.completions.create(
119
  model=GROQ_MODEL,
120
  messages=[
121
+ {"role": "system", "content": "Answer strictly from PDF context"},
 
122
  {"role": "user", "content": prompt}
123
  ],
124
  temperature=0.0
125
  )
126
 
127
+ return {
128
+ "answer": response.choices[0].message.content.strip(),
129
+ "sources": len(docs)
130
+ }
131
 
132
  except APIError as e:
133
+ raise HTTPException(500, str(e))
 
 
 
 
 
134
 
135
 
136
  # ==================================================
137
+ # Clear Memory
138
  # ==================================================
139
+ @app.post("/api/clear")
 
 
 
 
 
 
 
 
 
 
140
  async def clear():
 
141
  global retriever, vectorstore
142
 
 
143
  if vectorstore:
144
  del vectorstore
145
  retriever = None
146
  vectorstore = None
147
 
148
  gc.collect()
 
149
  if torch.cuda.is_available():
150
  torch.cuda.empty_cache()
151
 
152
+ return {"message": "Memory cleared"}
153
+
154
+
155
+ # ==================================================
156
+ # Health
157
+ # ==================================================
158
+ @app.get("/api/health")
159
+ async def health():
160
+ return {
161
+ "status": "running",
162
+ "pdf_loaded": retriever is not None,
163
+ "groq_client_ok": client is not None
164
+ }