"""
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 openai
from openai import RateLimitError, APIError, APIConnectionError, OpenAI
from typing import Dict, cast
from core.utils.rag import KnowledgeLoader, SimpleRAGRetriever
from core.utils.skills_registry import tool_registry, get_tool_by_name
import asyncio
import structlog
import logging
from structlog.stdlib import LoggerFactory, BoundLogger
from core.utils.llm_connector import AgentLLMConnector
import pandas as pd
from gradio import ChatMessage
from core.agents.agent_utils import linkify_citations, build_agent, load_prefilled, prepare_download, preload_demo_chat, _safe_title, extract_clinical_variables_from_history
from config import agents_config, skills_library, prefilled_agents
from core.ui.ui import build_ui, show_landing, show_builder, show_chat, refresh_active_agents_widgets
import tools
import os
# Print API keys for debugging
# Print all SERPER_* environment variables for robust debugging
#for k, v in os.environ.items():
# if k.startswith("SERPER"):
# print(f"{k} in Python: {v}")
#print("OPENAI_API_KEY in Python:", os.getenv("OPENAI_API_KEY"))
#print("SERPER_API_KEY in Python:", os.getenv("SERPER_API_KEY"))
# Logging setup
logging.basicConfig(filename="app.log", level=logging.INFO, format="%(message)s")
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,
)
# 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)
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) return the chat history (for gr.Chatbot) and clear the input box
return 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}]
invocation_log = ""
active_children= []
return chat_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?"}
]
return histories[agent_name]
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 = [{"role": "assistant", "content": "π **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 = [{"role": "assistant", "content": 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 [{"role": "assistant", "content": 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", type="messages")
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",
type="messages",
value=[{"role": "assistant", "content": "π **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})
return 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
return final_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
return final_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}"
return final_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", type="messages")
# --- 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
return (
[{"role": "assistant", "content": context_msg}],
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
return (
[{"role": "assistant", "content": context_msg}],
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
return (
[{"role": "assistant", "content": context_msg}],
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
return (
[{"role": "assistant", "content": context_msg}],
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
return (
[{"role": "assistant", "content": context_msg}],
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
return (
[{"role": "assistant", "content": context_msg}],
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):
updated_history, cleared = simple_chat_response(user_message, 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
return histories.get(agent_name, []), 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
return final_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
return final_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 = ""
return final_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
return (
chat_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]
return chat_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
if __name__ == "__main__":
try:
# 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
launch_config = get_hf_launch_config()
print("π Launching on Hugging Face Spaces...")
build_ui().launch(**launch_config)
else:
# Local development
print("π§ Launching locally...")
build_ui().launch()
except ImportError:
# Fallback if hf_config not available
print("π§ Launching with default configuration...")
build_ui().launch()
except Exception as e:
logger.error(f"Failed to launch Gradio app: {e}")
print(f"Failed to launch Gradio app: {e}")
print("π‘ Check your API keys and environment configuration")