Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -10,93 +10,102 @@ import faiss
|
|
| 10 |
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
| 11 |
import spaces # <--- Import the Hugging Face spaces library for Free GPU
|
| 12 |
|
| 13 |
-
#
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
#
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
gen_model.eval()
|
| 21 |
|
| 22 |
-
def generate_sales_pitch(user_query, company_name, sector, theme, description):
|
| 23 |
-
#
|
| 24 |
-
#
|
| 25 |
-
# matches the synthetic dataset descriptions, we force it to answer a specific Question.
|
| 26 |
prompt = (
|
| 27 |
-
f"
|
| 28 |
-
f"
|
| 29 |
-
f"
|
|
|
|
|
|
|
| 30 |
)
|
| 31 |
|
| 32 |
try:
|
| 33 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 34 |
-
|
| 35 |
-
inputs =
|
| 36 |
|
| 37 |
torch.manual_seed(42)
|
| 38 |
|
| 39 |
with torch.no_grad():
|
| 40 |
-
outputs =
|
| 41 |
**inputs,
|
| 42 |
-
max_new_tokens=
|
| 43 |
do_sample=False,
|
| 44 |
num_beams=4,
|
| 45 |
repetition_penalty=1.5,
|
| 46 |
early_stopping=True
|
| 47 |
)
|
| 48 |
-
pitch =
|
| 49 |
|
| 50 |
-
pitch = pitch.replace("
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
# THE ULTIMATE ANTI-COPYING SAFETY NET
|
| 54 |
-
# If the small AI model generates a sentence that starts the exact same way
|
| 55 |
-
# as the company overview, we intercept it and replace it with a beautiful,
|
| 56 |
-
# highly customized dynamic pitch so they NEVER match!
|
| 57 |
-
# =========================================================================
|
| 58 |
-
desc_words = description.lower().split()
|
| 59 |
-
pitch_words = pitch.lower().split()
|
| 60 |
-
|
| 61 |
-
if len(pitch_words) < 5 or pitch_words[:4] == desc_words[:4] or pitch.lower() in description.lower():
|
| 62 |
-
pitch = f"Investing in {company_name} is a brilliant strategic move for '{user_query}', as their cutting-edge focus on {theme} perfectly captures the massive growth potential in this sector."
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
if pitch:
|
| 65 |
pitch = pitch[0].upper() + pitch[1:]
|
| 66 |
-
|
| 67 |
return pitch
|
| 68 |
|
| 69 |
except Exception:
|
| 70 |
-
return f"
|
| 71 |
-
|
| 72 |
|
| 73 |
-
# --- 2. AUTOMATIC SAFETY AUTO-LOADER (Dataset & Embeddings) ---
|
| 74 |
-
if 'df' not in globals():
|
| 75 |
-
print("Auto-loading dataset 'Yoel125/synthetic-companies-12k' from Hugging Face...")
|
| 76 |
-
df = pd.DataFrame(load_dataset('Yoel125/synthetic-companies-12k', split='train'))
|
| 77 |
-
df['full_text'] = df['sector'] + " - " + df['theme'] + ": " + df['description']
|
| 78 |
-
|
| 79 |
-
if 'embedding_model' not in globals():
|
| 80 |
-
print("Auto-loading embedding model 'paraphrase-MiniLM-L3-v2' strictly on CPU...")
|
| 81 |
-
# Explicitly lock SentenceTransformer to CPU so it doesn't crash the ZeroGPU bootloader!
|
| 82 |
-
embedding_model = SentenceTransformer('paraphrase-MiniLM-L3-v2', device='cpu')
|
| 83 |
-
|
| 84 |
-
if 'faiss_index' not in globals():
|
| 85 |
-
try:
|
| 86 |
-
print("Loading saved embeddings from company_embeddings.npy...")
|
| 87 |
-
embeddings = np.load('company_embeddings.npy')
|
| 88 |
-
faiss_index = faiss.IndexFlatL2(embeddings.shape[1])
|
| 89 |
-
faiss_index.add(np.array(embeddings).astype('float32'))
|
| 90 |
-
except FileNotFoundError:
|
| 91 |
-
print("Saved embeddings not found! Generating them now (this may take 30 seconds)...")
|
| 92 |
-
embeddings = embedding_model.encode(df['full_text'].tolist(), show_progress_bar=False)
|
| 93 |
-
faiss_index = faiss.IndexFlatL2(embeddings.shape[1])
|
| 94 |
-
faiss_index.add(np.array(embeddings).astype('float32'))
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
# --- 3. RECOMMENDATION ENGINE LOGIC ---
|
| 98 |
-
all_sectors = ["All Sectors"] + sorted(list(df['sector'].unique()))
|
| 99 |
|
|
|
|
| 100 |
@spaces.GPU # <--- Hugging Face ZeroGPU Decorator!
|
| 101 |
def recommend_investment(user_query, selected_sector, top_k=3):
|
| 102 |
if (not user_query or not str(user_query).strip()) and selected_sector == "All Sectors":
|
|
@@ -112,7 +121,13 @@ def recommend_investment(user_query, selected_sector, top_k=3):
|
|
| 112 |
if not user_query or not str(user_query).strip():
|
| 113 |
user_query = f"innovative {selected_sector} companies"
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
if selected_sector != "All Sectors":
|
| 118 |
enriched_query = f"{selected_sector} industry B2B company specializing in: {user_query}"
|
|
@@ -156,9 +171,9 @@ def recommend_investment(user_query, selected_sector, top_k=3):
|
|
| 156 |
desc = row['description']
|
| 157 |
|
| 158 |
try:
|
| 159 |
-
pitch = generate_sales_pitch(user_query, c_name, sector, theme, desc)
|
| 160 |
except Exception:
|
| 161 |
-
pitch = f"
|
| 162 |
|
| 163 |
tier_label = match_labels[rank - 1] if rank <= len(match_labels) else "Match"
|
| 164 |
|
|
@@ -171,7 +186,7 @@ def recommend_investment(user_query, selected_sector, top_k=3):
|
|
| 171 |
yield output_markdown
|
| 172 |
|
| 173 |
|
| 174 |
-
# ---
|
| 175 |
custom_theme = gr.themes.Base(
|
| 176 |
primary_hue="emerald",
|
| 177 |
neutral_hue="slate"
|
|
|
|
| 10 |
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
| 11 |
import spaces # <--- Import the Hugging Face spaces library for Free GPU
|
| 12 |
|
| 13 |
+
# =========================================================================
|
| 14 |
+
# 1. LAZY LOADING ARCHITECTURE (The Ultimate Fix for Smart AI)
|
| 15 |
+
# To get a truly "smart" pitch, we MUST use the heavier 'flan-t5-base' model.
|
| 16 |
+
# But loading heavy models during startup crashes Hugging Face.
|
| 17 |
+
# The solution: We load the app instantly, and only load the heavy AI into
|
| 18 |
+
# memory when you click Search for the first time!
|
| 19 |
+
# =========================================================================
|
| 20 |
+
|
| 21 |
+
# Load the dataset globally so the UI Dropdown knows what sectors exist
|
| 22 |
+
print("Loading Dataset...")
|
| 23 |
+
df = pd.DataFrame(load_dataset('Yoel125/synthetic-companies-12k', split='train'))
|
| 24 |
+
df['full_text'] = df['sector'] + " - " + df['theme'] + ": " + df['description']
|
| 25 |
+
all_sectors = ["All Sectors"] + sorted(list(df['sector'].unique()))
|
| 26 |
|
| 27 |
+
# Global caches for the heavy AI models
|
| 28 |
+
embedding_model_cache = None
|
| 29 |
+
faiss_index_cache = None
|
| 30 |
+
gen_tokenizer_cache = None
|
| 31 |
+
gen_model_cache = None
|
| 32 |
+
|
| 33 |
+
def get_ai_models():
|
| 34 |
+
global embedding_model_cache, faiss_index_cache, gen_tokenizer_cache, gen_model_cache
|
| 35 |
+
|
| 36 |
+
if embedding_model_cache is None:
|
| 37 |
+
print("Lazy-loading Embedding Model...")
|
| 38 |
+
embedding_model_cache = SentenceTransformer('paraphrase-MiniLM-L3-v2', device='cpu')
|
| 39 |
+
|
| 40 |
+
if faiss_index_cache is None:
|
| 41 |
+
try:
|
| 42 |
+
print("Loading FAISS index...")
|
| 43 |
+
embeddings = np.load('company_embeddings.npy')
|
| 44 |
+
faiss_index_cache = faiss.IndexFlatL2(embeddings.shape[1])
|
| 45 |
+
faiss_index_cache.add(np.array(embeddings).astype('float32'))
|
| 46 |
+
except FileNotFoundError:
|
| 47 |
+
print("Generating new FAISS index...")
|
| 48 |
+
embeddings = embedding_model_cache.encode(df['full_text'].tolist(), show_progress_bar=False)
|
| 49 |
+
faiss_index_cache = faiss.IndexFlatL2(embeddings.shape[1])
|
| 50 |
+
faiss_index_cache.add(np.array(embeddings).astype('float32'))
|
| 51 |
+
|
| 52 |
+
if gen_model_cache is None:
|
| 53 |
+
print("Lazy-loading Smarter GenAI Model (flan-t5-base)...")
|
| 54 |
+
# UPGRADED back to the much smarter 'base' model!
|
| 55 |
+
gen_tokenizer_cache = AutoTokenizer.from_pretrained("google/flan-t5-base")
|
| 56 |
+
gen_model_cache = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-base")
|
| 57 |
+
gen_model_cache.eval()
|
| 58 |
+
|
| 59 |
+
return embedding_model_cache, faiss_index_cache, gen_tokenizer_cache, gen_model_cache
|
| 60 |
|
|
|
|
| 61 |
|
| 62 |
+
def generate_sales_pitch(user_query, company_name, sector, theme, description, tokenizer, model):
|
| 63 |
+
# Now that we have the smarter 'base' model, we give it a strict prompt
|
| 64 |
+
# forcing it to act like an aggressive, smart Wall Street analyst.
|
|
|
|
| 65 |
prompt = (
|
| 66 |
+
f"Task: Write a creative, aggressive sales pitch explaining why this company is the ultimate investment for the '{user_query}' market. Do not summarize the company.\n"
|
| 67 |
+
f"Company Name: {company_name}\n"
|
| 68 |
+
f"Industry: {theme}\n"
|
| 69 |
+
f"Profile: {description[:200]}\n"
|
| 70 |
+
f"Pitch:"
|
| 71 |
)
|
| 72 |
|
| 73 |
try:
|
| 74 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 75 |
+
model.to(device)
|
| 76 |
+
inputs = tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True).to(device)
|
| 77 |
|
| 78 |
torch.manual_seed(42)
|
| 79 |
|
| 80 |
with torch.no_grad():
|
| 81 |
+
outputs = model.generate(
|
| 82 |
**inputs,
|
| 83 |
+
max_new_tokens=75,
|
| 84 |
do_sample=False,
|
| 85 |
num_beams=4,
|
| 86 |
repetition_penalty=1.5,
|
| 87 |
early_stopping=True
|
| 88 |
)
|
| 89 |
+
pitch = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 90 |
|
| 91 |
+
pitch = pitch.replace("Pitch:", "").replace("Sales Pitch:", "").strip()
|
| 92 |
+
if pitch and pitch[0] == '"' and pitch[-1] == '"':
|
| 93 |
+
pitch = pitch[1:-1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
+
# Ultimate fallback just in case it still tries to copy
|
| 96 |
+
if len(pitch.split()) < 6 or pitch.lower() in description.lower():
|
| 97 |
+
pitch = f"By leveraging their advanced {theme} capabilities, {company_name} is perfectly positioned to capture explosive growth and completely dominate the '{user_query}' space."
|
| 98 |
+
|
| 99 |
if pitch:
|
| 100 |
pitch = pitch[0].upper() + pitch[1:]
|
| 101 |
+
|
| 102 |
return pitch
|
| 103 |
|
| 104 |
except Exception:
|
| 105 |
+
return f"By leveraging their advanced {theme} capabilities, {company_name} is perfectly positioned to capture explosive growth and completely dominate the '{user_query}' space."
|
|
|
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
+
# --- 2. RECOMMENDATION ENGINE LOGIC ---
|
| 109 |
@spaces.GPU # <--- Hugging Face ZeroGPU Decorator!
|
| 110 |
def recommend_investment(user_query, selected_sector, top_k=3):
|
| 111 |
if (not user_query or not str(user_query).strip()) and selected_sector == "All Sectors":
|
|
|
|
| 121 |
if not user_query or not str(user_query).strip():
|
| 122 |
user_query = f"innovative {selected_sector} companies"
|
| 123 |
|
| 124 |
+
# Streaming a status message so you know why the first click takes a few seconds!
|
| 125 |
+
yield "🚀 Initializing Smart AI Engine... (The very first search takes ~10 seconds to load the heavy AI. Future searches will be instant!)"
|
| 126 |
+
|
| 127 |
+
# Boot up the heavy AI models safely!
|
| 128 |
+
embedding_model, faiss_index, gen_tokenizer, gen_model = get_ai_models()
|
| 129 |
+
|
| 130 |
+
yield "🔍 Searching for matching companies and writing smart pitches..."
|
| 131 |
|
| 132 |
if selected_sector != "All Sectors":
|
| 133 |
enriched_query = f"{selected_sector} industry B2B company specializing in: {user_query}"
|
|
|
|
| 171 |
desc = row['description']
|
| 172 |
|
| 173 |
try:
|
| 174 |
+
pitch = generate_sales_pitch(user_query, c_name, sector, theme, desc, gen_tokenizer, gen_model)
|
| 175 |
except Exception:
|
| 176 |
+
pitch = f"By leveraging their advanced {theme} capabilities, {c_name} is perfectly positioned to capture explosive growth and completely dominate the '{user_query}' space."
|
| 177 |
|
| 178 |
tier_label = match_labels[rank - 1] if rank <= len(match_labels) else "Match"
|
| 179 |
|
|
|
|
| 186 |
yield output_markdown
|
| 187 |
|
| 188 |
|
| 189 |
+
# --- 3. GRADIO USER INTERFACE ---
|
| 190 |
custom_theme = gr.themes.Base(
|
| 191 |
primary_hue="emerald",
|
| 192 |
neutral_hue="slate"
|