Spaces:
Running
Running
| """Gradio UI for DeepCritical agent with MCP server support.""" | |
| import os | |
| from collections.abc import AsyncGenerator | |
| from typing import Any | |
| import gradio as gr | |
| from pydantic_ai.models.anthropic import AnthropicModel | |
| from pydantic_ai.models.openai import OpenAIModel | |
| from pydantic_ai.providers.anthropic import AnthropicProvider | |
| from pydantic_ai.providers.openai import OpenAIProvider | |
| from src.agent_factory.judges import HFInferenceJudgeHandler, JudgeHandler, MockJudgeHandler | |
| from src.orchestrator_factory import create_orchestrator | |
| from src.tools.clinicaltrials import ClinicalTrialsTool | |
| from src.tools.europepmc import EuropePMCTool | |
| from src.tools.pubmed import PubMedTool | |
| from src.tools.search_handler import SearchHandler | |
| from src.utils.config import settings | |
| from src.utils.models import OrchestratorConfig | |
| def configure_orchestrator( | |
| use_mock: bool = False, | |
| mode: str = "simple", | |
| user_api_key: str | None = None, | |
| api_provider: str = "openai", | |
| ) -> tuple[Any, str]: | |
| """ | |
| Create an orchestrator instance. | |
| Args: | |
| use_mock: If True, use MockJudgeHandler (no API key needed) | |
| mode: Orchestrator mode ("simple" or "magentic") | |
| user_api_key: Optional user-provided API key (BYOK) | |
| api_provider: API provider ("openai" or "anthropic") | |
| Returns: | |
| Tuple of (Orchestrator instance, backend_name) | |
| """ | |
| # Create orchestrator config | |
| config = OrchestratorConfig( | |
| max_iterations=10, | |
| max_results_per_tool=10, | |
| ) | |
| # Create search tools | |
| search_handler = SearchHandler( | |
| tools=[PubMedTool(), ClinicalTrialsTool(), EuropePMCTool()], | |
| timeout=config.search_timeout, | |
| ) | |
| # Create judge (mock, real, or free tier) | |
| judge_handler: JudgeHandler | MockJudgeHandler | HFInferenceJudgeHandler | |
| backend_info = "Unknown" | |
| # 1. Forced Mock (Unit Testing) | |
| if use_mock: | |
| judge_handler = MockJudgeHandler() | |
| backend_info = "Mock (Testing)" | |
| # 2. Paid API Key (User provided or Env) | |
| elif ( | |
| user_api_key | |
| or (api_provider == "openai" and os.getenv("OPENAI_API_KEY")) | |
| or (api_provider == "anthropic" and os.getenv("ANTHROPIC_API_KEY")) | |
| ): | |
| model: AnthropicModel | OpenAIModel | None = None | |
| if user_api_key: | |
| # Validate key/provider match to prevent silent auth failures | |
| if api_provider == "openai" and user_api_key.startswith("sk-ant-"): | |
| raise ValueError("Anthropic key provided but OpenAI provider selected") | |
| is_openai_key = user_api_key.startswith("sk-") and not user_api_key.startswith( | |
| "sk-ant-" | |
| ) | |
| if api_provider == "anthropic" and is_openai_key: | |
| raise ValueError("OpenAI key provided but Anthropic provider selected") | |
| if api_provider == "anthropic": | |
| anthropic_provider = AnthropicProvider(api_key=user_api_key) | |
| model = AnthropicModel(settings.anthropic_model, provider=anthropic_provider) | |
| elif api_provider == "openai": | |
| openai_provider = OpenAIProvider(api_key=user_api_key) | |
| model = OpenAIModel(settings.openai_model, provider=openai_provider) | |
| backend_info = f"Paid API ({api_provider.upper()})" | |
| else: | |
| backend_info = "Paid API (Env Config)" | |
| judge_handler = JudgeHandler(model=model) | |
| # 3. Free Tier (HuggingFace Inference) | |
| else: | |
| judge_handler = HFInferenceJudgeHandler() | |
| backend_info = "Free Tier (Llama 3.1 / Mistral)" | |
| orchestrator = create_orchestrator( | |
| search_handler=search_handler, | |
| judge_handler=judge_handler, | |
| config=config, | |
| mode=mode, # type: ignore | |
| ) | |
| return orchestrator, backend_info | |
| async def research_agent( | |
| message: str, | |
| history: list[dict[str, Any]], | |
| mode: str = "simple", | |
| api_key: str = "", | |
| api_provider: str = "openai", | |
| ) -> AsyncGenerator[str, None]: | |
| """ | |
| Gradio chat function that runs the research agent. | |
| Args: | |
| message: User's research question | |
| history: Chat history (Gradio format) | |
| mode: Orchestrator mode ("simple" or "magentic") | |
| api_key: Optional user-provided API key (BYOK - Bring Your Own Key) | |
| api_provider: API provider ("openai" or "anthropic") | |
| Yields: | |
| Markdown-formatted responses for streaming | |
| """ | |
| if not message.strip(): | |
| yield "Please enter a research question." | |
| return | |
| # Clean user-provided API key | |
| user_api_key = api_key.strip() if api_key else None | |
| # Check available keys | |
| has_openai = bool(os.getenv("OPENAI_API_KEY")) | |
| has_anthropic = bool(os.getenv("ANTHROPIC_API_KEY")) | |
| has_user_key = bool(user_api_key) | |
| has_paid_key = has_openai or has_anthropic or has_user_key | |
| # Magentic mode requires OpenAI specifically | |
| if mode == "magentic" and not (has_openai or (has_user_key and api_provider == "openai")): | |
| yield ( | |
| "β οΈ **Warning**: Magentic mode requires OpenAI API key. Falling back to simple mode.\n\n" | |
| ) | |
| mode = "simple" | |
| # Inform user about their key being used | |
| if has_user_key: | |
| yield ( | |
| f"π **Using your {api_provider.upper()} API key** - " | |
| "Your key is used only for this session and is never stored.\n\n" | |
| ) | |
| elif not has_paid_key: | |
| # No paid keys - will use FREE HuggingFace Inference | |
| yield ( | |
| "π€ **Free Tier**: Using HuggingFace Inference (Llama 3.1 / Mistral) for AI analysis.\n" | |
| "For premium models, enter an OpenAI or Anthropic API key below.\n\n" | |
| ) | |
| # Run the agent and stream events | |
| response_parts: list[str] = [] | |
| try: | |
| # use_mock=False - let configure_orchestrator decide based on available keys | |
| # It will use: Paid API > HF Inference (free tier) | |
| orchestrator, backend_name = configure_orchestrator( | |
| use_mock=False, # Never use mock in production - HF Inference is the free fallback | |
| mode=mode, | |
| user_api_key=user_api_key, | |
| api_provider=api_provider, | |
| ) | |
| yield f"π§ **Backend**: {backend_name}\n\n" | |
| async for event in orchestrator.run(message): | |
| # Format event as markdown | |
| event_md = event.to_markdown() | |
| response_parts.append(event_md) | |
| # If complete, show full response | |
| if event.type == "complete": | |
| yield event.message | |
| else: | |
| # Show progress | |
| yield "\n\n".join(response_parts) | |
| except Exception as e: | |
| yield f"β **Error**: {e!s}" | |
| def create_demo() -> Any: | |
| """ | |
| Create the Gradio demo interface with MCP support. | |
| Returns: | |
| Configured Gradio Blocks interface with MCP server enabled | |
| """ | |
| with gr.Blocks( | |
| title="DeepCritical - Drug Repurposing Research Agent", | |
| ) as demo: | |
| # 1. Minimal Header (Option A: 2 lines max) | |
| gr.Markdown( | |
| "# 𧬠DeepCritical\n" | |
| "*AI-Powered Drug Repurposing Agent β searches PubMed, ClinicalTrials.gov & bioRxiv*" | |
| ) | |
| # 2. Main Chat Interface | |
| # Config inputs will be in a collapsed accordion below the chat input | |
| gr.ChatInterface( | |
| fn=research_agent, | |
| examples=[ | |
| [ | |
| "What drugs could be repurposed for Alzheimer's disease?", | |
| "simple", | |
| "", | |
| "openai", | |
| ], | |
| [ | |
| "Is metformin effective for treating cancer?", | |
| "simple", | |
| "", | |
| "openai", | |
| ], | |
| [ | |
| "What medications show promise for Long COVID treatment?", | |
| "simple", | |
| "", | |
| "openai", | |
| ], | |
| ], | |
| additional_inputs_accordion=gr.Accordion(label="βοΈ Settings", open=False), | |
| additional_inputs=[ | |
| gr.Radio( | |
| choices=["simple", "magentic"], | |
| value="simple", | |
| label="Orchestrator Mode", | |
| info="Simple: Linear | Magentic: Multi-Agent (OpenAI)", | |
| ), | |
| gr.Textbox( | |
| label="π API Key (Optional - BYOK)", | |
| placeholder="sk-... or sk-ant-...", | |
| type="password", | |
| info="Enter your own API key. Never stored.", | |
| ), | |
| gr.Radio( | |
| choices=["openai", "anthropic"], | |
| value="openai", | |
| label="API Provider", | |
| info="Select the provider for your API key", | |
| ), | |
| ], | |
| ) | |
| # 3. Minimal Footer (Option C: Remove MCP Tabs, keep info) | |
| gr.Markdown( | |
| """ | |
| --- | |
| *Research tool only β not for medical advice.* | |
| **MCP Server Active**: Connect Claude Desktop to `/gradio_api/mcp/` | |
| """, | |
| elem_classes=["footer"], | |
| ) | |
| return demo | |
| def main() -> None: | |
| """Run the Gradio app with MCP server enabled.""" | |
| demo = create_demo() | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| mcp_server=True, | |
| ssr_mode=False, # Fix for intermittent loading/hydration issues in HF Spaces | |
| ) | |
| if __name__ == "__main__": | |
| main() | |