Spaces:
Runtime error
Runtime error
Arjun Moorthy
commited on
Commit
·
da47961
1
Parent(s):
cefbc35
Clean up repository and fix file locations
Browse files- Oncolife/requirements.txt +0 -20
- README.md +1 -1
- app.py +0 -259
Oncolife/requirements.txt
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
# Medical Chatbot HF Space Requirements
|
2 |
-
|
3 |
-
# Web framework
|
4 |
-
gradio>=4.44.0
|
5 |
-
|
6 |
-
# Machine learning libraries - specific versions for compatibility
|
7 |
-
torch>=2.1.0,<3.0.0
|
8 |
-
transformers>=4.35.0,<5.0.0
|
9 |
-
accelerate>=0.24.0
|
10 |
-
|
11 |
-
# HF Spaces GPU support
|
12 |
-
spaces>=0.1.0
|
13 |
-
|
14 |
-
# Basic utilities
|
15 |
-
numpy>=1.21.0,<2.0.0
|
16 |
-
requests>=2.28.0
|
17 |
-
|
18 |
-
# Additional dependencies for better device handling
|
19 |
-
safetensors>=0.4.0
|
20 |
-
tokenizers>=0.15.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
@@ -6,7 +6,7 @@ colorTo: green
|
|
6 |
sdk: gradio
|
7 |
sdk_version: 4.44.0
|
8 |
app_file: Oncolife/app.py
|
9 |
-
requirements_file:
|
10 |
pinned: false
|
11 |
---
|
12 |
|
|
|
6 |
sdk: gradio
|
7 |
sdk_version: 4.44.0
|
8 |
app_file: Oncolife/app.py
|
9 |
+
requirements_file: requirements.txt
|
10 |
pinned: false
|
11 |
---
|
12 |
|
app.py
DELETED
@@ -1,259 +0,0 @@
|
|
1 |
-
#!/usr/bin/env python3
|
2 |
-
"""
|
3 |
-
OncoLife Symptom & Triage Assistant
|
4 |
-
A medical chatbot that performs both symptom assessment and clinical triage for chemotherapy patients.
|
5 |
-
Updated: Using BioMistral-7B base model for medical conversations.
|
6 |
-
REBUILD: Simplified to use only base model, no adapters.
|
7 |
-
"""
|
8 |
-
|
9 |
-
import gradio as gr
|
10 |
-
import os
|
11 |
-
import json
|
12 |
-
from transformers import AutoTokenizer, MistralForCausalLM
|
13 |
-
import torch
|
14 |
-
from spaces import GPU
|
15 |
-
|
16 |
-
# Force GPU detection for HF Spaces
|
17 |
-
@GPU
|
18 |
-
def force_gpu_detection():
|
19 |
-
"""Force GPU detection for Hugging Face Spaces"""
|
20 |
-
return torch.cuda.is_available()
|
21 |
-
|
22 |
-
class OncoLifeAssistant:
|
23 |
-
def __init__(self):
|
24 |
-
# BioMistral base model configuration
|
25 |
-
BASE = "BioMistral/BioMistral-7B"
|
26 |
-
|
27 |
-
print("🔄 Initializing OncoLife Symptom & Triage Assistant")
|
28 |
-
print(f"📦 Loading base model: {BASE}")
|
29 |
-
|
30 |
-
# Force GPU detection first
|
31 |
-
try:
|
32 |
-
gpu_available = force_gpu_detection()
|
33 |
-
print(f"🖥️ GPU Detection: {gpu_available}")
|
34 |
-
except Exception as e:
|
35 |
-
print(f"⚠️ GPU detection error: {e}")
|
36 |
-
gpu_available = torch.cuda.is_available()
|
37 |
-
|
38 |
-
self._load_model(BASE, gpu_available)
|
39 |
-
|
40 |
-
# Initialize conversation state
|
41 |
-
self.conversation_state = {
|
42 |
-
"symptoms": [],
|
43 |
-
"asked_ids": [],
|
44 |
-
"answers": {},
|
45 |
-
"current_symptom": None,
|
46 |
-
"conversation_phase": "initial" # initial, symptom_assessment, triage, summary
|
47 |
-
}
|
48 |
-
|
49 |
-
def _load_model(self, model_id, gpu_available):
|
50 |
-
"""Load the BioMistral base model"""
|
51 |
-
try:
|
52 |
-
print("🔄 Loading BioMistral base model...")
|
53 |
-
|
54 |
-
# Determine device strategy
|
55 |
-
if gpu_available and torch.cuda.is_available():
|
56 |
-
device = "cuda"
|
57 |
-
dtype = torch.float16
|
58 |
-
print("🖥️ Loading BioMistral model on GPU...")
|
59 |
-
else:
|
60 |
-
device = "cpu"
|
61 |
-
dtype = torch.float32
|
62 |
-
print("💻 Loading BioMistral model on CPU...")
|
63 |
-
|
64 |
-
# Load tokenizer
|
65 |
-
print(f"📝 Loading tokenizer: {model_id}")
|
66 |
-
self.tokenizer = AutoTokenizer.from_pretrained(
|
67 |
-
model_id,
|
68 |
-
trust_remote_code=True
|
69 |
-
)
|
70 |
-
|
71 |
-
# Load the model
|
72 |
-
print(f"📦 Loading model: {model_id}")
|
73 |
-
self.model = MistralForCausalLM.from_pretrained(
|
74 |
-
model_id,
|
75 |
-
trust_remote_code=True,
|
76 |
-
device_map="auto",
|
77 |
-
torch_dtype=dtype,
|
78 |
-
low_cpu_mem_usage=True
|
79 |
-
)
|
80 |
-
|
81 |
-
# Add pad token if not present
|
82 |
-
if self.tokenizer.pad_token is None:
|
83 |
-
self.tokenizer.pad_token = self.tokenizer.eos_token
|
84 |
-
|
85 |
-
print(f"✅ BioMistral base model loaded successfully on {device.upper()}!")
|
86 |
-
|
87 |
-
except Exception as e:
|
88 |
-
print(f"❌ Error loading BioMistral model: {e}")
|
89 |
-
self.model = None
|
90 |
-
self.tokenizer = None
|
91 |
-
|
92 |
-
def generate_oncolife_response(self, user_input, conversation_history):
|
93 |
-
"""Generate response using OncoLife Symptom & Triage Assistant protocol"""
|
94 |
-
try:
|
95 |
-
if self.model is None or self.tokenizer is None:
|
96 |
-
return """❌ **Model Loading Error**
|
97 |
-
|
98 |
-
The OncoLife assistant model failed to load. This could be due to:
|
99 |
-
1. Model not available
|
100 |
-
2. Memory constraints
|
101 |
-
3. Network issues
|
102 |
-
|
103 |
-
Please check the Space logs for details."""
|
104 |
-
|
105 |
-
print(f"🔄 Generating OncoLife response for: {user_input}")
|
106 |
-
|
107 |
-
# Create OncoLife-specific prompt
|
108 |
-
system_prompt = """You are the OncoLife Symptom & Triage Assistant, a medical chatbot that performs both symptom assessment and clinical triage for chemotherapy patients. Your task is to guide users through structured symptom reporting and decide whether any responses require escalation to their care team.
|
109 |
-
|
110 |
-
Follow this workflow:
|
111 |
-
1. Ask for symptoms if none provided
|
112 |
-
2. For each symptom, ask severity rating (mild/moderate/severe)
|
113 |
-
3. Check for red flags and immediate escalation needs
|
114 |
-
4. Grade severity using CTCAE or UKONS criteria
|
115 |
-
5. Ask targeted questions based on utility scoring
|
116 |
-
6. Provide structured summary with triage recommendations
|
117 |
-
|
118 |
-
Safety protocols:
|
119 |
-
- Never provide medical advice or treatment recommendations
|
120 |
-
- Always redirect to oncology team for medical decisions
|
121 |
-
- Escalate immediately for dangerous symptoms
|
122 |
-
- Add legal disclaimer at session end
|
123 |
-
|
124 |
-
Current conversation state: {conversation_state}"""
|
125 |
-
|
126 |
-
# Format conversation history
|
127 |
-
history_text = ""
|
128 |
-
if conversation_history:
|
129 |
-
for entry in conversation_history:
|
130 |
-
history_text += f"User: {entry['user']}\nAssistant: {entry['assistant']}\n\n"
|
131 |
-
|
132 |
-
# Create full prompt
|
133 |
-
prompt = f"{system_prompt}\n\nConversation History:\n{history_text}\nUser: {user_input}\nAssistant:"
|
134 |
-
|
135 |
-
# Tokenize
|
136 |
-
inputs = self.tokenizer(prompt, return_tensors="pt", padding=True)
|
137 |
-
|
138 |
-
# Get the device the model is actually on
|
139 |
-
model_device = next(self.model.parameters()).device
|
140 |
-
print(f"🔧 Model device: {model_device}")
|
141 |
-
|
142 |
-
# Move inputs to the same device as the model
|
143 |
-
for key in inputs:
|
144 |
-
if isinstance(inputs[key], torch.Tensor):
|
145 |
-
inputs[key] = inputs[key].to(model_device)
|
146 |
-
print(f"📦 Moved {key} to {model_device}")
|
147 |
-
|
148 |
-
# Ensure model is in eval mode
|
149 |
-
self.model.eval()
|
150 |
-
|
151 |
-
# Generate with proper device handling
|
152 |
-
with torch.no_grad():
|
153 |
-
try:
|
154 |
-
outputs = self.model.generate(
|
155 |
-
**inputs,
|
156 |
-
max_new_tokens=512, # Longer responses for detailed medical assessment
|
157 |
-
temperature=0.7,
|
158 |
-
do_sample=True,
|
159 |
-
top_p=0.9,
|
160 |
-
pad_token_id=self.tokenizer.eos_token_id,
|
161 |
-
eos_token_id=self.tokenizer.eos_token_id
|
162 |
-
)
|
163 |
-
except RuntimeError as e:
|
164 |
-
if "device" in str(e).lower():
|
165 |
-
print("🔄 Device error detected, trying CPU fallback...")
|
166 |
-
# Move everything to CPU and try again
|
167 |
-
self.model = self.model.to("cpu")
|
168 |
-
for key in inputs:
|
169 |
-
if isinstance(inputs[key], torch.Tensor):
|
170 |
-
inputs[key] = inputs[key].to("cpu")
|
171 |
-
|
172 |
-
outputs = self.model.generate(
|
173 |
-
**inputs,
|
174 |
-
max_new_tokens=512,
|
175 |
-
temperature=0.7,
|
176 |
-
do_sample=True,
|
177 |
-
top_p=0.9,
|
178 |
-
pad_token_id=self.tokenizer.eos_token_id,
|
179 |
-
eos_token_id=self.tokenizer.eos_token_id
|
180 |
-
)
|
181 |
-
else:
|
182 |
-
raise e
|
183 |
-
|
184 |
-
# Decode response
|
185 |
-
response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
186 |
-
|
187 |
-
# Extract just the assistant's response
|
188 |
-
if "Assistant:" in response:
|
189 |
-
answer = response.split("Assistant:")[-1].strip()
|
190 |
-
else:
|
191 |
-
answer = response.strip()
|
192 |
-
|
193 |
-
# Add legal disclaimer if this appears to be end of session
|
194 |
-
if any(keyword in user_input.lower() for keyword in ['done', 'finished', 'complete', 'summary']):
|
195 |
-
answer += "\n\n" + self._get_legal_disclaimer()
|
196 |
-
|
197 |
-
print("✅ OncoLife response generated successfully")
|
198 |
-
return answer
|
199 |
-
|
200 |
-
except Exception as e:
|
201 |
-
print(f"❌ Error generating OncoLife response: {e}")
|
202 |
-
return f"""❌ **Generation Error**
|
203 |
-
|
204 |
-
Error: {str(e)}
|
205 |
-
|
206 |
-
This could be due to:
|
207 |
-
1. Model compatibility issues
|
208 |
-
2. Memory constraints
|
209 |
-
3. Input format problems
|
210 |
-
|
211 |
-
Please try a simpler question or check the logs for more details."""
|
212 |
-
|
213 |
-
def _get_legal_disclaimer(self):
|
214 |
-
"""Return the legal disclaimer as specified in the instructions"""
|
215 |
-
return """**Legal Disclaimer:**
|
216 |
-
|
217 |
-
Patient verbalizes agreement with plan of care and understanding of the information we have gone over today and has no further comments, questions or concerns at this time. Will follow up with Doctor or ONN if symptoms worsen, do not improve, or any other symptoms develop. Agrees to seek emergency care if pt believes is needed, including for increased dizziness, depression, or any thoughts of SI.
|
218 |
-
|
219 |
-
**Important:** I cannot provide medical advice or treatment recommendations. Please call your oncology team to confirm what's appropriate for your specific situation."""
|
220 |
-
|
221 |
-
def chat(self, message, history):
|
222 |
-
"""Main chat interface for OncoLife Assistant"""
|
223 |
-
if not message.strip():
|
224 |
-
return "Please describe your symptoms or concerns."
|
225 |
-
|
226 |
-
# Convert history to the format expected by generate_oncolife_response
|
227 |
-
conversation_history = []
|
228 |
-
for user_msg, assistant_msg in history:
|
229 |
-
conversation_history.append({
|
230 |
-
"user": user_msg,
|
231 |
-
"assistant": assistant_msg
|
232 |
-
})
|
233 |
-
|
234 |
-
# Generate response using OncoLife protocol
|
235 |
-
response = self.generate_oncolife_response(message, conversation_history)
|
236 |
-
|
237 |
-
return response
|
238 |
-
|
239 |
-
# Create interface
|
240 |
-
assistant = OncoLifeAssistant()
|
241 |
-
interface = gr.ChatInterface(
|
242 |
-
fn=assistant.chat,
|
243 |
-
title="🏥 OncoLife Symptom & Triage Assistant",
|
244 |
-
description="I'm here to help assess your symptoms and determine if you need to contact your care team. Please describe your symptoms or concerns.",
|
245 |
-
examples=[
|
246 |
-
["I'm feeling nauseous and tired"],
|
247 |
-
["I have a fever of 101"],
|
248 |
-
["My neuropathy is getting worse"],
|
249 |
-
["I'm having trouble eating"],
|
250 |
-
["I feel dizzy and lightheaded"]
|
251 |
-
],
|
252 |
-
theme=gr.themes.Soft()
|
253 |
-
)
|
254 |
-
|
255 |
-
if __name__ == "__main__":
|
256 |
-
print("=" * 60)
|
257 |
-
print("OncoLife Symptom & Triage Assistant")
|
258 |
-
print("=" * 60)
|
259 |
-
interface.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|