chrohi commited on
Commit
b86fdb2
Β·
verified Β·
1 Parent(s): b38c0cb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +209 -105
app.py CHANGED
@@ -6,10 +6,20 @@ import plotly.express as px
6
  import plotly.graph_objects as go
7
  from plotly.subplots import make_subplots
8
  import torch
9
- from chemistry_llm import ChemistryReactionExtractor
10
  import warnings
11
  warnings.filterwarnings('ignore')
12
 
 
 
 
 
 
 
 
 
 
 
 
13
  # Global variables
14
  extractor = None
15
  model_loading = False
@@ -24,15 +34,26 @@ def load_model():
24
  if extractor is not None:
25
  return "Model already loaded!"
26
 
 
 
 
27
  model_loading = True
28
  try:
29
- # Initialize the extractor
30
- extractor = ChemistryReactionExtractor.from_pretrained(
31
- "chemplusx/rxnextract-complete",
32
  device="cuda" if torch.cuda.is_available() else "cpu",
33
- load_in_4bit=True,
34
- temperature=0.1,
35
- max_length=512
 
 
 
 
 
 
 
 
36
  )
37
  model_loading = False
38
  return "βœ… RxNExtract model loaded successfully!"
@@ -40,8 +61,8 @@ def load_model():
40
  model_loading = False
41
  return f"❌ Error loading model: {str(e)}"
42
 
43
- def analyze_procedure(procedure_text, include_confidence=True, temperature=0.1):
44
- """Analyze a chemical procedure"""
45
  global extractor
46
 
47
  if extractor is None:
@@ -53,17 +74,20 @@ def analyze_procedure(procedure_text, include_confidence=True, temperature=0.1):
53
  try:
54
  start_time = time.time()
55
 
56
- # Analyze the procedure
57
  results = extractor.analyze_procedure(
58
- procedure_text,
59
- return_raw=False,
60
- temperature=temperature
61
  )
62
 
63
  processing_time = time.time() - start_time
64
 
 
 
 
 
65
  # Format the results
66
- formatted_output = format_extraction_results(results)
67
 
68
  # Create visualizations
69
  entity_plot = create_entity_visualization(results)
@@ -78,82 +102,123 @@ def analyze_procedure(procedure_text, include_confidence=True, temperature=0.1):
78
  error_msg = f"❌ Error during analysis: {str(e)}"
79
  return error_msg, "", "", ""
80
 
81
- def format_extraction_results(results):
82
- """Format extraction results for display"""
83
- data = results['extracted_data']
 
 
 
 
 
84
 
85
  output = []
86
  output.append("## πŸ“Š Extraction Results\n")
87
- output.append(f"**🎯 Confidence:** {results['confidence']:.1%}")
88
- output.append(f"**⏱️ Processing Time:** {results['processing_time']:.1f}s\n")
89
-
90
- # Reactants
91
- if data.get('reactants'):
92
- output.append("### πŸ”΅ Reactants")
93
- for i, reactant in enumerate(data['reactants'], 1):
94
- name = reactant.get('name', 'Unknown')
95
- amount = reactant.get('amount', 'N/A')
96
- output.append(f"{i}. **{name}** - Amount: {amount}")
97
- output.append("")
98
-
99
- # Reagents
100
- if data.get('reagents'):
101
- output.append("### 🟑 Reagents")
102
- for i, reagent in enumerate(data['reagents'], 1):
103
- name = reagent.get('name', 'Unknown')
104
- amount = reagent.get('amount', 'N/A')
105
- output.append(f"{i}. **{name}** - Amount: {amount}")
106
- output.append("")
107
-
108
- # Solvents
109
- if data.get('solvents'):
110
- output.append("### πŸ”΅ Solvents")
111
- for i, solvent in enumerate(data['solvents'], 1):
112
- name = solvent.get('name', 'Unknown')
113
- amount = solvent.get('amount', 'N/A')
114
- output.append(f"{i}. **{name}** - Amount: {amount}")
115
- output.append("")
116
-
117
- # Products
118
- if data.get('products'):
119
- output.append("### 🟒 Products")
120
- for i, product in enumerate(data['products'], 1):
121
- name = product.get('name', 'Unknown')
122
- amount = product.get('amount', 'N/A')
123
- yield_val = product.get('yield', 'N/A')
124
- output.append(f"{i}. **{name}** - Amount: {amount}, Yield: {yield_val}")
125
- output.append("")
126
-
127
- # Conditions
128
- if data.get('conditions'):
129
- output.append("### 🌑️ Reaction Conditions")
130
- conditions = data['conditions']
131
- for key, value in conditions.items():
132
- if value:
133
- output.append(f"- **{key.title()}:** {value}")
134
- output.append("")
135
-
136
- # Workup steps
137
- if data.get('workup'):
138
- output.append("### βš—οΈ Workup Steps")
139
- for i, step in enumerate(data['workup'], 1):
140
- output.append(f"{i}. {step}")
141
- output.append("")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  return "\n".join(output)
144
 
145
  def create_entity_visualization(results):
146
  """Create entity count visualization"""
147
- data = results['extracted_data']
 
 
 
 
 
 
148
 
149
  # Count entities
150
  entity_counts = {
151
- 'Reactants': len(data.get('reactants', [])),
152
- 'Reagents': len(data.get('reagents', [])),
153
- 'Solvents': len(data.get('solvents', [])),
154
- 'Products': len(data.get('products', [])),
155
- 'Conditions': len([v for v in data.get('conditions', {}).values() if v]),
156
- 'Workup Steps': len(data.get('workup', []))
157
  }
158
 
159
  # Remove zero counts
@@ -183,12 +248,19 @@ def create_entity_visualization(results):
183
 
184
  def create_confidence_visualization(results, processing_time):
185
  """Create confidence and timing visualization"""
186
- confidence = results['confidence']
 
 
 
 
 
 
 
187
 
188
  # Create gauge chart for confidence
189
  fig = go.Figure(go.Indicator(
190
  mode = "gauge+number+delta",
191
- value = confidence * 100,
192
  domain = {'x': [0, 1], 'y': [0, 1]},
193
  title = {'text': "Confidence Score (%)"},
194
  delta = {'reference': 80},
@@ -213,31 +285,47 @@ def create_confidence_visualization(results, processing_time):
213
 
214
  def create_summary(results, processing_time):
215
  """Create a summary of the analysis"""
216
- data = results['extracted_data']
217
- confidence = results['confidence']
218
 
219
- total_entities = sum([
220
- len(data.get('reactants', [])),
221
- len(data.get('reagents', [])),
222
- len(data.get('solvents', [])),
223
- len(data.get('products', []))
224
- ])
225
 
226
- confidence_level = "High" if confidence >= 0.8 else "Medium" if confidence >= 0.6 else "Low"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
  summary = f"""
229
  ## πŸ“ˆ Analysis Summary
 
230
  **🎯 Overall Performance:**
231
- - **Confidence Level:** {confidence_level} ({confidence:.1%})
232
  - **Processing Speed:** {processing_time:.1f} seconds
233
  - **Total Entities Extracted:** {total_entities}
 
234
  **πŸ“Š Extraction Breakdown:**
235
- - **Reactants:** {len(data.get('reactants', []))}
236
- - **Products:** {len(data.get('products', []))}
237
- - **Reagents:** {len(data.get('reagents', []))}
238
- - **Solvents:** {len(data.get('solvents', []))}
239
- - **Conditions:** {len([v for v in data.get('conditions', {}).values() if v])}
240
- - **Workup Steps:** {len(data.get('workup', []))}
 
241
  **πŸ’‘ Quality Assessment:**
242
  {get_quality_assessment(confidence, total_entities)}
243
  """
@@ -246,11 +334,17 @@ def create_summary(results, processing_time):
246
 
247
  def get_quality_assessment(confidence, total_entities):
248
  """Get quality assessment based on confidence and entities"""
249
- if confidence >= 0.8 and total_entities >= 3:
 
 
 
 
 
 
250
  return "βœ… Excellent extraction quality with high confidence and comprehensive entity recognition."
251
- elif confidence >= 0.6 and total_entities >= 2:
252
  return "βœ… Good extraction quality with moderate confidence. Results are reliable."
253
- elif confidence >= 0.4:
254
  return "⚠️ Moderate extraction quality. Some information may be missing or uncertain."
255
  else:
256
  return "❌ Low extraction quality. Consider reviewing the procedure text for clarity."
@@ -288,6 +382,8 @@ def create_interface():
288
  # πŸ§ͺ RxNExtract - Chemical Reaction Extraction
289
 
290
  Extract chemical entities and reaction information from synthetic procedures using advanced NLP models.
 
 
291
  """)
292
 
293
  # Model loading section
@@ -332,14 +428,15 @@ def create_interface():
332
  examples = get_example_procedures()
333
 
334
  for i, example in enumerate(examples, 1):
335
- with gr.Accordion(f"Example {i}", open=False):
336
- gr.Textbox(
337
  value=example,
338
  label=f"Example {i}",
339
  lines=4,
340
  interactive=False
341
  )
342
- gr.Button(f"Use Example {i}", size="sm").click(
 
343
  fn=lambda ex=example: ex,
344
  outputs=procedure_input
345
  )
@@ -369,7 +466,7 @@ def create_interface():
369
 
370
  analyze_btn.click(
371
  fn=analyze_procedure,
372
- inputs=[procedure_input, gr.State(True), temperature_slider],
373
  outputs=[summary_output, detailed_output, entity_plot, confidence_plot]
374
  )
375
 
@@ -384,7 +481,14 @@ def create_interface():
384
  **About RxNExtract:** This tool uses advanced natural language processing to extract chemical entities,
385
  reaction conditions, and procedural information from synthetic chemistry procedures.
386
 
387
- **Powered by:** Hugging Face Transformers, Gradio, and the ChemPlusX team.
 
 
 
 
 
 
 
388
  """)
389
 
390
  return demo
 
6
  import plotly.graph_objects as go
7
  from plotly.subplots import make_subplots
8
  import torch
 
9
  import warnings
10
  warnings.filterwarnings('ignore')
11
 
12
+ # Import the correct module based on the repository structure
13
+ try:
14
+ from chemistry_llm import ChemistryReactionExtractor
15
+ except ImportError:
16
+ # Fallback for different import structure
17
+ try:
18
+ from chemistry_llm.core.extractor import ChemistryReactionExtractor
19
+ except ImportError:
20
+ print("Warning: ChemistryReactionExtractor not found. Using mock implementation.")
21
+ ChemistryReactionExtractor = None
22
+
23
  # Global variables
24
  extractor = None
25
  model_loading = False
 
34
  if extractor is not None:
35
  return "Model already loaded!"
36
 
37
+ if ChemistryReactionExtractor is None:
38
+ return "❌ ChemistryReactionExtractor module not available. Please check the installation."
39
+
40
  model_loading = True
41
  try:
42
+ # Initialize the extractor with proper configuration based on repository documentation
43
+ extractor = ChemistryReactionExtractor(
44
+ model_path="chemplusx/rxnextract-complete", # Use the model from repository
45
  device="cuda" if torch.cuda.is_available() else "cpu",
46
+ config={
47
+ "quantization": {
48
+ "load_in_4bit": True,
49
+ "bnb_4bit_quant_type": "nf4",
50
+ "bnb_4bit_compute_dtype": "float16"
51
+ },
52
+ "model": {
53
+ "default_temperature": 0.1,
54
+ "max_new_tokens": 512
55
+ }
56
+ }
57
  )
58
  model_loading = False
59
  return "βœ… RxNExtract model loaded successfully!"
 
61
  model_loading = False
62
  return f"❌ Error loading model: {str(e)}"
63
 
64
+ def analyze_procedure(procedure_text, temperature=0.1):
65
+ """Analyze a chemical procedure using the actual RxNExtract API"""
66
  global extractor
67
 
68
  if extractor is None:
 
74
  try:
75
  start_time = time.time()
76
 
77
+ # Use the correct API method from the repository
78
  results = extractor.analyze_procedure(
79
+ procedure_text=procedure_text,
80
+ return_raw=False
 
81
  )
82
 
83
  processing_time = time.time() - start_time
84
 
85
+ # Add processing time to results
86
+ if isinstance(results, dict):
87
+ results['processing_time'] = processing_time
88
+
89
  # Format the results
90
+ formatted_output = format_extraction_results(results, processing_time)
91
 
92
  # Create visualizations
93
  entity_plot = create_entity_visualization(results)
 
102
  error_msg = f"❌ Error during analysis: {str(e)}"
103
  return error_msg, "", "", ""
104
 
105
+ def format_extraction_results(results, processing_time):
106
+ """Format extraction results for display based on actual API structure"""
107
+ if not isinstance(results, dict):
108
+ return "Error: Invalid results format"
109
+
110
+ # Handle the actual data structure from RxNExtract
111
+ extracted_data = results.get('extracted_data', results)
112
+ confidence = results.get('confidence', 'N/A')
113
 
114
  output = []
115
  output.append("## πŸ“Š Extraction Results\n")
116
+
117
+ if confidence != 'N/A':
118
+ output.append(f"**🎯 Confidence:** {confidence:.1%}" if isinstance(confidence, float) else f"**🎯 Confidence:** {confidence}")
119
+ output.append(f"**⏱️ Processing Time:** {processing_time:.1f}s\n")
120
+
121
+ # Handle different possible data structures
122
+ if isinstance(extracted_data, dict):
123
+ # Reactants
124
+ reactants = extracted_data.get('reactants', [])
125
+ if reactants:
126
+ output.append("### πŸ”΅ Reactants")
127
+ for i, reactant in enumerate(reactants, 1):
128
+ if isinstance(reactant, dict):
129
+ name = reactant.get('name', reactant.get('compound', 'Unknown'))
130
+ amount = reactant.get('amount', reactant.get('quantity', 'N/A'))
131
+ output.append(f"{i}. **{name}** - Amount: {amount}")
132
+ else:
133
+ output.append(f"{i}. **{reactant}**")
134
+ output.append("")
135
+
136
+ # Reagents
137
+ reagents = extracted_data.get('reagents', [])
138
+ if reagents:
139
+ output.append("### 🟑 Reagents")
140
+ for i, reagent in enumerate(reagents, 1):
141
+ if isinstance(reagent, dict):
142
+ name = reagent.get('name', reagent.get('compound', 'Unknown'))
143
+ amount = reagent.get('amount', reagent.get('quantity', 'N/A'))
144
+ output.append(f"{i}. **{name}** - Amount: {amount}")
145
+ else:
146
+ output.append(f"{i}. **{reagent}**")
147
+ output.append("")
148
+
149
+ # Solvents
150
+ solvents = extracted_data.get('solvents', [])
151
+ if solvents:
152
+ output.append("### πŸ”΅ Solvents")
153
+ for i, solvent in enumerate(solvents, 1):
154
+ if isinstance(solvent, dict):
155
+ name = solvent.get('name', solvent.get('compound', 'Unknown'))
156
+ amount = solvent.get('amount', solvent.get('quantity', 'N/A'))
157
+ output.append(f"{i}. **{name}** - Amount: {amount}")
158
+ else:
159
+ output.append(f"{i}. **{solvent}**")
160
+ output.append("")
161
+
162
+ # Products
163
+ products = extracted_data.get('products', [])
164
+ if products:
165
+ output.append("### 🟒 Products")
166
+ for i, product in enumerate(products, 1):
167
+ if isinstance(product, dict):
168
+ name = product.get('name', product.get('compound', 'Unknown'))
169
+ amount = product.get('amount', product.get('quantity', 'N/A'))
170
+ yield_val = product.get('yield', 'N/A')
171
+ output.append(f"{i}. **{name}** - Amount: {amount}, Yield: {yield_val}")
172
+ else:
173
+ output.append(f"{i}. **{product}**")
174
+ output.append("")
175
+
176
+ # Conditions
177
+ conditions = extracted_data.get('conditions', {})
178
+ if conditions:
179
+ output.append("### 🌑️ Reaction Conditions")
180
+ if isinstance(conditions, dict):
181
+ for key, value in conditions.items():
182
+ if value:
183
+ output.append(f"- **{key.title()}:** {value}")
184
+ else:
185
+ output.append(f"- {conditions}")
186
+ output.append("")
187
+
188
+ # Workup steps
189
+ workup = extracted_data.get('workup', extracted_data.get('workup_steps', []))
190
+ if workup:
191
+ output.append("### βš—οΈ Workup Steps")
192
+ if isinstance(workup, list):
193
+ for i, step in enumerate(workup, 1):
194
+ output.append(f"{i}. {step}")
195
+ else:
196
+ output.append(f"1. {workup}")
197
+ output.append("")
198
+
199
+ if len(output) <= 3: # Only header and processing time
200
+ output.append("No specific reaction data extracted. The model may need adjustment or the procedure text may not contain clear chemical information.")
201
 
202
  return "\n".join(output)
203
 
204
  def create_entity_visualization(results):
205
  """Create entity count visualization"""
206
+ if not isinstance(results, dict):
207
+ return None
208
+
209
+ extracted_data = results.get('extracted_data', results)
210
+
211
+ if not isinstance(extracted_data, dict):
212
+ return None
213
 
214
  # Count entities
215
  entity_counts = {
216
+ 'Reactants': len(extracted_data.get('reactants', [])),
217
+ 'Reagents': len(extracted_data.get('reagents', [])),
218
+ 'Solvents': len(extracted_data.get('solvents', [])),
219
+ 'Products': len(extracted_data.get('products', [])),
220
+ 'Conditions': len(extracted_data.get('conditions', {})) if isinstance(extracted_data.get('conditions', {}), dict) else 1 if extracted_data.get('conditions') else 0,
221
+ 'Workup Steps': len(extracted_data.get('workup', extracted_data.get('workup_steps', [])))
222
  }
223
 
224
  # Remove zero counts
 
248
 
249
  def create_confidence_visualization(results, processing_time):
250
  """Create confidence and timing visualization"""
251
+ confidence = results.get('confidence', 0.5) if isinstance(results, dict) else 0.5
252
+
253
+ # Handle different confidence formats
254
+ if isinstance(confidence, str):
255
+ try:
256
+ confidence = float(confidence)
257
+ except:
258
+ confidence = 0.5
259
 
260
  # Create gauge chart for confidence
261
  fig = go.Figure(go.Indicator(
262
  mode = "gauge+number+delta",
263
+ value = confidence * 100 if confidence <= 1.0 else confidence,
264
  domain = {'x': [0, 1], 'y': [0, 1]},
265
  title = {'text': "Confidence Score (%)"},
266
  delta = {'reference': 80},
 
285
 
286
  def create_summary(results, processing_time):
287
  """Create a summary of the analysis"""
288
+ if not isinstance(results, dict):
289
+ return "## πŸ“ˆ Analysis Summary\nError processing results."
290
 
291
+ extracted_data = results.get('extracted_data', results)
292
+ confidence = results.get('confidence', 'N/A')
 
 
 
 
293
 
294
+ # Calculate total entities
295
+ total_entities = 0
296
+ if isinstance(extracted_data, dict):
297
+ total_entities = sum([
298
+ len(extracted_data.get('reactants', [])),
299
+ len(extracted_data.get('reagents', [])),
300
+ len(extracted_data.get('solvents', [])),
301
+ len(extracted_data.get('products', []))
302
+ ])
303
+
304
+ # Determine confidence level
305
+ confidence_level = "Unknown"
306
+ if isinstance(confidence, (int, float)):
307
+ confidence_val = confidence if confidence <= 1.0 else confidence / 100
308
+ confidence_level = "High" if confidence_val >= 0.8 else "Medium" if confidence_val >= 0.6 else "Low"
309
+
310
+ # Format confidence display
311
+ conf_display = f"{confidence:.1%}" if isinstance(confidence, float) and confidence <= 1.0 else str(confidence)
312
 
313
  summary = f"""
314
  ## πŸ“ˆ Analysis Summary
315
+
316
  **🎯 Overall Performance:**
317
+ - **Confidence Level:** {confidence_level} ({conf_display})
318
  - **Processing Speed:** {processing_time:.1f} seconds
319
  - **Total Entities Extracted:** {total_entities}
320
+
321
  **πŸ“Š Extraction Breakdown:**
322
+ - **Reactants:** {len(extracted_data.get('reactants', [])) if isinstance(extracted_data, dict) else 0}
323
+ - **Products:** {len(extracted_data.get('products', [])) if isinstance(extracted_data, dict) else 0}
324
+ - **Reagents:** {len(extracted_data.get('reagents', [])) if isinstance(extracted_data, dict) else 0}
325
+ - **Solvents:** {len(extracted_data.get('solvents', [])) if isinstance(extracted_data, dict) else 0}
326
+ - **Conditions:** {len(extracted_data.get('conditions', {})) if isinstance(extracted_data, dict) and isinstance(extracted_data.get('conditions', {}), dict) else (1 if isinstance(extracted_data, dict) and extracted_data.get('conditions') else 0)}
327
+ - **Workup Steps:** {len(extracted_data.get('workup', extracted_data.get('workup_steps', []))) if isinstance(extracted_data, dict) else 0}
328
+
329
  **πŸ’‘ Quality Assessment:**
330
  {get_quality_assessment(confidence, total_entities)}
331
  """
 
334
 
335
  def get_quality_assessment(confidence, total_entities):
336
  """Get quality assessment based on confidence and entities"""
337
+ # Handle different confidence formats
338
+ if isinstance(confidence, (int, float)):
339
+ conf_val = confidence if confidence <= 1.0 else confidence / 100
340
+ else:
341
+ conf_val = 0.5 # Default for unknown confidence
342
+
343
+ if conf_val >= 0.8 and total_entities >= 3:
344
  return "βœ… Excellent extraction quality with high confidence and comprehensive entity recognition."
345
+ elif conf_val >= 0.6 and total_entities >= 2:
346
  return "βœ… Good extraction quality with moderate confidence. Results are reliable."
347
+ elif conf_val >= 0.4:
348
  return "⚠️ Moderate extraction quality. Some information may be missing or uncertain."
349
  else:
350
  return "❌ Low extraction quality. Consider reviewing the procedure text for clarity."
 
382
  # πŸ§ͺ RxNExtract - Chemical Reaction Extraction
383
 
384
  Extract chemical entities and reaction information from synthetic procedures using advanced NLP models.
385
+
386
+ **Professional-grade system for extracting chemical reaction information from procedure texts using fine-tuned LLM with Dynamic prompting and self grounding.**
387
  """)
388
 
389
  # Model loading section
 
428
  examples = get_example_procedures()
429
 
430
  for i, example in enumerate(examples, 1):
431
+ with gr.Accordion(f"Example {i}: {['Benzoic Acid Synthesis', 'Aniline Reduction', 'Suzuki Coupling'][i-1]}", open=False):
432
+ example_text = gr.Textbox(
433
  value=example,
434
  label=f"Example {i}",
435
  lines=4,
436
  interactive=False
437
  )
438
+ use_example_btn = gr.Button(f"Use Example {i}", size="sm")
439
+ use_example_btn.click(
440
  fn=lambda ex=example: ex,
441
  outputs=procedure_input
442
  )
 
466
 
467
  analyze_btn.click(
468
  fn=analyze_procedure,
469
+ inputs=[procedure_input, temperature_slider],
470
  outputs=[summary_output, detailed_output, entity_plot, confidence_plot]
471
  )
472
 
 
481
  **About RxNExtract:** This tool uses advanced natural language processing to extract chemical entities,
482
  reaction conditions, and procedural information from synthetic chemistry procedures.
483
 
484
+ **Features:**
485
+ - Modular Architecture with clean, maintainable codebase
486
+ - Dynamic Prompting for better extraction accuracy
487
+ - Memory Efficient 4-bit quantization support
488
+ - Robust XML parsing with structured output
489
+ - Professional logging and error handling
490
+
491
+ **Powered by:** ChemPlusX Team | [GitHub Repository](https://github.com/chemplusx/RxNExtract)
492
  """)
493
 
494
  return demo