Switch to HuggingFace Inference API - no local model loading
Browse files- app.py +123 -151
- requirements.txt +0 -0
app.py
CHANGED
|
@@ -17,6 +17,8 @@ import torch
|
|
| 17 |
import plotly.express as px
|
| 18 |
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
| 19 |
from streamlit_option_menu import option_menu
|
|
|
|
|
|
|
| 20 |
import os
|
| 21 |
import sys
|
| 22 |
from pathlib import Path
|
|
@@ -24,7 +26,7 @@ import yfinance as yf
|
|
| 24 |
from datetime import datetime, timedelta
|
| 25 |
import warnings
|
| 26 |
warnings.filterwarnings('ignore')
|
| 27 |
-
|
| 28 |
|
| 29 |
# Get project root dynamically (no hardcoded paths!)
|
| 30 |
PROJECT_ROOT = Path(__file__).parent
|
|
@@ -44,142 +46,42 @@ EMOTION_EMOJIS = {
|
|
| 44 |
"neutral": "😐", "sadness": "😔", "shame": "😳", "surprise": "😮"
|
| 45 |
}
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
import time
|
| 50 |
-
|
| 51 |
-
# Global download status tracking
|
| 52 |
-
if 'models_downloading' not in st.session_state:
|
| 53 |
-
st.session_state.models_downloading = False
|
| 54 |
-
if 'models_ready' not in st.session_state:
|
| 55 |
-
st.session_state.models_ready = False
|
| 56 |
-
if 'download_progress' not in st.session_state:
|
| 57 |
-
st.session_state.download_progress = ""
|
| 58 |
-
if 'download_error' not in st.session_state:
|
| 59 |
-
st.session_state.download_error = None
|
| 60 |
-
|
| 61 |
-
def download_models_background():
|
| 62 |
-
"""Download models in background thread - doesn't block app startup"""
|
| 63 |
-
try:
|
| 64 |
-
emotion_dir = PROJECT_ROOT / "Models" / "sentiment_model_distilbert"
|
| 65 |
-
finbert_dir = PROJECT_ROOT / "finance" / "finbert_large_emotion_model"
|
| 66 |
-
|
| 67 |
-
# Download emotion model
|
| 68 |
-
if not emotion_dir.exists() or not (emotion_dir / "model.safetensors").exists():
|
| 69 |
-
st.session_state.download_progress = "📥 Downloading emotion model..."
|
| 70 |
-
temp_dir = snapshot_download("Ani-404/emotion-model")
|
| 71 |
-
emotion_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 72 |
-
if emotion_dir.exists():
|
| 73 |
-
shutil.rmtree(emotion_dir)
|
| 74 |
-
shutil.move(temp_dir, emotion_dir)
|
| 75 |
-
|
| 76 |
-
# Download financial model
|
| 77 |
-
if not finbert_dir.exists() or not (finbert_dir / "model.safetensors").exists():
|
| 78 |
-
st.session_state.download_progress = "📥 Downloading financial model..."
|
| 79 |
-
temp_dir = snapshot_download("Ani-404/finbert-model")
|
| 80 |
-
finbert_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 81 |
-
if finbert_dir.exists():
|
| 82 |
-
shutil.rmtree(finbert_dir)
|
| 83 |
-
shutil.move(temp_dir, finbert_dir)
|
| 84 |
-
|
| 85 |
-
# Success!
|
| 86 |
-
st.session_state.download_progress = "✅ Your trained models are now ready!"
|
| 87 |
-
st.session_state.models_ready = True
|
| 88 |
-
st.session_state.models_downloading = False
|
| 89 |
-
|
| 90 |
-
except Exception as e:
|
| 91 |
-
st.session_state.download_error = str(e)
|
| 92 |
-
st.session_state.download_progress = f"❌ Download failed: {str(e)[:100]}"
|
| 93 |
-
st.session_state.models_downloading = False
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
def check_and_start_download():
|
| 97 |
-
"""Check if models exist, start background download if needed"""
|
| 98 |
-
emotion_dir = PROJECT_ROOT / "Models" / "sentiment_model_distilbert"
|
| 99 |
-
finbert_dir = PROJECT_ROOT / "finance" / "finbert_large_emotion_model"
|
| 100 |
-
|
| 101 |
-
# Check if models already exist
|
| 102 |
-
emotion_exists = emotion_dir.exists() and (emotion_dir / "model.safetensors").exists()
|
| 103 |
-
finbert_exists = finbert_dir.exists() and (finbert_dir / "model.safetensors").exists()
|
| 104 |
-
|
| 105 |
-
if emotion_exists and finbert_exists:
|
| 106 |
-
st.session_state.models_ready = True
|
| 107 |
-
st.session_state.download_progress = "✅ Your trained models are ready!"
|
| 108 |
-
return
|
| 109 |
-
|
| 110 |
-
# Start download in background if not already downloading
|
| 111 |
-
if not st.session_state.models_downloading and not st.session_state.models_ready:
|
| 112 |
-
st.session_state.models_downloading = True
|
| 113 |
-
st.session_state.download_progress = "🚀 Starting model download..."
|
| 114 |
-
|
| 115 |
-
# Start background thread (daemon=True means it won't block app shutdown)
|
| 116 |
-
download_thread = threading.Thread(target=download_models_background, daemon=True)
|
| 117 |
-
download_thread.start()
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
def show_download_status():
|
| 121 |
-
"""Show download status without blocking the app"""
|
| 122 |
-
if st.session_state.models_downloading:
|
| 123 |
-
# Show progress WITHOUT sleep/rerun (these block the app)
|
| 124 |
-
col1, col2 = st.columns([3, 1])
|
| 125 |
-
with col1:
|
| 126 |
-
st.info(f"🔄 {st.session_state.download_progress}")
|
| 127 |
-
with col2:
|
| 128 |
-
if st.button("🔄 Refresh Status"):
|
| 129 |
-
st.rerun()
|
| 130 |
-
|
| 131 |
-
st.info("💡 **App is fully functional** with high-quality fallback models!")
|
| 132 |
-
st.caption("⏱️ Downloads happen in background. Click refresh to check progress.")
|
| 133 |
-
|
| 134 |
-
elif st.session_state.models_ready:
|
| 135 |
-
st.success("🎉 **Upgrade Complete!** Now using your trained models.")
|
| 136 |
-
|
| 137 |
-
elif st.session_state.download_error:
|
| 138 |
-
st.warning(f"⚠️ Download issue: {st.session_state.download_error[:100]}")
|
| 139 |
-
st.info("📊 **App running perfectly** with professional fallback models.")
|
| 140 |
-
|
| 141 |
-
else:
|
| 142 |
-
st.info("🚀 **App ready!** Using high-quality fallback models.")
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
-
|
| 146 |
-
show_download_status()
|
| 147 |
|
| 148 |
|
| 149 |
@st.cache_resource
|
| 150 |
def load_models():
|
| 151 |
-
"""
|
| 152 |
models = {}
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
else:
|
| 162 |
-
st.sidebar.warning("Emotion model not found - using demo mode")
|
| 163 |
-
models['general_tokenizer'] = None
|
| 164 |
-
models['general_model'] = None
|
| 165 |
-
|
| 166 |
-
# Load Financial Model (existing path)
|
| 167 |
-
finbert_path = PROJECT_ROOT / "finance" / "finbert_large_emotion_model"
|
| 168 |
-
if finbert_path.exists():
|
| 169 |
-
models['finbert_tokenizer'] = AutoTokenizer.from_pretrained(str(finbert_path))
|
| 170 |
-
models['finbert_model'] = AutoModelForSequenceClassification.from_pretrained(str(finbert_path))
|
| 171 |
-
st.sidebar.success("FinBERT model loaded")
|
| 172 |
-
else:
|
| 173 |
-
st.sidebar.warning("FinBERT model not found - using demo mode")
|
| 174 |
-
models['finbert_tokenizer'] = None
|
| 175 |
-
models['finbert_model'] = None
|
| 176 |
-
|
| 177 |
-
except Exception as e:
|
| 178 |
-
st.sidebar.error(f"Error loading models: {e}")
|
| 179 |
return None
|
| 180 |
|
| 181 |
-
return models
|
| 182 |
-
|
| 183 |
def predict_emotions_demo(text):
|
| 184 |
"""Demo emotion prediction using keyword analysis"""
|
| 185 |
text_lower = text.lower()
|
|
@@ -213,33 +115,104 @@ def predict_emotions_demo(text):
|
|
| 213 |
return emotion, confidence, probs
|
| 214 |
|
| 215 |
def predict_emotions_real(text, model, tokenizer):
|
| 216 |
-
"""
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
def analyze_financial_sentiment(text, ticker=None):
|
| 231 |
-
"""
|
| 232 |
if not text.strip():
|
| 233 |
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
|
| 235 |
-
|
|
|
|
|
|
|
| 236 |
text_lower = text.lower()
|
| 237 |
positive_words = ['growth', 'profit', 'exceeded', 'strong', 'robust', 'expansion', 'record', 'beat', 'success', 'revenue']
|
| 238 |
negative_words = ['decline', 'loss', 'weak', 'concern', 'challenge', 'disruption', 'headwinds', 'drop', 'fall', 'miss']
|
| 239 |
-
|
| 240 |
pos_count = sum(1 for word in positive_words if word in text_lower)
|
| 241 |
neg_count = sum(1 for word in negative_words if word in text_lower)
|
| 242 |
-
|
| 243 |
if pos_count > neg_count:
|
| 244 |
sentiment_score = 0.3 + (pos_count - neg_count) * 0.15
|
| 245 |
signal = "BUY" if sentiment_score > 0.5 else "HOLD"
|
|
@@ -249,18 +222,17 @@ def analyze_financial_sentiment(text, ticker=None):
|
|
| 249 |
else:
|
| 250 |
sentiment_score = 0.0
|
| 251 |
signal = "HOLD"
|
| 252 |
-
|
| 253 |
-
# Clamp between -1 and 1
|
| 254 |
sentiment_score = max(-1.0, min(1.0, sentiment_score))
|
| 255 |
-
|
| 256 |
-
# Get stock data
|
| 257 |
stock_data = None
|
| 258 |
if ticker:
|
| 259 |
try:
|
| 260 |
stock = yf.Ticker(ticker)
|
| 261 |
hist = stock.history(period="5d")
|
| 262 |
if len(hist) >= 2:
|
| 263 |
-
recent_change = (hist['Close'][-1] - hist['Close'][-2]) / hist['Close'][-2] * 100
|
| 264 |
stock_data = {
|
| 265 |
'ticker': ticker,
|
| 266 |
'price': hist['Close'][-1],
|
|
@@ -268,7 +240,7 @@ def analyze_financial_sentiment(text, ticker=None):
|
|
| 268 |
}
|
| 269 |
except Exception as e:
|
| 270 |
st.warning(f"Could not fetch stock data for {ticker}: {e}")
|
| 271 |
-
|
| 272 |
return {
|
| 273 |
'sentiment_score': sentiment_score,
|
| 274 |
'signal': signal,
|
|
|
|
| 17 |
import plotly.express as px
|
| 18 |
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
| 19 |
from streamlit_option_menu import option_menu
|
| 20 |
+
from huggingface_hub import InferenceClient
|
| 21 |
+
import requests
|
| 22 |
import os
|
| 23 |
import sys
|
| 24 |
from pathlib import Path
|
|
|
|
| 26 |
from datetime import datetime, timedelta
|
| 27 |
import warnings
|
| 28 |
warnings.filterwarnings('ignore')
|
| 29 |
+
|
| 30 |
|
| 31 |
# Get project root dynamically (no hardcoded paths!)
|
| 32 |
PROJECT_ROOT = Path(__file__).parent
|
|
|
|
| 46 |
"neutral": "😐", "sadness": "😔", "shame": "😳", "surprise": "😮"
|
| 47 |
}
|
| 48 |
|
| 49 |
+
# HuggingFace Inference API Setup
|
| 50 |
+
HF_TOKEN = st.secrets.get("HF_TOKEN")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
+
@st.cache_resource
|
| 53 |
+
def get_inference_clients():
|
| 54 |
+
"""Initialize HuggingFace Inference clients"""
|
| 55 |
+
try:
|
| 56 |
+
emotion_client = InferenceClient(
|
| 57 |
+
model="Ani-404/emotion-model", # Your actual model name
|
| 58 |
+
token=HF_TOKEN
|
| 59 |
+
)
|
| 60 |
+
finbert_client = InferenceClient(
|
| 61 |
+
model="Ani-404/finbert-model", # Your actual model name
|
| 62 |
+
token=HF_TOKEN
|
| 63 |
+
)
|
| 64 |
+
return emotion_client, finbert_client
|
| 65 |
+
except:
|
| 66 |
+
return None, None
|
| 67 |
|
| 68 |
+
emotion_client, finbert_client = get_inference_clients()
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
@st.cache_resource
|
| 72 |
def load_models():
|
| 73 |
+
"""Check if HF Inference API is available"""
|
| 74 |
models = {}
|
| 75 |
+
|
| 76 |
+
if emotion_client and finbert_client:
|
| 77 |
+
models['general_model'] = "hf_api"
|
| 78 |
+
models['finbert_model'] = "hf_api"
|
| 79 |
+
st.sidebar.success("✅ Using your trained models via HuggingFace API")
|
| 80 |
+
return models
|
| 81 |
+
else:
|
| 82 |
+
st.sidebar.warning("⚠️ Using demo mode - add HF_TOKEN to secrets")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
return None
|
| 84 |
|
|
|
|
|
|
|
| 85 |
def predict_emotions_demo(text):
|
| 86 |
"""Demo emotion prediction using keyword analysis"""
|
| 87 |
text_lower = text.lower()
|
|
|
|
| 115 |
return emotion, confidence, probs
|
| 116 |
|
| 117 |
def predict_emotions_real(text, model, tokenizer):
|
| 118 |
+
"""Use HuggingFace Inference API for emotion prediction"""
|
| 119 |
+
try:
|
| 120 |
+
# Call your emotion model via API
|
| 121 |
+
response = emotion_client.text_classification(text)
|
| 122 |
+
|
| 123 |
+
if response and len(response) > 0:
|
| 124 |
+
# Get top prediction
|
| 125 |
+
top_pred = max(response, key=lambda x: x['score'])
|
| 126 |
+
emotion = top_pred['label'].lower()
|
| 127 |
+
confidence = top_pred['score']
|
| 128 |
+
|
| 129 |
+
# Create probability distribution
|
| 130 |
+
emotion_labels = ['anger', 'disgust', 'fear', 'joy', 'neutral', 'sadness', 'shame', 'surprise']
|
| 131 |
+
probs = [0.1] * len(emotion_labels) # Default small probabilities
|
| 132 |
+
|
| 133 |
+
# Set the predicted emotion's probability
|
| 134 |
+
if emotion in emotion_labels:
|
| 135 |
+
idx = emotion_labels.index(emotion)
|
| 136 |
+
probs[idx] = confidence
|
| 137 |
+
|
| 138 |
+
return emotion, confidence, probs
|
| 139 |
+
else:
|
| 140 |
+
# Fallback to demo mode
|
| 141 |
+
return predict_emotions_demo(text)
|
| 142 |
+
|
| 143 |
+
except Exception as e:
|
| 144 |
+
st.error(f"API Error: {e}")
|
| 145 |
+
return predict_emotions_demo(text)
|
| 146 |
|
| 147 |
def analyze_financial_sentiment(text, ticker=None):
|
| 148 |
+
"""Use HuggingFace Inference API for financial sentiment"""
|
| 149 |
if not text.strip():
|
| 150 |
return None
|
| 151 |
+
|
| 152 |
+
try:
|
| 153 |
+
# Call your finbert model via API
|
| 154 |
+
response = finbert_client.text_classification(text)
|
| 155 |
+
|
| 156 |
+
if response and len(response) > 0:
|
| 157 |
+
top_pred = max(response, key=lambda x: x['score'])
|
| 158 |
+
label = top_pred['label'].lower()
|
| 159 |
+
confidence = top_pred['score']
|
| 160 |
+
|
| 161 |
+
# Map to trading signals
|
| 162 |
+
if label == 'positive':
|
| 163 |
+
signal = "BUY" if confidence > 0.7 else "HOLD"
|
| 164 |
+
sentiment_score = confidence
|
| 165 |
+
elif label == 'negative':
|
| 166 |
+
signal = "SELL" if confidence > 0.7 else "HOLD"
|
| 167 |
+
sentiment_score = -confidence
|
| 168 |
+
else:
|
| 169 |
+
signal = "HOLD"
|
| 170 |
+
sentiment_score = 0.0
|
| 171 |
+
|
| 172 |
+
else:
|
| 173 |
+
# Fallback to keyword analysis
|
| 174 |
+
return analyze_financial_sentiment_demo(text, ticker)
|
| 175 |
+
|
| 176 |
+
except Exception as e:
|
| 177 |
+
st.error(f"Financial API Error: {e}")
|
| 178 |
+
return analyze_financial_sentiment_demo(text, ticker)
|
| 179 |
+
|
| 180 |
+
# Get stock data if ticker provided
|
| 181 |
+
stock_data = None
|
| 182 |
+
if ticker:
|
| 183 |
+
try:
|
| 184 |
+
stock = yf.Ticker(ticker)
|
| 185 |
+
hist = stock.history(period="5d")
|
| 186 |
+
if len(hist) >= 2:
|
| 187 |
+
recent_change = ((hist['Close'][-1] - hist['Close'][-2]) / hist['Close'][-2]) * 100
|
| 188 |
+
stock_data = {
|
| 189 |
+
'ticker': ticker,
|
| 190 |
+
'price': hist['Close'][-1],
|
| 191 |
+
'change': recent_change
|
| 192 |
+
}
|
| 193 |
+
except Exception as e:
|
| 194 |
+
st.warning(f"Could not fetch stock data for {ticker}: {e}")
|
| 195 |
+
|
| 196 |
+
return {
|
| 197 |
+
'sentiment_score': sentiment_score,
|
| 198 |
+
'signal': signal,
|
| 199 |
+
'confidence': confidence,
|
| 200 |
+
'stock_data': stock_data,
|
| 201 |
+
'positive_ratio': max(0, sentiment_score),
|
| 202 |
+
'negative_ratio': max(0, -sentiment_score),
|
| 203 |
+
'neutral_ratio': 1 - abs(sentiment_score)
|
| 204 |
+
}
|
| 205 |
|
| 206 |
+
def analyze_financial_sentiment_demo(text, ticker=None):
|
| 207 |
+
"""Original keyword-based analysis as fallback"""
|
| 208 |
+
# Your existing keyword analysis code here
|
| 209 |
text_lower = text.lower()
|
| 210 |
positive_words = ['growth', 'profit', 'exceeded', 'strong', 'robust', 'expansion', 'record', 'beat', 'success', 'revenue']
|
| 211 |
negative_words = ['decline', 'loss', 'weak', 'concern', 'challenge', 'disruption', 'headwinds', 'drop', 'fall', 'miss']
|
| 212 |
+
|
| 213 |
pos_count = sum(1 for word in positive_words if word in text_lower)
|
| 214 |
neg_count = sum(1 for word in negative_words if word in text_lower)
|
| 215 |
+
|
| 216 |
if pos_count > neg_count:
|
| 217 |
sentiment_score = 0.3 + (pos_count - neg_count) * 0.15
|
| 218 |
signal = "BUY" if sentiment_score > 0.5 else "HOLD"
|
|
|
|
| 222 |
else:
|
| 223 |
sentiment_score = 0.0
|
| 224 |
signal = "HOLD"
|
| 225 |
+
|
|
|
|
| 226 |
sentiment_score = max(-1.0, min(1.0, sentiment_score))
|
| 227 |
+
|
| 228 |
+
# Get stock data
|
| 229 |
stock_data = None
|
| 230 |
if ticker:
|
| 231 |
try:
|
| 232 |
stock = yf.Ticker(ticker)
|
| 233 |
hist = stock.history(period="5d")
|
| 234 |
if len(hist) >= 2:
|
| 235 |
+
recent_change = ((hist['Close'][-1] - hist['Close'][-2]) / hist['Close'][-2]) * 100
|
| 236 |
stock_data = {
|
| 237 |
'ticker': ticker,
|
| 238 |
'price': hist['Close'][-1],
|
|
|
|
| 240 |
}
|
| 241 |
except Exception as e:
|
| 242 |
st.warning(f"Could not fetch stock data for {ticker}: {e}")
|
| 243 |
+
|
| 244 |
return {
|
| 245 |
'sentiment_score': sentiment_score,
|
| 246 |
'signal': signal,
|
requirements.txt
CHANGED
|
Binary files a/requirements.txt and b/requirements.txt differ
|
|
|