Ali2206 commited on
Commit
0456412
·
verified ·
1 Parent(s): a8cd932

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +119 -165
app.py CHANGED
@@ -11,10 +11,16 @@ import shutil
11
  import re
12
  import psutil
13
  import subprocess
14
- import multiprocessing
15
- from functools import partial
 
 
16
  import time
17
 
 
 
 
 
18
  # Persistent directory
19
  persistent_dir = "/data/hf_cache"
20
  os.makedirs(persistent_dir, exist_ok=True)
@@ -40,6 +46,9 @@ sys.path.insert(0, src_path)
40
 
41
  from txagent.txagent import TxAgent
42
 
 
 
 
43
  def sanitize_utf8(text: str) -> str:
44
  return text.encode("utf-8", "ignore").decode("utf-8")
45
 
@@ -47,58 +56,47 @@ def file_hash(path: str) -> str:
47
  with open(path, "rb") as f:
48
  return hashlib.md5(f.read()).hexdigest()
49
 
50
- def extract_page_range(file_path: str, start_page: int, end_page: int) -> str:
51
- """Extract text from a range of PDF pages."""
52
- try:
53
- text_chunks = []
54
- with pdfplumber.open(file_path) as pdf:
55
- for page in pdf.pages[start_page:end_page]:
56
- page_text = page.extract_text() or ""
57
- text_chunks.append(f"=== Page {start_page + pdf.pages.index(page) + 1} ===\n{page_text.strip()}")
58
- return "\n\n".join(text_chunks)
59
- except Exception:
60
- return ""
61
-
62
  def extract_all_pages(file_path: str, progress_callback=None) -> str:
63
- """Extract text from all pages of a PDF using parallel processing."""
64
  try:
65
  with pdfplumber.open(file_path) as pdf:
66
  total_pages = len(pdf.pages)
67
-
68
- if total_pages == 0:
69
- return ""
70
-
71
- # Use 6 processes (adjust based on CPU cores)
72
- num_processes = min(6, multiprocessing.cpu_count())
73
- pages_per_process = max(1, total_pages // num_processes)
74
-
75
- # Create page ranges for parallel processing
76
- ranges = [(i * pages_per_process, min((i + 1) * pages_per_process, total_pages))
77
- for i in range(num_processes)]
78
- if ranges[-1][1] != total_pages:
79
- ranges[-1] = (ranges[-1][0], total_pages)
80
-
81
- # Process page ranges in parallel
82
- with multiprocessing.Pool(processes=num_processes) as pool:
83
- extract_func = partial(extract_page_range, file_path)
84
  results = []
85
- for idx, result in enumerate(pool.starmap(extract_func, ranges)):
86
- results.append(result)
 
 
 
 
 
 
 
 
 
 
 
87
  if progress_callback:
88
- processed_pages = min((idx + 1) * pages_per_process, total_pages)
89
- progress_callback(processed_pages, total_pages)
90
-
91
- return "\n\n".join(filter(None, results))
92
  except Exception as e:
 
93
  return f"PDF processing error: {str(e)}"
94
 
95
  def convert_file_to_json(file_path: str, file_type: str, progress_callback=None) -> str:
96
  try:
97
- h = file_hash(file_path)
98
- cache_path = os.path.join(file_cache_dir, f"{h}.json")
99
- if os.path.exists(cache_path):
100
- with open(cache_path, "r", encoding="utf-8") as f:
101
- return f.read()
102
 
103
  if file_type == "pdf":
104
  text = extract_all_pages(file_path, progress_callback)
@@ -117,46 +115,41 @@ def convert_file_to_json(file_path: str, file_type: str, progress_callback=None)
117
  result = json.dumps({"filename": os.path.basename(file_path), "rows": content})
118
  else:
119
  result = json.dumps({"error": f"Unsupported file type: {file_type}"})
120
- with open(cache_path, "w", encoding="utf-8") as f:
121
- f.write(result)
122
  return result
123
  except Exception as e:
 
124
  return json.dumps({"error": f"Error processing {os.path.basename(file_path)}: {str(e)}"})
125
 
126
  def log_system_usage(tag=""):
127
  try:
128
  cpu = psutil.cpu_percent(interval=1)
129
  mem = psutil.virtual_memory()
130
- print(f"[{tag}] CPU: {cpu}% | RAM: {mem.used // (1024**2)}MB / {mem.total // (1024**2)}MB")
131
  result = subprocess.run(
132
  ["nvidia-smi", "--query-gpu=memory.used,memory.total,utilization.gpu", "--format=csv,nounits,noheader"],
133
  capture_output=True, text=True
134
  )
135
  if result.returncode == 0:
136
  used, total, util = result.stdout.strip().split(", ")
137
- print(f"[{tag}] GPU: {used}MB / {total}MB | Utilization: {util}%")
138
  except Exception as e:
139
- print(f"[{tag}] GPU/CPU monitor failed: {e}")
140
 
141
  def clean_response(text: str) -> str:
142
- """Clean TxAgent response to group findings under tool-derived headings."""
143
  text = sanitize_utf8(text)
144
- # Remove tool call artifacts, None, and reasoning
145
  text = re.sub(r"\[.*?\]|\bNone\b|To analyze the patient record excerpt.*?medications\.|Since the previous attempts.*?\.|I need to.*?medications\.|Retrieving tools.*?\.", "", text, flags=re.DOTALL)
146
- # Remove extra whitespace and non-markdown content
147
  text = re.sub(r"\n{3,}", "\n\n", text)
148
- text = re.sub(r"[^\n#\-\*\w\s\.\,\:\(\)]+", "", text) # Keep markdown-relevant characters
149
-
150
- # Define tool-to-heading mapping
151
  tool_to_heading = {
152
  "get_abuse_info_by_drug_name": "Drugs",
153
  "get_dependence_info_by_drug_name": "Drugs",
154
  "get_abuse_types_and_related_adverse_reactions_and_controlled_substance_status_by_drug_name": "Drugs",
155
  "get_info_for_patients_by_drug_name": "Drugs",
156
- # Add other tools from new_tool.json if applicable
157
  }
158
-
159
- # Parse sections and findings
160
  sections = {}
161
  current_section = None
162
  current_tool = None
@@ -165,22 +158,18 @@ def clean_response(text: str) -> str:
165
  line = line.strip()
166
  if not line:
167
  continue
168
- # Detect tool tag
169
  tool_match = re.match(r"\[TOOL:\s*(\w+)\]", line)
170
  if tool_match:
171
  current_tool = tool_match.group(1)
172
  continue
173
- # Detect section heading
174
  section_match = re.match(r"###\s*(Missed Diagnoses|Medication Conflicts|Incomplete Assessments|Urgent Follow-up)", line)
175
  if section_match:
176
  current_section = section_match.group(1)
177
  if current_section not in sections:
178
  sections[current_section] = []
179
  continue
180
- # Detect finding
181
  finding_match = re.match(r"-\s*.+", line)
182
  if finding_match and current_section and not re.match(r"-\s*No issues identified", line):
183
- # Assign to tool-derived heading if tool is specified
184
  if current_tool and current_tool in tool_to_heading:
185
  heading = tool_to_heading[current_tool]
186
  if heading not in sections:
@@ -188,20 +177,17 @@ def clean_response(text: str) -> str:
188
  sections[heading].append(line)
189
  else:
190
  sections[current_section].append(line)
191
-
192
- # Combine non-empty sections
193
  cleaned = []
194
  for heading, findings in sections.items():
195
- if findings: # Only include sections with findings
196
  cleaned.append(f"### {heading}\n" + "\n".join(findings))
197
-
198
  text = "\n\n".join(cleaned).strip()
199
- if not text:
200
- text = "" # Return empty string if no valid findings
201
- return text
202
 
203
  def init_agent():
204
- print("🔁 Initializing model...")
205
  log_system_usage("Before Load")
206
  default_tool_path = os.path.abspath("data/new_tool.json")
207
  target_tool_path = os.path.join(tool_cache_dir, "new_tool.json")
@@ -213,14 +199,14 @@ def init_agent():
213
  rag_model_name="mims-harvard/ToolRAG-T1-GTE-Qwen2-1.5B",
214
  tool_files_dict={"new_tool": target_tool_path},
215
  force_finish=True,
216
- enable_checker=True,
217
  step_rag_num=4,
218
  seed=100,
219
  additional_default_tools=[],
220
  )
221
  agent.init_model()
222
  log_system_usage("After Load")
223
- print("Agent Ready")
224
  return agent
225
 
226
  def create_ui(agent):
@@ -231,24 +217,24 @@ def create_ui(agent):
231
  msg_input = gr.Textbox(placeholder="Ask about potential oversights...", show_label=False)
232
  send_btn = gr.Button("Analyze", variant="primary")
233
  download_output = gr.File(label="Download Full Report")
 
234
 
235
- def analyze(message: str, history: List[dict], files: List):
 
 
 
 
 
 
 
236
  history.append({"role": "user", "content": message})
237
- history.append({"role": "assistant", "content": "⏳ Extracting text from files..."})
238
  yield history, None
239
 
240
  extracted = ""
241
  file_hash_value = ""
242
  if files:
243
- # Progress callback for extraction
244
- total_pages = 0
245
- processed_pages = 0
246
  def update_extraction_progress(current, total):
247
- nonlocal processed_pages, total_pages
248
- processed_pages = current
249
- total_pages = total
250
- animation = ["🌀", "🔄", "⚙️", "🔃"][(int(time.time() * 2) % 4)]
251
- history[-1] = {"role": "assistant", "content": f"Extracting text... {animation} Page {processed_pages}/{total_pages}"}
252
  return history, None
253
 
254
  with ThreadPoolExecutor(max_workers=6) as executor:
@@ -257,92 +243,56 @@ def create_ui(agent):
257
  extracted = "\n".join(results)
258
  file_hash_value = file_hash(files[0].name) if files else ""
259
 
260
- history.pop() # Remove extraction message
261
- history.append({"role": "assistant", "content": "✅ Text extraction complete."})
262
- yield history, None
263
 
264
- # Split extracted text into chunks of ~6,000 characters
265
  chunk_size = 6000
266
  chunks = [extracted[i:i + chunk_size] for i in range(0, len(extracted), chunk_size)]
267
  combined_response = ""
268
-
269
- prompt_template = """
270
- You are a medical analysis assistant. Analyze the following patient record excerpt for clinical oversights and provide a concise, evidence-based summary in markdown format. Group findings under appropriate headings based on the tool used (e.g., drug-related findings under 'Drugs'). For each finding, include:
271
- - Clinical context (why the issue was missed or relevant details from the record).
272
- - Potential risks if unaddressed (e.g., disease progression, adverse events).
273
- - Actionable recommendations (e.g., tests, referrals, medication adjustments).
274
- Output ONLY the markdown-formatted findings, with bullet points under each heading. Precede each finding with a tool tag (e.g., [TOOL: get_abuse_info_by_drug_name]) to indicate the tool used. Do NOT include reasoning, tool calls, or intermediate steps. If no issues are found for a tool or category, state "No issues identified" for that section. Ensure the output is specific to the provided text and avoids generic responses.
275
- Example Output:
276
- ### Drugs
277
- [TOOL: get_abuse_info_by_drug_name]
278
- - Opioid use disorder not addressed. Missed due to lack of screening. Risks: overdose. Recommend: addiction specialist referral.
279
- ### Missed Diagnoses
280
- - Elevated BP noted without diagnosis. Missed due to inconsistent visits. Risks: stroke. Recommend: BP monitoring, antihypertensives.
281
- ### Incomplete Assessments
282
- - Chest pain not evaluated. Time constraints likely cause. Risks: cardiac issues. Recommend: ECG, stress test.
283
- ### Urgent Follow-up
284
- - Abnormal creatinine not addressed. Delayed lab review. Risks: renal failure. Recommend: nephrology referral.
285
- Patient Record Excerpt (Chunk {0} of {1}):
286
- {chunk}
287
- """
288
 
289
  try:
290
- # Process each chunk and stream results in real-time
291
- for chunk_idx, chunk in enumerate(chunks, 1):
292
- # Update UI with chunk progress
293
- animation = ["🔍", "📊", "🧠", "🔎"][(int(time.time() * 2) % 4)]
294
- history.append({"role": "assistant", "content": f"Analyzing records... {animation} Chunk {chunk_idx}/{len(chunks)}"})
295
- yield history, None
296
-
297
- prompt = prompt_template.format(chunk_idx, len(chunks), chunk=chunk[:4000]) # Truncate to avoid token limits
298
- chunk_response = ""
299
- for chunk_output in agent.run_gradio_chat(
300
- message=prompt,
301
- history=[],
302
- temperature=0.2,
303
- max_new_tokens=1024,
304
- max_token=4096,
305
- call_agent=False,
306
- conversation=[],
307
- ):
308
- if chunk_output is None:
309
- continue
310
- if isinstance(chunk_output, list):
311
- for m in chunk_output:
312
- if hasattr(m, 'content') and m.content:
313
- cleaned = clean_response(m.content)
314
  if cleaned and re.search(r"###\s*\w+", cleaned):
315
  chunk_response += cleaned + "\n\n"
316
- # Update UI with partial response
317
- if history[-1]["content"].startswith("Analyzing"):
318
- history[-1] = {"role": "assistant", "content": f"--- Analysis for Chunk {chunk_idx} ---\n{chunk_response.strip()}"}
319
- else:
320
- history[-1]["content"] = f"--- Analysis for Chunk {chunk_idx} ---\n{chunk_response.strip()}"
321
- yield history, None
322
- elif isinstance(chunk_output, str) and chunk_output.strip():
323
- cleaned = clean_response(chunk_output)
324
- if cleaned and re.search(r"###\s*\w+", cleaned):
325
- chunk_response += cleaned + "\n\n"
326
- # Update UI with partial response
327
- if history[-1]["content"].startswith("Analyzing"):
328
- history[-1] = {"role": "assistant", "content": f"--- Analysis for Chunk {chunk_idx} ---\n{chunk_response.strip()}"}
329
- else:
330
- history[-1]["content"] = f"--- Analysis for Chunk {chunk_idx} ---\n{chunk_response.strip()}"
331
- yield history, None
332
-
333
- # Append completed chunk response to combined response
334
- if chunk_response:
335
- combined_response += f"--- Analysis for Chunk {chunk_idx} ---\n{chunk_response}\n"
336
- else:
337
- combined_response += f"--- Analysis for Chunk {chunk_idx} ---\nNo oversights identified for this chunk.\n\n"
338
-
339
- # Finalize UI with complete response
340
  if combined_response.strip() and not all("No oversights identified" in chunk for chunk in combined_response.split("--- Analysis for Chunk")):
341
  history[-1]["content"] = combined_response.strip()
342
  else:
343
  history.append({"role": "assistant", "content": "No oversights identified in the provided records."})
344
 
345
- # Generate report file
346
  report_path = os.path.join(report_dir, f"{file_hash_value}_report.txt") if file_hash_value else None
347
  if report_path:
348
  with open(report_path, "w", encoding="utf-8") as f:
@@ -350,7 +300,7 @@ Patient Record Excerpt (Chunk {0} of {1}):
350
  yield history, report_path if report_path and os.path.exists(report_path) else None
351
 
352
  except Exception as e:
353
- print("🚨 ERROR:", e)
354
  history.append({"role": "assistant", "content": f"❌ Error occurred: {str(e)}"})
355
  yield history, None
356
 
@@ -359,13 +309,17 @@ Patient Record Excerpt (Chunk {0} of {1}):
359
  return demo
360
 
361
  if __name__ == "__main__":
362
- print("🚀 Launching app...")
363
- agent = init_agent()
364
- demo = create_ui(agent)
365
- demo.queue(api_open=False).launch(
366
- server_name="0.0.0.0",
367
- server_port=7860,
368
- show_error=True,
369
- allowed_paths=[report_dir],
370
- share=False
371
- )
 
 
 
 
 
11
  import re
12
  import psutil
13
  import subprocess
14
+ import logging
15
+ import torch
16
+ import gc
17
+ from diskcache import Cache
18
  import time
19
 
20
+ # Configure logging
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger(__name__)
23
+
24
  # Persistent directory
25
  persistent_dir = "/data/hf_cache"
26
  os.makedirs(persistent_dir, exist_ok=True)
 
46
 
47
  from txagent.txagent import TxAgent
48
 
49
+ # Initialize cache with 10GB limit
50
+ cache = Cache(file_cache_dir, size_limit=10 * 1024**3)
51
+
52
  def sanitize_utf8(text: str) -> str:
53
  return text.encode("utf-8", "ignore").decode("utf-8")
54
 
 
56
  with open(path, "rb") as f:
57
  return hashlib.md5(f.read()).hexdigest()
58
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  def extract_all_pages(file_path: str, progress_callback=None) -> str:
 
60
  try:
61
  with pdfplumber.open(file_path) as pdf:
62
  total_pages = len(pdf.pages)
63
+ if total_pages == 0:
64
+ return ""
65
+
66
+ batch_size = 10 # Process 10 pages per thread
67
+ batches = [(i, min(i + batch_size, total_pages)) for i in range(0, total_pages, batch_size)]
68
+ text_chunks = [""] * total_pages
69
+ processed_pages = 0
70
+
71
+ def extract_batch(start: int, end: int) -> List[tuple]:
 
 
 
 
 
 
 
 
72
  results = []
73
+ with pdfplumber.open(file_path) as pdf:
74
+ for page in pdf.pages[start:end]:
75
+ page_num = start + pdf.pages.index(page)
76
+ page_text = page.extract_text() or ""
77
+ results.append((page_num, f"=== Page {page_num + 1} ===\n{page_text.strip()}"))
78
+ return results
79
+
80
+ with ThreadPoolExecutor(max_workers=6) as executor:
81
+ futures = [executor.submit(extract_batch, start, end) for start, end in batches]
82
+ for future in as_completed(futures):
83
+ for page_num, text in future.result():
84
+ text_chunks[page_num] = text
85
+ processed_pages += batch_size
86
  if progress_callback:
87
+ progress_callback(min(processed_pages, total_pages), total_pages)
88
+
89
+ return "\n\n".join(filter(None, text_chunks))
 
90
  except Exception as e:
91
+ logger.error("PDF processing error: %s", e)
92
  return f"PDF processing error: {str(e)}"
93
 
94
  def convert_file_to_json(file_path: str, file_type: str, progress_callback=None) -> str:
95
  try:
96
+ file_h = file_hash(file_path)
97
+ cache_key = f"{file_h}_{file_type}"
98
+ if cache_key in cache:
99
+ return cache[cache_key]
 
100
 
101
  if file_type == "pdf":
102
  text = extract_all_pages(file_path, progress_callback)
 
115
  result = json.dumps({"filename": os.path.basename(file_path), "rows": content})
116
  else:
117
  result = json.dumps({"error": f"Unsupported file type: {file_type}"})
118
+
119
+ cache[cache_key] = result
120
  return result
121
  except Exception as e:
122
+ logger.error("Error processing %s: %s", os.path.basename(file_path), e)
123
  return json.dumps({"error": f"Error processing {os.path.basename(file_path)}: {str(e)}"})
124
 
125
  def log_system_usage(tag=""):
126
  try:
127
  cpu = psutil.cpu_percent(interval=1)
128
  mem = psutil.virtual_memory()
129
+ logger.info("[%s] CPU: %.1f%% | RAM: %dMB / %dMB", tag, cpu, mem.used // (1024**2), mem.total // (1024**2))
130
  result = subprocess.run(
131
  ["nvidia-smi", "--query-gpu=memory.used,memory.total,utilization.gpu", "--format=csv,nounits,noheader"],
132
  capture_output=True, text=True
133
  )
134
  if result.returncode == 0:
135
  used, total, util = result.stdout.strip().split(", ")
136
+ logger.info("[%s] GPU: %sMB / %sMB | Utilization: %s%%", tag, used, total, util)
137
  except Exception as e:
138
+ logger.error("[%s] GPU/CPU monitor failed: %s", tag, e)
139
 
140
  def clean_response(text: str) -> str:
 
141
  text = sanitize_utf8(text)
 
142
  text = re.sub(r"\[.*?\]|\bNone\b|To analyze the patient record excerpt.*?medications\.|Since the previous attempts.*?\.|I need to.*?medications\.|Retrieving tools.*?\.", "", text, flags=re.DOTALL)
 
143
  text = re.sub(r"\n{3,}", "\n\n", text)
144
+ text = re.sub(r"[^\n#\-\*\w\s\.\,\:\(\)]+", "", text)
145
+
 
146
  tool_to_heading = {
147
  "get_abuse_info_by_drug_name": "Drugs",
148
  "get_dependence_info_by_drug_name": "Drugs",
149
  "get_abuse_types_and_related_adverse_reactions_and_controlled_substance_status_by_drug_name": "Drugs",
150
  "get_info_for_patients_by_drug_name": "Drugs",
 
151
  }
152
+
 
153
  sections = {}
154
  current_section = None
155
  current_tool = None
 
158
  line = line.strip()
159
  if not line:
160
  continue
 
161
  tool_match = re.match(r"\[TOOL:\s*(\w+)\]", line)
162
  if tool_match:
163
  current_tool = tool_match.group(1)
164
  continue
 
165
  section_match = re.match(r"###\s*(Missed Diagnoses|Medication Conflicts|Incomplete Assessments|Urgent Follow-up)", line)
166
  if section_match:
167
  current_section = section_match.group(1)
168
  if current_section not in sections:
169
  sections[current_section] = []
170
  continue
 
171
  finding_match = re.match(r"-\s*.+", line)
172
  if finding_match and current_section and not re.match(r"-\s*No issues identified", line):
 
173
  if current_tool and current_tool in tool_to_heading:
174
  heading = tool_to_heading[current_tool]
175
  if heading not in sections:
 
177
  sections[heading].append(line)
178
  else:
179
  sections[current_section].append(line)
180
+
 
181
  cleaned = []
182
  for heading, findings in sections.items():
183
+ if findings:
184
  cleaned.append(f"### {heading}\n" + "\n".join(findings))
185
+
186
  text = "\n\n".join(cleaned).strip()
187
+ return text if text else ""
 
 
188
 
189
  def init_agent():
190
+ logger.info("Initializing model...")
191
  log_system_usage("Before Load")
192
  default_tool_path = os.path.abspath("data/new_tool.json")
193
  target_tool_path = os.path.join(tool_cache_dir, "new_tool.json")
 
199
  rag_model_name="mims-harvard/ToolRAG-T1-GTE-Qwen2-1.5B",
200
  tool_files_dict={"new_tool": target_tool_path},
201
  force_finish=True,
202
+ enable_checker=False, # Disabled for speed
203
  step_rag_num=4,
204
  seed=100,
205
  additional_default_tools=[],
206
  )
207
  agent.init_model()
208
  log_system_usage("After Load")
209
+ logger.info("Agent Ready")
210
  return agent
211
 
212
  def create_ui(agent):
 
217
  msg_input = gr.Textbox(placeholder="Ask about potential oversights...", show_label=False)
218
  send_btn = gr.Button("Analyze", variant="primary")
219
  download_output = gr.File(label="Download Full Report")
220
+ progress_bar = gr.Progress()
221
 
222
+ prompt_template = """
223
+ Analyze the patient record excerpt for clinical oversights. Provide a concise, evidence-based summary in markdown with findings grouped under tool-derived headings (e.g., 'Drugs'). For each finding, include clinical context, risks, and recommendations. Precede findings with a tool tag (e.g., [TOOL: get_abuse_info_by_drug_name]). Output only markdown bullet points under headings. If no issues, state "No issues identified".
224
+
225
+ Patient Record Excerpt (Chunk {0} of {1}):
226
+ {chunk}
227
+ """
228
+
229
+ def analyze(message: str, history: List[dict], files: List, progress=gr.Progress()):
230
  history.append({"role": "user", "content": message})
 
231
  yield history, None
232
 
233
  extracted = ""
234
  file_hash_value = ""
235
  if files:
 
 
 
236
  def update_extraction_progress(current, total):
237
+ progress(current / total, desc=f"Extracting text... Page {current}/{total}")
 
 
 
 
238
  return history, None
239
 
240
  with ThreadPoolExecutor(max_workers=6) as executor:
 
243
  extracted = "\n".join(results)
244
  file_hash_value = file_hash(files[0].name) if files else ""
245
 
246
+ history.append({"role": "assistant", "content": "✅ Text extraction complete."})
247
+ yield history, None
 
248
 
 
249
  chunk_size = 6000
250
  chunks = [extracted[i:i + chunk_size] for i in range(0, len(extracted), chunk_size)]
251
  combined_response = ""
252
+ batch_size = 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  try:
255
+ for batch_idx in range(0, len(chunks), batch_size):
256
+ batch_chunks = chunks[batch_idx:batch_idx + batch_size]
257
+ batch_prompts = [prompt_template.format(i + 1, len(chunks), chunk=chunk[:4000]) for i, chunk in enumerate(batch_chunks)]
258
+ batch_responses = []
259
+
260
+ progress((batch_idx + 1) / len(chunks), desc=f"Analyzing chunks {batch_idx + 1}-{min(batch_idx + batch_size, len(chunks))}/{len(chunks)}")
261
+
262
+ with ThreadPoolExecutor(max_workers=len(batch_chunks)) as executor:
263
+ futures = [executor.submit(agent.run_gradio_chat, prompt, [], 0.2, 512, 2048, False, []) for prompt in batch_prompts]
264
+ for future in as_completed(futures):
265
+ chunk_response = ""
266
+ for chunk_output in future.result():
267
+ if chunk_output is None:
268
+ continue
269
+ if isinstance(chunk_output, list):
270
+ for m in chunk_output:
271
+ if hasattr(m, 'content') and m.content:
272
+ cleaned = clean_response(m.content)
273
+ if cleaned and re.search(r"###\s*\w+", cleaned):
274
+ chunk_response += cleaned + "\n\n"
275
+ elif isinstance(chunk_output, str) and chunk_output.strip():
276
+ cleaned = clean_response(chunk_output)
 
 
277
  if cleaned and re.search(r"###\s*\w+", cleaned):
278
  chunk_response += cleaned + "\n\n"
279
+ batch_responses.append(chunk_response)
280
+ torch.cuda.empty_cache()
281
+ gc.collect()
282
+
283
+ for chunk_idx, chunk_response in enumerate(batch_responses, batch_idx + 1):
284
+ if chunk_response:
285
+ combined_response += f"--- Analysis for Chunk {chunk_idx} ---\n{chunk_response}\n"
286
+ else:
287
+ combined_response += f"--- Analysis for Chunk {chunk_idx} ---\nNo oversights identified for this chunk.\n\n"
288
+ history[-1] = {"role": "assistant", "content": combined_response.strip()}
289
+ yield history, None
290
+
 
 
 
 
 
 
 
 
 
 
 
 
291
  if combined_response.strip() and not all("No oversights identified" in chunk for chunk in combined_response.split("--- Analysis for Chunk")):
292
  history[-1]["content"] = combined_response.strip()
293
  else:
294
  history.append({"role": "assistant", "content": "No oversights identified in the provided records."})
295
 
 
296
  report_path = os.path.join(report_dir, f"{file_hash_value}_report.txt") if file_hash_value else None
297
  if report_path:
298
  with open(report_path, "w", encoding="utf-8") as f:
 
300
  yield history, report_path if report_path and os.path.exists(report_path) else None
301
 
302
  except Exception as e:
303
+ logger.error("Analysis error: %s", e)
304
  history.append({"role": "assistant", "content": f"❌ Error occurred: {str(e)}"})
305
  yield history, None
306
 
 
309
  return demo
310
 
311
  if __name__ == "__main__":
312
+ try:
313
+ logger.info("Launching app...")
314
+ agent = init_agent()
315
+ demo = create_ui(agent)
316
+ demo.queue(api_open=False).launch(
317
+ server_name="0.0.0.0",
318
+ server_port=7860,
319
+ show_error=True,
320
+ allowed_paths=[report_dir],
321
+ share=False
322
+ )
323
+ finally:
324
+ if torch.distributed.is_initialized():
325
+ torch.distributed.destroy_process_group()