evd / app.py
Benedette Otieno
Restore working dependencies and Space configuration
604c2c3
Raw
History Blame Contribute Delete
5.85 kB
from __future__ import annotations
import os
import socket
from pathlib import Path
from typing import Any
import gradio as gr
try:
from dotenv import load_dotenv
except ImportError:
load_dotenv = None
from evd_agent.conversation import ConversationManager
from evd_agent.explainability import (
render_alert_banner,
render_classification_panel,
)
def _load_environment_file() -> None:
if load_dotenv is not None:
load_dotenv()
return
env_path = Path(".env")
if not env_path.exists():
return
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip().strip('"').strip("'")
if key and key not in os.environ:
os.environ[key] = value
_load_environment_file()
def _require_llm_configuration() -> None:
if not (
os.getenv("EVD_LLM_API_KEY")
or os.getenv("OPENAI_API_KEY")
):
raise RuntimeError(
"LLM credentials are required. Set EVD_LLM_API_KEY or OPENAI_API_KEY before starting the app."
)
manager = ConversationManager(context_path=os.getenv("EVD_CONTEXT_PATH"))
def _resolve_server_port() -> int:
preferred_port = int(os.getenv("GRADIO_SERVER_PORT", os.getenv("PORT", "7860")))
for port in range(preferred_port, preferred_port + 100):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind(("0.0.0.0", port))
except OSError:
continue
return port
raise RuntimeError(f"Could not find a free port starting at {preferred_port}.")
def _history_to_chatbot(state) -> list[dict[str, Any]]:
messages: list[dict[str, Any]] = []
for turn in state.history:
messages.append({"role": turn.role, "content": turn.content})
return messages
def _context_panel() -> str:
return manager.context_engine.context_summary()
def initialize_session():
state, _ = manager.start_session()
return (
state,
_history_to_chatbot(state),
"",
render_classification_panel(state.decision),
_context_panel(),
)
def submit_message(user_message: str, state):
if state is None:
state = manager.new_state()
cleaned = (user_message or "").strip()
if not cleaned:
return (
state,
_history_to_chatbot(state),
"",
render_classification_panel(state.decision),
_context_panel(),
"",
)
result = manager.process_turn(state, cleaned)
alert = render_alert_banner(result.decision)
classification = render_classification_panel(result.decision)
return (
state,
_history_to_chatbot(state),
alert,
classification,
_context_panel(),
"",
)
def build_app() -> gr.Blocks:
with gr.Blocks(title="EVD Clinical Screening Agent") as demo:
gr.Markdown("# EVD Clinical Screening AI Agent")
gr.Markdown(
"Adaptive clinical reasoning assistant for rapid Ebola suspected/probable case screening. "
"This tool is decision support and does not replace national case management protocols."
)
interview_state = gr.State()
with gr.Row():
with gr.Column(scale=2):
chatbot = gr.Chatbot(label="Clinical Interview", height=500)
message_box = gr.Textbox(
label="Clinician Input",
placeholder="Enter findings, symptoms, exposures, travel, and context."
)
with gr.Row():
submit_btn = gr.Button("Send", variant="primary")
reset_btn = gr.Button("Reset Session")
with gr.Column(scale=1):
alert_banner = gr.Markdown(label="Alert")
classification_panel = gr.Markdown(label="Classification")
context_panel = gr.Markdown(label="Epidemiological Context")
demo.load(
initialize_session,
inputs=[],
outputs=[
interview_state,
chatbot,
alert_banner,
classification_panel,
context_panel,
],
)
submit_btn.click(
submit_message,
inputs=[message_box, interview_state],
outputs=[
interview_state,
chatbot,
alert_banner,
classification_panel,
context_panel,
message_box,
],
)
message_box.submit(
submit_message,
inputs=[message_box, interview_state],
outputs=[
interview_state,
chatbot,
alert_banner,
classification_panel,
context_panel,
message_box,
],
)
reset_btn.click(
initialize_session,
inputs=[],
outputs=[
interview_state,
chatbot,
alert_banner,
classification_panel,
context_panel,
],
)
return demo
if __name__ == "__main__":
_require_llm_configuration()
app = build_app()
running_in_space = bool(os.getenv("SPACE_ID") or os.getenv("HF_SPACE_ID"))
app.launch(
server_name="0.0.0.0",
server_port=_resolve_server_port(),
share=running_in_space,
theme=gr.themes.Soft(),
)