Spaces:
Sleeping
Sleeping
Add application file
Browse files- Dockerfile +34 -0
- app.py +281 -0
- requirements.txt +9 -0
Dockerfile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
| 2 |
+
|
| 3 |
+
# Set working directory
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
git \
|
| 9 |
+
build-essential \
|
| 10 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
+
|
| 12 |
+
# Copy requirements first for better caching
|
| 13 |
+
COPY requirements.txt .
|
| 14 |
+
|
| 15 |
+
# Install Python dependencies
|
| 16 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 17 |
+
|
| 18 |
+
# Copy application code
|
| 19 |
+
COPY . .
|
| 20 |
+
|
| 21 |
+
# Create cache directory for models
|
| 22 |
+
RUN mkdir -p /app/cache
|
| 23 |
+
|
| 24 |
+
# Set environment variables
|
| 25 |
+
ENV HF_HOME=/app/cache
|
| 26 |
+
ENV TRANSFORMERS_CACHE=/app/cache
|
| 27 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
| 28 |
+
ENV GRADIO_SERVER_PORT=7860
|
| 29 |
+
|
| 30 |
+
# Expose port
|
| 31 |
+
EXPOSE 7860
|
| 32 |
+
|
| 33 |
+
# Run the application
|
| 34 |
+
CMD ["python", "app.py"]
|
app.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import json
|
| 3 |
+
import time
|
| 4 |
+
import pandas as pd
|
| 5 |
+
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
|
| 16 |
+
|
| 17 |
+
def load_model():
|
| 18 |
+
"""Load the RxNExtract model"""
|
| 19 |
+
global extractor, model_loading
|
| 20 |
+
|
| 21 |
+
if model_loading:
|
| 22 |
+
return "Model is currently loading, please wait..."
|
| 23 |
+
|
| 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!"
|
| 39 |
+
except Exception as e:
|
| 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:
|
| 48 |
+
return "Please load the model first!", "", "", ""
|
| 49 |
+
|
| 50 |
+
if not procedure_text.strip():
|
| 51 |
+
return "Please enter a chemical procedure to analyze.", "", "", ""
|
| 52 |
+
|
| 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)
|
| 70 |
+
confidence_plot = create_confidence_visualization(results, processing_time)
|
| 71 |
+
|
| 72 |
+
# Create summary
|
| 73 |
+
summary = create_summary(results, processing_time)
|
| 74 |
+
|
| 75 |
+
return summary, formatted_output, entity_plot, confidence_plot
|
| 76 |
+
|
| 77 |
+
except Exception as e:
|
| 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
|
| 160 |
+
entity_counts = {k: v for k, v in entity_counts.items() if v > 0}
|
| 161 |
+
|
| 162 |
+
if not entity_counts:
|
| 163 |
+
return None
|
| 164 |
+
|
| 165 |
+
# Create bar chart
|
| 166 |
+
fig = px.bar(
|
| 167 |
+
x=list(entity_counts.keys()),
|
| 168 |
+
y=list(entity_counts.values()),
|
| 169 |
+
title="Extracted Chemical Entities",
|
| 170 |
+
labels={'x': 'Entity Type', 'y': 'Count'},
|
| 171 |
+
color=list(entity_counts.keys()),
|
| 172 |
+
color_discrete_sequence=px.colors.qualitative.Set3
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
fig.update_layout(
|
| 176 |
+
showlegend=False,
|
| 177 |
+
height=400,
|
| 178 |
+
title_x=0.5,
|
| 179 |
+
xaxis_tickangle=-45
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
return fig
|
| 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},
|
| 195 |
+
gauge = {
|
| 196 |
+
'axis': {'range': [None, 100]},
|
| 197 |
+
'bar': {'color': "darkblue"},
|
| 198 |
+
'steps': [
|
| 199 |
+
{'range': [0, 50], 'color': "lightgray"},
|
| 200 |
+
{'range': [50, 80], 'color': "yellow"},
|
| 201 |
+
{'range': [80, 100], 'color': "green"}],
|
| 202 |
+
'threshold': {
|
| 203 |
+
'line': {'color': "red", 'width': 4},
|
| 204 |
+
'thickness': 0.75,
|
| 205 |
+
'value': 90}}))
|
| 206 |
+
|
| 207 |
+
fig.update_layout(
|
| 208 |
+
height=300,
|
| 209 |
+
title=f"Processing Time: {processing_time:.1f}s"
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
return fig
|
| 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 |
+
|
| 231 |
+
**🎯 Overall Performance:**
|
| 232 |
+
- **Confidence Level:** {confidence_level} ({confidence:.1%})
|
| 233 |
+
- **Processing Speed:** {processing_time:.1f} seconds
|
| 234 |
+
- **Total Entities Extracted:** {total_entities}
|
| 235 |
+
|
| 236 |
+
**📊 Extraction Breakdown:**
|
| 237 |
+
- **Reactants:** {len(data.get('reactants', []))}
|
| 238 |
+
- **Products:** {len(data.get('products', []))}
|
| 239 |
+
- **Reagents:** {len(data.get('reagents', []))}
|
| 240 |
+
- **Solvents:** {len(data.get('solvents', []))}
|
| 241 |
+
- **Conditions:** {len([v for v in data.get('conditions', {}).values() if v])}
|
| 242 |
+
- **Workup Steps:** {len(data.get('workup', []))}
|
| 243 |
+
|
| 244 |
+
**💡 Quality Assessment:**
|
| 245 |
+
{get_quality_assessment(confidence, total_entities)}
|
| 246 |
+
"""
|
| 247 |
+
|
| 248 |
+
return summary
|
| 249 |
+
|
| 250 |
+
def get_quality_assessment(confidence, total_entities):
|
| 251 |
+
"""Get quality assessment based on confidence and entities"""
|
| 252 |
+
if confidence >= 0.8 and total_entities >= 3:
|
| 253 |
+
return "✅ Excellent extraction quality with high confidence and comprehensive entity recognition."
|
| 254 |
+
elif confidence >= 0.6 and total_entities >= 2:
|
| 255 |
+
return "✅ Good extraction quality with moderate confidence. Results are reliable."
|
| 256 |
+
elif confidence >= 0.4:
|
| 257 |
+
return "⚠️ Moderate extraction quality. Some information may be missing or uncertain."
|
| 258 |
+
else:
|
| 259 |
+
return "❌ Low extraction quality. Consider reviewing the procedure text for clarity."
|
| 260 |
+
|
| 261 |
+
def get_example_procedures():
|
| 262 |
+
"""Get example procedures for the interface"""
|
| 263 |
+
examples = [
|
| 264 |
+
"""Add 2.5 g of benzoic acid to 50 mL of ethanol in a round-bottom flask.
|
| 265 |
+
Heat the mixture to reflux for 4 hours while stirring.
|
| 266 |
+
Cool the solution to room temperature and filter the precipitate.
|
| 267 |
+
Wash the solid with cold ethanol and dry to obtain 2.1 g of product (84% yield).""",
|
| 268 |
+
|
| 269 |
+
"""Dissolve 10.0 g of 4-nitroaniline in 200 mL of concentrated HCl.
|
| 270 |
+
Add 15.0 g of tin powder portionwise while maintaining temperature below 10°C.
|
| 271 |
+
Stir for 2 hours at room temperature, then heat to 60°C for 1 hour.
|
| 272 |
+
Neutralize with NaOH solution and extract with ethyl acetate (3 × 50 mL).
|
| 273 |
+
Dry over MgSO4 and concentrate to give 7.2 g of product (78% yield).""",
|
| 274 |
+
|
| 275 |
+
"""In a round-bottom flask, combine 5.0 mmol of styrene, 6.0 mmol of phenylboronic acid,
|
| 276 |
+
and 0.1 mmol of Pd(PPh3)4 catalyst in 20 mL of DMF.
|
| 277 |
+
Add 15.0 mmol of K2CO3 and heat to 100°C under nitrogen atmosphere.
|
| 278 |
+
Stir for 12 hours, then cool and filter through celite.
|
| 279 |
+
Purify by column chromatography to obtain 0.85 g of biphenyl derivative (92% yield)."""
|
| 280 |
+
]
|
| 281 |
+
return examples
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==4.44.0
|
| 2 |
+
torch>=1.9.0
|
| 3 |
+
transformers>=4.20.0
|
| 4 |
+
rxnextract>=1.2.0
|
| 5 |
+
pandas>=1.3.0
|
| 6 |
+
plotly>=5.0.0
|
| 7 |
+
numpy>=1.21.0
|
| 8 |
+
scikit-learn>=1.0.0
|
| 9 |
+
scipy>=1.7.0
|