praneth02 commited on
Commit
17d4361
·
verified ·
1 Parent(s): 153cdda

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +307 -0
app.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import time
3
+ import pdfplumber
4
+ import torch
5
+ from sentence_transformers import SentenceTransformer
6
+ import chromadb
7
+ import google.generativeai as genai
8
+ import gradio as gr
9
+
10
+ # Load embedding model with GPU support (optimized for QA tasks)
11
+ embedder = SentenceTransformer('multi-qa-mpnet-base-dot-v1', device='cuda' if torch.cuda.is_available() else 'cpu')
12
+
13
+ # Configure Gemini API
14
+ genai.configure(api_key="AIzaSyDXG4o4UnII5VFD1u5TaWgleG2kCfJ0Ofw")
15
+
16
+ # Initialize Gemini model
17
+ gemini_instance = genai.GenerativeModel('gemini-2.0-flash')
18
+
19
+ # Define document paths and labels
20
+ doc_paths = [
21
+ '1002215.pdf',
22
+ 'cancers-15-00321.pdf',
23
+ 'ijo-57-06-1245.pdf'
24
+ ]
25
+ doc_labels = [
26
+ "Early-stage triple negative breast cancer: the therapeutic role of immunotherapy and the prognostic value of pathological complete response",
27
+ "Immunotherapy for Triple-Negative Breast Cancer: Combination Strategies to Improve Outcome",
28
+ "Triple‑negative breast cancer therapy: Current and future perspectives (Review)"
29
+ ]
30
+
31
+ def extract_and_chunk_docs(doc_paths, doc_labels):
32
+ segmented_docs = []
33
+ doc_info = []
34
+
35
+ for doc_path, doc_label in zip(doc_paths, doc_labels):
36
+ try:
37
+ # Extract text page-by-page
38
+ with pdfplumber.open(doc_path) as pdf:
39
+ full_text = []
40
+ for page_num, page in enumerate(pdf.pages, 1):
41
+ text = page.extract_text() or ""
42
+ lines = text.split('\n')
43
+ for line in lines:
44
+ line = line.strip()
45
+ if line:
46
+ full_text.append({'text': line, 'page': page_num})
47
+
48
+ if not full_text:
49
+ print(f"No content extracted from {doc_label}")
50
+ segmented_docs.append([])
51
+ doc_info.append({"label": doc_label, "gemini_structure": "No content extracted"})
52
+ continue
53
+
54
+ # Use Gemini to identify titles with improved prompt
55
+ text_for_gemini = "\n".join([entry['text'] for entry in full_text])
56
+ prompt = f"""You are an expert in analyzing research papers. Given the following text from a PDF, identify all section titles (e.g., Abstract, Introduction, Methods, Results, Discussion) and subsections (e.g., '2.1 Data Analysis'). Include headings that might define or characterize triple-negative breast cancer (TNBC), such as 'Definition,' 'Characteristics,' or similar, even if unconventional. Return only the titles, one per line, without explanation.
57
+
58
+ Text:
59
+ {text_for_gemini}
60
+ """
61
+
62
+ max_attempts = 5
63
+ for attempt in range(max_attempts):
64
+ try:
65
+ response = gemini_instance.generate_content(prompt)
66
+ titles = response.text.strip().split('\n')
67
+ break
68
+ except Exception as e:
69
+ if "429" in str(e):
70
+ delay = 2 ** attempt
71
+ print(f"Rate limit hit for {doc_label}. Waiting {delay} seconds...")
72
+ time.sleep(delay)
73
+ else:
74
+ print(f"Error identifying titles for {doc_label}: {e}")
75
+ segmented_docs.append([])
76
+ doc_info.append({"label": doc_label, "gemini_structure": f"Error: {e}"})
77
+ break
78
+ else:
79
+ print(f"Error for {doc_label}: Max retries exceeded.")
80
+ segmented_docs.append([])
81
+ doc_info.append({"label": doc_label, "gemini_structure": "Max retries exceeded"})
82
+ continue
83
+
84
+ # Clean and deduplicate titles
85
+ titles = list(dict.fromkeys([title.strip() for title in titles if title.strip()]))
86
+
87
+ # Chunk text by titles and pages
88
+ chunks = []
89
+ current_chunk = ""
90
+ current_title = "Unknown"
91
+ current_page = 1
92
+
93
+ for line_info in full_text:
94
+ line = line_info['text']
95
+ page = line_info['page']
96
+
97
+ cleaned_line = re.sub(r'(?i)copyright.*|all\s*rights\s*reserved', '', line)
98
+ cleaned_line = re.sub(r'\s+', ' ', cleaned_line).strip()
99
+ if not cleaned_line:
100
+ continue
101
+
102
+ # Check if this line matches a title (case-insensitive)
103
+ matched_title = next((title for title in titles if cleaned_line.lower() == title.lower()), None)
104
+
105
+ if matched_title:
106
+ # Save the previous chunk if it exists
107
+ if current_chunk:
108
+ chunks.append({
109
+ 'text': current_chunk.strip(),
110
+ 'page': current_page,
111
+ 'section': current_title
112
+ })
113
+ # Start a new chunk with the new title
114
+ current_title = matched_title
115
+ current_chunk = ""
116
+ current_page = page
117
+ else:
118
+ # Handle page breaks within the same section
119
+ if page != current_page and current_chunk:
120
+ chunks.append({
121
+ 'text': current_chunk.strip(),
122
+ 'page': current_page,
123
+ 'section': current_title
124
+ })
125
+ current_chunk = cleaned_line
126
+ current_page = page
127
+ else:
128
+ # Append to current chunk, split if too long
129
+ current_chunk += " " + cleaned_line
130
+ if len(current_chunk.split()) > 100:
131
+ chunks.append({
132
+ 'text': current_chunk.strip(),
133
+ 'page': current_page,
134
+ 'section': current_title
135
+ })
136
+ current_chunk = ""
137
+
138
+ # Save the final chunk
139
+ if current_chunk:
140
+ chunks.append({
141
+ 'text': current_chunk.strip(),
142
+ 'page': current_page,
143
+ 'section': current_title
144
+ })
145
+
146
+ segmented_docs.append(chunks)
147
+ doc_info.append({"label": doc_label, "gemini_structure": "Chunked by titles and pages"})
148
+
149
+ # Debugging output
150
+ print(f"\n=== {doc_label} ===")
151
+ print("Identified Section Titles:", titles)
152
+ print("\nChunks:")
153
+ for i, chunk in enumerate(chunks):
154
+ print(f"Chunk {i + 1}: [Page: {chunk['page']}, Section: '{chunk['section']}'] {chunk['text'][:100]}...")
155
+
156
+ except Exception as e:
157
+ print(f"Error processing {doc_label}: {e}")
158
+ segmented_docs.append([])
159
+ doc_info.append({"label": doc_label, "gemini_structure": f"Error: {str(e)}"})
160
+
161
+ return segmented_docs, doc_info
162
+ # Step 2: Generate embeddings for document segments
163
+ def compute_segment_embeddings(segmented_docs):
164
+ segment_embeddings = []
165
+ for doc_segments in segmented_docs:
166
+ if doc_segments:
167
+ embeddings = embedder.encode(
168
+ [seg['text'] for seg in doc_segments],
169
+ convert_to_tensor=False,
170
+ show_progress_bar=True,
171
+ batch_size=32 # Increased batch size for efficiency
172
+ )
173
+ segment_embeddings.append(embeddings)
174
+ else:
175
+ segment_embeddings.append([])
176
+ return segment_embeddings
177
+
178
+ # Step 3: Store embeddings in Chroma vector store
179
+ def save_to_vector_store(segmented_docs, segment_embeddings, doc_labels):
180
+ db_instance = chromadb.Client()
181
+ try:
182
+ embeddings_store = db_instance.create_collection("research_docs_mpnet_run_b")
183
+ except:
184
+ embeddings_store = db_instance.get_collection("research_docs_mpnet_run_b")
185
+
186
+ for i, (doc_segments, doc_embeds) in enumerate(zip(segmented_docs, segment_embeddings)):
187
+ if doc_segments and doc_embeds.size > 0:
188
+ for j, (segment, embed) in enumerate(zip(doc_segments, doc_embeds)):
189
+ embeddings_store.add(
190
+ embeddings=[embed.tolist()],
191
+ documents=[segment['text']],
192
+ metadatas=[{
193
+ "label": doc_labels[i],
194
+ "page": segment['page'],
195
+ "section": segment['section']
196
+ }],
197
+ ids=[f"{doc_labels[i]}seg{j}"]
198
+ )
199
+ return embeddings_store
200
+
201
+ # Step 4: Query the agent and generate answers
202
+ def process_query(query, embeddings_store, doc_labels):
203
+ query_embed = embedder.encode([query], convert_to_tensor=False)[0].tolist()
204
+ query_results = embeddings_store.query(query_embeddings=[query_embed], n_results=5) # Increased to 5
205
+
206
+ retrieved_contexts = []
207
+ ref_citations = []
208
+ for doc, meta in zip(query_results["documents"][0], query_results["metadatas"][0]):
209
+ label = meta["label"]
210
+ page = meta["page"]
211
+ section = meta["section"]
212
+ retrieved_contexts.append(doc)
213
+ ref_citations.append(f"[Ref: {label}, Page: {page}, Section: '{section}']")
214
+
215
+ # Debugging: Log retrieved contexts
216
+ print(f"\nQuery: {query}")
217
+ print("Retrieved Contexts:")
218
+ for i, ctx in enumerate(retrieved_contexts):
219
+ print(f"{i + 1}: {ctx[:200]}... [Ref: {ref_citations[i]}]")
220
+
221
+ combined_context = "\n".join(retrieved_contexts) if retrieved_contexts else "No relevant context found."
222
+ citation_str = " | ".join(ref_citations) if ref_citations else "N/A"
223
+
224
+ answer_prompt = f"""You are an AI assistant for research papers. Use only the provided context to answer the query concisely (1-2 sentences max). If the context lacks a clear answer, state so briefly.
225
+
226
+ Context:
227
+ {combined_context}
228
+
229
+ Query: {query}
230
+
231
+ Answer:"""
232
+
233
+ max_attempts = 5
234
+ for attempt in range(max_attempts):
235
+ try:
236
+ response = gemini_instance.generate_content(answer_prompt)
237
+ response_text = response.text.strip()
238
+ break
239
+ except Exception as e:
240
+ if "429" in str(e):
241
+ delay = 2 ** attempt
242
+ print(f"Rate limit for query '{query}'. Waiting {delay} seconds...")
243
+ time.sleep(delay)
244
+ else:
245
+ response_text = f"Error generating answer: {e}"
246
+ break
247
+ else:
248
+ response_text = "Error: Max retries exceeded."
249
+
250
+ return f"{response_text}\n\n*References*: {citation_str}"
251
+
252
+ # Gradio chatbot function
253
+ def chatbot_response(message, history):
254
+ response = process_query(message, embeddings_store, doc_labels)
255
+ return history + [{"role": "user", "content": message}, {"role": "assistant", "content": response}]
256
+
257
+ # Custom theme (unchanged)
258
+ custom_theme = gr.themes.Soft(
259
+ primary_hue="blue",
260
+ secondary_hue="gray",
261
+ neutral_hue="slate",
262
+ text_size="lg",
263
+ spacing_size="md",
264
+ radius_size="lg",
265
+ ).set(
266
+ body_background_fill="#f0f4f8",
267
+ body_text_color="#1e293b",
268
+ input_background_fill="#ffffff",
269
+ input_border_color="#cbd5e1",
270
+ input_shadow="0 2px 4px rgba(0,0,0,0.1)",
271
+ button_primary_background_fill="#3b82f6",
272
+ button_primary_text_color="#ffffff",
273
+ button_primary_background_fill_hover="#2563eb",
274
+ block_title_text_color="#1e40af",
275
+ block_border_color="#e2e8f0",
276
+ block_background_fill="#ffffff",
277
+ )
278
+
279
+ # Initialize system
280
+ doc_segments, doc_metadata = extract_and_chunk_docs(doc_paths, doc_labels)
281
+ segment_embeds = compute_segment_embeddings(doc_segments)
282
+ embeddings_store = save_to_vector_store(doc_segments, segment_embeds, doc_labels)
283
+
284
+ # Custom CSS (unchanged)
285
+ css = """
286
+ .header { text-align: center; margin-bottom: 20px; }
287
+ .gradio-container { max-width: 900px; margin: auto; }
288
+ .chatbot .prose { max-width: 100%; }
289
+ .chatbot .bubble-wrap:nth-child(even) { background-color: #dbeafe; color: #1e40af; }
290
+ .chatbot .bubble-wrap:nth-child(odd) { background-color: #f1f5f9; color: #1e293b; }
291
+ .chatbot .bubble { border-radius: 10px; padding: 10px; }
292
+ """
293
+
294
+ # Gradio interface
295
+ with gr.Blocks(theme=custom_theme, css=css, title="Research Paper Chatbot") as interface:
296
+ gr.Markdown("# Research Paper Chatbot\nAsk questions about the research papers and get concise, referenced answers.", elem_classes="header")
297
+ chatbot = gr.Chatbot(label="Conversation", height=500, type="messages", avatar_images=(None, "https://png.pngtree.com/png-vector/20201224/ourmid/pngtree-future-intelligent-technology-robot-ai-png-image_2588803.jpg"))
298
+ with gr.Row():
299
+ with gr.Column(scale=8):
300
+ msg = gr.Textbox(placeholder="Type your question here", show_label=False, container=False)
301
+ with gr.Column(scale=2):
302
+ submit_btn = gr.Button("Send", variant="primary")
303
+ msg.submit(chatbot_response, [msg, chatbot], chatbot).then(lambda: "", None, msg)
304
+ submit_btn.click(chatbot_response, [msg, chatbot], chatbot).then(lambda: "", None, msg)
305
+
306
+ if __name__ == "__main__":
307
+ interface.launch()