praneth02 commited on
Commit
cdf96cb
·
verified ·
1 Parent(s): a804e3a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -79
app.py CHANGED
@@ -1,6 +1,5 @@
1
  import re
2
  import time
3
- import pdfplumber
4
  import torch
5
  from sentence_transformers import SentenceTransformer
6
  import chromadb
@@ -16,11 +15,11 @@ genai.configure(api_key="AIzaSyDXG4o4UnII5VFD1u5TaWgleG2kCfJ0Ofw")
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",
@@ -34,27 +33,27 @@ def extract_and_chunk_docs(doc_paths, doc_labels):
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
  """
@@ -81,75 +80,64 @@ def extract_and_chunk_docs(doc_paths, doc_labels):
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
 
@@ -159,7 +147,7 @@ def extract_and_chunk_docs(doc_paths, doc_labels):
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:
@@ -168,14 +156,13 @@ def compute_segment_embeddings(segmented_docs):
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:
@@ -191,20 +178,20 @@ def save_to_vector_store(segmented_docs, segment_embeddings, doc_labels):
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"]
@@ -212,22 +199,13 @@ def process_query(query, embeddings_store, doc_labels):
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
@@ -247,9 +225,9 @@ def process_query(query, embeddings_store, doc_labels):
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}]
@@ -281,7 +259,7 @@ 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; }
 
1
  import re
2
  import time
 
3
  import torch
4
  from sentence_transformers import SentenceTransformer
5
  import chromadb
 
15
  # Initialize Gemini model
16
  gemini_instance = genai.GenerativeModel('gemini-2.0-flash')
17
 
18
+ # Define document paths and labels (using .md files)
19
  doc_paths = [
20
+ '1002215.md',
21
+ 'cancers-15-00321.md',
22
+ 'ijo-57-06-1245.md'
23
  ]
24
  doc_labels = [
25
  "Early-stage triple negative breast cancer: the therapeutic role of immunotherapy and the prognostic value of pathological complete response",
 
33
 
34
  for doc_path, doc_label in zip(doc_paths, doc_labels):
35
  try:
36
+ with open(doc_path, 'r', encoding='utf-8') as md_file:
37
+ full_text = md_file.read()
 
 
 
 
 
 
 
 
38
 
39
+ if not full_text.strip():
40
  print(f"No content extracted from {doc_label}")
41
  segmented_docs.append([])
42
  doc_info.append({"label": doc_label, "gemini_structure": "No content extracted"})
43
  continue
44
 
45
+ # Identify pages based on '-----'
46
+ pages = full_text.split('-----')
47
+ full_text_lines = []
48
+ for page_num, page_content in enumerate(pages, 1):
49
+ lines = page_content.split('\n')
50
+ for line in lines:
51
+ line = line.strip()
52
+ if line:
53
+ full_text_lines.append({'text': line, 'page': page_num})
54
+
55
+ text_for_gemini = "\n".join([entry['text'] for entry in full_text_lines])
56
+ prompt = f"""You are an expert in analyzing research papers. Given the following text from a Markdown file, identify all potential 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, and explicitly include 'References' as a section title if present. Only headings starting with '## **' are sections. Return only the titles (without '## **'), one per line, without explanation.
57
  Text:
58
  {text_for_gemini}
59
  """
 
80
  doc_info.append({"label": doc_label, "gemini_structure": "Max retries exceeded"})
81
  continue
82
 
 
83
  titles = list(dict.fromkeys([title.strip() for title in titles if title.strip()]))
84
+ print(f"Gemini Identified Titles for {doc_label}: {titles}")
85
 
 
86
  chunks = []
87
  current_chunk = ""
88
  current_title = "Unknown"
 
89
 
90
+ for line_info in full_text_lines:
91
  line = line_info['text']
92
+ page_num = line_info['page'] # Use page number from full_text_lines
93
 
94
  cleaned_line = re.sub(r'(?i)copyright.*|all\s*rights\s*reserved', '', line)
95
  cleaned_line = re.sub(r'\s+', ' ', cleaned_line).strip()
96
  if not cleaned_line:
97
  continue
98
 
99
+ if cleaned_line.startswith('## **'):
100
+ normalized_line = re.sub(r'^##\s*\*\*|\*\*', '', cleaned_line).strip()
101
+ matched_title = next((title for title in titles if normalized_line.lower() == title.lower()), None)
102
+
103
+ if matched_title:
104
+ # Save the previous chunk if it exists and is not "References"
105
+ if current_chunk and current_title.lower() != "references":
106
+ chunks.append({
107
+ 'text': current_chunk.strip(),
108
+ 'page': page_num, # Assign the current page
109
+ 'section': current_title
110
+ })
111
+ current_title = matched_title
112
+ current_chunk = ""
113
+ print(f"Detected title on page {page_num}: '{current_title}'")
114
+ continue
115
 
116
+ # Only add to chunk if current section is not "References"
117
+ if current_title.lower() != "references":
118
+ current_chunk += " " + cleaned_line
119
+ if len(current_chunk.split()) > 100:
120
  chunks.append({
121
  'text': current_chunk.strip(),
122
+ 'page': page_num, # Assign the current page
123
  'section': current_title
124
  })
125
+ current_chunk = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ # Save the final chunk if it exists and is not "References"
128
+ if current_chunk and current_title.lower() != "references":
129
  chunks.append({
130
  'text': current_chunk.strip(),
131
+ 'page': page_num, # Assign the final page
132
  'section': current_title
133
  })
134
 
135
  segmented_docs.append(chunks)
136
+ doc_info.append({"label": doc_label, "gemini_structure": "Chunked by titles starting with '## **', excluding 'References'"})
137
 
 
138
  print(f"\n=== {doc_label} ===")
139
  print("Identified Section Titles:", titles)
140
+ print("\nChunks (excluding References):")
141
  for i, chunk in enumerate(chunks):
142
  print(f"Chunk {i + 1}: [Page: {chunk['page']}, Section: '{chunk['section']}'] {chunk['text'][:100]}...")
143
 
 
147
  doc_info.append({"label": doc_label, "gemini_structure": f"Error: {str(e)}"})
148
 
149
  return segmented_docs, doc_info
150
+
151
  def compute_segment_embeddings(segmented_docs):
152
  segment_embeddings = []
153
  for doc_segments in segmented_docs:
 
156
  [seg['text'] for seg in doc_segments],
157
  convert_to_tensor=False,
158
  show_progress_bar=True,
159
+ batch_size=32
160
  )
161
  segment_embeddings.append(embeddings)
162
  else:
163
  segment_embeddings.append([])
164
  return segment_embeddings
165
 
 
166
  def save_to_vector_store(segmented_docs, segment_embeddings, doc_labels):
167
  db_instance = chromadb.Client()
168
  try:
 
178
  documents=[segment['text']],
179
  metadatas=[{
180
  "label": doc_labels[i],
181
+ "page": segment['page'], # Include page in metadata
182
  "section": segment['section']
183
  }],
184
  ids=[f"{doc_labels[i]}seg{j}"]
185
  )
186
  return embeddings_store
187
 
 
188
  def process_query(query, embeddings_store, doc_labels):
189
  query_embed = embedder.encode([query], convert_to_tensor=False)[0].tolist()
190
+ query_results = embeddings_store.query(query_embeddings=[query_embed], n_results=3) # Limit to top 3
191
 
192
  retrieved_contexts = []
193
  ref_citations = []
194
+
195
  for doc, meta in zip(query_results["documents"][0], query_results["metadatas"][0]):
196
  label = meta["label"]
197
  page = meta["page"]
 
199
  retrieved_contexts.append(doc)
200
  ref_citations.append(f"[Ref: {label}, Page: {page}, Section: '{section}']")
201
 
 
 
 
 
 
 
202
  combined_context = "\n".join(retrieved_contexts) if retrieved_contexts else "No relevant context found."
203
  citation_str = " | ".join(ref_citations) if ref_citations else "N/A"
204
 
205
  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.
 
206
  Context:
207
  {combined_context}
 
208
  Query: {query}
 
209
  Answer:"""
210
 
211
  max_attempts = 5
 
225
  else:
226
  response_text = "Error: Max retries exceeded."
227
 
228
+ final_response = f"{response_text}\n\n*References*: {citation_str}"
229
+ return final_response
230
 
 
231
  def chatbot_response(message, history):
232
  response = process_query(message, embeddings_store, doc_labels)
233
  return history + [{"role": "user", "content": message}, {"role": "assistant", "content": response}]
 
259
  segment_embeds = compute_segment_embeddings(doc_segments)
260
  embeddings_store = save_to_vector_store(doc_segments, segment_embeds, doc_labels)
261
 
262
+ # Custom CSS (simplified, no debug styling)
263
  css = """
264
  .header { text-align: center; margin-bottom: 20px; }
265
  .gradio-container { max-width: 900px; margin: auto; }