"""
app.py
------
Main application entry point for the modular AI agent system. Handles UI (Gradio), agent orchestration, knowledge loading, and chat interface logic.
- Initializes logging and structlog for consistent, timestamped logs.
- Loads and manages agent configurations and RAG retriever.
- Provides chat, knowledge ingestion, and agent builder functions.
- Integrates with OpenAI via llm_connector and supports streaming responses.
- Designed for extensibility and future integration with Model Context Protocol (MCP) and Agent-to-Agent (A2A) standards.
"""
# --- Imports ---
import gradio as gr
import json
import re
import os
import asyncio
import logging
from typing import Dict, Optional, Any, cast
# Try to import authentication components
try:
from auth_manager import create_auth_interface, check_user_permission, get_user_limits, auth_manager
AUTH_AVAILABLE = True
print("✅ Authentication system loaded")
except ImportError as e:
print(f"⚠️ Authentication system not available: {e}")
AUTH_AVAILABLE = False
# Create dummy functions for compatibility
def create_auth_interface():
return None, None
def check_user_permission(session_id, permission):
return True
def get_user_limits(session_id):
return {}
auth_manager = None
# Try to import OpenAI
try:
import openai
from openai import RateLimitError, APIError, APIConnectionError, OpenAI
OPENAI_AVAILABLE = True
print("✅ OpenAI client loaded")
except ImportError as e:
print(f"⚠️ OpenAI not available: {e}")
OPENAI_AVAILABLE = False
# Try to import core modules
try:
from core.utils.rag import KnowledgeLoader, SimpleRAGRetriever
from core.utils.skills_registry import tool_registry, get_tool_by_name
from core.utils.llm_connector import AgentLLMConnector
from core.agents.agent_utils import linkify_citations, build_agent, load_prefilled, prepare_download, preload_demo_chat, _safe_title, extract_clinical_variables_from_history
CORE_MODULES_AVAILABLE = True
print("✅ Core modules loaded")
except ImportError as e:
print(f"⚠️ Core modules not available: {e}")
CORE_MODULES_AVAILABLE = False
# Create dummy functions
def build_agent(*args, **kwargs):
return json.dumps({"agent_name": "Demo Agent", "error": "Core modules not available"})
def extract_clinical_variables_from_history(*args, **kwargs):
return {}
# Try to import configuration
try:
from config import agents_config, skills_library, prefilled_agents
CONFIG_AVAILABLE = True
print("✅ Configuration loaded")
except ImportError as e:
print(f"⚠️ Configuration not available: {e}")
CONFIG_AVAILABLE = False
# Create dummy config
agents_config = {}
skills_library = {}
prefilled_agents = {"Demo Agent": {"agent_name": "Demo Agent"}}
# Try to import UI modules
try:
from core.ui.ui import show_landing, show_builder, show_chat, refresh_active_agents_widgets
UI_MODULES_AVAILABLE = True
print("✅ UI modules loaded")
except ImportError as e:
print(f"⚠️ UI modules not available: {e}")
UI_MODULES_AVAILABLE = False
# Create dummy functions
def show_landing():
return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
def show_builder():
return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
def show_chat():
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
def refresh_active_agents_widgets():
return "No agents available", []
# Try to import tools
try:
import tools
TOOLS_AVAILABLE = True
print("✅ Tools loaded")
except ImportError as e:
print(f"⚠️ Tools not available: {e}")
TOOLS_AVAILABLE = False
# Try to import pandas
try:
import pandas as pd
PANDAS_AVAILABLE = True
except ImportError:
PANDAS_AVAILABLE = False
print("⚠️ Pandas not available")
# Try to import structlog
try:
import structlog
from structlog.stdlib import LoggerFactory, BoundLogger
STRUCTLOG_AVAILABLE = True
print("✅ Structured logging loaded")
except ImportError:
STRUCTLOG_AVAILABLE = False
print("⚠️ Structured logging not available")
# Logging setup
logging.basicConfig(filename="app.log", level=logging.INFO, format="%(message)s")
if STRUCTLOG_AVAILABLE:
structlog.configure(logger_factory=LoggerFactory())
logger: BoundLogger = structlog.get_logger()
# Structlog config
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.dev.ConsoleRenderer()
],
logger_factory=LoggerFactory(),
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
cache_logger_on_first_use=True,
)
else:
# Fallback to basic logging
logger = logging.getLogger(__name__)
# OpenAI API Key Check
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
if not OPENAI_API_KEY:
raise RuntimeError("OPENAI_API_KEY environment variable is not set. Please set it before running the application.")
OPENAI_API_KEY = cast(str, OPENAI_API_KEY)
# initialize client once, pulling key from your environment
client = OpenAI(api_key=OPENAI_API_KEY)
# Gradio 4.20.0 compatibility function
def convert_messages_for_gradio(messages):
"""
Convert role/content format to list-of-lists format for Gradio 4.20.0 compatibility
Input: [{"role": "user/assistant", "content": "message"}, ...]
Output: [["user_message", "assistant_message"], ...]
"""
if not messages:
return []
result = []
current_pair = ["", ""]
for msg in messages:
if isinstance(msg, dict) and "role" in msg and "content" in msg:
if msg["role"] == "user":
current_pair[0] = msg["content"]
elif msg["role"] == "assistant":
current_pair[1] = msg["content"]
result.append(current_pair.copy())
current_pair = ["", ""]
elif isinstance(msg, list) and len(msg) == 2:
# Already in correct format
result.append(msg)
# Handle case where conversation ends with user message
if current_pair[0] and not current_pair[1]:
result.append(current_pair)
return result
def convert_gradio_to_messages(gradio_history):
"""
Convert Gradio list-of-lists format back to role/content format
Input: [["user_message", "assistant_message"], ...]
Output: [{"role": "user", "content": "message"}, {"role": "assistant", "content": "response"}, ...]
"""
if not gradio_history:
return []
result = []
for pair in gradio_history:
if isinstance(pair, list) and len(pair) >= 2:
if pair[0]: # user message
result.append({"role": "user", "content": pair[0]})
if pair[1]: # assistant message
result.append({"role": "assistant", "content": pair[1]})
return result
def simple_chat_response(user_message, history):
"""
A bare-bones GPT-3.5-turbo chat using the v1.0+ SDK.
- history is a list of dicts: [{"role":"user"|"assistant","content":...}, …]
- Returns (updated_history, "")
"""
if history is None:
history = []
# 1) record the user’s message
history.append({"role": "user", "content": user_message})
# 2) call the new chat endpoint
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=history,
temperature=0.7,
) # :contentReference[oaicite:0]{index=0}
# 3) extract and record the assistant reply
reply_content = completion.choices[0].message.content
reply = reply_content.strip() if reply_content else ""
history.append({"role": "assistant", "content": reply})
# 4) Convert back to Gradio format and return
gradio_history = convert_messages_for_gradio(history)
return gradio_history, ""
# --- Chat orchestration logic is now in chat_orchestrator.py ---
from core.agents.chat_orchestrator import simulate_agent_response_stream, build_log, _stream_to_agent, MAX_HISTORY, orchestrators
def reset_chat(agent_json):
"""
Clears history, input, invocation log, and active_children state.
Also clears orchestrator state to prevent response persistence.
"""
# Clear orchestrator state to prevent persistence across conversations
from core.agents.chat_orchestrator import orchestrators
orchestrators.clear()
agent_data = json.loads(agent_json)
name = agent_data.get("agent_name", "Agent")
welcome = f"👋 Hello! I'm {name}. How can I assist you today?"
chat_history = [{"role":"assistant","content":welcome}]
gradio_history = convert_messages_for_gradio(chat_history)
invocation_log = ""
active_children= []
return gradio_history, "", invocation_log, active_children
def load_agent_to_builder(agent_name):
if agent_name in agents_config:
agent_data = json.loads(agents_config[agent_name])
return (
agent_data.get("agent_type", ""),
agent_data.get("agent_name", ""),
agent_data.get("agent_mission", ""),
agent_data.get("skills", [])
)
else:
return None, "", "", []
def remove_selected_agent(agent_name: str):
# 1) Remove from your in-memory store
if agent_name in agents_config:
del agents_config[agent_name]
# 2) Re-render the list of active agents
if agents_config:
active_md = "### 🧠 Active Agents\n" + "\n".join(f"- {name}" for name in agents_config)
else:
active_md = "### 🧠 Active Agents\n_(None yet)_"
# 3) Build the new dropdown state
new_choices = list(agents_config.keys())
# gr.update to reset selection (value=None) and update choices
dropdown_update = gr.update(choices=new_choices, value=None)
# Return in the same order you wired the outputs
return active_md, dropdown_update
def update_skills(selected_type: str):
"""Callback to repopulate the skills checkbox based on chosen agent type."""
return gr.update(choices=skills_library.get(selected_type, []))
def handle_uploaded_files(files):
"""
Callback for the File component: spin up (or reuse) your RAG retriever
and index any newly uploaded docs.
"""
global rag_retriever
rag_retriever = SimpleRAGRetriever(openai_api_key=os.getenv("OPENAI_API_KEY"))
if files:
for f in files:
rag_retriever.add_knowledge(f)
# Return a tuple of (visible-update, message) to your upload_alert Markdown
return gr.update(visible=True), "✅ Files uploaded and indexed successfully!"
def populate_from_preset(prefilled_name):
if prefilled_name != "None":
at, an, am, sk = load_prefilled(prefilled_name, prefilled_agents)
return at, an, am, sk, True
return None, "", "", [], False
def on_agent_type_change(selected_type, was_prefilled):
# always refresh the skill choices
new_skills = skills_library.get(selected_type, [])
skill_update = gr.update(choices=new_skills, value=[])
if was_prefilled:
# consume the flag, but leave name & mission intact
return skill_update, gr.update(), gr.update(), False
# manual change: clear everything
return skill_update, gr.update(value=""), gr.update(value=""), False
def chat_selected_agent(agent_name):
agent_json = agents_config.get(agent_name, "")
if agent_json:
# reset_chat returns 4 values:
# (chat_history, cleared_input, invocation_log, active_children)
chat_history, cleared_input, invocation_log, active_children = reset_chat(agent_json)
return chat_history, cleared_input, invocation_log, active_children, agent_json
# If no agent is selected, clear everything
return [], "", "", [], ""
def load_history(agent_name, histories):
# if we’ve never chatted, start with a greeting
if agent_name not in histories:
histories[agent_name] = [
{"role":"assistant","content":f"👋 Hello! I'm {agent_name}. How can I help?"}
]
# Convert to Gradio format before returning
gradio_history = convert_messages_for_gradio(histories[agent_name])
return gradio_history
def chatpanel_handle(agent_name, user_text, histories):
"""
Uses your simulate_agent_response_stream (tool-aware) in a blocking way,
so that tool invocations actually happen.
Returns (final_history, updated_histories, cleared_input).
"""
# 1) Look up the JSON you saved in agents_config
agent_json = agents_config.get(agent_name)
if not agent_json:
return [], histories, "", ""
# 2) Grab the prior history (or seed a greeting)
history = histories.get(agent_name, [])
if not history:
history = [{"role":"assistant",
"content":f"👋 Hello! I'm {agent_name}. How can I help?"}]
# 3) Call your streaming function synchronously
# simulate_agent_response_stream is async, so we need to run it in an event loop
import asyncio
async def run_stream():
async for updated_history, _, invocation_log, _, challenger_info in simulate_agent_response_stream(
agent_json=agent_json,
history=history,
user_input=user_text,
debug_flag=False,
active_children=[]
):
yield updated_history, invocation_log
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
gen = run_stream()
final_history = history
final_invocation_log = ""
try:
while True:
updated_history, invocation_log = loop.run_until_complete(gen.__anext__())
final_history = updated_history
final_invocation_log = invocation_log
except StopAsyncIteration:
pass
# 4) Save back and clear the input box
histories[agent_name] = final_history
return final_history, histories, "", final_invocation_log
def refresh_chat_dropdown():
return gr.update(choices=list(agents_config.keys()), value=None)
def build_ui():
# --- App Layout ---
with gr.Blocks() as app:
# Separate chat histories for each panel
simple_chat_history = gr.State([])
builder_chat_histories = gr.State({})
deployed_chat_histories = gr.State({})
# --- Custom CSS ---
app.css = """
/* Futuristic color palette (lighter background) */
:root {
--futuristic-bg: #fcfdff; /* nearly white */
--futuristic-panel: #e8ecf7;
--futuristic-accent: #00ffe7;
--futuristic-accent2: #7f5cff;
--futuristic-card: #fafdff;
--futuristic-border: #c7d0e6;
--futuristic-text: #23263a;
--futuristic-muted: #7a7e8c;
}
body, .gradio-container, .gr-block, .gr-app {
background: var(--futuristic-bg) !important;
color: var(--futuristic-text) !important;
}
#landing_card,
#agent_form {
opacity: 1;
transition: opacity 0.5s ease;
}
.hidden {
opacity: 0 !important;
pointer-events: none;
}
/* Ensure the row containing the panels stretches and has a white background */
#agent_form .gr-row {
display: flex !important;
align-items: stretch !important;
background: #fff !important;
border-radius: 20px;
box-shadow: 0 4px 32px #7f5cff11, 0 1.5px 8px #00ffe711;
min-height: 600px;
padding: 0 0.5em;
}
/* Panels: remove min-height/height, keep original proportions */
#agent_form .left-panel,
#agent_form .right-panel {
display: flex !important;
flex-direction: column !important;
/* No height or min-height here */
}
/* Vibrant animated gradient for 'Get Started' button */
#start_button {
background: linear-gradient(90deg, #ff6b6b, #fddb3a, #00ffe7, #7f5cff);
background-size: 300% 300%;
color: #181c27 !important;
border: none;
border-radius: 8px;
font-weight: 700;
box-shadow: 0 0 16px #fddb3a88, 0 0 4px #ff6b6b88;
animation: gradient-move 3s ease-in-out infinite, pulse 2s infinite;
font-size: 18px;
padding: 12px 24px;
transition: background-position 0.5s;
}
/* Exciting but static look for 'Chat With the Agents You Deployed' button */
#to_chat_button {
background: linear-gradient(90deg, #7f5cff, #a259ff, #6e4cff, #c2a3ff);
background-size: 300% 300%;
color: #fff !important;
border: none;
border-radius: 8px;
font-weight: 700;
box-shadow: 0 0 16px #a259ff88, 0 0 4px #7f5cff88;
/* No animation here, just static gradient */
font-size: 18px;
padding: 12px 24px;
}
@keyframes gradient-move {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes pulse {
0% { box-shadow: 0 0 16px var(--futuristic-accent2), 0 0 4px var(--futuristic-accent); }
50% { box-shadow: 0 0 32px var(--futuristic-accent), 0 0 8px var(--futuristic-accent2); }
100% { box-shadow: 0 0 16px var(--futuristic-accent2), 0 0 4px var(--futuristic-accent); }
}
.left-panel {
background: linear-gradient(135deg, #e8ecf7 60%, #fafdff 100%);
padding: 15px;
border-radius: 16px;
border: 1px solid var(--futuristic-border);
box-shadow: 0 2px 16px #7f5cff22, 0 1.5px 8px #00ffe722;
}
.right-panel {
background: linear-gradient(135deg, #e8ecf7 60%, #fafdff 100%);
padding: 15px;
border-radius: 16px;
border: 1px solid var(--futuristic-border);
box-shadow: 0 2px 16px #7f5cff22, 0 1.5px 8px #00ffe722;
}
#chat_history {
height: 520px;
overflow: auto;
background: #181c27;
border-radius: 12px;
border: 1px solid var(--futuristic-border);
}
#chat_input {
width: 100%;
background: #23263a;
color: var(--futuristic-text);
border-radius: 8px;
border: 1px solid var(--futuristic-accent2);
}
#chat_buttons .gr-button {
width: 49%;
}
.right-panel {
display: flex;
flex-direction: column;
}
.landing-card {
background: linear-gradient(135deg, #23263a 60%, #1a1f2b 100%);
border: 1.5px solid var(--futuristic-accent2);
border-radius: 28px;
padding: 32px 36px 32px 36px;
margin-bottom: 40px;
box-shadow: 0 4px 32px #00ffe733, 0 1.5px 8px #7f5cff33;
box-sizing: border-box;
}
.landing-title {
text-align: center;
font-size: 2.1rem;
font-weight: 700;
margin-bottom: 0.5em;
}
.landing-subtitle {
text-align: center;
font-size: 1.15rem;
color: var(--futuristic-muted);
margin-bottom: 1.2em;
}
.steps-box {
background: var(--futuristic-card);
border: 1px solid var(--futuristic-border);
border-radius: 16px;
max-width: 840px;
margin: 0 auto 1.5em auto;
padding: 18px 28px 18px 28px;
box-shadow: 0 2px 12px #7f5cff22;
text-align: left;
}
.steps-box ul {
margin: 0;
padding-left: 1.2em;
}
.steps-box li {
margin-bottom: 0.5em;
font-size: 1.05em;
}
.animated-button {
animation: pulse 2s ease-out 1;
font-size: 18px;
padding: 12px 24px;
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 16px var(--futuristic-accent2); }
50% { transform: scale(1.05); box-shadow: 0 0 32px var(--futuristic-accent); }
100% { transform: scale(1); box-shadow: 0 0 16px var(--futuristic-accent2); }
}
#advanced_options .gr-accordion-content {
font-size: 0.85em;
line-height: 1.3;
padding-left: 1rem;
}
#chat_history,
#chat_input,
#chat_input textarea {
box-sizing: border-box;
}
button.generate-btn {
background: linear-gradient(90deg, var(--futuristic-accent2), var(--futuristic-accent));
color: #181c27 !important;
border: none !important;
font-weight: 600;
box-shadow: 0 4px 24px var(--futuristic-accent2), 0 2px 8px var(--futuristic-accent);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
button.generate-btn:hover {
transform: translateY(-2px) scale(1.03);
box-shadow: 0 8px 32px var(--futuristic-accent), 0 4px 16px var(--futuristic-accent2);
}
button.generate-btn:active {
transform: translateY(0) scale(0.98);
box-shadow: 0 3px 8px var(--futuristic-accent2);
}
.agent-controls {
display: inline-flex !important;
flex-wrap: nowrap !important;
align-items: center;
gap: 0.5rem;
}
.agent-controls .action-btn,
.agent-controls .agent-dropdown {
width: auto !important;
flex: none !important;
}
.agent-controls .agent-dropdown-column,
.agent-controls .agent-button-group {
background: var(--futuristic-panel) !important;
box-shadow: none !important;
padding: 0 !important;
}
.agent-controls > div,
.agent-controls > div > div,
.agent-controls > div > div > div {
background: transparent !important;
box-shadow: none !important;
padding: 0 !important;
margin: 0 !important;
}
/* Chatbot message bubbles */
.gr-chat-message.user {
background: linear-gradient(90deg, #23263a 60%, #181c27 100%);
color: var(--futuristic-accent);
border-radius: 12px 12px 4px 12px;
border: 1px solid var(--futuristic-accent2);
}
.gr-chat-message.assistant {
background: linear-gradient(90deg, #181c27 60%, #23263a 100%);
color: var(--futuristic-text);
border-radius: 12px 12px 12px 4px;
border: 1px solid var(--futuristic-accent);
}
/* Muted text */
.gr-markdown, .gradio-markdown, .gradio-container .gr-markdown {
color: var(--futuristic-muted) !important;
}
/* Patient Cards Section */
.patient-cards-section {
margin-top: 2rem !important;
padding-top: 1.5rem !important;
border-top: 2px solid #ffffff !important;
padding-bottom: 1rem !important;
background: var(--futuristic-panel) !important;
border-radius: 16px !important;
border: 1px solid var(--futuristic-border) !important;
box-shadow: 0 2px 12px rgba(127, 92, 255, 0.1) !important;
}
.patient-cards-header {
text-align: center !important;
margin-bottom: 1.5rem !important;
color: var(--futuristic-text) !important;
font-size: 1.1rem !important;
font-weight: 600 !important;
}
.patient-cards-row {
margin-bottom: 1rem !important;
gap: 1rem !important;
}
.patient-cards-row:last-child {
margin-bottom: 0 !important;
}
.patient-card {
background: linear-gradient(135deg, var(--futuristic-card) 60%, #ffffff 100%) !important;
border: 2px solid var(--futuristic-border) !important;
border-radius: 12px !important;
padding: 1rem !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
box-shadow: 0 2px 8px rgba(127, 92, 255, 0.1) !important;
display: flex !important;
flex-direction: column !important;
justify-content: space-between !important;
min-height: 120px !important;
margin: 0.5rem !important;
}
.patient-card:hover {
transform: translateY(-3px) !important;
box-shadow: 0 6px 20px rgba(127, 92, 255, 0.2) !important;
border-color: var(--futuristic-accent2) !important;
background: linear-gradient(135deg, #f0f4ff 60%, #ffffff 100%) !important;
}
.patient-card.selected {
border-color: var(--futuristic-accent) !important;
box-shadow: 0 6px 24px rgba(0, 255, 231, 0.3) !important;
background: linear-gradient(135deg, var(--futuristic-accent) 5%, var(--futuristic-card) 60%, #ffffff 100%) !important;
}
.patient-card-btn {
background: transparent !important;
border: none !important;
padding: 0 !important;
width: 100% !important;
height: 100% !important;
text-align: left !important;
font-size: 0.85rem !important;
line-height: 1.3 !important;
color: var(--futuristic-text) !important;
cursor: pointer !important;
white-space: pre-line !important;
}
.patient-card-btn:hover {
background: transparent !important;
}
/* Hide chat panel elements globally by default */
.chat-only-btn {
display: none !important;
}
.chat-panel-buttons {
display: none !important;
}
.patient-cards-group {
display: none !important;
}
.patient-cards-section {
display: none !important;
}
.patient-cards-row {
display: none !important;
}
.patient-cards-grid {
display: none !important;
}
.chat-only-content {
display: none !important;
}
.patient-card-btn {
display: none !important;
}
.patient-cards-container {
display: none !important;
}
.chat-only-section {
display: none !important;
}
/* Hide chat panel elements in builder panel (redundant but explicit) */
#agent_form .chat-only-btn {
display: none !important;
}
#agent_form .chat-panel-buttons {
display: none !important;
}
#agent_form .patient-cards-group {
display: none !important;
}
#agent_form .patient-cards-section {
display: none !important;
}
#agent_form .patient-cards-row {
display: none !important;
}
#agent_form .patient-cards-grid {
display: none !important;
}
#agent_form .chat-only-content {
display: none !important;
}
#agent_form .patient-card-btn {
display: none !important;
}
#agent_form .patient-cards-container {
display: none !important;
}
#agent_form .chat-only-section {
display: none !important;
}
/* Show chat panel elements only in chat panel */
#agent_chat .chat-only-btn {
display: inline-block !important;
}
#agent_chat .chat-panel-buttons {
display: flex !important;
}
#agent_chat .patient-cards-group {
display: block !important;
}
#agent_chat .patient-cards-section {
display: block !important;
}
#agent_chat .patient-cards-row {
display: flex !important;
}
#agent_chat .patient-cards-grid {
display: grid !important;
}
#agent_chat .chat-only-content {
display: block !important;
}
#agent_chat .patient-card-btn {
display: block !important;
}
#agent_chat .patient-cards-container {
display: flex !important;
}
#agent_chat .chat-only-section {
display: block !important;
}
"""
# Helper functions for chat control state management
def show_initial_instruction_state():
"""Return initial state with instruction message and disabled controls"""
instruction_chat = [["", "📋 **Agent generated successfully!**\n\nTo start testing your agent:\n1. Select your agent from the dropdown menu above\n2. Click '💬 Chat with Selected Agent'\n3. Then you can type your questions in the chat box\n\n*Please select an agent from the dropdown to begin testing.*"]]
return (
instruction_chat, # builder_chatbot
gr.update(value="", interactive=False, placeholder="Please select an agent to start chatting..."), # chat_input
gr.update(interactive=False), # builder_send_button
gr.update(interactive=False), # reset_button
"", # invocation_log
[] # active_children
)
def enable_chat_controls_with_agent(agent_name):
"""Enable chat controls and show proper agent greeting when agent is selected"""
agent_json = agents_config.get(agent_name, "")
if agent_json:
# Get agent data for greeting
agent_data = json.loads(agent_json)
agent_display_name = agent_data.get("agent_name", agent_name)
# Create greeting message
greeting_chat = [["", f"👋 Hello! I'm {agent_display_name}. How can I assist you today?"]]
return (
greeting_chat, # builder_chatbot with agent greeting
gr.update(value="", interactive=True, placeholder="Type your question here…"), # chat_input enabled
gr.update(interactive=True), # builder_send_button enabled
gr.update(interactive=True), # reset_button enabled
"", # clear invocation_log
[], # clear active_children
agent_json # agent_output
)
else:
# No valid agent selected, return disabled state
return show_initial_instruction_state() + ("",) # Add empty agent_output
# Update instruction when agent is selected from dropdown
def update_instruction_on_dropdown_change(agent_name):
"""Update instruction message when agent is selected from dropdown"""
if agent_name:
instruction_msg = f"📋 **Agent '{agent_name}' selected!**\n\nTo start testing this agent:\n• Click '💬 Chat with Selected Agent' button above\n• Then you can type your questions in the chat box\n\n*Click the chat button to begin testing.*"
else:
instruction_msg = "📋 **Welcome to the Agent Builder!**\n\nTo start testing your agents:\n1. Generate an agent using the form on the left\n2. Select your agent from the dropdown menu above\n3. Click '💬 Chat with Selected Agent'\n4. Then you can type your questions in the chat box\n\n*Please create and select an agent to begin testing.*"
return [["", instruction_msg]]
# 1) HEADER & LANDING CARD
with gr.Group(elem_id="landing_card", elem_classes="landing-card", visible=True) as landing_panel:
gr.Markdown("
🦠 Infectious Diseases Agent Builder
", elem_id=None, elem_classes=None)
gr.Markdown("Build your own ID-focused chat agent in 5 easy steps — no coding required.
", elem_id=None, elem_classes=None)
gr.HTML("""
- Step 1: Pick an agent template or start from scratch
- Step 2: Choose your agent’s focus (Stewardship, Research, Clinical…)
- Step 3: Select from prebuilt skills (PubMed search, guideline summaries…)
- Step 4: (Optional) Upload your own documents or trusted URLs
- Step 5: Generate & start chatting live
""")
start_button = gr.Button(
"🚀 Get Started",
elem_id="start_button",
elem_classes="animated-button"
)
gr.HTML("")
# Only the simple GPT-3.5 Chatbot (no active agents or builder UI)
gr.Markdown("### 💬 Try A Simple Chatbot Before You Build Your ID Agents")
simple_chatbot = gr.Chatbot(label="GPT-3.5 Chat")
simple_input = gr.Textbox(
placeholder="Ask anything…",
show_label=False,
lines=2,
max_lines=4,
)
simple_send = gr.Button("Send")
simple_reset = gr.Button("Reset")
# 2) AGENT FORM (HIDDEN UNTIL CLICK)
prefill_flag = gr.State(False)
with gr.Group(elem_id="agent_form", visible=False) as agent_form:
# Move Back to Home button to the very top
back_button = gr.Button("🔙 Back to Home", elem_id="back_button")
# Steps box at the top of the builder panel for user guidance
gr.HTML("""
- Step 1: Pick a prefilled agent template or start from scratch
- Step 2: Choose your agent’s focus (Stewardship, Research, Clinical…), name, and mission.
- Step 3: Select from prebuilt skills.
- Step 4: (Optional) Upload your own documents or trusted URLs
- Step 5: Generate & test & iterate & start chatting live with your deployed agents
""")
gr.Markdown("### 🎛️ Infectious Diseases Agent Builder")
with gr.Row():
# Left panel
with gr.Column(scale=3, elem_classes="left-panel"):
prefilled = gr.Dropdown(choices=["None"] + list(prefilled_agents.keys()), label="Start with a prefilled agent?")
with gr.Accordion("🛠️ Basic Settings", open=True):
agent_type = gr.Radio(
choices=[
"🛡️ Antimicrobial Stewardship",
"🦠 Infection Prevention and Control",
"🔬 Research Assistant",
"🏥 Clinical Assistant",
"📚 Education Assistant",
"🎼 Orchestrator",
],
label="Select Agent Type",
elem_id="select_agent_type_radio"
)
agent_name = gr.Textbox(label="Agent Name", placeholder="e.g., SmartSteward", max_lines=1)
agent_mission = gr.Textbox(label="Agent Mission", placeholder="Describe what your agent should do…", lines=4)
skills = gr.CheckboxGroup(choices=[], label="Select Skills")
with gr.Accordion("⚙️ Advanced Options", open=False):
link1, link2, link3, link4 = [
gr.Textbox(label=f"Trusted Source Link {i} (optional)")
for i in range(1,5)
]
web_access_toggle = gr.Checkbox(label="Allow Internet Search 🌐", value=True, interactive=True)
allow_fallback_toggle = gr.Checkbox(label="Allow Fallback to LLM General Knowledge 🤖", value=True)
challenger_toggle = gr.Checkbox(label="Enable Adversarial AI Validation (Challenger)", value=False, info="If enabled, agent replies will be critiqued by an adversarial LLM before being shown to the user.")
# --- Auto-toggle logic for web_access_toggle ---
def update_web_access_toggle(l1, l2, l3, l4):
links = [l1, l2, l3, l4]
any_links = any(l.strip() for l in links if l)
if any_links:
# If any trusted link is present, force checked and disable
return gr.update(value=True, interactive=False)
else:
# If all empty, allow user to toggle
return gr.update(interactive=True)
# Wire up the logic: any change to link1-4 updates web_access_toggle
for link in [link1, link2, link3, link4]:
link.change(
fn=update_web_access_toggle,
inputs=[link1, link2, link3, link4],
outputs=[web_access_toggle]
)
generate_button = gr.Button("✨ Generate Agent Config", elem_classes="generate-btn")
with gr.Accordion("📦 Generated Agent Config", open=False):
agent_loader = gr.Markdown("")
agent_output = gr.Code(label="Configuration (JSON)", language="json")
download_button = gr.DownloadButton(label="Download Config")
# Move Upload Knowledge Files section below agent config
with gr.Accordion("📚 Upload Knowledge Files (Global)", open=False):
uploaded_files = gr.File(label="Upload Knowledge Files", file_count="multiple")
upload_alert = gr.Markdown("", visible=False)
# Right panel
with gr.Column(scale=9, elem_classes="right-panel"):
builder_active_agents = gr.Markdown("### 🧠 Active Agents\n_(None yet)_")
# dropdown + action buttons inline
with gr.Row(elem_classes="agent-controls"):
with gr.Column(scale=3, elem_classes="agent-dropdown-column"):
agent_remove_dropdown = gr.Dropdown(
label="Select an agent",
choices=[],
elem_classes="agent-dropdown"
)
with gr.Column(scale=1, elem_classes="agent-button-group"):
chat_agent_button = gr.Button(
"💬 Chat with Selected Agent",
elem_classes="action-btn"
)
edit_agent_button = gr.Button(
"🛠 Edit Selected Agent",
elem_classes="action-btn"
)
remove_agent_button = gr.Button(
"❌ Remove Selected Agent",
elem_classes="action-btn"
)
show_debug = gr.Checkbox(label="🔎 Show tool reasoning", value=False)
# Only one chatbot in builder panel
builder_chatbot = gr.Chatbot(
label="💬 Live Conversation with Your ID Agent",
value=[["", "📋 **Welcome to the Agent Builder!**\n\nTo start testing your agents:\n1. Generate an agent using the form on the left\n2. Select your agent from the dropdown menu above\n3. Click '💬 Chat with Selected Agent'\n4. Then you can type your questions in the chat box\n\n*Please create and select an agent to begin testing.*"]]
)
chat_input = gr.Textbox(
placeholder="Please select an agent to start chatting...",
show_label=False,
lines=3,
max_lines=5,
interactive=False
)
active_children = gr.State([]) # will hold a list of JSON-configs
# --- Builder panel clinical variable fields (hidden, but needed for wiring) ---
builder_deescalation_culture = gr.Textbox(visible=False)
builder_deescalation_meds = gr.Textbox(visible=False)
builder_stewardship_site = gr.Textbox(visible=False)
builder_stewardship_biofilm = gr.Textbox(visible=False)
builder_stewardship_response = gr.Textbox(visible=False)
builder_stewardship_crcl = gr.Textbox(visible=False)
builder_stewardship_severity = gr.Textbox(visible=False)
builder_stewardship_allergies = gr.Textbox(visible=False)
builder_empiric_age = gr.Textbox(visible=False)
builder_empiric_allergies = gr.Textbox(visible=False)
builder_empiric_labs = gr.Textbox(visible=False)
builder_empiric_culture = gr.Textbox(visible=False)
builder_empiric_meds = gr.Textbox(visible=False)
builder_empiric_site = gr.Textbox(visible=False)
builder_empiric_biofilm = gr.Textbox(visible=False)
builder_empiric_response = gr.Textbox(visible=False)
builder_empiric_crcl = gr.Textbox(visible=False)
builder_empiric_severity = gr.Textbox(visible=False)
# Add the builder send button under the chatbox
builder_send_button = gr.Button("Send", elem_id="builder_send_button", interactive=False)
reset_button = gr.Button("🔄 Reset Chat", interactive=False)
invocation_log = gr.Markdown(
value="",
label="🔍 Tool Invocation Log",
visible=True
)
gr.Markdown("---\nBuilt with ❤️ for ID Week 2025 — Empowering Infectious Diseases Innovation")
# Move the Chat With the Agents You Deployed button to the bottom, below the disclaimer
to_chat_button = gr.Button("🗨️ Chat With the Agents You Deployed", elem_id="to_chat_button")
# --- Builder panel send button logic ---
def builderpanel_handle_with_dynamic_vars(
agent_name, user_text, histories,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity
):
agent_json = agents_config.get(agent_name)
if agent_json:
agent_data = json.loads(agent_json)
skills = agent_data.get("skills", [])
history = histories.get(agent_name, [])
# --- Trusted links wiring ---
trusted_links = []
for k in ["trusted_links", "trusted_links_1", "trusted_links_2", "trusted_links_3", "trusted_links_4"]:
# Support both list and individual keys
if isinstance(agent_data.get(k), list):
trusted_links.extend([l for l in agent_data[k] if l])
elif isinstance(agent_data.get(k), str) and agent_data[k]:
trusted_links.append(agent_data[k])
# Also check for link1-link4 keys (legacy)
for k in ["link1", "link2", "link3", "link4"]:
if agent_data.get(k):
trusted_links.append(agent_data[k])
trusted_links = [l for l in trusted_links if l]
# Do not prepend trusted links to every user message; just keep them available for tools
# if trusted_links:
# links_str = ", ".join(trusted_links)
# user_text = f"Trusted sources for this agent: {links_str}\n\n" + user_text
# --- End trusted links wiring ---
# Deescalation tool
if "recommend_deescalation" in skills:
var_names = ["culture", "meds", "site_of_infection", "risk_of_biofilm", "current_response", "creatinine_clearance", "severity_of_infection", "known_allergies"]
user_vars = {
"culture": deescalation_culture,
"meds": deescalation_meds,
"site_of_infection": stewardship_site,
"risk_of_biofilm": stewardship_biofilm,
"current_response": stewardship_response,
"creatinine_clearance": stewardship_crcl,
"severity_of_infection": stewardship_severity,
"known_allergies": stewardship_allergies
}
extracted = extract_clinical_variables_from_history(history, var_names)
for k in var_names:
if not user_vars[k]:
user_vars[k] = extracted.get(k) or ""
# Only prepend if at least one field is non-empty
if any(user_vars[k] for k in var_names):
user_text = f"[DEESCALATION_TOOL_INPUT] {json.dumps(user_vars)}\n" + user_text
elif "alert_prolonged_antibiotic_use" in skills:
var_names = ["site_of_infection", "risk_of_biofilm", "current_response", "creatinine_clearance", "severity_of_infection", "known_allergies"]
user_vars = {
"site_of_infection": stewardship_site,
"risk_of_biofilm": stewardship_biofilm,
"current_response": stewardship_response,
"creatinine_clearance": stewardship_crcl,
"severity_of_infection": stewardship_severity,
"known_allergies": stewardship_allergies
}
extracted = extract_clinical_variables_from_history(history, var_names)
for k in var_names:
if not user_vars[k]:
user_vars[k] = extracted.get(k) or ""
if any(user_vars[k] for k in var_names):
user_text = f"[ALERT_PROLONGED_ABX_INPUT] {json.dumps(user_vars)}\n" + user_text
elif "recommend_empiric_therapy" in skills:
# Remove 'known_allergies' as a separate required field (it's covered by 'allergies')
var_names = [
"age", "allergies", "labs", "culture", "meds", "site_of_infection",
"risk_of_biofilm", "current_response", "creatinine_clearance", "severity_of_infection"
]
user_vars = {
"age": empiric_age,
"allergies": empiric_allergies,
"labs": empiric_labs,
"culture": empiric_culture,
"meds": empiric_meds,
"site_of_infection": empiric_site,
"risk_of_biofilm": empiric_biofilm,
"current_response": empiric_response,
"creatinine_clearance": empiric_crcl,
"severity_of_infection": empiric_severity
}
extracted = extract_clinical_variables_from_history(history, var_names)
for k in var_names:
if not user_vars[k]:
user_vars[k] = extracted.get(k) or ""
# If any required field is missing, prompt = ...
missing = [k.replace('_', ' ').capitalize() for k in var_names if not user_vars[k].strip()]
if missing:
prompt = f"Please provide the following required information for empiric therapy: {', '.join(missing)}."
# Show this as an assistant message and do not call the tool
history.append({"role": "assistant", "content": prompt})
gradio_history = convert_messages_for_gradio(history)
return gradio_history, histories, "", "", ""
# All required fields present, prepend tool input
user_text = f"[EMPIRIC_THERAPY_INPUT] {json.dumps(user_vars)}\n" + user_text
# Use the same chat handling logic, but ensure the builder_chatbot is updated and history is preserved
# Call chatpanel_handle, but get extra challenger info from simulate_agent_response_stream
import asyncio
from core.agents.chat_orchestrator import simulate_agent_response_stream
agent_json_val = agents_config.get(agent_name)
history_val = histories.get(agent_name, [])
result = None
async def run_stream():
gen = simulate_agent_response_stream(
agent_json=agent_json_val,
history=history_val,
user_input=user_text,
debug_flag=False,
active_children=[]
)
last_result = None
async for result in gen:
last_result = result
return last_result
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
result = loop.run_until_complete(run_stream())
if result is not None and len(result) == 5:
final_history, _, invocation_log, _, challenger_info = result
else:
final_history, updated_histories, cleared_input, invocation_log = chatpanel_handle(agent_name, user_text, histories)
challenger_info = None
gradio_history = convert_messages_for_gradio(final_history)
return gradio_history, updated_histories, cleared_input, invocation_log, ""
except Exception:
# fallback to old behavior if error
final_history, updated_histories, cleared_input, invocation_log = chatpanel_handle(agent_name, user_text, histories)
challenger_info = None
gradio_history = convert_messages_for_gradio(final_history)
return gradio_history, updated_histories, cleared_input, invocation_log, ""
# Update histories
updated_histories = histories.copy()
updated_histories[agent_name] = final_history
# Prepare challenger markdown (debug log for builder panel)
challenger_md = ""
if isinstance(challenger_info, dict):
orig = challenger_info.get("original_reply", "")
crit = challenger_info.get("challenger_critique", "")
final = challenger_info.get("final_reply", "")
# Ensure critique is never None or empty in the UI
if not crit or str(crit).strip().lower() == "none":
crit = "OK"
# Only show the final (challenger-approved) answer in the chatbox
if final and final_history and isinstance(final_history, list):
final_history[-1]["content"] = final
# In the builder panel log, show the critique, but in the chatbox, only show the final answer
# If the challenger changed the answer, only show the suggested revision as the final answer, not the critique text
if final != orig and crit != "OK":
challenger_md = f"**Original Agent Answer:**\n\n{orig}\n\n**Challenger Critique:**\n\n{crit}\n\n**Final Answer Shown to User:**\n\n{final}"
else:
challenger_md = f"**Original Agent Answer:**\n\n{orig}\n\n**Final Answer Shown to User:**\n\n{final}"
# Convert final_history to Gradio format before returning
gradio_history = convert_messages_for_gradio(final_history)
return gradio_history, updated_histories, "", invocation_log, challenger_md
# Add a Markdown for challenger debug info under the invocation log, only in the builder panel
challenger_debug_md = gr.Markdown("", visible=True)
# Place the Markdown visually under the invocation log in the builder panel only
builder_send_button.click(
fn=builderpanel_handle_with_dynamic_vars,
inputs=[
agent_remove_dropdown, chat_input, builder_chat_histories,
builder_deescalation_culture, builder_deescalation_meds,
builder_stewardship_site, builder_stewardship_biofilm, builder_stewardship_response, builder_stewardship_crcl, builder_stewardship_severity, builder_stewardship_allergies,
builder_empiric_age, builder_empiric_allergies, builder_empiric_labs, builder_empiric_culture, builder_empiric_meds, builder_empiric_site, builder_empiric_biofilm, builder_empiric_response, builder_empiric_crcl, builder_empiric_severity
],
outputs=[builder_chatbot, builder_chat_histories, chat_input, invocation_log, challenger_debug_md]
)
# Hide the challenger debug markdown in the deployed chat and simple chat panels
# (No code needed, as those panels do not use this Markdown block)
# (3) Agent-Chat panel, hidden by default
with gr.Group(elem_id="agent_chat", visible=False) as chat_panel:
gr.Markdown("### 🗨️ Chat with Your ID Agents")
chat_active_agents = gr.Markdown("### 🧠 Active Agents\n_(None yet)_")
# Dropdown to pick which agent to chat with
agent_picker = gr.Dropdown(
label="Select Agent",
choices=list(agents_config.keys()),
interactive=True
)
# The ChatGPT-style history box
chat_view = gr.Chatbot(label="Conversation")
# --- Stewardship Tool Clinical Variables Section (Deescalation & Alert Prolonged Abx Use) ---
with gr.Accordion("Stewardship Clinical Variables", open=False, visible=False) as stewardship_vars_section:
deescalation_culture = gr.Textbox(label="Culture & Sensitivity Results", lines=2, visible=False)
deescalation_meds = gr.Textbox(label="Current Antibiotic Regimen", lines=2, visible=False)
stewardship_site = gr.Textbox(label="Site of Infection", lines=1)
stewardship_biofilm = gr.Textbox(label="Risk or Presence of Biofilm", lines=1)
stewardship_response = gr.Textbox(label="Current Response to Antibiotics", lines=1)
stewardship_crcl = gr.Textbox(label="Creatinine Clearance", lines=1)
stewardship_severity = gr.Textbox(label="Severity of Infection", lines=1)
stewardship_allergies = gr.Textbox(label="Known Drug Allergies", lines=1)
# --- Empiric Therapy Tool Clinical Variables Section ---
with gr.Accordion("Empiric Therapy Clinical Variables", open=False, visible=False) as empiric_vars_section:
empiric_age = gr.Textbox(label="Age", lines=1)
empiric_allergies = gr.Textbox(label="Allergies", lines=1)
empiric_labs = gr.Textbox(label="Recent Labs", lines=2)
empiric_culture = gr.Textbox(label="Culture & Sensitivity Results", lines=2)
empiric_meds = gr.Textbox(label="Current Antibiotic Regimen", lines=2)
empiric_site = gr.Textbox(label="Site of Infection", lines=1)
empiric_biofilm = gr.Textbox(label="Risk or Presence of Biofilm", lines=1)
empiric_response = gr.Textbox(label="Current Response to Antibiotics", lines=1)
empiric_crcl = gr.Textbox(label="Creatinine Clearance", lines=1)
empiric_severity = gr.Textbox(label="Severity of Infection", lines=1)
empiric_known_allergies = gr.Textbox(label="Known Drug Allergies", lines=1)
# --- Infection Prevention and Control Clinical Variables Section ---
with gr.Accordion("IPC Clinical Variables", open=False, visible=False) as ipc_vars_section:
ipc_facility_name = gr.Textbox(label="Facility Name", lines=1)
ipc_location = gr.Textbox(label="Location/Unit", lines=1)
ipc_infection_type = gr.Textbox(label="Type of Infection (HAI, SSI, CLABSI, etc.)", lines=1)
ipc_onset_date = gr.Textbox(label="Infection Onset Date", lines=1)
ipc_device_days = gr.Textbox(label="Device Days (Central Line, Ventilator, etc.)", lines=1)
ipc_pathogen = gr.Textbox(label="Pathogen Identified", lines=1)
ipc_resistance_pattern = gr.Textbox(label="Resistance Pattern (MRSA, CRE, etc.)", lines=1)
ipc_isolation_status = gr.Textbox(label="Current Isolation Precautions", lines=1)
ipc_compliance_issues = gr.Textbox(label="Compliance/Breach Issues", lines=2)
# --- Clinical Assistant Clinical Variables Section ---
with gr.Accordion("Clinical Assessment Variables", open=False, visible=False) as clinical_vars_section:
clinical_chief_complaint = gr.Textbox(label="Chief Complaint", lines=2)
clinical_history_present = gr.Textbox(label="History of Present Illness", lines=3)
clinical_past_medical = gr.Textbox(label="Past Medical History", lines=2)
clinical_medications = gr.Textbox(label="Current Medications", lines=2)
clinical_allergies = gr.Textbox(label="Allergies", lines=1)
clinical_social_history = gr.Textbox(label="Social History (Travel, Exposures)", lines=2)
clinical_vital_signs = gr.Textbox(label="Vital Signs", lines=1)
clinical_physical_exam = gr.Textbox(label="Physical Examination Findings", lines=3)
clinical_lab_results = gr.Textbox(label="Laboratory Results", lines=2)
clinical_imaging = gr.Textbox(label="Imaging Results", lines=2)
# --- Orchestrator Coordination Variables Section ---
with gr.Accordion("Multi-Agent Coordination Variables", open=False, visible=False) as orchestrator_vars_section:
# Stewardship Variables (8 fields)
orchestrator_culture = gr.Textbox(label="Culture Results", lines=1)
orchestrator_meds = gr.Textbox(label="Current Medications", lines=1)
orchestrator_site = gr.Textbox(label="Site of Infection", lines=1)
orchestrator_biofilm = gr.Textbox(label="Risk of Biofilm", lines=1)
orchestrator_response = gr.Textbox(label="Current Response", lines=1)
orchestrator_crcl = gr.Textbox(label="Creatinine Clearance", lines=1)
orchestrator_severity = gr.Textbox(label="Severity of Infection", lines=1)
orchestrator_allergies = gr.Textbox(label="Known Allergies", lines=1)
# IPC Variables (9 fields)
orchestrator_facility_name = gr.Textbox(label="Facility Name", lines=1)
orchestrator_location = gr.Textbox(label="Location/Unit", lines=1)
orchestrator_infection_type = gr.Textbox(label="Type of Infection (HAI, SSI, CLABSI, etc.)", lines=1)
orchestrator_onset_date = gr.Textbox(label="Infection Onset Date", lines=1)
orchestrator_device_days = gr.Textbox(label="Device Days (Central Line, Ventilator, etc.)", lines=1)
orchestrator_pathogen = gr.Textbox(label="Pathogen Identified", lines=1)
orchestrator_resistance_pattern = gr.Textbox(label="Resistance Pattern (MRSA, CRE, etc.)", lines=1)
orchestrator_isolation_status = gr.Textbox(label="Current Isolation Precautions", lines=1)
orchestrator_compliance_issues = gr.Textbox(label="Compliance/Breach Issues", lines=2)
# Clinical Assistant Variables (10 fields)
orchestrator_chief_complaint = gr.Textbox(label="Chief Complaint", lines=2)
orchestrator_history_present = gr.Textbox(label="History of Present Illness", lines=3)
orchestrator_past_medical = gr.Textbox(label="Past Medical History", lines=2)
orchestrator_medications = gr.Textbox(label="Current Medications", lines=2)
orchestrator_patient_allergies = gr.Textbox(label="Patient Allergies", lines=1)
orchestrator_social_history = gr.Textbox(label="Social History (Travel, Exposures)", lines=2)
orchestrator_vital_signs = gr.Textbox(label="Vital Signs", lines=1)
orchestrator_physical_exam = gr.Textbox(label="Physical Examination Findings", lines=3)
orchestrator_lab_results = gr.Textbox(label="Laboratory Results", lines=2)
orchestrator_imaging = gr.Textbox(label="Imaging Results", lines=2)
# Add chat input box to chat panel
chat_panel_input = gr.Textbox(
placeholder="Type your question here…",
show_label=False,
lines=3,
max_lines=5
)
# Only show chat_send, chat_reset, and chat_back in the chat panel
with gr.Row(elem_classes="chat-panel-buttons"):
chat_send = gr.Button("Send", elem_classes="chat-only-btn")
chat_reset = gr.Button("🔄 Reset Chat", elem_classes="chat-only-btn")
chat_back = gr.Button("🔙 Back to Builder", elem_id="chat_back", elem_classes="chat-only-btn")
# Patient cards section - positioned at bottom of chat panel
with gr.Group(elem_classes="patient-cards-section"):
gr.Markdown("### 🎯 Select Context-Aware Chat Scenario (Optional)", elem_classes="patient-cards-header")
# First row - 3 cards
with gr.Row(elem_classes="patient-cards-row"):
# Patient Card 1: Stewardship Case
with gr.Column(elem_classes="patient-card", scale=1):
patient_card_1 = gr.Button(
"📋 Patient A: ICU Sepsis\n\n🛡️ SmartSteward Case\n\n68F, ICU day 5, on vancomycin + piperacillin-tazobactam for sepsis. Blood cultures positive for MSSA. Patient improving, normal renal function.",
elem_classes="patient-card-btn"
)
# Patient Card 2: IPC Case
with gr.Column(elem_classes="patient-card", scale=1):
patient_card_2 = gr.Button(
"🦠 Patient B: CLABSI Investigation\n\n🦠 InfectoGuard Case\n\n45M, ICU patient with central line x6 days. Developed fever, positive blood cultures for MRSA. Potential healthcare-associated infection.",
elem_classes="patient-card-btn"
)
# Patient Card 3: Research Case
with gr.Column(elem_classes="patient-card", scale=1):
patient_card_3 = gr.Button(
"🔬 Research Query\n\n🔬 ResearchRanger Case\n\nLiterature search needed for novel carbapenem-resistant Enterobacterales treatment options and resistance mechanisms.",
elem_classes="patient-card-btn"
)
# Second row - 3 cards
with gr.Row(elem_classes="patient-cards-row"):
# Patient Card 4: Clinical Case
with gr.Column(elem_classes="patient-card", scale=1):
patient_card_4 = gr.Button(
"🏥 Patient C: Complex Diagnosis\n\n🏥 ClinicoPilot Case\n\n32M with fever, rash, and joint pain after recent travel to Southeast Asia. Multiple differential diagnoses to consider.",
elem_classes="patient-card-btn"
)
# Patient Card 5: Education Case
with gr.Column(elem_classes="patient-card", scale=1):
patient_card_5 = gr.Button(
"📚 Education Request\n\n📚 EduMedCoach Case\n\nMedical student requesting board exam questions and educational materials on antimicrobial resistance mechanisms.",
elem_classes="patient-card-btn"
)
# Patient Card 6: Orchestrator Case
with gr.Column(elem_classes="patient-card", scale=1):
patient_card_6 = gr.Button(
"🎼 Complex Multi-Agent Case\n\n🎼 ID Maestro Case\n\n75M with multiple ID issues: MDRO pneumonia, C. diff colitis, and suspected endocarditis requiring comprehensive analysis.",
elem_classes="patient-card-btn"
)
# Store patient data in hidden state
patient_data = gr.State({})
# Define patient case data
patient_cases = {
"patient_1": {
"name": "Patient A",
"age": "68",
"summary": "68-year-old female in ICU, day 5 of admission for sepsis",
"current_meds": "vancomycin 1g q12h, piperacillin-tazobactam 4.5g q6h",
"culture_results": "Blood cultures (day 3): methicillin-sensitive Staphylococcus aureus (MSSA), sensitive to cefazolin, nafcillin, clindamycin",
"site_of_infection": "bloodstream",
"biofilm_risk": "central venous catheter present",
"response": "clinically improving, fever resolved, WBC trending down",
"creatinine_clearance": "75 mL/min (normal)",
"severity": "severe sepsis, now stable",
"allergies": "NKDA",
"agent_focus": "🛡️ Antimicrobial Stewardship",
"context": "This patient is a perfect candidate for antibiotic deescalation given the MSSA blood culture results and clinical improvement. Current broad-spectrum therapy can likely be narrowed."
},
"patient_2": {
"name": "Patient B",
"age": "45",
"summary": "45-year-old male, ICU patient with central line-associated bloodstream infection",
"diagnosis": "Central line-associated bloodstream infection (CLABSI)",
"central_line_days": "6 days",
"culture_results": "Blood cultures positive for methicillin-resistant Staphylococcus aureus (MRSA)",
"symptoms": "fever (38.8°C), chills, no other obvious source",
"location": "Methodist Hospital, Dallas, Texas",
"agent_focus": "🦠 Infection Prevention and Control",
"context": "This case requires evaluation for NHSN CLABSI criteria, appropriate isolation precautions for MRSA, and reporting requirements for healthcare-associated infections in Texas."
},
"patient_3": {
"name": "Research Query",
"topic": "Carbapenem-resistant Enterobacterales (CRE) treatment",
"research_focus": "Novel treatment options for CRE infections",
"specific_interests": "resistance mechanisms, combination therapies, newer antibiotics",
"urgency": "clinical decision support needed",
"agent_focus": "🔬 Research Assistant",
"context": "Literature search and evidence synthesis needed for treatment of carbapenem-resistant Enterobacterales infections, including mechanism-based approaches and newest therapeutic options."
},
"patient_4": {
"name": "Patient C",
"age": "32",
"summary": "32-year-old male with fever, rash, and arthralgia after travel",
"travel_history": "Recent travel to Southeast Asia (Thailand, Vietnam) 3 weeks ago",
"symptoms": "fever (39.1°C), maculopapular rash on trunk and extremities, polyarthralgia",
"duration": "symptoms for 5 days",
"differential": "considering dengue fever, chikungunya, Zika virus, typhus, malaria",
"agent_focus": "🏥 Clinical Assistant",
"context": "Complex infectious disease case requiring systematic evaluation of travel-related illnesses and patient education about diagnostic workup and treatment options."
},
"patient_5": {
"name": "Education Request",
"level": "Medical student, 3rd year",
"topic": "Antimicrobial resistance mechanisms",
"request": "Board exam questions and educational materials",
"focus_areas": "beta-lactamase types, carbapenemases, ESBL, AmpC",
"format_needed": "multiple choice questions, flashcards, presentation slides",
"agent_focus": "📚 Education Assistant",
"context": "Educational content creation for antimicrobial resistance mechanisms, suitable for medical student board exam preparation with varying difficulty levels."
},
"patient_6": {
"name": "Patient D",
"age": "75",
"summary": "75-year-old male with multiple infectious complications",
"problem_1": "Ventilator-associated pneumonia with XDR Pseudomonas aeruginosa",
"problem_2": "Clostridioides difficile colitis (severe, recurrent)",
"problem_3": "Suspected infective endocarditis (blood cultures pending)",
"comorbidities": "diabetes, chronic kidney disease (CrCl 30 mL/min), heart failure",
"current_status": "ICU day 12, on multiple antibiotics, clinically complex",
"agent_focus": "🎼 Orchestrator",
"context": "Complex multi-system infectious disease case requiring coordination between stewardship, infection control, and clinical decision-making across multiple agents and specialties."
}
}
# Patient card click handlers
def load_patient_1():
case = patient_cases["patient_1"]
context_msg = f"""**Patient Case Loaded: {case['name']}**
**Clinical Summary:** {case['summary']}
- **Age:** {case['age']} years old
- **Current Antibiotics:** {case['current_meds']}
- **Culture Results:** {case['culture_results']}
- **Site of Infection:** {case['site_of_infection']}
- **Current Response:** {case['response']}
- **Renal Function:** {case['creatinine_clearance']}
- **Allergies:** {case['allergies']}
**Agent Focus:** {case['agent_focus']}
*How can I help with this stewardship case?*"""
# Auto-populate stewardship clinical variables
role_content_history = [{"role": "assistant", "content": context_msg}]
gradio_history = convert_messages_for_gradio(role_content_history)
return (
gradio_history,
case,
case.get('culture_results', ''), # deescalation_culture
case.get('current_meds', ''), # deescalation_meds
case.get('site_of_infection', ''), # stewardship_site
case.get('biofilm_risk', ''), # stewardship_biofilm
case.get('response', ''), # stewardship_response
case.get('creatinine_clearance', ''), # stewardship_crcl
case.get('severity', ''), # stewardship_severity
case.get('allergies', ''), # stewardship_allergies
case.get('age', ''), # empiric_age
case.get('allergies', ''), # empiric_allergies
'', # empiric_labs (not in patient data)
case.get('culture_results', ''), # empiric_culture
case.get('current_meds', ''), # empiric_meds
case.get('site_of_infection', ''), # empiric_site
case.get('biofilm_risk', ''), # empiric_biofilm
case.get('response', ''), # empiric_response
case.get('creatinine_clearance', ''), # empiric_crcl
case.get('severity', ''), # empiric_severity
'', '', '', '', '', '', '', '', '', # ipc fields (9 fields)
'', '', '', '', '', '', '', '', '', '', # clinical fields (10 fields)
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' # orchestrator fields (27 fields)
)
def load_patient_2():
case = patient_cases["patient_2"]
context_msg = f"""**Patient Case Loaded: {case['name']}**
**Clinical Summary:** {case['summary']}
- **Age:** {case['age']} years old
- **Diagnosis:** {case['diagnosis']}
- **Central Line Duration:** {case['central_line_days']}
- **Culture Results:** {case['culture_results']}
- **Symptoms:** {case['symptoms']}
- **Location:** {case['location']}
**Agent Focus:** {case['agent_focus']}
*How can I help with this infection prevention case?*"""
# Auto-populate IPC clinical variables for Patient B
role_content_history = [{"role": "assistant", "content": context_msg}]
gradio_history = convert_messages_for_gradio(role_content_history)
return (
gradio_history,
case,
'', '', '', '', '', '', '', '', # stewardship fields (8 fields)
'', '', '', '', '', '', '', '', '', '', # empiric fields (10 fields)
'', # ipc_facility_name (blank for user input)
'', # ipc_location (blank for user input)
case.get('diagnosis', ''), # ipc_infection_type
'admission + ' + case.get('central_line_days', ''), # ipc_onset_date
case.get('central_line_days', ''), # ipc_device_days
'MRSA' if 'MRSA' in case.get('culture_results', '') else case.get('culture_results', ''), # ipc_pathogen
'MRSA' if 'MRSA' in case.get('culture_results', '') else 'pending resistance testing', # ipc_resistance_pattern
'Contact precautions for MRSA', # ipc_isolation_status
'Review central line maintenance', # ipc_compliance_issues
'', '', '', '', '', '', '', '', '', '', # clinical fields (10 fields)
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' # orchestrator fields (27 fields)
)
def load_patient_3():
case = patient_cases["patient_3"]
context_msg = f"""**Research Query Loaded: {case['name']}**
**Research Focus:** {case['research_focus']}
- **Topic:** {case['topic']}
- **Specific Interests:** {case['specific_interests']}
- **Urgency:** {case['urgency']}
**Agent Focus:** {case['agent_focus']}
*How can I help with your research needs?*"""
# Return empty clinical variables for research case
role_content_history = [{"role": "assistant", "content": context_msg}]
gradio_history = convert_messages_for_gradio(role_content_history)
return (
gradio_history,
case,
'', '', '', '', '', '', '', '', # stewardship fields (8 fields)
'', '', '', '', '', '', '', '', '', '', # empiric fields (10 fields)
'', '', '', '', '', '', '', '', '', # ipc fields (9 fields)
'', '', '', '', '', '', '', '', '', '', # clinical fields (10 fields)
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' # orchestrator fields (27 fields)
)
def load_patient_4():
case = patient_cases["patient_4"]
context_msg = f"""**Patient Case Loaded: {case['name']}**
**Clinical Summary:** {case['summary']}
- **Age:** {case['age']} years old
- **Travel History:** {case['travel_history']}
- **Symptoms:** {case['symptoms']}
- **Duration:** {case['duration']}
- **Differential Diagnosis:** {case['differential']}
**Agent Focus:** {case['agent_focus']}
*How can I help with this clinical case?*"""
# Return clinical assistant-focused variables for Patient E case
role_content_history = [{"role": "assistant", "content": context_msg}]
gradio_history = convert_messages_for_gradio(role_content_history)
return (
gradio_history,
case,
'', '', '', '', '', '', '', '', # stewardship fields (8 fields)
'', '', '', '', '', '', '', '', '', '', # empiric fields (10 fields)
'', '', '', '', '', '', '', '', '', # ipc fields (9 fields)
case['symptoms'], # chief_complaint
f"Patient with travel history to {case['travel_history']} presenting with {case['symptoms']} for {case['duration']}", # history_present
'Travel medicine history as noted', # past_medical_history
'', # current_medications
'', # allergies
f"Recent travel to {case['travel_history']}", # social_history
'', # vital_signs
'', # physical_exam
'', # lab_results
'', # imaging_results
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' # orchestrator fields (27 fields)
)
def load_patient_5():
case = patient_cases["patient_5"]
context_msg = f"""**Education Request Loaded: {case['name']}**
**Educational Details:**
- **Student Level:** {case['level']}
- **Topic:** {case['topic']}
- **Request:** {case['request']}
- **Focus Areas:** {case['focus_areas']}
- **Formats Needed:** {case['format_needed']}
**Agent Focus:** {case['agent_focus']}
*How can I help with your educational materials?*"""
# Return empty clinical variables for education case
role_content_history = [{"role": "assistant", "content": context_msg}]
gradio_history = convert_messages_for_gradio(role_content_history)
return (
gradio_history,
case,
'', '', '', '', '', '', '', '', # stewardship fields (8 fields)
'', '', '', '', '', '', '', '', '', '', # empiric fields (10 fields)
'', '', '', '', '', '', '', '', '', # ipc fields (9 fields)
'', '', '', '', '', '', '', '', '', '', # clinical fields (10 fields)
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' # orchestrator fields (27 fields)
)
def load_patient_6():
case = patient_cases["patient_6"]
context_msg = f"""**Complex Case Loaded: {case['name']}**
**Clinical Summary:** {case['summary']}
- **Age:** {case['age']} years old
- **Problem 1:** {case['problem_1']}
- **Problem 2:** {case['problem_2']}
- **Problem 3:** {case['problem_3']}
- **Comorbidities:** {case['comorbidities']}
- **Current Status:** {case['current_status']}
**Agent Focus:** {case['agent_focus']}
*This complex case requires multi-agent coordination. How can I help orchestrate the care?*"""
# Auto-populate clinical variables for complex orchestrator case
role_content_history = [{"role": "assistant", "content": context_msg}]
gradio_history = convert_messages_for_gradio(role_content_history)
return (
gradio_history,
case,
'', # deescalation_culture (no specific culture data)
'multiple antibiotics', # deescalation_meds (from current_status)
'multiple sites (respiratory, GI)', # stewardship_site (inferred from problems)
'biofilm risk from devices', # stewardship_biofilm (inferred)
'complex, multiple infections', # stewardship_response
case.get('comorbidities', '').split(',')[1].strip() if 'CrCl' in case.get('comorbidities', '') else '30 mL/min', # stewardship_crcl
'severe, multiple complications', # stewardship_severity
'review allergies needed', # stewardship_allergies
case.get('age', ''), # empiric_age
'review allergies needed', # empiric_allergies
'', # empiric_labs
'', # empiric_culture
'multiple antibiotics', # empiric_meds
'multiple sites (respiratory, GI)', # empiric_site
'biofilm risk from devices', # empiric_biofilm
'complex, multiple infections', # empiric_response
'30 mL/min', # empiric_crcl
'severe, multiple complications', # empiric_severity
'', '', '', '', '', '', '', '', '', # ipc fields (9 fields)
'Multiple acute medical problems', # chief_complaint
f"Complex patient with {case['problem_1']}, {case['problem_2']}, and {case['problem_3']}", # history_present
case['comorbidities'], # past_medical_history
'Multiple antibiotics, supportive care', # current_medications
'Review needed', # allergies
'ICU setting, complex care', # social_history
'Multiple abnormalities expected', # vital_signs
'Complex findings across systems', # physical_exam
'Multiple abnormal values', # lab_results
'Multiple studies indicated', # imaging_results
# Auto-populate orchestrator variables from case data
'', # orchestrator_culture
'multiple antibiotics', # orchestrator_meds
'multiple sites (respiratory, GI)', # orchestrator_site
'biofilm risk from devices', # orchestrator_biofilm
'complex, multiple infections', # orchestrator_response
'30 mL/min', # orchestrator_crcl
'severe, multiple complications', # orchestrator_severity
'review allergies needed', # orchestrator_allergies
'', # orchestrator_facility_name
'ICU', # orchestrator_location
'multiple HAIs', # orchestrator_infection_type
'admission', # orchestrator_onset_date
'multiple devices', # orchestrator_device_days
'multiple pathogens', # orchestrator_pathogen
'review resistance patterns', # orchestrator_resistance_pattern
'contact precautions', # orchestrator_isolation_status
'complex care coordination', # orchestrator_compliance_issues
'Multiple acute medical problems', # orchestrator_chief_complaint
f"Complex patient with {case['problem_1']}, {case['problem_2']}, and {case['problem_3']}", # orchestrator_history_present
case['comorbidities'], # orchestrator_past_medical
'Multiple antibiotics, supportive care', # orchestrator_medications
'Review needed', # orchestrator_patient_allergies
'ICU setting, complex care', # orchestrator_social_history
'Multiple abnormalities expected', # orchestrator_vital_signs
'Complex findings across systems', # orchestrator_physical_exam
'Multiple abnormal values', # orchestrator_lab_results
'Multiple studies indicated' # orchestrator_imaging
)
# Store patient data in hidden state
patient_data = gr.State({})
# Define patient case data
patient_cases = {
"patient_1": {
"name": "Patient A",
"age": "68",
"summary": "68-year-old female in ICU, day 5 of admission for sepsis",
"current_meds": "vancomycin 1g q12h, piperacillin-tazobactam 4.5g q6h",
"culture_results": "Blood cultures (day 3): methicillin-sensitive Staphylococcus aureus (MSSA), sensitive to cefazolin, nafcillin, clindamycin",
"site_of_infection": "bloodstream",
"biofilm_risk": "central venous catheter present",
"response": "clinically improving, fever resolved, WBC trending down",
"creatinine_clearance": "75 mL/min (normal)",
"severity": "severe sepsis, now stable",
"allergies": "NKDA",
"agent_focus": "🛡️ Antimicrobial Stewardship",
"context": "This patient is a perfect candidate for antibiotic deescalation given the MSSA blood culture results and clinical improvement. Current broad-spectrum therapy can likely be narrowed."
},
"patient_2": {
"name": "Patient B",
"age": "45",
"summary": "45-year-old male, ICU patient with central line-associated bloodstream infection",
"diagnosis": "Central line-associated bloodstream infection (CLABSI)",
"central_line_days": "6 days",
"culture_results": "Blood cultures positive for methicillin-resistant Staphylococcus aureus (MRSA)",
"symptoms": "fever (38.8°C), chills, no other obvious source",
"location": "Methodist Hospital, Dallas, Texas",
"agent_focus": "🦠 Infection Prevention and Control",
"context": "This case requires evaluation for NHSN CLABSI criteria, appropriate isolation precautions for MRSA, and reporting requirements for healthcare-associated infections in Texas."
},
"patient_3": {
"name": "Research Query",
"topic": "Carbapenem-resistant Enterobacterales (CRE) treatment",
"research_focus": "Novel treatment options for CRE infections",
"specific_interests": "resistance mechanisms, combination therapies, newer antibiotics",
"urgency": "clinical decision support needed",
"agent_focus": "🔬 Research Assistant",
"context": "Literature search and evidence synthesis needed for treatment of carbapenem-resistant Enterobacterales infections, including mechanism-based approaches and newest therapeutic options."
},
"patient_4": {
"name": "Patient C",
"age": "32",
"summary": "32-year-old male with fever, rash, and arthralgia after travel",
"travel_history": "Recent travel to Southeast Asia (Thailand, Vietnam) 3 weeks ago",
"symptoms": "fever (39.1°C), maculopapular rash on trunk and extremities, polyarthralgia",
"duration": "symptoms for 5 days",
"differential": "considering dengue fever, chikungunya, Zika virus, typhus, malaria",
"agent_focus": "🏥 Clinical Assistant",
"context": "Complex infectious disease case requiring systematic evaluation of travel-related illnesses and patient education about diagnostic workup and treatment options."
},
"patient_5": {
"name": "Education Request",
"level": "Medical student, 3rd year",
"topic": "Antimicrobial resistance mechanisms",
"request": "Board exam questions and educational materials",
"focus_areas": "beta-lactamase types, carbapenemases, ESBL, AmpC",
"format_needed": "multiple choice questions, flashcards, presentation slides",
"agent_focus": "📚 Education Assistant",
"context": "Educational content creation for antimicrobial resistance mechanisms, suitable for medical student board exam preparation with varying difficulty levels."
},
"patient_6": {
"name": "Patient D",
"age": "75",
"summary": "75-year-old male with multiple infectious complications",
"problem_1": "Ventilator-associated pneumonia with XDR Pseudomonas aeruginosa",
"problem_2": "Clostridioides difficile colitis (severe, recurrent)",
"problem_3": "Suspected infective endocarditis (blood cultures pending)",
"comorbidities": "diabetes, chronic kidney disease (CrCl 30 mL/min), heart failure",
"current_status": "ICU day 12, on multiple antibiotics, clinically complex",
"agent_focus": "🎼 Orchestrator",
"context": "Complex multi-system infectious disease case requiring coordination between stewardship, infection control, and clinical decision-making across multiple agents and specialties."
}
}
# Connect patient card click handlers
patient_card_1.click(
fn=load_patient_1,
inputs=[],
outputs=[
chat_view, patient_data,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity,
ipc_facility_name, ipc_location, ipc_infection_type, ipc_onset_date, ipc_device_days, ipc_pathogen, ipc_resistance_pattern, ipc_isolation_status, ipc_compliance_issues,
clinical_chief_complaint, clinical_history_present, clinical_past_medical, clinical_medications, clinical_allergies, clinical_social_history, clinical_vital_signs, clinical_physical_exam, clinical_lab_results, clinical_imaging,
orchestrator_culture, orchestrator_meds, orchestrator_site, orchestrator_biofilm, orchestrator_response, orchestrator_crcl, orchestrator_severity, orchestrator_allergies, orchestrator_facility_name, orchestrator_location, orchestrator_infection_type, orchestrator_onset_date, orchestrator_device_days, orchestrator_pathogen, orchestrator_resistance_pattern, orchestrator_isolation_status, orchestrator_compliance_issues, orchestrator_chief_complaint, orchestrator_history_present, orchestrator_past_medical, orchestrator_medications, orchestrator_patient_allergies, orchestrator_social_history, orchestrator_vital_signs, orchestrator_physical_exam, orchestrator_lab_results, orchestrator_imaging
]
)
patient_card_2.click(
fn=load_patient_2,
inputs=[],
outputs=[
chat_view, patient_data,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity,
ipc_facility_name, ipc_location, ipc_infection_type, ipc_onset_date, ipc_device_days, ipc_pathogen, ipc_resistance_pattern, ipc_isolation_status, ipc_compliance_issues,
clinical_chief_complaint, clinical_history_present, clinical_past_medical, clinical_medications, clinical_allergies, clinical_social_history, clinical_vital_signs, clinical_physical_exam, clinical_lab_results, clinical_imaging,
orchestrator_culture, orchestrator_meds, orchestrator_site, orchestrator_biofilm, orchestrator_response, orchestrator_crcl, orchestrator_severity, orchestrator_allergies, orchestrator_facility_name, orchestrator_location, orchestrator_infection_type, orchestrator_onset_date, orchestrator_device_days, orchestrator_pathogen, orchestrator_resistance_pattern, orchestrator_isolation_status, orchestrator_compliance_issues, orchestrator_chief_complaint, orchestrator_history_present, orchestrator_past_medical, orchestrator_medications, orchestrator_patient_allergies, orchestrator_social_history, orchestrator_vital_signs, orchestrator_physical_exam, orchestrator_lab_results, orchestrator_imaging
]
)
patient_card_3.click(
fn=load_patient_3,
inputs=[],
outputs=[
chat_view, patient_data,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity,
ipc_facility_name, ipc_location, ipc_infection_type, ipc_onset_date, ipc_device_days, ipc_pathogen, ipc_resistance_pattern, ipc_isolation_status, ipc_compliance_issues,
clinical_chief_complaint, clinical_history_present, clinical_past_medical, clinical_medications, clinical_allergies, clinical_social_history, clinical_vital_signs, clinical_physical_exam, clinical_lab_results, clinical_imaging,
orchestrator_culture, orchestrator_meds, orchestrator_site, orchestrator_biofilm, orchestrator_response, orchestrator_crcl, orchestrator_severity, orchestrator_allergies, orchestrator_facility_name, orchestrator_location, orchestrator_infection_type, orchestrator_onset_date, orchestrator_device_days, orchestrator_pathogen, orchestrator_resistance_pattern, orchestrator_isolation_status, orchestrator_compliance_issues, orchestrator_chief_complaint, orchestrator_history_present, orchestrator_past_medical, orchestrator_medications, orchestrator_patient_allergies, orchestrator_social_history, orchestrator_vital_signs, orchestrator_physical_exam, orchestrator_lab_results, orchestrator_imaging
]
)
patient_card_4.click(
fn=load_patient_4,
inputs=[],
outputs=[
chat_view, patient_data,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity,
ipc_facility_name, ipc_location, ipc_infection_type, ipc_onset_date, ipc_device_days, ipc_pathogen, ipc_resistance_pattern, ipc_isolation_status, ipc_compliance_issues,
clinical_chief_complaint, clinical_history_present, clinical_past_medical, clinical_medications, clinical_allergies, clinical_social_history, clinical_vital_signs, clinical_physical_exam, clinical_lab_results, clinical_imaging,
orchestrator_culture, orchestrator_meds, orchestrator_site, orchestrator_biofilm, orchestrator_response, orchestrator_crcl, orchestrator_severity, orchestrator_allergies, orchestrator_facility_name, orchestrator_location, orchestrator_infection_type, orchestrator_onset_date, orchestrator_device_days, orchestrator_pathogen, orchestrator_resistance_pattern, orchestrator_isolation_status, orchestrator_compliance_issues, orchestrator_chief_complaint, orchestrator_history_present, orchestrator_past_medical, orchestrator_medications, orchestrator_patient_allergies, orchestrator_social_history, orchestrator_vital_signs, orchestrator_physical_exam, orchestrator_lab_results, orchestrator_imaging
]
)
patient_card_5.click(
fn=load_patient_5,
inputs=[],
outputs=[
chat_view, patient_data,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity,
ipc_facility_name, ipc_location, ipc_infection_type, ipc_onset_date, ipc_device_days, ipc_pathogen, ipc_resistance_pattern, ipc_isolation_status, ipc_compliance_issues,
clinical_chief_complaint, clinical_history_present, clinical_past_medical, clinical_medications, clinical_allergies, clinical_social_history, clinical_vital_signs, clinical_physical_exam, clinical_lab_results, clinical_imaging,
orchestrator_culture, orchestrator_meds, orchestrator_site, orchestrator_biofilm, orchestrator_response, orchestrator_crcl, orchestrator_severity, orchestrator_allergies, orchestrator_facility_name, orchestrator_location, orchestrator_infection_type, orchestrator_onset_date, orchestrator_device_days, orchestrator_pathogen, orchestrator_resistance_pattern, orchestrator_isolation_status, orchestrator_compliance_issues, orchestrator_chief_complaint, orchestrator_history_present, orchestrator_past_medical, orchestrator_medications, orchestrator_patient_allergies, orchestrator_social_history, orchestrator_vital_signs, orchestrator_physical_exam, orchestrator_lab_results, orchestrator_imaging
]
)
patient_card_6.click(
fn=load_patient_6,
inputs=[],
outputs=[
chat_view, patient_data,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity,
ipc_facility_name, ipc_location, ipc_infection_type, ipc_onset_date, ipc_device_days, ipc_pathogen, ipc_resistance_pattern, ipc_isolation_status, ipc_compliance_issues,
clinical_chief_complaint, clinical_history_present, clinical_past_medical, clinical_medications, clinical_allergies, clinical_social_history, clinical_vital_signs, clinical_physical_exam, clinical_lab_results, clinical_imaging,
orchestrator_culture, orchestrator_meds, orchestrator_site, orchestrator_biofilm, orchestrator_response, orchestrator_crcl, orchestrator_severity, orchestrator_allergies, orchestrator_facility_name, orchestrator_location, orchestrator_infection_type, orchestrator_onset_date, orchestrator_device_days, orchestrator_pathogen, orchestrator_resistance_pattern, orchestrator_isolation_status, orchestrator_compliance_issues, orchestrator_chief_complaint, orchestrator_history_present, orchestrator_past_medical, orchestrator_medications, orchestrator_patient_allergies, orchestrator_social_history, orchestrator_vital_signs, orchestrator_physical_exam, orchestrator_lab_results, orchestrator_imaging
]
)
# --- Show/hide stewardship fields based on agent selection ---
def update_dynamic_vars_visibility(agent_name):
agent_json = agents_config.get(agent_name)
if agent_json:
agent_data = json.loads(agent_json)
skills = agent_data.get("skills", [])
agent_type = agent_data.get("agent_type", "")
# Stewardship tools
if "recommend_deescalation" in skills or "alert_prolonged_antibiotic_use" in skills:
show_culture = "recommend_deescalation" in skills
show_meds = "recommend_deescalation" in skills
return (
gr.update(visible=True), # stewardship_vars_section
gr.update(visible=show_culture), # deescalation_culture
gr.update(visible=show_meds), # deescalation_meds
gr.update(visible=False), # empiric_vars_section
gr.update(visible=False), # ipc_vars_section
gr.update(visible=False), # clinical_vars_section
gr.update(visible=False) # orchestrator_vars_section
)
# Empiric therapy tool
elif "recommend_empiric_therapy" in skills:
return (
gr.update(visible=False), # stewardship_vars_section
gr.update(visible=False), # deescalation_culture
gr.update(visible=False), # deescalation_meds
gr.update(visible=True), # empiric_vars_section
gr.update(visible=False), # ipc_vars_section
gr.update(visible=False), # clinical_vars_section
gr.update(visible=False) # orchestrator_vars_section
)
# IPC tools
elif "IPC_reporting" in skills or "NHSN_criteria_evaluator" in skills or "recommend_isolation_precautions" in skills:
return (
gr.update(visible=False), # stewardship_vars_section
gr.update(visible=False), # deescalation_culture
gr.update(visible=False), # deescalation_meds
gr.update(visible=False), # empiric_vars_section
gr.update(visible=True), # ipc_vars_section
gr.update(visible=False), # clinical_vars_section
gr.update(visible=False) # orchestrator_vars_section
)
# Clinical Assistant tools
elif "retrieve_guidelines" in skills or "explain_in_layman_language" in skills or "history_taking" in skills:
return (
gr.update(visible=False), # stewardship_vars_section
gr.update(visible=False), # deescalation_culture
gr.update(visible=False), # deescalation_meds
gr.update(visible=False), # empiric_vars_section
gr.update(visible=False), # ipc_vars_section
gr.update(visible=True), # clinical_vars_section
gr.update(visible=False) # orchestrator_vars_section
)
# Orchestrator (check by agent type since it has no specific skills)
elif "🎼 Orchestrator" in agent_type:
return (
gr.update(visible=False), # stewardship_vars_section
gr.update(visible=False), # deescalation_culture
gr.update(visible=False), # deescalation_meds
gr.update(visible=False), # empiric_vars_section
gr.update(visible=False), # ipc_vars_section
gr.update(visible=False), # clinical_vars_section
gr.update(visible=True) # orchestrator_vars_section
)
# Hide all
return (
gr.update(visible=False), # stewardship_vars_section
gr.update(visible=False), # deescalation_culture
gr.update(visible=False), # deescalation_meds
gr.update(visible=False), # empiric_vars_section
gr.update(visible=False), # ipc_vars_section
gr.update(visible=False), # clinical_vars_section
gr.update(visible=False) # orchestrator_vars_section
)
agent_picker.change(
fn=update_dynamic_vars_visibility,
inputs=[agent_picker],
outputs=[
stewardship_vars_section, deescalation_culture, deescalation_meds, empiric_vars_section,
ipc_vars_section, clinical_vars_section, orchestrator_vars_section
]
)
# Client-side script
gr.HTML("""
""")
# --- Interactions ---
# Simple GPT-3.5 Chat callbacks (no skills, no internet)
def simple_send_handler(user_message, history):
# Convert Gradio format to role/content format for OpenAI API
role_content_history = convert_gradio_to_messages(history)
updated_history, cleared = simple_chat_response(user_message, role_content_history)
return updated_history, cleared, updated_history
simple_send.click(
simple_send_handler,
inputs=[simple_input, simple_chat_history],
outputs=[simple_chatbot, simple_input, simple_chat_history],
)
simple_input.submit(
simple_send_handler,
inputs=[simple_input, simple_chat_history],
outputs=[simple_chatbot, simple_input, simple_chat_history],
)
def simple_reset_handler():
return [], "", []
simple_reset.click(
simple_reset_handler,
inputs=[],
outputs=[simple_chatbot, simple_input, simple_chat_history],
)
start_button.click(
fn=lambda: (gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), ""),
inputs=[],
outputs=[landing_panel, agent_form, chat_panel, challenger_debug_md],
)
back_button.click(
fn=show_landing,
inputs=[],
outputs=[landing_panel, agent_form, chat_panel],
)
to_chat_button.click(
fn=lambda: (gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), ""),
outputs=[landing_panel, agent_form, chat_panel, challenger_debug_md]
).then(
refresh_active_agents_widgets,
inputs=[],
outputs=[chat_active_agents, agent_picker]
)
# Only wire up chat_back for the agent_chat panel (third page)
chat_back.click(
fn=lambda: (gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), ""),
outputs=[landing_panel, agent_form, chat_panel, challenger_debug_md]
)
# when you pick a new agent, reload its history into chat_view
agent_picker.change(
fn=load_history,
inputs=[agent_picker, deployed_chat_histories],
outputs=[chat_view]
)
# when you click Send, append & re-render
def chatpanel_handle_with_dynamic_vars(
agent_name, user_text, histories,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity
):
agent_json = agents_config.get(agent_name)
# Prevent tool invocation on empty/whitespace input (e.g., initial greeting or agent selection)
if user_text is None or not str(user_text).strip():
# Just return the current history, do not invoke any tool
current_history = histories.get(agent_name, [])
gradio_history = convert_messages_for_gradio(current_history)
return gradio_history, histories, user_text or ""
# --- IPC dynamic requirements integration ---
ipc_tool_triggered = False
ipc_jurisdiction = None
ipc_info = None
if agent_json:
agent_data = json.loads(agent_json)
skills = agent_data.get("skills", [])
history = histories.get(agent_name, [])
# --- Trusted links wiring ---
trusted_links = []
for k in ["trusted_links", "trusted_links_1", "trusted_links_2", "trusted_links_3", "trusted_links_4"]:
if isinstance(agent_data.get(k), list):
trusted_links.extend([l for l in agent_data[k] if l])
elif isinstance(agent_data.get(k), str) and agent_data[k]:
trusted_links.append(agent_data[k])
for k in ["link1", "link2", "link3", "link4"]:
if agent_data.get(k):
trusted_links.append(agent_data[k])
trusted_links = [l for l in trusted_links if l]
# --- End trusted links wiring ---
# IPC tool dynamic requirements fetch
if "ipc_reporting" in skills:
# TODO: Fix IPC requirements fetch
# fetch_ipc_requirements = tools.fetch_ipc_requirements
# Always extract the latest jurisdiction from the most recent user message
us_states = [
"Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"
]
# Try to find a state name (case-insensitive) in the latest user message
state_match = None
for state in us_states:
if re.search(rf"\\b{state}\\b", user_text, re.IGNORECASE):
state_match = state
break
match = re.search(r"\b(CDC|WHO|United States|US|World Health Organization|global)\b", user_text, re.IGNORECASE)
if state_match:
ipc_jurisdiction = state_match
elif match:
ipc_jurisdiction = match.group(1)
else:
# If not in this message, look back in history for last mentioned jurisdiction
ipc_jurisdiction = None
for msg in reversed(history):
if msg["role"] == "user":
for state in us_states:
if re.search(rf"\\b{state}\\b", msg["content"], re.IGNORECASE):
ipc_jurisdiction = state
break
if not ipc_jurisdiction:
match2 = re.search(r"\b(CDC|WHO|United States|US|World Health Organization|global)\b", msg["content"], re.IGNORECASE)
if match2:
ipc_jurisdiction = match2.group(1)
if ipc_jurisdiction:
break
if not ipc_jurisdiction:
ipc_jurisdiction = "CDC" # Default
# Fetch requirements for the extracted jurisdiction
# ipc_info = fetch_ipc_requirements(ipc_jurisdiction)
ipc_info = None # TODO: Fix IPC requirements fetch
ipc_tool_triggered = True
# Prepend IPC requirements info to user_text for the agent
if ipc_info:
req_fields_list = ipc_info.get("fields", [])
req_fields = ", ".join(req_fields_list)
summary = ipc_info.get("summary", "")
source_url = ipc_info.get("source_url")
warning = ipc_info.get("warning")
else:
req_fields_list = []
req_fields = ""
summary = ""
source_url = ""
warning = ""
if ipc_info: # Only process if we have valid info
# --- Prevent repeated tool invocation logic ---
# Gather current field values from user_text/history (simple: just use user_text for now)
current_submission = {
"jurisdiction": ipc_jurisdiction,
"fields": req_fields_list,
"summary": summary,
"user_text": user_text.strip()
}
# Find last IPC_SUBMISSION in history
last_ipc_submission = None
for msg in reversed(history):
if msg["role"] == "system" and msg["content"].startswith("[IPC_SUBMISSION]"):
try:
last_ipc_submission = json.loads(msg["content"][len("[IPC_SUBMISSION]"):].strip())
except Exception:
last_ipc_submission = None
break
# Only proceed if something changed
if last_ipc_submission == current_submission:
# Requirements and user input unchanged, do not re-invoke
return history, histories, ""
# --- Dynamic required fields logic ---
# Only prompt for missing fields if the live/static requirements specify them
missing_fields = []
if req_fields_list:
# Try to extract field values from user_text (very basic: look for each field name in user_text, case-insensitive)
for field in req_fields_list:
if not re.search(rf"\b{re.escape(field)}\b", user_text, re.IGNORECASE):
missing_fields.append(field)
if missing_fields:
prompt = f"Please provide the following required information for IPC reporting: {', '.join(missing_fields)}."
history.append({"role": "assistant", "content": prompt})
return history, histories, ""
# If no required fields or all are present, proceed
user_text = f"[IPC_REQUIREMENTS] Jurisdiction: {ipc_jurisdiction}\nRequired fields: {req_fields}\nSummary: {summary}\n" + user_text
# Always add a visible assistant message to the chat so the user sees which requirements are being used
visible_msg = f"📋 Reporting requirements for {ipc_jurisdiction}:
"
if req_fields:
visible_msg += f"Required fields: {req_fields}. "
if summary:
visible_msg += f"Summary: {summary} "
if source_url:
visible_msg += f'
Source: {source_url}'
if warning:
visible_msg += f'
Warning: {warning}'
try:
with open("debug_log.txt", "a", encoding="utf-8") as f:
f.write(f"[DEBUG] IPC visible_msg: {visible_msg}\n")
except Exception as e:
pass
# Add the requirements message if it's new or the jurisdiction changed
if not (history and history[-1]["role"] == "assistant" and ipc_jurisdiction in history[-1]["content"]):
history.append({"role": "assistant", "content": visible_msg})
# Store this submission in history as a hidden system message
history.append({"role": "system", "content": "[IPC_SUBMISSION] " + json.dumps(current_submission)})
# Deescalation tool
if "recommend_deescalation" in skills:
var_names = ["culture", "meds", "site_of_infection", "risk_of_biofilm", "current_response", "creatinine_clearance", "severity_of_infection", "known_allergies"]
user_vars = {
"culture": deescalation_culture,
"meds": deescalation_meds,
"site_of_infection": stewardship_site,
"risk_of_biofilm": stewardship_biofilm,
"current_response": stewardship_response,
"creatinine_clearance": stewardship_crcl,
"severity_of_infection": stewardship_severity,
"known_allergies": stewardship_allergies
}
extracted = extract_clinical_variables_from_history(history, var_names)
for k in var_names:
if not user_vars[k]:
user_vars[k] = extracted.get(k) or ""
# Only prepend if at least one field is non-empty
if any(user_vars[k] for k in var_names):
user_text = f"[DEESCALATION_TOOL_INPUT] {json.dumps(user_vars)}\n" + user_text
# Alert prolonged antibiotic use tool
elif "alert_prolonged_antibiotic_use" in skills:
var_names = ["site_of_infection", "risk_of_biofilm", "current_response", "creatinine_clearance", "severity_of_infection", "known_allergies"]
user_vars = {
"site_of_infection": stewardship_site,
"risk_of_biofilm": stewardship_biofilm,
"current_response": stewardship_response,
"creatinine_clearance": stewardship_crcl,
"severity_of_infection": stewardship_severity,
"known_allergies": stewardship_allergies
}
extracted = extract_clinical_variables_from_history(history, var_names)
for k in var_names:
if not user_vars[k]:
user_vars[k] = extracted.get(k) or ""
if any(user_vars[k] for k in var_names):
user_text = f"[ALERT_PROLONGED_ABX_INPUT] {json.dumps(user_vars)}\n" + user_text
# Empiric therapy tool
elif "recommend_empiric_therapy" in skills:
var_names = [
"age", "allergies", "labs", "culture", "meds", "site_of_infection",
"risk_of_biofilm", "current_response", "creatinine_clearance", "severity_of_infection"
]
user_vars = {
"age": empiric_age,
"allergies": empiric_allergies,
"labs": empiric_labs,
"culture": empiric_culture,
"meds": empiric_meds,
"site_of_infection": empiric_site,
"risk_of_biofilm": empiric_biofilm,
"current_response": empiric_response,
"creatinine_clearance": empiric_crcl,
"severity_of_infection": empiric_severity
}
extracted = extract_clinical_variables_from_history(history, var_names)
for k in var_names:
if not user_vars[k]:
user_vars[k] = extracted.get(k) or ""
# If any required field is missing, prompt = ...
missing = [k.replace('_', ' ').capitalize() for k in var_names if not user_vars[k].strip()]
if missing:
prompt = f"Please provide the following required information for empiric therapy: {', '.join(missing)}."
# Show this as an assistant message and do not call the tool
history.append({"role": "assistant", "content": prompt})
return history, histories, ""
# All required fields present, prepend tool input
if any(user_vars[k] for k in var_names):
user_text = f"[EMPIRIC_THERAPY_INPUT] {json.dumps(user_vars)}\n" + user_text
# Use simulate_agent_response_stream for all agents to ensure challenger logic is applied
import asyncio
from core.agents.chat_orchestrator import simulate_agent_response_stream
agent_json_val = agents_config.get(agent_name)
history_val = histories.get(agent_name, [])
result = None
# Prevent repeated tool invocation: if the last assistant message is a tool request for the same required fields, do not re-invoke
if history_val and history_val[-1]["role"] == "assistant":
last_content = history_val[-1]["content"]
if "required fields" in last_content.lower() and "ipc_reporting" in last_content.lower():
# Don't re-invoke, just return
return history_val, histories, ""
async def run_stream():
final_history = history_val
final_invocation_log = ""
challenger_info = None
gen = simulate_agent_response_stream(
agent_json=agent_json_val,
history=history_val,
user_input=user_text,
debug_flag=False,
active_children=[]
)
async for updated_history, _, invocation_log, _, challenger_info in gen:
final_history = updated_history
final_invocation_log = invocation_log
return final_history, final_invocation_log, challenger_info
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
result = loop.run_until_complete(run_stream())
if result is not None and len(result) == 3:
final_history, invocation_log, challenger_info = result
else:
final_history, updated_histories, cleared_input, invocation_log = chatpanel_handle(agent_name, user_text, histories)
challenger_info = None
gradio_history = convert_messages_for_gradio(final_history)
return gradio_history, updated_histories, cleared_input, invocation_log
except Exception:
# fallback to old behavior if error
final_history, updated_histories, cleared_input, invocation_log = chatpanel_handle(agent_name, user_text, histories)
challenger_info = None
gradio_history = convert_messages_for_gradio(final_history)
return gradio_history, updated_histories, cleared_input, invocation_log
# Update histories
updated_histories = histories.copy()
updated_histories[agent_name] = final_history
# Prepare challenger markdown (debug log for builder panel)
challenger_md = ""
challenger_enabled = False
try:
if agent_json_val:
agent_data = json.loads(agent_json_val)
challenger_enabled = agent_data.get("challenger_enabled", False)
except Exception:
pass
if isinstance(challenger_info, dict) and challenger_enabled:
orig = challenger_info.get("original_reply", "")
crit = challenger_info.get("challenger_critique", "")
final = challenger_info.get("final_reply", "")
challenger_md = f"**Original Agent Answer:**\n\n{orig}\n\n**Challenger Critique:**\n\n{crit}\n\n**Final Answer Shown to User:**\n\n{final}"
# Only show the final (challenger-approved) answer in the chatbox
if final and final_history and isinstance(final_history, list):
final_history[-1]["content"] = final
# If challenger is not enabled, do not show the markdown at all
elif not challenger_enabled:
challenger_md = ""
# Convert to Gradio format before returning
gradio_history = convert_messages_for_gradio(final_history)
return gradio_history, updated_histories, "", invocation_log, challenger_md
chat_send.click(
fn=chatpanel_handle_with_dynamic_vars,
inputs=[
agent_picker, chat_panel_input, deployed_chat_histories,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity
],
outputs=[chat_view, deployed_chat_histories, chat_panel_input]
)
# --- Reset button for deployed agent chat panel ---
def reset_and_clear_deployed_history(agent_name, histories):
# Clear orchestrator state to prevent persistence across conversations
from core.agents.chat_orchestrator import orchestrators
orchestrators.clear()
if not agent_name:
return (
[], histories, "",
"", "", "", "", "", "", "", "", # stewardship fields cleared (8 fields)
"", "", "", "", "", "", "", "", "", "", # empiric fields cleared (10 fields)
"", "", "", "", "", "", "", "", "", # ipc fields cleared (9 fields)
"", "", "", "", "", "", "", "", "", "", # clinical fields cleared (10 fields)
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" # orchestrator fields cleared (27 fields)
)
# Remove the agent's chat history and re-seed with greeting
if agent_name in histories:
del histories[agent_name]
chat_history = [{"role": "assistant", "content": f"👋 Hello! I'm {agent_name}. How can I help?"}]
histories[agent_name] = chat_history
# Convert to Gradio format
gradio_history = convert_messages_for_gradio(chat_history)
return (
gradio_history, histories, "",
"", "", "", "", "", "", "", "", # stewardship fields cleared (8 fields)
"", "", "", "", "", "", "", "", "", "", # empiric fields cleared (10 fields)
"", "", "", "", "", "", "", "", "", # ipc fields cleared (9 fields)
"", "", "", "", "", "", "", "", "", "", # clinical fields cleared (10 fields)
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" # orchestrator fields cleared (27 fields)
)
chat_reset.click(
reset_and_clear_deployed_history,
inputs=[agent_picker, deployed_chat_histories],
outputs=[
chat_view, deployed_chat_histories, chat_panel_input,
deescalation_culture, deescalation_meds,
stewardship_site, stewardship_biofilm, stewardship_response, stewardship_crcl, stewardship_severity, stewardship_allergies,
empiric_age, empiric_allergies, empiric_labs, empiric_culture, empiric_meds, empiric_site, empiric_biofilm, empiric_response, empiric_crcl, empiric_severity,
ipc_facility_name, ipc_location, ipc_infection_type, ipc_onset_date, ipc_device_days, ipc_pathogen, ipc_resistance_pattern, ipc_isolation_status, ipc_compliance_issues,
clinical_chief_complaint, clinical_history_present, clinical_past_medical, clinical_medications, clinical_allergies, clinical_social_history, clinical_vital_signs, clinical_physical_exam, clinical_lab_results, clinical_imaging,
orchestrator_culture, orchestrator_meds, orchestrator_site, orchestrator_biofilm, orchestrator_response, orchestrator_crcl, orchestrator_severity, orchestrator_allergies, orchestrator_facility_name, orchestrator_location, orchestrator_infection_type, orchestrator_onset_date, orchestrator_device_days, orchestrator_pathogen, orchestrator_resistance_pattern, orchestrator_isolation_status, orchestrator_compliance_issues, orchestrator_chief_complaint, orchestrator_history_present, orchestrator_past_medical, orchestrator_medications, orchestrator_patient_allergies, orchestrator_social_history, orchestrator_vital_signs, orchestrator_physical_exam, orchestrator_lab_results, orchestrator_imaging
]
)
agent_type.change(
fn=on_agent_type_change,
inputs=[agent_type, prefill_flag],
outputs=[skills, agent_name, agent_mission, prefill_flag]
)
prefilled.change(
fn=populate_from_preset,
inputs=[prefilled],
outputs=[agent_type, agent_name, agent_mission, skills, prefill_flag]
)
uploaded_files.upload(fn=handle_uploaded_files, inputs=[uploaded_files], outputs=[upload_alert, upload_alert])
def handle_generate(agent_type, agent_name, agent_mission, selected_skills, web_access, allow_fallback, uploaded_files, link1, link2, link3, link4, challenger_toggle):
# Accept challenger_toggle as an argument
agent_json = build_agent(agent_type, agent_name, agent_mission, selected_skills, web_access, allow_fallback, uploaded_files, link1, link2, link3, link4)
# Add challenger_enabled to the agent config JSON
agent_data = json.loads(agent_json)
agent_data["challenger_enabled"] = challenger_toggle
agent_json = json.dumps(agent_data)
agents_config[agent_name] = agent_json
return agent_json
generate_button.click(
lambda: gr.update(visible=True, value="⏳ Generating your agent..."),
inputs=[], outputs=[agent_loader]
).then( # 1) add / save the agent
handle_generate,
inputs=[agent_type, agent_name, agent_mission, skills,
web_access_toggle, allow_fallback_toggle,
uploaded_files, link1, link2, link3, link4, challenger_toggle],
outputs=[agent_output] # <- only the JSON
).then( # 2) show initial instruction instead of preload demo chat
lambda: show_initial_instruction_state(),
inputs=[], outputs=[builder_chatbot, chat_input, builder_send_button, reset_button, invocation_log, active_children]
).then( # 2.5) auto-load agent fields into builder panel
lambda agent_json: (
json.loads(agent_json).get("agent_type", ""),
json.loads(agent_json).get("agent_name", ""),
json.loads(agent_json).get("agent_mission", ""),
json.loads(agent_json).get("skills", [])
),
inputs=[agent_output],
outputs=[agent_type, agent_name, agent_mission, skills]
).then( # 3) refresh markdown & dropdown *atomically*
refresh_active_agents_widgets,
inputs=[], outputs=[builder_active_agents, agent_remove_dropdown]
).then( # 4) done spinner
lambda: gr.update(visible=True, value="✅ Agent generated successfully!"),
inputs=[], outputs=[agent_loader]
).then(
refresh_chat_dropdown,
inputs=[],
outputs=[agent_picker]
)
edit_agent_button.click(
load_agent_to_builder,
inputs=[agent_remove_dropdown],
outputs=[agent_type, agent_name, agent_mission, skills]
)
chat_agent_button.click(
fn=enable_chat_controls_with_agent,
inputs=[agent_remove_dropdown],
outputs=[builder_chatbot, chat_input, builder_send_button, reset_button, invocation_log, active_children, agent_output]
)
remove_agent_button.click(
remove_selected_agent,
inputs=[agent_remove_dropdown],
outputs=[builder_active_agents, agent_remove_dropdown]
).then(
refresh_chat_dropdown,
inputs=[],
outputs=[agent_picker]
)
download_button.click(
prepare_download,
inputs=[agent_output],
outputs=[download_button]
)
# Only keep reset for builder panel, and chat_send for chat panel
def reset_and_clear_builder_history(agent_json, histories):
# Clear orchestrator state to prevent persistence across conversations
from core.agents.chat_orchestrator import orchestrators
orchestrators.clear()
if not agent_json or agent_json.strip() == "":
# No agent selected, return disabled state with instruction
instruction_state = show_initial_instruction_state()
return instruction_state[0], instruction_state[1], instruction_state[5], histories # chatbot, input, active_children, histories
# Valid agent selected, show agent greeting with enabled controls
agent_data = json.loads(agent_json)
name = agent_data.get("agent_name", "Agent")
welcome = f"👋 Hello! I'm {name}. How can I assist you today?"
chat_history = [{"role":"assistant","content":welcome}]
if name in histories:
del histories[name]
# Convert to Gradio format
gradio_history = convert_messages_for_gradio(chat_history)
return gradio_history, gr.update(value="", interactive=True, placeholder="Type your question here…"), [], histories
reset_button.click(
reset_and_clear_builder_history,
inputs=[agent_output, builder_chat_histories],
outputs=[builder_chatbot, chat_input, active_children, builder_chat_histories]
)
return app
def build_authenticated_app():
"""Build the main application with authentication wrapper"""
# Create the authentication interface
auth_interface, session_state = create_auth_interface()
# Create the main ID Agents app
main_app = build_ui()
# Combine authentication with main app
with gr.Blocks(title="ID Agents - Authenticated", css=main_app.css) as app:
# Add authentication state management
current_session = gr.State("")
user_info = gr.State({})
# Add the authentication interface
with gr.Group() as auth_group:
auth_interface.render()
# Add the main app (initially hidden)
with gr.Group(visible=False) as main_group:
main_app.render()
# Authentication success handler
def on_auth_success(session_id: str):
"""Handle successful authentication"""
session_info = auth_manager.get_session_info(session_id)
if session_info:
user_data = session_info["user_info"]
capabilities = session_info["capabilities"]
# Show main app, hide auth
return (
gr.update(visible=False), # Hide auth group
gr.update(visible=True), # Show main group
session_id, # Update session state
user_data # Update user info state
)
return gr.update(), gr.update(), "", {}
# Authentication failure/logout handler
def on_auth_logout():
"""Handle logout or authentication failure"""
return (
gr.update(visible=True), # Show auth group
gr.update(visible=False), # Hide main group
"", # Clear session state
{} # Clear user info state
)
return app
def build_ui_with_auth_check(session_id: str = ""):
"""Build UI with authentication check and capability restrictions"""
# Validate session
session_info = auth_manager.get_session_info(session_id) if session_id else None
if not session_info:
# Return basic app with limited functionality
return build_ui()
# Get user capabilities
capabilities = session_info["capabilities"]
user_info = session_info["user_info"]
# Build app with capability restrictions
app = build_ui()
# Add authentication status to the app
def add_auth_status():
auth_status = f"""
**🔐 Authenticated as:** {user_info['full_name']} ({user_info['role']})
**Access Level:** {user_info['access_level']} | **Session:** Active
"""
return auth_status
# You could add capability-based UI modifications here
# For now, we'll handle restrictions in the function calls
return app
if __name__ == "__main__":
try:
# Always enable authentication for this version
print("🚀 Launching ID Agents with Authentication...")
# Create main app
main_app = build_ui()
# Authentication credentials
auth_credentials = [
("dr_smith", "idweek2025"),
("id_fellow", "hello"),
("pharmacist", "stewardship"),
("ipc_nurse", "infection"),
("researcher", "research"),
("educator", "education"),
("student", "learning"),
("admin", "idagents2025"),
("guest1", "guest123"),
("guest2", "guest456")
]
auth_message = """
🦠 **ID Agents Beta Testing Access**
Welcome to the ID Agents beta testing environment!
**Test Accounts:**
• **dr_smith** / idweek2025 (ID Physician)
• **id_fellow** / hello (ID Fellow)
• **pharmacist** / stewardship (Clinical Pharmacist)
• **ipc_nurse** / infection (IPC Coordinator)
• **researcher** / research (Clinical Researcher)
• **educator** / education (Medical Educator)
• **student** / learning (Medical Student)
• **admin** / idagents2025 (Administrator)
• **guest1** / guest123 (Guest Access)
• **guest2** / guest456 (Guest Access)
Please use your assigned credentials to access the application.
"""
# Check if running on Hugging Face Spaces
try:
from hf_config import configure_hf_environment, get_hf_launch_config
if configure_hf_environment():
# Use HF Spaces configuration with authentication
launch_config = get_hf_launch_config()
print("� Authentication enabled for HF Spaces deployment")
else:
# Local development with authentication for testing
launch_config = {
"share": False,
"server_name": "127.0.0.1",
"server_port": 7860
}
print("🔐 Authentication enabled for local testing")
except ImportError:
# Fallback configuration with authentication
launch_config = {
"share": False,
"server_name": "127.0.0.1",
"server_port": 7860
}
print("� Authentication enabled with fallback configuration")
# Always add authentication
launch_config["auth"] = auth_credentials
launch_config["auth_message"] = auth_message
print("📋 Available test accounts:")
for username, password in auth_credentials:
print(f" • {username} / {password}")
main_app.launch(**launch_config)
except Exception as e:
print(f"Failed to launch Gradio app: {e}")
print("💡 Check your API keys and environment configuration")