Spaces:
Sleeping
Sleeping
File size: 8,552 Bytes
dc83e12 140d9d4 dc83e12 140d9d4 dc83e12 89db8f5 140d9d4 398c674 89db8f5 398c674 89db8f5 140d9d4 89db8f5 dc83e12 89db8f5 dc83e12 140d9d4 dc83e12 140d9d4 dc83e12 140d9d4 dc83e12 140d9d4 dc83e12 89db8f5 140d9d4 dc83e12 140d9d4 89db8f5 140d9d4 89db8f5 dc83e12 140d9d4 dc83e12 140d9d4 dc83e12 89db8f5 140d9d4 dc83e12 89db8f5 140d9d4 dc83e12 89db8f5 140d9d4 89db8f5 140d9d4 89db8f5 140d9d4 89db8f5 140d9d4 89db8f5 140d9d4 89db8f5 140d9d4 89db8f5 140d9d4 89db8f5 140d9d4 89db8f5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# modules/orchestrator.py
"""
The Central Nervous System of Project Asclepius.
This module is the master conductor, orchestrating high-performance, asynchronous
workflows for each of the application's features. It intelligently sequences
calls to API clients and the Gemini handler to transform user queries into
comprehensive, synthesized reports.
"""
import asyncio
import aiohttp
from itertools import chain
from PIL import Image
# Import all our specialized tools
from . import gemini_handler, prompts, utils
# ==============================================================================
# CORRECTED LINES: The import path is now an absolute import from the project root.
# The leading dot '.' has been removed.
from api_clients import (
pubmed_client,
clinicaltrials_client,
openfda_client,
rxnorm_client
)
# ==============================================================================
# --- Internal Helper for Data Formatting ---
def _format_api_data_for_prompt(api_results: dict) -> dict[str, str]:
"""
Takes the raw dictionary of API results and formats each entry into a
clean, readable string suitable for injection into a Gemini prompt.
Args:
api_results (dict): The dictionary of results from asyncio.gather.
Returns:
dict[str, str]: A dictionary with the same keys but formatted string values.
"""
formatted_strings = {}
# Format PubMed data
pubmed_data = api_results.get('pubmed', [])
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)
else:
formatted_strings['pubmed'] = "No relevant review articles were found on PubMed for this query."
# Format Clinical Trials data
trials_data = api_results.get('trials', [])
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)
else:
formatted_strings['trials'] = "No actively recruiting clinical trials were found matching this query."
# Format OpenFDA Adverse Events data
# This data often comes from multiple queries, so we flatten it.
fda_data = api_results.get('openfda', [])
if isinstance(fda_data, list):
# The result is a list of lists, so we flatten it
all_events = list(chain.from_iterable(filter(None, fda_data)))
if all_events:
lines = [f"- {evt['term']} (Reported {evt['count']} times)" for evt in all_events]
formatted_strings['openfda'] = "\n".join(lines)
else:
formatted_strings['openfda'] = "No specific adverse event data was found for this query."
else:
formatted_strings['openfda'] = "No specific adverse event data was found for this query."
# Format Vision analysis
vision_data = api_results.get('vision', "")
if isinstance(vision_data, str) and vision_data:
formatted_strings['vision'] = vision_data
elif isinstance(vision_data, Exception):
formatted_strings['vision'] = f"An error occurred during image analysis: {vision_data}"
else:
formatted_strings['vision'] = ""
return formatted_strings
# --- FEATURE 1: Symptom Synthesizer Pipeline ---
async def run_symptom_synthesis(user_query: str, image_input: Image.Image | None) -> str:
"""The complete, asynchronous pipeline for the Symptom Synthesizer tab."""
if not user_query:
return "Please enter a symptom description or a medical question to begin."
# STEP 1: AI-Powered Concept Extraction
# Use Gemini to find the core medical terms in the user's natural language query.
term_prompt = prompts.get_term_extraction_prompt(user_query)
concepts_str = await gemini_handler.generate_text_response(term_prompt)
concepts = utils.safe_literal_eval(concepts_str)
if not isinstance(concepts, list) or not concepts:
concepts = [user_query] # Fallback to the raw query if parsing fails
# Use "OR" for a broader, more inclusive search across APIs
search_query = " OR ".join(f'"{c}"' for c in concepts)
# STEP 2: Massively Parallel Evidence Gathering
# Launch all API calls concurrently for maximum performance.
async with aiohttp.ClientSession() as session:
# Define the portfolio of data we need to collect
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)),
}
# If an image is provided, add the vision analysis to our task portfolio
if image_input:
tasks["vision"] = gemini_handler.analyze_image_with_text(
"In the context of the user query, analyze this image objectively. Describe visual features like color, shape, texture, and patterns. Do not diagnose or offer medical advice.", image_input
)
# Execute all tasks and wait for them all to complete
raw_results = await asyncio.gather(*tasks.values(), return_exceptions=True)
api_data = dict(zip(tasks.keys(), raw_results))
# STEP 3: Data Formatting
# Convert the raw JSON/list results into clean, prompt-ready strings.
formatted_data = _format_api_data_for_prompt(api_data)
# STEP 4: The Grand Synthesis
# Feed all the structured, evidence-based data into Gemini for the final report generation.
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']
)
final_report = await gemini_handler.generate_text_response(synthesis_prompt)
# STEP 5: Final Delivery
# Prepend the mandatory disclaimer to the AI-generated report.
return f"{prompts.DISCLAIMER}\n\n{final_report}"
# --- FEATURE 2: Drug Interaction & Safety Analyzer Pipeline ---
async def run_drug_interaction_analysis(drug_list_str: str) -> str:
"""The complete, asynchronous pipeline for the Drug Interaction Analyzer tab."""
if not drug_list_str:
return "Please enter a comma-separated list of medications."
drug_names = [name.strip() for name in drug_list_str.split(',') if name.strip()]
if len(drug_names) < 2:
return "Please enter at least two medications to check for interactions."
# STEP 1: Concurrent Drug Data Gathering
async with aiohttp.ClientSession() as session:
tasks = {
"interactions": rxnorm_client.run_interaction_check(drug_names),
"safety_profiles": asyncio.gather(*(openfda_client.get_safety_profile(session, name) for name in drug_names))
}
raw_results = await asyncio.gather(*tasks.values(), return_exceptions=True)
api_data = dict(zip(tasks.keys(), raw_results))
# STEP 2: Data Formatting for AI Synthesis
interaction_data = api_data.get('interactions', [])
if isinstance(interaction_data, Exception):
interaction_data = [{"error": str(interaction_data)}]
safety_profiles = api_data.get('safety_profiles', [])
if isinstance(safety_profiles, Exception):
safety_profiles = [{"error": str(safety_profiles)}]
# Combine safety profiles with their drug names for clarity in the prompt
safety_data_dict = dict(zip(drug_names, safety_profiles))
# Format the complex data into clean strings
interaction_formatted = utils.format_list_as_markdown([str(i) for i in interaction_data]) if interaction_data else "No interactions found."
safety_formatted = "\n".join([f"Profile for {drug}: {profile}" for drug, profile in safety_data_dict.items()])
# STEP 3: AI-Powered Safety Briefing
synthesis_prompt = prompts.get_drug_interaction_synthesis_prompt(
drug_names=drug_names,
interaction_data=interaction_formatted,
safety_data=safety_formatted
)
final_report = await gemini_handler.generate_text_response(synthesis_prompt)
# STEP 4: Final Delivery
return f"{prompts.DISCLAIMER}\n\n{final_report}" |