Ali2206 commited on
Commit
0fb33af
Β·
verified Β·
1 Parent(s): 70f70c1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +182 -146
app.py CHANGED
@@ -1,15 +1,14 @@
1
  import sys
2
  import os
3
  import pandas as pd
 
4
  import gradio as gr
5
- from typing import List, Tuple, Dict, Any, Union, Generator
 
6
  import shutil
7
  import re
8
  from datetime import datetime
9
  import time
10
- import asyncio
11
- import logging
12
- from concurrent.futures import ThreadPoolExecutor, as_completed
13
 
14
  # Configuration and setup
15
  persistent_dir = "/data/hf_cache"
@@ -24,6 +23,7 @@ for directory in [model_cache_dir, tool_cache_dir, file_cache_dir, report_dir]:
24
  os.makedirs(directory, exist_ok=True)
25
 
26
  os.environ["HF_HOME"] = model_cache_dir
 
27
 
28
  current_dir = os.path.dirname(os.path.abspath(__file__))
29
  src_path = os.path.abspath(os.path.join(current_dir, "src"))
@@ -32,15 +32,10 @@ sys.path.insert(0, src_path)
32
  from txagent.txagent import TxAgent
33
 
34
  # Constants
35
- MAX_MODEL_TOKENS = 131072 # TxAgent's max token limit
36
- MAX_CHUNK_TOKENS = 32768 # Larger chunks to reduce number of chunks
37
- MAX_NEW_TOKENS = 512 # Optimized for fast generation
38
- PROMPT_OVERHEAD = 500 # Estimated tokens for prompt template
39
- MAX_CONCURRENT = 4 # Reduced concurrency to avoid vLLM issues
40
-
41
- # Setup logging
42
- logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
43
- logger = logging.getLogger(__name__)
44
 
45
  def clean_response(text: str) -> str:
46
  try:
@@ -53,9 +48,11 @@ def clean_response(text: str) -> str:
53
  return text.strip()
54
 
55
  def estimate_tokens(text: str) -> int:
56
- return len(text) // 3.5 + 1 # Conservative estimate
 
57
 
58
  def extract_text_from_excel(file_path: str) -> str:
 
59
  all_text = []
60
  try:
61
  xls = pd.ExcelFile(file_path)
@@ -66,16 +63,18 @@ def extract_text_from_excel(file_path: str) -> str:
66
  sheet_text = [f"[{sheet_name}] {line}" for line in rows]
67
  all_text.extend(sheet_text)
68
  except Exception as e:
69
- logger.error(f"Error extracting Excel: {str(e)}")
70
- raise ValueError(f"Failed to process Excel file: {str(e)}")
71
  return "\n".join(all_text)
72
 
73
- def split_text_into_chunks(text: str) -> List[str]:
74
- """Split text into chunks respecting MAX_CHUNK_TOKENS and PROMPT_OVERHEAD"""
75
- effective_max = MAX_CHUNK_TOKENS - PROMPT_OVERHEAD
76
- if effective_max <= 0:
77
- raise ValueError("Effective max tokens must be positive")
78
-
 
 
 
79
  lines = text.split("\n")
80
  chunks = []
81
  current_chunk = []
@@ -83,8 +82,8 @@ def split_text_into_chunks(text: str) -> List[str]:
83
 
84
  for line in lines:
85
  line_tokens = estimate_tokens(line)
86
- if current_tokens + line_tokens > effective_max:
87
- if current_chunk:
88
  chunks.append("\n".join(current_chunk))
89
  current_chunk = [line]
90
  current_tokens = line_tokens
@@ -94,11 +93,11 @@ def split_text_into_chunks(text: str) -> List[str]:
94
 
95
  if current_chunk:
96
  chunks.append("\n".join(current_chunk))
97
-
98
- logger.info(f"Split text into {len(chunks)} chunks")
99
  return chunks
100
 
101
  def build_prompt_from_text(chunk: str) -> str:
 
102
  return f"""
103
  ### Unstructured Clinical Records
104
 
@@ -110,7 +109,7 @@ Here is the extracted content chunk:
110
 
111
  {chunk}
112
 
113
- Please analyze the above and provide concise responses (max {MAX_NEW_TOKENS} tokens):
114
  - Diagnostic Patterns
115
  - Medication Issues
116
  - Missed Opportunities
@@ -119,7 +118,7 @@ Please analyze the above and provide concise responses (max {MAX_NEW_TOKENS} tok
119
  """
120
 
121
  def init_agent():
122
- """Initialize TxAgent with conservative settings to avoid vLLM issues"""
123
  default_tool_path = os.path.abspath("data/new_tool.json")
124
  target_tool_path = os.path.join(tool_cache_dir, "new_tool.json")
125
 
@@ -139,168 +138,205 @@ def init_agent():
139
  agent.init_model()
140
  return agent
141
 
142
- def process_chunk_sync(agent, chunk: str, chunk_idx: int) -> Tuple[int, str]:
143
- """Synchronous wrapper for chunk processing"""
144
- try:
145
- prompt = build_prompt_from_text(chunk)
146
- prompt_tokens = estimate_tokens(prompt)
147
-
148
- if prompt_tokens > MAX_MODEL_TOKENS:
149
- logger.warning(f"Chunk {chunk_idx} prompt too long ({prompt_tokens} tokens)")
150
- return chunk_idx, ""
151
-
152
- response = ""
153
- for result in agent.run_gradio_chat(
154
- message=prompt,
155
- history=[],
156
- temperature=0.2,
157
- max_new_tokens=MAX_NEW_TOKENS,
158
- max_token=MAX_MODEL_TOKENS,
159
- call_agent=False,
160
- conversation=[],
161
- ):
162
- if isinstance(result, str):
163
- response += result
164
- elif hasattr(result, "content"):
165
- response += result.content
166
- elif isinstance(result, list):
167
- for r in result:
168
- if hasattr(r, "content"):
169
- response += r.content
170
-
171
- return chunk_idx, clean_response(response)
172
- except Exception as e:
173
- logger.error(f"Error processing chunk {chunk_idx}: {str(e)}")
174
- return chunk_idx, ""
175
-
176
- async def process_file(agent: TxAgent, file_path: str) -> Generator[Tuple[List[Dict[str, str]], Union[str, None]], None, None]:
177
- messages = []
178
  report_path = None
179
-
180
- if file_path is None:
181
  messages.append({"role": "assistant", "content": "❌ Please upload a valid Excel file before analyzing."})
182
- yield messages, None
183
- return
184
 
185
  try:
186
- # Initial messages
187
- messages.append({"role": "user", "content": f"Processing file: {os.path.basename(file_path)}"})
188
- messages.append({"role": "assistant", "content": "⏳ Extracting data from Excel..."})
189
- yield messages, None
190
-
191
- # Extract and chunk text
192
- start_time = time.time()
193
- text = extract_text_from_excel(file_path)
194
- chunks = split_text_into_chunks(text)
195
- messages.append({"role": "assistant", "content": f"βœ… Extracted {len(chunks)} chunks in {time.time()-start_time:.1f}s"})
196
- yield messages, None
197
-
198
- # Process chunks sequentially
199
  chunk_responses = []
200
- for idx, chunk in enumerate(chunks):
201
- messages.append({"role": "assistant", "content": f"πŸ” Processing chunk {idx+1}/{len(chunks)}..."})
202
- yield messages, None
 
203
 
204
- _, response = await process_chunk(agent, chunk, idx)
205
- chunk_responses.append(response)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
- messages.append({"role": "assistant", "content": f"βœ… Chunk {idx+1} processed"})
208
- yield messages, None
209
-
210
- # Combine and summarize
211
- combined = "\n\n".join([r for r in chunk_responses if r])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  messages.append({"role": "assistant", "content": "πŸ“Š Generating final report..."})
213
- yield messages, None
214
-
215
- final_response = ""
216
- for result in agent.run_gradio_chat(
217
- message=f"Summarize these clinical findings:\n\n{combined}",
218
- history=[],
219
- temperature=0.2,
220
- max_new_tokens=MAX_NEW_TOKENS*2,
221
- max_token=MAX_MODEL_TOKENS,
222
- call_agent=False,
223
- conversation=[],
224
- ):
225
- if isinstance(result, str):
226
- final_response += result
227
- elif hasattr(result, "content"):
228
- final_response += result.content
229
- elif isinstance(result, list):
230
- for r in result:
231
- if hasattr(r, "content"):
232
- final_response += r.content
233
-
234
- messages[-1]["content"] = f"πŸ“Š Generating final report...\n\n{clean_response(final_response)}"
235
- yield messages, None
236
-
237
- # Save report
238
- final_report = f"# Final Clinical Report\n\n{clean_response(final_response)}"
 
 
239
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
240
  report_path = os.path.join(report_dir, f"report_{timestamp}.md")
241
 
242
  with open(report_path, 'w') as f:
243
  f.write(final_report)
244
-
245
- messages.append({"role": "assistant", "content": f"βœ… Report saved: report_{timestamp}.md"})
246
- yield messages, report_path
247
-
248
  except Exception as e:
249
- logger.error(f"Processing failed: {str(e)}")
250
- messages.append({"role": "assistant", "content": f"❌ Error: {str(e)}"})
251
- yield messages, None
252
- def create_ui(agent: TxAgent):
253
- """Create the Gradio interface with simplified interaction"""
254
- with gr.Blocks(title="Clinical Analysis", css=".gradio-container {max-width: 900px}") as demo:
255
- gr.Markdown("## πŸ₯ Clinical Data Analysis (TxAgent)")
 
256
 
257
  with gr.Row():
258
  with gr.Column(scale=3):
259
  chatbot = gr.Chatbot(
260
- label="Analysis Progress",
261
  show_copy_button=True,
262
  height=600,
263
- type="messages"
 
 
 
 
264
  )
265
  with gr.Column(scale=1):
266
- file_input = gr.File(
267
  label="Upload Excel File",
268
  file_types=[".xlsx"],
269
  height=100
270
  )
271
  analyze_btn = gr.Button(
272
- "🧠 Analyze Data",
273
  variant="primary"
274
  )
275
  report_output = gr.File(
276
  label="Download Report",
277
- visible=False
 
278
  )
279
-
 
 
 
 
 
 
 
 
280
  analyze_btn.click(
281
- fn=lambda file: process_file(agent, file.name if file else None),
282
- inputs=[file_input],
283
- outputs=[chatbot, report_output],
284
- concurrency_limit=1 # Ensure sequential processing
285
  )
286
-
287
  return demo
288
 
289
  if __name__ == "__main__":
290
  try:
291
- # Initialize with conservative settings
292
  agent = init_agent()
293
  demo = create_ui(agent)
294
-
295
- # Launch with stability optimizations
296
  demo.launch(
297
  server_name="0.0.0.0",
298
  server_port=7860,
299
  show_error=True,
300
- allowed_paths=[report_dir],
301
- share=False,
302
- max_threads=4 # Reduced thread count for stability
303
  )
304
  except Exception as e:
305
- logger.error(f"Application failed: {str(e)}")
306
  sys.exit(1)
 
1
  import sys
2
  import os
3
  import pandas as pd
4
+ import json
5
  import gradio as gr
6
+ from typing import List, Tuple, Dict, Any, Union
7
+ import hashlib
8
  import shutil
9
  import re
10
  from datetime import datetime
11
  import time
 
 
 
12
 
13
  # Configuration and setup
14
  persistent_dir = "/data/hf_cache"
 
23
  os.makedirs(directory, exist_ok=True)
24
 
25
  os.environ["HF_HOME"] = model_cache_dir
26
+ os.environ["TRANSFORMERS_CACHE"] = model_cache_dir
27
 
28
  current_dir = os.path.dirname(os.path.abspath(__file__))
29
  src_path = os.path.abspath(os.path.join(current_dir, "src"))
 
32
  from txagent.txagent import TxAgent
33
 
34
  # Constants
35
+ MAX_MODEL_TOKENS = 32768 # Model's maximum sequence length
36
+ MAX_CHUNK_TOKENS = 8192 # Chunk size aligned with max_num_batched_tokens
37
+ MAX_NEW_TOKENS = 2048 # Maximum tokens for generation
38
+ PROMPT_OVERHEAD = 500 # Estimated tokens for prompt template overhead
 
 
 
 
 
39
 
40
  def clean_response(text: str) -> str:
41
  try:
 
48
  return text.strip()
49
 
50
  def estimate_tokens(text: str) -> int:
51
+ """Estimate the number of tokens based on character length."""
52
+ return len(text) // 3.5 + 1 # Add 1 to avoid zero estimates
53
 
54
  def extract_text_from_excel(file_path: str) -> str:
55
+ """Extract text from all sheets in an Excel file."""
56
  all_text = []
57
  try:
58
  xls = pd.ExcelFile(file_path)
 
63
  sheet_text = [f"[{sheet_name}] {line}" for line in rows]
64
  all_text.extend(sheet_text)
65
  except Exception as e:
66
+ raise ValueError(f"Failed to extract text from Excel file: {str(e)}")
 
67
  return "\n".join(all_text)
68
 
69
+ def split_text_into_chunks(text: str, max_tokens: int = MAX_CHUNK_TOKENS) -> List[str]:
70
+ """
71
+ Split text into chunks, ensuring each chunk is within token limits,
72
+ accounting for prompt overhead.
73
+ """
74
+ effective_max_tokens = max_tokens - PROMPT_OVERHEAD
75
+ if effective_max_tokens <= 0:
76
+ raise ValueError(f"Effective max tokens ({effective_max_tokens}) must be positive.")
77
+
78
  lines = text.split("\n")
79
  chunks = []
80
  current_chunk = []
 
82
 
83
  for line in lines:
84
  line_tokens = estimate_tokens(line)
85
+ if current_tokens + line_tokens > effective_max_tokens:
86
+ if current_chunk: # Save the current chunk if it's not empty
87
  chunks.append("\n".join(current_chunk))
88
  current_chunk = [line]
89
  current_tokens = line_tokens
 
93
 
94
  if current_chunk:
95
  chunks.append("\n".join(current_chunk))
96
+
 
97
  return chunks
98
 
99
  def build_prompt_from_text(chunk: str) -> str:
100
+ """Build a prompt for analyzing a chunk of clinical data."""
101
  return f"""
102
  ### Unstructured Clinical Records
103
 
 
109
 
110
  {chunk}
111
 
112
+ Please analyze the above and provide:
113
  - Diagnostic Patterns
114
  - Medication Issues
115
  - Missed Opportunities
 
118
  """
119
 
120
  def init_agent():
121
+ """Initialize the TxAgent with model and tool configurations."""
122
  default_tool_path = os.path.abspath("data/new_tool.json")
123
  target_tool_path = os.path.join(tool_cache_dir, "new_tool.json")
124
 
 
138
  agent.init_model()
139
  return agent
140
 
141
+ def process_final_report(agent, file, chatbot_state: List[Dict[str, str]]) -> Tuple[List[Dict[str, str]], Union[str, None]]:
142
+ """Process the Excel file and generate a final report."""
143
+ messages = chatbot_state if chatbot_state else []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  report_path = None
145
+
146
+ if file is None or not hasattr(file, "name"):
147
  messages.append({"role": "assistant", "content": "❌ Please upload a valid Excel file before analyzing."})
148
+ return messages, report_path
 
149
 
150
  try:
151
+ messages.append({"role": "user", "content": f"Processing Excel file: {os.path.basename(file.name)}"})
152
+ messages.append({"role": "assistant", "content": "⏳ Extracting and analyzing data..."})
153
+
154
+ # Extract text and split into chunks
155
+ extracted_text = extract_text_from_excel(file.name)
156
+ chunks = split_text_into_chunks(extracted_text, max_tokens=MAX_CHUNK_TOKENS)
 
 
 
 
 
 
 
157
  chunk_responses = []
158
+
159
+ # Process each chunk
160
+ for i, chunk in enumerate(chunks):
161
+ messages.append({"role": "assistant", "content": f"πŸ” Analyzing chunk {i+1}/{len(chunks)}..."})
162
 
163
+ prompt = build_prompt_from_text(chunk)
164
+ prompt_tokens = estimate_tokens(prompt)
165
+ if prompt_tokens > MAX_MODEL_TOKENS:
166
+ messages.append({"role": "assistant", "content": f"❌ Chunk {i+1} prompt too long ({prompt_tokens} tokens). Skipping..."})
167
+ continue
168
+
169
+ response = ""
170
+ try:
171
+ for result in agent.run_gradio_chat(
172
+ message=prompt,
173
+ history=[],
174
+ temperature=0.2,
175
+ max_new_tokens=MAX_NEW_TOKENS,
176
+ max_token=MAX_MODEL_TOKENS,
177
+ call_agent=False,
178
+ conversation=[],
179
+ ):
180
+ if isinstance(result, str):
181
+ response += result
182
+ elif hasattr(result, "content"):
183
+ response += result.content
184
+ elif isinstance(result, list):
185
+ for r in result:
186
+ if hasattr(r, "content"):
187
+ response += r.content
188
+ except Exception as e:
189
+ messages.append({"role": "assistant", "content": f"❌ Error analyzing chunk {i+1}: {str(e)}"})
190
+ continue
191
 
192
+ chunk_responses.append(clean_response(response))
193
+ messages.append({"role": "assistant", "content": f"βœ… Chunk {i+1} analysis complete"})
194
+
195
+ if not chunk_responses:
196
+ messages.append({"role": "assistant", "content": "❌ No valid chunk responses to summarize."})
197
+ return messages, report_path
198
+
199
+ # Summarize chunk responses incrementally to avoid token limit
200
+ summary = ""
201
+ current_summary_tokens = 0
202
+ for i, response in enumerate(chunk_responses):
203
+ response_tokens = estimate_tokens(response)
204
+ if current_summary_tokens + response_tokens > MAX_MODEL_TOKENS - PROMPT_OVERHEAD - MAX_NEW_TOKENS:
205
+ # Summarize current summary
206
+ summary_prompt = f"Summarize the following analysis:\n\n{summary}\n\nProvide a concise summary."
207
+ summary_response = ""
208
+ try:
209
+ for result in agent.run_gradio_chat(
210
+ message=summary_prompt,
211
+ history=[],
212
+ temperature=0.2,
213
+ max_new_tokens=MAX_NEW_TOKENS,
214
+ max_token=MAX_MODEL_TOKENS,
215
+ call_agent=False,
216
+ conversation=[],
217
+ ):
218
+ if isinstance(result, str):
219
+ summary_response += result
220
+ elif hasattr(result, "content"):
221
+ summary_response += result.content
222
+ elif isinstance(result, list):
223
+ for r in result:
224
+ if hasattr(r, "content"):
225
+ summary_response += r.content
226
+ summary = clean_response(summary_response)
227
+ current_summary_tokens = estimate_tokens(summary)
228
+ except Exception as e:
229
+ messages.append({"role": "assistant", "content": f"❌ Error summarizing intermediate results: {str(e)}"})
230
+ return messages, report_path
231
+
232
+ summary += f"\n\n### Chunk {i+1} Analysis\n{response}"
233
+ current_summary_tokens += response_tokens
234
+
235
+ # Final summarization
236
+ final_prompt = f"Summarize the key findings from the following analyses:\n\n{summary}"
237
  messages.append({"role": "assistant", "content": "πŸ“Š Generating final report..."})
238
+
239
+ final_report_text = ""
240
+ try:
241
+ for result in agent.run_gradio_chat(
242
+ message=final_prompt,
243
+ history=[],
244
+ temperature=0.2,
245
+ max_new_tokens=MAX_NEW_TOKENS,
246
+ max_token=MAX_MODEL_TOKENS,
247
+ call_agent=False,
248
+ conversation=[],
249
+ ):
250
+ if isinstance(result, str):
251
+ final_report_text += result
252
+ elif hasattr(result, "content"):
253
+ final_report_text += result.content
254
+ elif isinstance(result, list):
255
+ for r in result:
256
+ if hasattr(r, "content"):
257
+ final_report_text += r.content
258
+ except Exception as e:
259
+ messages.append({"role": "assistant", "content": f"❌ Error generating final report: {str(e)}"})
260
+ return messages, report_path
261
+
262
+ final_report = f"# \U0001f9e0 Final Patient Report\n\n{clean_response(final_report_text)}"
263
+ messages[-1]["content"] = f"πŸ“Š Final Report:\n\n{clean_response(final_report_text)}"
264
+
265
+ # Save the report
266
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
267
  report_path = os.path.join(report_dir, f"report_{timestamp}.md")
268
 
269
  with open(report_path, 'w') as f:
270
  f.write(final_report)
271
+
272
+ messages.append({"role": "assistant", "content": f"βœ… Report generated and saved: report_{timestamp}.md"})
273
+
 
274
  except Exception as e:
275
+ messages.append({"role": "assistant", "content": f"❌ Error processing file: {str(e)}"})
276
+
277
+ return messages, report_path
278
+
279
+ def create_ui(agent):
280
+ """Create the Gradio UI for the patient history analysis tool."""
281
+ with gr.Blocks(title="Patient History Chat", css=".gradio-container {max-width: 900px !important}") as demo:
282
+ gr.Markdown("## πŸ₯ Patient History Analysis Tool")
283
 
284
  with gr.Row():
285
  with gr.Column(scale=3):
286
  chatbot = gr.Chatbot(
287
+ label="Clinical Assistant",
288
  show_copy_button=True,
289
  height=600,
290
+ type="messages",
291
+ avatar_images=(
292
+ None,
293
+ "https://i.imgur.com/6wX7Zb4.png"
294
+ )
295
  )
296
  with gr.Column(scale=1):
297
+ file_upload = gr.File(
298
  label="Upload Excel File",
299
  file_types=[".xlsx"],
300
  height=100
301
  )
302
  analyze_btn = gr.Button(
303
+ "🧠 Analyze Patient History",
304
  variant="primary"
305
  )
306
  report_output = gr.File(
307
  label="Download Report",
308
+ visible=False,
309
+ interactive=False
310
  )
311
+
312
+ # State to maintain chatbot messages
313
+ chatbot_state = gr.State(value=[])
314
+
315
+ def update_ui(file, current_state):
316
+ messages, report_path = process_final_report(agent, file, current_state)
317
+ report_update = gr.update(visible=report_path is not None, value=report_path)
318
+ return messages, report_update, messages
319
+
320
  analyze_btn.click(
321
+ fn=update_ui,
322
+ inputs=[file_upload, chatbot_state],
323
+ outputs=[chatbot, report_output, chatbot_state],
324
+ api_name="analyze"
325
  )
326
+
327
  return demo
328
 
329
  if __name__ == "__main__":
330
  try:
 
331
  agent = init_agent()
332
  demo = create_ui(agent)
 
 
333
  demo.launch(
334
  server_name="0.0.0.0",
335
  server_port=7860,
336
  show_error=True,
337
+ allowed_paths=["/data/hf_cache/reports"],
338
+ share=False
 
339
  )
340
  except Exception as e:
341
+ print(f"Error: {str(e)}")
342
  sys.exit(1)