Yoel125 commited on
Commit
6692892
·
verified ·
1 Parent(s): 88a447b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +67 -30
app.py CHANGED
@@ -13,11 +13,20 @@ import spaces # <--- Import the Hugging Face spaces library for Free GPU
13
  # --- 1. LOAD GENERATIVE AI COMPONENT ---
14
  device = "cuda" if torch.cuda.is_available() else "cpu"
15
  print(f"Loading GenAI Component on {device}...")
16
-
17
  # Using the smarter base model for high-quality text generation
18
  gen_tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-base")
19
  gen_model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-base").to(device)
20
 
 
 
 
 
 
 
 
 
 
 
21
  def generate_sales_pitch(user_query, company_name, sector, theme, description):
22
  # Feeding the company's description to the AI so it knows exactly what it's selling
23
  prompt = (
@@ -25,26 +34,29 @@ def generate_sales_pitch(user_query, company_name, sector, theme, description):
25
  f"Write one single, highly persuasive sentence explaining why investing in {company_name} "
26
  f"is the perfect choice for someone looking for '{user_query}'."
27
  )
28
-
29
  try:
30
  inputs = gen_tokenizer(prompt, return_tensors="pt", max_length=256, truncation=True).to(device)
31
-
32
- # THE FIX: do_sample=False and num_beams=4 ensures the AI mathematically finds
33
- # the single "best" response and locks it in every time without changing.
34
- outputs = gen_model.generate(
35
- **inputs,
36
- max_new_tokens=60,
37
- do_sample=False,
38
- num_beams=4,
39
- repetition_penalty=2.0,
40
- early_stopping=True
41
- )
 
 
 
 
42
  pitch = gen_tokenizer.decode(outputs[0], skip_special_tokens=True)
43
-
44
  # Clean up any leftover prompt artifacts
45
  pitch = pitch.replace("Based on this company description:", "").strip()
46
  return pitch
47
-
48
  except Exception:
49
  return f"{company_name} is an exceptional strategic match for '{user_query}' within the {sector} space."
50
 
@@ -66,15 +78,15 @@ if 'faiss_index' not in globals():
66
  faiss_index = faiss.IndexFlatL2(embeddings.shape[1])
67
  faiss_index.add(np.array(embeddings).astype('float32'))
68
  except FileNotFoundError:
69
- print("Saved embeddings not scratch...")
70
  embeddings = embedding_model.encode(df['full_text'].tolist(), show_progress_bar=False)
71
  faiss_index = faiss.IndexFlatL2(embeddings.shape[1])
72
  faiss_index.add(np.array(embeddings).astype('float32'))
73
 
74
-
75
  # --- 3. RECOMMENDATION ENGINE LOGIC ---
76
  all_sectors = ["All Sectors"] + sorted(list(df['sector'].unique()))
77
 
 
78
  @spaces.GPU # <--- Hugging Face ZeroGPU Decorator!
79
  def recommend_investment(user_query, selected_sector, top_k=3):
80
  # 1. Check if completely empty
@@ -86,7 +98,7 @@ def recommend_investment(user_query, selected_sector, top_k=3):
86
  if user_query and str(user_query).strip():
87
  non_english_chars = sum(1 for char in str(user_query) if ord(char) > 127)
88
  if non_english_chars > 2:
89
- yield "⚠️ **Language Not Supported:** SectorSync AI is currently optimized exclusively for English data. Please write your investment thesis in English and try again."
90
  return
91
 
92
  # 3. Auto-fill sector if text box is empty
@@ -122,9 +134,9 @@ def recommend_investment(user_query, selected_sector, top_k=3):
122
  sim_score = 1 / (1 + distances[0][i])
123
  matches.append((df.iloc[idx], sim_score))
124
 
125
- output_markdown = f"### Top {len(matches)} AI-Recommended Matches for: *'{user_query}'*\n\n"
126
  if selected_sector != "All Sectors":
127
- output_markdown += f"**Filtered by Sector:** `{selected_sector}`\n\n---\n\n"
128
  else:
129
  output_markdown += "---\n\n"
130
 
@@ -145,18 +157,18 @@ def recommend_investment(user_query, selected_sector, top_k=3):
145
  tier_label = match_labels[rank - 1] if rank <= len(match_labels) else "Match"
146
 
147
  # The Fix: No bullet points, reads like a clean, professional report!
148
- output_markdown += f"#### #{rank}. {c_name} (`{ticker}`) — *{sector}*\n\n"
149
- output_markdown += f"**{tier_label}** (Similarity Score: `{score*100:.1f}%`)\n\n"
150
- output_markdown += f"**Industry:** {theme}\n\n"
151
- output_markdown += f"**GenAI Investment Pitch:** *'{pitch}'*\n\n"
152
- output_markdown += f"**Company Overview:** {desc[:200]}...\n\n---\n\n"
153
 
154
  # Streams the results instantly!
155
  yield output_markdown
156
 
157
 
158
  # --- 4. GRADIO USER INTERFACE ---
159
- # Instead of CSS hacks, we build a native Gradio Dark Theme to fix the white boxes!
160
  custom_theme = gr.themes.Base(
161
  primary_hue="emerald",
162
  neutral_hue="slate"
@@ -177,16 +189,40 @@ custom_theme = gr.themes.Base(
177
  block_label_text_color_dark="#f0f4f8",
178
  input_background_fill="#2a2a2a",
179
  input_background_fill_dark="#2a2a2a",
 
 
 
 
180
  button_primary_background_fill="#10b981",
181
  button_primary_background_fill_dark="#10b981",
182
  button_primary_text_color="#121212",
183
- button_primary_text_color_dark="#121212"
 
 
 
 
 
 
 
 
 
184
  )
185
 
186
- with gr.Blocks(theme=custom_theme, title="SectorSync AI") as demo:
 
 
 
 
 
 
 
 
 
 
 
187
  gr.Markdown("# SectorSync AI")
188
  gr.Markdown("Cut Through the Market Noise — Find Your Next Winning Stock in Seconds. Discover high-growth companies matching your investment thesis using FAISS Vector Search and Generative AI.")
189
- gr.Markdown("*Similarity Score reflects how closely a company profile matches your query in AI-embedding space — a relative ranking signal, not a calibrated financial confidence rating.*")
190
 
191
  with gr.Row():
192
  with gr.Column(scale=2):
@@ -219,6 +255,7 @@ with gr.Blocks(theme=custom_theme, title="SectorSync AI") as demo:
219
  search_button.click(fn=recommend_investment, inputs=[query_input, sector_dropdown], outputs=results_output)
220
  query_input.submit(fn=recommend_investment, inputs=[query_input, sector_dropdown], outputs=results_output)
221
 
222
- if __name__ == "__main__":
223
  print("Launching SectorSync AI Recommender App...")
 
224
  demo.launch(share=False)
 
13
  # --- 1. LOAD GENERATIVE AI COMPONENT ---
14
  device = "cuda" if torch.cuda.is_available() else "cpu"
15
  print(f"Loading GenAI Component on {device}...")
 
16
  # Using the smarter base model for high-quality text generation
17
  gen_tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-base")
18
  gen_model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-base").to(device)
19
 
20
+ # THE FIX for "the pitch changes every time I search":
21
+ # from_pretrained() does NOT put the model in inference mode by default, so its
22
+ # dropout layers stay active and randomly perturb every forward pass -- meaning
23
+ # the model can genuinely produce a different sentence for the exact same company
24
+ # and query, even with do_sample=False. Calling .eval() turns dropout off, which
25
+ # combined with do_sample=False/num_beams below makes generation fully deterministic:
26
+ # same company + same query -> same pitch, every single time.
27
+ gen_model.eval()
28
+
29
+
30
  def generate_sales_pitch(user_query, company_name, sector, theme, description):
31
  # Feeding the company's description to the AI so it knows exactly what it's selling
32
  prompt = (
 
34
  f"Write one single, highly persuasive sentence explaining why investing in {company_name} "
35
  f"is the perfect choice for someone looking for '{user_query}'."
36
  )
 
37
  try:
38
  inputs = gen_tokenizer(prompt, return_tensors="pt", max_length=256, truncation=True).to(device)
39
+ # Extra safety net: pin the RNG state right before generation too, in case any
40
+ # part of the model still touches it. With eval() already set this is now
41
+ # belt-and-suspenders, not the primary fix -- but it costs nothing to keep.
42
+ torch.manual_seed(42)
43
+ # do_sample=False and num_beams=4 make the AI mathematically find the single
44
+ # "best" response and lock it in every time, instead of rolling dice on each call.
45
+ with torch.no_grad():
46
+ outputs = gen_model.generate(
47
+ **inputs,
48
+ max_new_tokens=60,
49
+ do_sample=False,
50
+ num_beams=4,
51
+ repetition_penalty=2.0,
52
+ early_stopping=True
53
+ )
54
  pitch = gen_tokenizer.decode(outputs[0], skip_special_tokens=True)
55
+
56
  # Clean up any leftover prompt artifacts
57
  pitch = pitch.replace("Based on this company description:", "").strip()
58
  return pitch
59
+
60
  except Exception:
61
  return f"{company_name} is an exceptional strategic match for '{user_query}' within the {sector} space."
62
 
 
78
  faiss_index = faiss.IndexFlatL2(embeddings.shape[1])
79
  faiss_index.add(np.array(embeddings).astype('float32'))
80
  except FileNotFoundError:
81
+ print("Saved embeddings not found, encoding from scratch...")
82
  embeddings = embedding_model.encode(df['full_text'].tolist(), show_progress_bar=False)
83
  faiss_index = faiss.IndexFlatL2(embeddings.shape[1])
84
  faiss_index.add(np.array(embeddings).astype('float32'))
85
 
 
86
  # --- 3. RECOMMENDATION ENGINE LOGIC ---
87
  all_sectors = ["All Sectors"] + sorted(list(df['sector'].unique()))
88
 
89
+
90
  @spaces.GPU # <--- Hugging Face ZeroGPU Decorator!
91
  def recommend_investment(user_query, selected_sector, top_k=3):
92
  # 1. Check if completely empty
 
98
  if user_query and str(user_query).strip():
99
  non_english_chars = sum(1 for char in str(user_query) if ord(char) > 127)
100
  if non_english_chars > 2:
101
+ yield "⚠️ *Language Not Supported:* SectorSync AI is currently optimized exclusively for English data. Please write your investment thesis in English and try again."
102
  return
103
 
104
  # 3. Auto-fill sector if text box is empty
 
134
  sim_score = 1 / (1 + distances[0][i])
135
  matches.append((df.iloc[idx], sim_score))
136
 
137
+ output_markdown = f"### Top {len(matches)} AI-Recommended Matches for: '{user_query}'\n\n"
138
  if selected_sector != "All Sectors":
139
+ output_markdown += f"*Filtered by Sector:* ⁠ {selected_sector} ⁠\n\n---\n\n"
140
  else:
141
  output_markdown += "---\n\n"
142
 
 
157
  tier_label = match_labels[rank - 1] if rank <= len(match_labels) else "Match"
158
 
159
  # The Fix: No bullet points, reads like a clean, professional report!
160
+ output_markdown += f"#### #{rank}. {c_name} (⁠ {ticker} ⁠) — {sector}\n\n"
161
+ output_markdown += f"*{tier_label}* (Similarity Score: ⁠ {score*100:.1f}% ⁠)\n\n"
162
+ output_markdown += f"*Industry:* {theme}\n\n"
163
+ output_markdown += f"*GenAI Investment Pitch:* '{pitch}'\n\n"
164
+ output_markdown += f"*Company Overview:* {desc[:200]}...\n\n---\n\n"
165
 
166
  # Streams the results instantly!
167
  yield output_markdown
168
 
169
 
170
  # --- 4. GRADIO USER INTERFACE ---
171
+ # Native Gradio Dark Theme to fix the white boxes.
172
  custom_theme = gr.themes.Base(
173
  primary_hue="emerald",
174
  neutral_hue="slate"
 
189
  block_label_text_color_dark="#f0f4f8",
190
  input_background_fill="#2a2a2a",
191
  input_background_fill_dark="#2a2a2a",
192
+ input_border_color="#333333",
193
+ input_border_color_dark="#333333",
194
+ input_placeholder_color="#9ca3af",
195
+ input_placeholder_color_dark="#9ca3af",
196
  button_primary_background_fill="#10b981",
197
  button_primary_background_fill_dark="#10b981",
198
  button_primary_text_color="#121212",
199
+ button_primary_text_color_dark="#121212",
200
+ # THE FIX for "the table is still white": the Quick Starters examples list
201
+ # renders as a table, and the theme's generic background tokens above don't
202
+ # touch it -- it has its own dedicated table_* tokens that must be set separately.
203
+ table_even_background_fill="#1a1a1a",
204
+ table_even_background_fill_dark="#1a1a1a",
205
+ table_odd_background_fill="#242424",
206
+ table_odd_background_fill_dark="#242424",
207
+ table_border_color="#333333",
208
+ table_border_color_dark="#333333",
209
  )
210
 
211
+ # Belt-and-suspenders CSS in case this Gradio version's Examples table doesn't
212
+ # fully respect the table_* theme tokens above -- forces its background/text
213
+ # directly regardless of theme token coverage.
214
+ table_css = """
215
+ table, table.dataset, tbody, thead, tr, td, th {
216
+ background-color: #1a1a1a !important;
217
+ color: #f0f4f8 !important;
218
+ border-color: #333333 !important;
219
+ }
220
+ """
221
+
222
+ with gr.Blocks(theme=custom_theme, css=table_css, title="SectorSync AI") as demo:
223
  gr.Markdown("# SectorSync AI")
224
  gr.Markdown("Cut Through the Market Noise — Find Your Next Winning Stock in Seconds. Discover high-growth companies matching your investment thesis using FAISS Vector Search and Generative AI.")
225
+ gr.Markdown("Similarity Score reflects how closely a company profile matches your query in AI-embedding space — a relative ranking signal, not a calibrated financial confidence rating.")
226
 
227
  with gr.Row():
228
  with gr.Column(scale=2):
 
255
  search_button.click(fn=recommend_investment, inputs=[query_input, sector_dropdown], outputs=results_output)
256
  query_input.submit(fn=recommend_investment, inputs=[query_input, sector_dropdown], outputs=results_output)
257
 
258
+ if _name_ == "_main_":
259
  print("Launching SectorSync AI Recommender App...")
260
+ # share=False is best for Hugging Face spaces
261
  demo.launch(share=False)