mgbam commited on
Commit
666355f
·
verified ·
1 Parent(s): 4e9aea4

Update modules/orchestrator.py

Browse files
Files changed (1) hide show
  1. modules/orchestrator.py +49 -56
modules/orchestrator.py CHANGED
@@ -1,10 +1,8 @@
1
  # modules/orchestrator.py
2
  """
3
  The Central Nervous System of Project Asclepius.
4
- This module is the master conductor, orchestrating high-performance, asynchronous
5
- workflows for each of the application's features. It intelligently sequences
6
- calls to API clients and the Gemini handler to transform user queries into
7
- comprehensive, synthesized reports. (v1.3 - Final Polish)
8
  """
9
 
10
  import asyncio
@@ -12,91 +10,85 @@ import aiohttp
12
  from itertools import chain
13
  from PIL import Image
14
 
15
- # Import all our specialized tools
16
  from . import gemini_handler, prompts, utils
17
  from api_clients import (
18
- pubmed_client,
19
- clinicaltrials_client,
20
- openfda_client,
21
- rxnorm_client
22
  )
23
 
24
- # --- Internal Helper for Data Formatting ---
25
- # (This helper function remains unchanged)
26
  def _format_api_data_for_prompt(api_results: dict) -> dict[str, str]:
 
27
  formatted_strings = {}
28
  pubmed_data = api_results.get('pubmed', [])
29
- if isinstance(pubmed_data, list) and pubmed_data:
30
- lines = [f"- Title: {a.get('title', 'N/A')} (Journal: {a.get('journal', 'N/A')}, URL: {a.get('url')})" for a in pubmed_data]
31
- formatted_strings['pubmed'] = "\n".join(lines)
32
- else:
33
- formatted_strings['pubmed'] = "No relevant review articles were found on PubMed for this query."
34
  trials_data = api_results.get('trials', [])
35
- if isinstance(trials_data, list) and trials_data:
36
- lines = [f"- Title: {t.get('title', 'N/A')} (Status: {t.get('status', 'N/A')}, URL: {t.get('url')})" for t in trials_data]
37
- formatted_strings['trials'] = "\n".join(lines)
38
- else:
39
- formatted_strings['trials'] = "No actively recruiting clinical trials were found matching this query."
40
  fda_data = api_results.get('openfda', [])
41
  if isinstance(fda_data, list):
42
  all_events = list(chain.from_iterable(filter(None, fda_data)))
43
- if all_events:
44
- lines = [f"- {evt['term']} (Reported {evt['count']} times)" for evt in all_events]
45
- formatted_strings['openfda'] = "\n".join(lines)
46
- else:
47
- formatted_strings['openfda'] = "No specific adverse event data was found for this query."
48
- else:
49
- formatted_strings['openfda'] = "No specific adverse event data was found for this query."
50
  vision_data = api_results.get('vision', "")
51
- if isinstance(vision_data, str) and vision_data:
52
- formatted_strings['vision'] = vision_data
53
- elif isinstance(vision_data, Exception):
54
- formatted_strings['vision'] = f"An error occurred during image analysis: {vision_data}"
55
- else:
56
- formatted_strings['vision'] = ""
57
  return formatted_strings
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- # --- FEATURE 1: Symptom Synthesizer Pipeline (v1.3) ---
 
61
  async def run_symptom_synthesis(user_query: str, image_input: Image.Image | None) -> str:
62
- """The complete, asynchronous pipeline for the Symptom Synthesizer tab."""
63
- # (Steps 1-5 remain the same)
64
- if not user_query:
65
- return "Please enter a symptom description or a medical question to begin."
66
  correction_prompt = prompts.get_query_correction_prompt(user_query)
67
  corrected_query = await gemini_handler.generate_text_response(correction_prompt)
68
- if not corrected_query:
69
- corrected_query = user_query
70
  term_prompt = prompts.get_term_extraction_prompt(corrected_query)
71
  concepts_str = await gemini_handler.generate_text_response(term_prompt)
72
  concepts = utils.safe_literal_eval(concepts_str)
73
- if not isinstance(concepts, list) or not concepts:
74
- concepts = [corrected_query]
75
  search_query = " OR ".join(f'"{c}"' for c in concepts)
76
  async with aiohttp.ClientSession() as session:
77
  tasks = { "pubmed": pubmed_client.search_pubmed(session, search_query, max_results=3), "trials": clinicaltrials_client.find_trials(session, search_query, max_results=3), "openfda": asyncio.gather(*(openfda_client.get_adverse_events(session, c, top_n=3) for c in concepts)), }
78
- if image_input:
79
- tasks["vision"] = gemini_handler.analyze_image_with_text("In the context of the user query, analyze this image objectively. Describe visual features. Do not diagnose.", image_input)
80
  raw_results = await asyncio.gather(*tasks.values(), return_exceptions=True)
81
  api_data = dict(zip(tasks.keys(), raw_results))
82
  formatted_data = _format_api_data_for_prompt(api_data)
 
 
83
  synthesis_prompt = prompts.get_synthesis_prompt(user_query=user_query, concepts=concepts, pubmed_data=formatted_data['pubmed'], trials_data=formatted_data['trials'], fda_data=formatted_data['openfda'], vision_analysis=formatted_data['vision'])
84
  final_report = await gemini_handler.generate_text_response(synthesis_prompt)
85
 
86
- # ==============================================================================
87
- # STEP 6 (V1.3 UPGRADE): Deterministic Post-Processing
88
- # We will manually remove the AI's redundant disclaimer to ensure a clean output.
89
- # ==============================================================================
90
- ghost_disclaimer = "⚠️ IMPORTANT DISCLAIMER: This report is for informational purposes only and should not be considered medical advice. Always consult with a qualified healthcare professional for diagnosis and treatment of any medical condition."
91
- cleaned_report = final_report.replace(ghost_disclaimer, "").strip()
92
 
93
  # STEP 7: Final Delivery
94
  return f"{prompts.DISCLAIMER}\n\n{cleaned_report}"
95
 
96
 
97
- # --- FEATURE 2: Drug Interaction & Safety Analyzer Pipeline (v1.3) ---
98
  async def run_drug_interaction_analysis(drug_list_str: str) -> str:
99
- """The complete, asynchronous pipeline for the Drug Interaction Analyzer tab."""
100
  # (Steps remain the same)
101
  if not drug_list_str: return "Please enter a comma-separated list of medications."
102
  drug_names = [name.strip() for name in drug_list_str.split(',') if name.strip()]
@@ -112,11 +104,12 @@ async def run_drug_interaction_analysis(drug_list_str: str) -> str:
112
  safety_data_dict = dict(zip(drug_names, safety_profiles))
113
  interaction_formatted = utils.format_list_as_markdown([str(i) for i in interaction_data]) if interaction_data else "No interactions found."
114
  safety_formatted = "\n".join([f"Profile for {drug}: {profile}" for drug, profile in safety_data_dict.items()])
 
 
115
  synthesis_prompt = prompts.get_drug_interaction_synthesis_prompt(drug_names=drug_names, interaction_data=interaction_formatted, safety_data=safety_formatted)
116
  final_report = await gemini_handler.generate_text_response(synthesis_prompt)
117
 
118
- # Deterministic Post-Processing for the drug report
119
- ghost_disclaimer_drug = "DISCLAIMER: This report is for informational purposes only and should not be considered medical advice. Always consult with a healthcare professional before making any decisions related to your health or treatment. This information is based on the provided data and may not be exhaustive."
120
- cleaned_report = final_report.replace(ghost_disclaimer_drug, "").strip()
121
 
122
  return f"{prompts.DISCLAIMER}\n\n{cleaned_report}"
 
1
  # modules/orchestrator.py
2
  """
3
  The Central Nervous System of Project Asclepius.
4
+ (v2.0 - The "Clinical Insight Engine" Upgrade)
5
+ This version uses a smarter post-processing function to guarantee clean output.
 
 
6
  """
7
 
8
  import asyncio
 
10
  from itertools import chain
11
  from PIL import Image
12
 
 
13
  from . import gemini_handler, prompts, utils
14
  from api_clients import (
15
+ pubmed_client, clinicaltrials_client, openfda_client, rxnorm_client
 
 
 
16
  )
17
 
18
+ # --- Internal Helper for Data Formatting (Unchanged) ---
 
19
  def _format_api_data_for_prompt(api_results: dict) -> dict[str, str]:
20
+ # This function is unchanged.
21
  formatted_strings = {}
22
  pubmed_data = api_results.get('pubmed', [])
23
+ if isinstance(pubmed_data, list) and pubmed_data: lines = [f"- Title: {a.get('title', 'N/A')} (Journal: {a.get('journal', 'N/A')}, URL: {a.get('url')})" for a in pubmed_data]; formatted_strings['pubmed'] = "\n".join(lines)
24
+ else: formatted_strings['pubmed'] = "No relevant review articles were found on PubMed for this query."
 
 
 
25
  trials_data = api_results.get('trials', [])
26
+ if isinstance(trials_data, list) and trials_data: lines = [f"- Title: {t.get('title', 'N/A')} (Status: {t.get('status', 'N/A')}, URL: {t.get('url')})" for t in trials_data]; formatted_strings['trials'] = "\n".join(lines)
27
+ else: formatted_strings['trials'] = "No actively recruiting clinical trials were found matching this query."
 
 
 
28
  fda_data = api_results.get('openfda', [])
29
  if isinstance(fda_data, list):
30
  all_events = list(chain.from_iterable(filter(None, fda_data)))
31
+ if all_events: lines = [f"- {evt['term']} (Reported {evt['count']} times)" for evt in all_events]; formatted_strings['openfda'] = "\n".join(lines)
32
+ else: formatted_strings['openfda'] = "No specific adverse event data was found for this query."
33
+ else: formatted_strings['openfda'] = "No specific adverse event data was found for this query."
 
 
 
 
34
  vision_data = api_results.get('vision', "")
35
+ if isinstance(vision_data, str) and vision_data: formatted_strings['vision'] = vision_data
36
+ elif isinstance(vision_data, Exception): formatted_strings['vision'] = f"An error occurred during image analysis: {vision_data}"
37
+ else: formatted_strings['vision'] = ""
 
 
 
38
  return formatted_strings
39
 
40
+ # ==============================================================================
41
+ # V2.0 UPGRADE: A robust function to remove any AI-generated preamble/disclaimer.
42
+ # ==============================================================================
43
+ def _clean_ai_preamble(report_text: str) -> str:
44
+ """Intelligently removes redundant disclaimers or preambles added by the AI."""
45
+ lines = report_text.strip().split('\n')
46
+ # AI disclaimers are often short, in the first few lines, and contain specific keywords.
47
+ # We find the first line that looks like real content (starts with '##' for our format).
48
+ start_index = 0
49
+ for i, line in enumerate(lines):
50
+ if line.strip().startswith('##'):
51
+ start_index = i
52
+ break
53
+ # Failsafe for the first 5 lines if no '##' is found
54
+ if i > 5:
55
+ break
56
+
57
+ return '\n'.join(lines[start_index:])
58
 
59
+
60
+ # --- FEATURE 1: Symptom Synthesizer Pipeline (v2.0) ---
61
  async def run_symptom_synthesis(user_query: str, image_input: Image.Image | None) -> str:
62
+ # (Steps 1-4 remain the same)
63
+ if not user_query: return "Please enter a symptom description or a medical question to begin."
 
 
64
  correction_prompt = prompts.get_query_correction_prompt(user_query)
65
  corrected_query = await gemini_handler.generate_text_response(correction_prompt)
66
+ if not corrected_query: corrected_query = user_query
 
67
  term_prompt = prompts.get_term_extraction_prompt(corrected_query)
68
  concepts_str = await gemini_handler.generate_text_response(term_prompt)
69
  concepts = utils.safe_literal_eval(concepts_str)
70
+ if not isinstance(concepts, list) or not concepts: concepts = [corrected_query]
 
71
  search_query = " OR ".join(f'"{c}"' for c in concepts)
72
  async with aiohttp.ClientSession() as session:
73
  tasks = { "pubmed": pubmed_client.search_pubmed(session, search_query, max_results=3), "trials": clinicaltrials_client.find_trials(session, search_query, max_results=3), "openfda": asyncio.gather(*(openfda_client.get_adverse_events(session, c, top_n=3) for c in concepts)), }
74
+ if image_input: tasks["vision"] = gemini_handler.analyze_image_with_text("In the context of the user query, analyze this image objectively...", image_input)
 
75
  raw_results = await asyncio.gather(*tasks.values(), return_exceptions=True)
76
  api_data = dict(zip(tasks.keys(), raw_results))
77
  formatted_data = _format_api_data_for_prompt(api_data)
78
+
79
+ # STEP 5: The Grand Synthesis (using new v2.0 prompt)
80
  synthesis_prompt = prompts.get_synthesis_prompt(user_query=user_query, concepts=concepts, pubmed_data=formatted_data['pubmed'], trials_data=formatted_data['trials'], fda_data=formatted_data['openfda'], vision_analysis=formatted_data['vision'])
81
  final_report = await gemini_handler.generate_text_response(synthesis_prompt)
82
 
83
+ # STEP 6: Intelligent Post-Processing
84
+ cleaned_report = _clean_ai_preamble(final_report)
 
 
 
 
85
 
86
  # STEP 7: Final Delivery
87
  return f"{prompts.DISCLAIMER}\n\n{cleaned_report}"
88
 
89
 
90
+ # --- FEATURE 2: Drug Interaction & Safety Analyzer Pipeline (v2.0) ---
91
  async def run_drug_interaction_analysis(drug_list_str: str) -> str:
 
92
  # (Steps remain the same)
93
  if not drug_list_str: return "Please enter a comma-separated list of medications."
94
  drug_names = [name.strip() for name in drug_list_str.split(',') if name.strip()]
 
104
  safety_data_dict = dict(zip(drug_names, safety_profiles))
105
  interaction_formatted = utils.format_list_as_markdown([str(i) for i in interaction_data]) if interaction_data else "No interactions found."
106
  safety_formatted = "\n".join([f"Profile for {drug}: {profile}" for drug, profile in safety_data_dict.items()])
107
+
108
+ # Synthesis (using new v2.0 prompt)
109
  synthesis_prompt = prompts.get_drug_interaction_synthesis_prompt(drug_names=drug_names, interaction_data=interaction_formatted, safety_data=safety_formatted)
110
  final_report = await gemini_handler.generate_text_response(synthesis_prompt)
111
 
112
+ # Intelligent Post-Processing
113
+ cleaned_report = _clean_ai_preamble(final_report)
 
114
 
115
  return f"{prompts.DISCLAIMER}\n\n{cleaned_report}"