BrianIsaac commited on
Commit
bc45c22
·
1 Parent(s): cea2220

fix: resolve MCP tool exposure and persona agent signature issues

Browse files

- Add sentiment_data parameter to PersonaAnalysisAgent.analyze_portfolio()
to match PortfolioAnalystAgent signature (fixes Phase 3 TypeError)
- Add show_api=False to 28 lambda event handlers to hide UI-only
functions from MCP tool exposure (reduces tools from 91 to 63)
- Integrate sentiment data into persona-based analysis prompts

Fixes "No analysis content available" warning caused by method
signature mismatch between agent classes.

Files changed (2) hide show
  1. app.py +56 -28
  2. backend/agents/personas.py +10 -1
app.py CHANGED
@@ -2366,23 +2366,28 @@ def create_interface() -> gr.Blocks:
2366
  # Add click handlers for example buttons
2367
  tech_btn.click(
2368
  fn=lambda: "AAPL 50 shares\nTSLA 25 shares\nNVDA 30 shares\nMETA 20 shares",
2369
- outputs=portfolio_input
 
2370
  )
2371
  conservative_btn.click(
2372
  fn=lambda: "VOO 100 shares\nVTI 75 shares\nSCHD 50 shares\nTLT 40 shares\nVXUS 60 shares",
2373
- outputs=portfolio_input
 
2374
  )
2375
  balanced_btn.click(
2376
  fn=lambda: "VTI $25000\nVXUS $15000\nBND $15000\nGLD $5000",
2377
- outputs=portfolio_input
 
2378
  )
2379
  global_btn.click(
2380
  fn=lambda: "VTI $15000\nVXUS $10000\nVWO $5000\nBND $10000\nGLD $3000\nVNQ $2000",
2381
- outputs=portfolio_input
 
2382
  )
2383
  risk_btn.click(
2384
  fn=lambda: "TSLA 100 shares",
2385
- outputs=portfolio_input
 
2386
  )
2387
 
2388
  # Feature highlights grid (1x4 compact layout)
@@ -4247,92 +4252,110 @@ Please try again with different parameters.
4247
  history_search.change(
4248
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4249
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4250
- outputs=[history_table, history_page_info]
 
4251
  ).then(
4252
  lambda: 1, # Reset to page 1 on search
4253
- outputs=[history_current_page]
 
4254
  )
4255
 
4256
  history_date_filter.change(
4257
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4258
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4259
- outputs=[history_table, history_page_info]
 
4260
  ).then(
4261
  lambda: 1, # Reset to page 1 on filter change
4262
- outputs=[history_current_page]
 
4263
  )
4264
 
4265
  history_refresh_btn.click(
4266
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4267
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4268
- outputs=[history_table, history_page_info]
 
4269
  )
4270
 
4271
  # Pagination handlers (standalone)
4272
  history_prev_btn.click(
4273
  lambda page: max(1, page - 1),
4274
  inputs=[history_current_page],
4275
- outputs=[history_current_page]
 
4276
  ).then(
4277
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4278
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4279
- outputs=[history_table, history_page_info]
 
4280
  )
4281
 
4282
  history_next_btn.click(
4283
  lambda page: page + 1,
4284
  inputs=[history_current_page],
4285
- outputs=[history_current_page]
 
4286
  ).then(
4287
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4288
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4289
- outputs=[history_table, history_page_info]
 
4290
  )
4291
 
4292
  # History search and filter handlers (results page tab)
4293
  history_search_results.change(
4294
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4295
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4296
- outputs=[history_table_results, history_page_info_results]
 
4297
  ).then(
4298
  lambda: 1, # Reset to page 1 on search
4299
- outputs=[history_current_page_results]
 
4300
  )
4301
 
4302
  history_date_filter_results.change(
4303
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4304
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4305
- outputs=[history_table_results, history_page_info_results]
 
4306
  ).then(
4307
  lambda: 1, # Reset to page 1 on filter change
4308
- outputs=[history_current_page_results]
 
4309
  )
4310
 
4311
  history_refresh_btn_results.click(
4312
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4313
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4314
- outputs=[history_table_results, history_page_info_results]
 
4315
  )
4316
 
4317
  # Pagination handlers (results page)
4318
  history_prev_btn_results.click(
4319
  lambda page: max(1, page - 1),
4320
  inputs=[history_current_page_results],
4321
- outputs=[history_current_page_results]
 
4322
  ).then(
4323
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4324
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4325
- outputs=[history_table_results, history_page_info_results]
 
4326
  )
4327
 
4328
  history_next_btn_results.click(
4329
  lambda page: page + 1,
4330
  inputs=[history_current_page_results],
4331
- outputs=[history_current_page_results]
 
4332
  ).then(
4333
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4334
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4335
- outputs=[history_table_results, history_page_info_results]
 
4336
  )
4337
 
4338
  # Tax load holdings button
@@ -4597,17 +4620,20 @@ Please try again with different parameters.
4597
  # Password reset handlers
4598
  forgot_password_btn.click(
4599
  lambda: (gr.update(visible=False), gr.update(visible=True)),
4600
- outputs=[login_container, password_reset_modal]
 
4601
  )
4602
 
4603
  reset_cancel_btn.click(
4604
  lambda: (gr.update(visible=True), gr.update(visible=False), ""),
4605
- outputs=[login_container, password_reset_modal, reset_message]
 
4606
  )
4607
 
4608
  update_cancel_btn.click(
4609
  lambda: (gr.update(visible=True), gr.update(visible=False), ""),
4610
- outputs=[login_container, password_update_modal, update_password_message]
 
4611
  )
4612
 
4613
  reset_submit_btn.click(
@@ -4623,7 +4649,8 @@ Please try again with different parameters.
4623
  gr.update(visible=True) if token else gr.update(visible=False)
4624
  ),
4625
  inputs=[recovery_token_hash],
4626
- outputs=[login_container, password_update_modal]
 
4627
  )
4628
 
4629
  # Password update handler (separate modal after email link click)
@@ -4638,7 +4665,8 @@ Please try again with different parameters.
4638
  gr.update(visible=True) if "✅" in msg else gr.update(visible=False)
4639
  ),
4640
  inputs=[update_password_message],
4641
- outputs=[password_update_modal, login_container]
 
4642
  )
4643
 
4644
  def handle_demo_mode(current_session: Dict):
 
2366
  # Add click handlers for example buttons
2367
  tech_btn.click(
2368
  fn=lambda: "AAPL 50 shares\nTSLA 25 shares\nNVDA 30 shares\nMETA 20 shares",
2369
+ outputs=portfolio_input,
2370
+ show_api=False
2371
  )
2372
  conservative_btn.click(
2373
  fn=lambda: "VOO 100 shares\nVTI 75 shares\nSCHD 50 shares\nTLT 40 shares\nVXUS 60 shares",
2374
+ outputs=portfolio_input,
2375
+ show_api=False
2376
  )
2377
  balanced_btn.click(
2378
  fn=lambda: "VTI $25000\nVXUS $15000\nBND $15000\nGLD $5000",
2379
+ outputs=portfolio_input,
2380
+ show_api=False
2381
  )
2382
  global_btn.click(
2383
  fn=lambda: "VTI $15000\nVXUS $10000\nVWO $5000\nBND $10000\nGLD $3000\nVNQ $2000",
2384
+ outputs=portfolio_input,
2385
+ show_api=False
2386
  )
2387
  risk_btn.click(
2388
  fn=lambda: "TSLA 100 shares",
2389
+ outputs=portfolio_input,
2390
+ show_api=False
2391
  )
2392
 
2393
  # Feature highlights grid (1x4 compact layout)
 
4252
  history_search.change(
4253
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4254
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4255
+ outputs=[history_table, history_page_info],
4256
+ show_api=False
4257
  ).then(
4258
  lambda: 1, # Reset to page 1 on search
4259
+ outputs=[history_current_page],
4260
+ show_api=False
4261
  )
4262
 
4263
  history_date_filter.change(
4264
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4265
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4266
+ outputs=[history_table, history_page_info],
4267
+ show_api=False
4268
  ).then(
4269
  lambda: 1, # Reset to page 1 on filter change
4270
+ outputs=[history_current_page],
4271
+ show_api=False
4272
  )
4273
 
4274
  history_refresh_btn.click(
4275
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4276
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4277
+ outputs=[history_table, history_page_info],
4278
+ show_api=False
4279
  )
4280
 
4281
  # Pagination handlers (standalone)
4282
  history_prev_btn.click(
4283
  lambda page: max(1, page - 1),
4284
  inputs=[history_current_page],
4285
+ outputs=[history_current_page],
4286
+ show_api=False
4287
  ).then(
4288
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4289
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4290
+ outputs=[history_table, history_page_info],
4291
+ show_api=False
4292
  )
4293
 
4294
  history_next_btn.click(
4295
  lambda page: page + 1,
4296
  inputs=[history_current_page],
4297
+ outputs=[history_current_page],
4298
+ show_api=False
4299
  ).then(
4300
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4301
  inputs=[session_state, history_search, history_date_filter, history_current_page],
4302
+ outputs=[history_table, history_page_info],
4303
+ show_api=False
4304
  )
4305
 
4306
  # History search and filter handlers (results page tab)
4307
  history_search_results.change(
4308
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4309
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4310
+ outputs=[history_table_results, history_page_info_results],
4311
+ show_api=False
4312
  ).then(
4313
  lambda: 1, # Reset to page 1 on search
4314
+ outputs=[history_current_page_results],
4315
+ show_api=False
4316
  )
4317
 
4318
  history_date_filter_results.change(
4319
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4320
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4321
+ outputs=[history_table_results, history_page_info_results],
4322
+ show_api=False
4323
  ).then(
4324
  lambda: 1, # Reset to page 1 on filter change
4325
+ outputs=[history_current_page_results],
4326
+ show_api=False
4327
  )
4328
 
4329
  history_refresh_btn_results.click(
4330
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4331
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4332
+ outputs=[history_table_results, history_page_info_results],
4333
+ show_api=False
4334
  )
4335
 
4336
  # Pagination handlers (results page)
4337
  history_prev_btn_results.click(
4338
  lambda page: max(1, page - 1),
4339
  inputs=[history_current_page_results],
4340
+ outputs=[history_current_page_results],
4341
+ show_api=False
4342
  ).then(
4343
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4344
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4345
+ outputs=[history_table_results, history_page_info_results],
4346
+ show_api=False
4347
  )
4348
 
4349
  history_next_btn_results.click(
4350
  lambda page: page + 1,
4351
  inputs=[history_current_page_results],
4352
+ outputs=[history_current_page_results],
4353
+ show_api=False
4354
  ).then(
4355
  lambda sess, query, date_filter, page: sync_filter_history(sess, query, date_filter, page),
4356
  inputs=[session_state, history_search_results, history_date_filter_results, history_current_page_results],
4357
+ outputs=[history_table_results, history_page_info_results],
4358
+ show_api=False
4359
  )
4360
 
4361
  # Tax load holdings button
 
4620
  # Password reset handlers
4621
  forgot_password_btn.click(
4622
  lambda: (gr.update(visible=False), gr.update(visible=True)),
4623
+ outputs=[login_container, password_reset_modal],
4624
+ show_api=False
4625
  )
4626
 
4627
  reset_cancel_btn.click(
4628
  lambda: (gr.update(visible=True), gr.update(visible=False), ""),
4629
+ outputs=[login_container, password_reset_modal, reset_message],
4630
+ show_api=False
4631
  )
4632
 
4633
  update_cancel_btn.click(
4634
  lambda: (gr.update(visible=True), gr.update(visible=False), ""),
4635
+ outputs=[login_container, password_update_modal, update_password_message],
4636
+ show_api=False
4637
  )
4638
 
4639
  reset_submit_btn.click(
 
4649
  gr.update(visible=True) if token else gr.update(visible=False)
4650
  ),
4651
  inputs=[recovery_token_hash],
4652
+ outputs=[login_container, password_update_modal],
4653
+ show_api=False
4654
  )
4655
 
4656
  # Password update handler (separate modal after email link click)
 
4665
  gr.update(visible=True) if "✅" in msg else gr.update(visible=False)
4666
  ),
4667
  inputs=[update_password_message],
4668
+ outputs=[password_update_modal, login_container],
4669
+ show_api=False
4670
  )
4671
 
4672
  def handle_demo_mode(current_session: Dict):
backend/agents/personas.py CHANGED
@@ -239,6 +239,7 @@ class PersonaAnalysisAgent(BasePortfolioAgent[PortfolioAnalysisOutput]):
239
  optimization_results: Dict[str, Any],
240
  risk_analysis: Dict[str, Any],
241
  ensemble_forecasts: Optional[Dict[str, Any]] = None,
 
242
  risk_tolerance: str = "moderate",
243
  ) -> "AgentResult[PortfolioAnalysisOutput]":
244
  """Analyse a complete portfolio through the persona's lens.
@@ -254,7 +255,8 @@ class PersonaAnalysisAgent(BasePortfolioAgent[PortfolioAnalysisOutput]):
254
  economic_data: Macroeconomic indicators
255
  optimization_results: Portfolio optimisation outputs
256
  risk_analysis: VaR, CVaR, and risk metrics
257
- ensemble_forecasts: ML-based price forecasts
 
258
  risk_tolerance: Investor's risk tolerance
259
 
260
  Returns:
@@ -265,6 +267,7 @@ class PersonaAnalysisAgent(BasePortfolioAgent[PortfolioAnalysisOutput]):
265
  format_economic_summary,
266
  format_risk_analysis_xml,
267
  format_optimisation_results_xml,
 
268
  )
269
 
270
  # Construct comprehensive analysis prompt (same as standard analyst)
@@ -277,6 +280,9 @@ class PersonaAnalysisAgent(BasePortfolioAgent[PortfolioAnalysisOutput]):
277
  if ensemble_forecasts:
278
  forecasts_section = f"\n\nML FORECASTS (30-day predictions):\n{ensemble_forecasts}"
279
 
 
 
 
280
  # Add persona-specific context to the prompt
281
  prompt = f"""Analyse this investment portfolio from {self.persona.name}'s perspective:
282
 
@@ -301,6 +307,9 @@ OPTIMISATION ANALYSIS:
301
  RISK ANALYSIS:
302
  {risk_xml}{forecasts_section}
303
 
 
 
 
304
  INVESTOR RISK TOLERANCE: {risk_tolerance}
305
 
306
  Provide a comprehensive analysis as {self.persona.name} would, with health score, key insights, and actionable recommendations
 
239
  optimization_results: Dict[str, Any],
240
  risk_analysis: Dict[str, Any],
241
  ensemble_forecasts: Optional[Dict[str, Any]] = None,
242
+ sentiment_data: Optional[Dict[str, Any]] = None,
243
  risk_tolerance: str = "moderate",
244
  ) -> "AgentResult[PortfolioAnalysisOutput]":
245
  """Analyse a complete portfolio through the persona's lens.
 
255
  economic_data: Macroeconomic indicators
256
  optimization_results: Portfolio optimisation outputs
257
  risk_analysis: VaR, CVaR, and risk metrics
258
+ ensemble_forecasts: ML-based price forecasts (Chronos + statistical models)
259
+ sentiment_data: News sentiment analysis for each ticker
260
  risk_tolerance: Investor's risk tolerance
261
 
262
  Returns:
 
267
  format_economic_summary,
268
  format_risk_analysis_xml,
269
  format_optimisation_results_xml,
270
+ format_sentiment_data,
271
  )
272
 
273
  # Construct comprehensive analysis prompt (same as standard analyst)
 
280
  if ensemble_forecasts:
281
  forecasts_section = f"\n\nML FORECASTS (30-day predictions):\n{ensemble_forecasts}"
282
 
283
+ # Format sentiment data if available
284
+ sentiment_table = format_sentiment_data(sentiment_data) if sentiment_data else "No sentiment data available"
285
+
286
  # Add persona-specific context to the prompt
287
  prompt = f"""Analyse this investment portfolio from {self.persona.name}'s perspective:
288
 
 
307
  RISK ANALYSIS:
308
  {risk_xml}{forecasts_section}
309
 
310
+ NEWS SENTIMENT (7-day analysis):
311
+ {sentiment_table}
312
+
313
  INVESTOR RISK TOLERANCE: {risk_tolerance}
314
 
315
  Provide a comprehensive analysis as {self.persona.name} would, with health score, key insights, and actionable recommendations