# modules/orchestrator.py """ The main conductor. This module sequences the calls to APIs and the AI model. It's the heart of the application's logic. """ import asyncio import aiohttp import ast from . import gemini_handler, prompts from .api_clients import umls_client, pubmed_client async def run_symptom_synthesis(user_query: str, image_input=None): """ The complete pipeline for the Symptom Synthesizer tab. """ if not user_query: return "Please enter your symptoms or query." # --- Step 1: Extract Key Concepts with Gemini --- term_extraction_prompt = prompts.get_term_extraction_prompt(user_query) concepts_str = await gemini_handler.generate_gemini_response(term_extraction_prompt) try: # Safely evaluate the string representation of the list concepts = ast.literal_eval(concepts_str) if not isinstance(concepts, list): concepts = [user_query] # Fallback except (ValueError, SyntaxError): concepts = [user_query] # Fallback if Gemini doesn't return a perfect list search_query = " AND ".join(concepts) # --- Step 2: Gather Evidence Asynchronously --- async with aiohttp.ClientSession() as session: # Create a UMLS client instance for this session umls = umls_client.UMLSClient(session) # Define all async tasks tasks = { "pubmed": pubmed_client.search_pubmed(session, search_query, max_results=3), "umls_cui": umls.get_cui_for_term(concepts[0] if concepts else user_query), # Add other clients here as they are built e.g., # "trials": clinicaltrials_client.find_trials(session, search_query), # "fda": openfda_client.get_adverse_events(session, concepts) } # Run all tasks concurrently results = await asyncio.gather(*tasks.values(), return_exceptions=True) # Map results back to their keys, handling potential errors api_data = dict(zip(tasks.keys(), results)) for key, value in api_data.items(): if isinstance(value, Exception): print(f"Error fetching data from {key}: {value}") api_data[key] = None # Nullify data if fetch failed # --- Step 3: Format Data for the Synthesis Prompt --- # Convert raw JSON/list data into clean, readable strings for the AI pubmed_formatted = "\n".join([f"- Title: {a.get('title', 'N/A')}, PMID: {a.get('uid', 'N/A')}" for a in api_data.get('pubmed', [])]) # In a real implementation, you'd format trials and fda data here too trials_formatted = "Trial data fetching is not yet fully implemented in this demo." fda_formatted = "FDA data fetching is not yet fully implemented in this demo." # --- Step 4: The Grand Synthesis with Gemini --- synthesis_prompt = prompts.get_synthesis_prompt( user_query, concepts, pubmed_data=pubmed_formatted, trials_data=trials_formatted, fda_data=fda_formatted ) final_report = await gemini_handler.generate_gemini_response(synthesis_prompt) # --- Step 5: Prepend Disclaimer and Return --- return f"{prompts.DISCLAIMER}\n\n{final_report}" # You would create similar orchestrator functions for other tabs # e.g., async def run_drug_interaction_analysis(drug_list): ...