Spaces:
Sleeping
Sleeping
Rajan Sharma
commited on
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
# app.py
|
|
|
|
| 2 |
from __future__ import annotations
|
| 3 |
import os
|
| 4 |
import traceback
|
|
@@ -8,6 +9,11 @@ from typing import List, Tuple, Dict, Any
|
|
| 8 |
import gradio as gr
|
| 9 |
import pandas as pd
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# ---- Local modules
|
| 12 |
from settings import (
|
| 13 |
HEALTHCARE_SETTINGS, GENERAL_CONVERSATION_PROMPT, USE_SCENARIO_ENGINE, DEBUG_PLAN,
|
|
@@ -75,11 +81,12 @@ def ping_cohere() -> str:
|
|
| 75 |
def handle(user_msg: str, history_messages: List[Dict[str, str]], files: list) -> Tuple[List[Dict[str, str]], str]:
|
| 76 |
"""
|
| 77 |
One entrypoint for both healthcare scenarios and general conversation.
|
| 78 |
-
-
|
| 79 |
-
-
|
|
|
|
| 80 |
"""
|
| 81 |
try:
|
| 82 |
-
# Safety
|
| 83 |
safe_in, blocked_in, reason_in = safety_filter(user_msg, mode="input")
|
| 84 |
if blocked_in:
|
| 85 |
reply = refusal_reply(reason_in)
|
|
@@ -87,70 +94,74 @@ def handle(user_msg: str, history_messages: List[Dict[str, str]], files: list) -
|
|
| 87 |
new_hist = _append_msg(new_hist, "assistant", reply)
|
| 88 |
return new_hist, ""
|
| 89 |
|
| 90 |
-
# Normalize files into paths (Gradio can return temp file objects or paths)
|
| 91 |
file_paths: List[str] = [getattr(f, "name", None) or f for f in (files or [])]
|
| 92 |
|
| 93 |
-
#
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
try:
|
| 97 |
-
|
| 98 |
-
|
| 99 |
except Exception as e:
|
| 100 |
-
log_event("
|
| 101 |
-
|
| 102 |
-
# Lightweight RAG: ingest any text/markdown for grounding (embeddings via Cohere)
|
| 103 |
-
rag = RAGIndex()
|
| 104 |
-
try:
|
| 105 |
-
ing = extract_text_from_files(file_paths)
|
| 106 |
-
rag.add(ing.get("chunks", []))
|
| 107 |
-
except Exception as e:
|
| 108 |
-
log_event("rag_ingest_error", None, {"err": str(e)})
|
| 109 |
-
|
| 110 |
-
# Decide mode
|
| 111 |
-
if is_healthcare_scenario(safe_in, bool(file_paths)) and USE_SCENARIO_ENGINE:
|
| 112 |
-
# 1) Deterministic dataset exposure
|
| 113 |
analyzer = HealthcareAnalyzer(registry)
|
| 114 |
-
datasets = analyzer.comprehensive_analysis(safe_in)
|
| 115 |
catalog = _dataset_catalog(datasets)
|
| 116 |
-
|
| 117 |
-
# 2) Plan (Cohere-first; auto safety-net if LLM parse fails)
|
| 118 |
plan = parse_to_plan(safe_in, catalog)
|
| 119 |
-
|
| 120 |
-
# 3) Execute plan deterministically
|
| 121 |
structured_md = ScenarioEngine.execute_plan(plan, datasets)
|
| 122 |
-
|
| 123 |
-
# 4) Narrative (Cohere-first), grounded with RAG hits
|
| 124 |
rag_hits = [txt for txt, _ in rag.retrieve(safe_in, k=6)]
|
| 125 |
narrative = generate_narrative(safe_in, structured_md, rag_hits)
|
| 126 |
|
| 127 |
-
# 5) Safety-net narrative if LLM narrative absent/failed
|
| 128 |
if not narrative or "Unable to generate narrative" in narrative:
|
| 129 |
-
# Provide generic hints only (dynamic, not hard-coded to any schema)
|
| 130 |
narrative = build_narrative(
|
| 131 |
-
scenario_text=safe_in,
|
| 132 |
-
datasets=datasets,
|
| 133 |
-
structured_tables=None,
|
| 134 |
metric_hints=["surgery_median", "consult_median", "wait", "median", "p90", "90th"],
|
| 135 |
group_hints=["facility", "specialty", "zone", "hospital", "city", "region"],
|
| 136 |
min_sample=5
|
| 137 |
)
|
| 138 |
|
| 139 |
-
debug_note = ""
|
| 140 |
-
|
| 141 |
-
debug_note = f"\n\n> **Planner note:** {getattr(plan, 'notes', '')}"
|
| 142 |
-
|
| 143 |
-
reply = _sanitize_text(
|
| 144 |
-
f"{structured_md}\n\n# Narrative & Recommendations\n\n{narrative}{debug_note}"
|
| 145 |
-
)
|
| 146 |
|
| 147 |
else:
|
| 148 |
-
# General conversation mode (
|
| 149 |
prompt = f"{GENERAL_CONVERSATION_PROMPT}\n\nUser: {safe_in}\nAssistant:"
|
| 150 |
reply = cohere_chat(prompt) or open_fallback_chat(prompt) or "How can I help further?"
|
| 151 |
reply = _sanitize_text(reply)
|
| 152 |
|
| 153 |
-
# Append to chat history
|
| 154 |
new_hist = _append_msg(history_messages, "user", user_msg)
|
| 155 |
new_hist = _append_msg(new_hist, "assistant", reply)
|
| 156 |
return new_hist, ""
|
|
@@ -159,7 +170,7 @@ def handle(user_msg: str, history_messages: List[Dict[str, str]], files: list) -
|
|
| 159 |
tb = traceback.format_exc()
|
| 160 |
log_event("app_error", None, {"err": str(e), "tb": tb})
|
| 161 |
new_hist = _append_msg(history_messages, "user", user_msg)
|
| 162 |
-
new_hist = _append_msg(new_hist, "assistant", f"
|
| 163 |
return new_hist, ""
|
| 164 |
|
| 165 |
|
|
@@ -168,15 +179,15 @@ with gr.Blocks(analytics_enabled=False) as demo:
|
|
| 168 |
gr.Markdown("## Canadian Healthcare AI • Cohere API • Scenario-Agnostic • Deterministic Analytics")
|
| 169 |
|
| 170 |
with gr.Row():
|
| 171 |
-
|
| 172 |
-
chat = gr.Chatbot(type="messages", height=520)
|
| 173 |
files = gr.Files(
|
|
|
|
| 174 |
file_count="multiple",
|
| 175 |
type="filepath",
|
| 176 |
file_types=HEALTHCARE_SETTINGS["supported_file_types"]
|
| 177 |
)
|
| 178 |
|
| 179 |
-
msg = gr.Textbox(placeholder="Paste any scenario (Background / Situation / Tasks / Deliverables) or just chat.")
|
| 180 |
with gr.Row():
|
| 181 |
send = gr.Button("Send")
|
| 182 |
clear = gr.Button("Clear")
|
|
@@ -189,14 +200,18 @@ with gr.Blocks(analytics_enabled=False) as demo:
|
|
| 189 |
|
| 190 |
send.click(_on_send, inputs=[msg, chat, files], outputs=[chat, msg])
|
| 191 |
msg.submit(_on_send, inputs=[msg, chat, files], outputs=[chat, msg])
|
| 192 |
-
clear.click(lambda: ([], ""), outputs=[chat, msg])
|
| 193 |
ping_btn.click(lambda: ping_cohere(), outputs=[ping_out])
|
| 194 |
|
| 195 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
log_event("startup", None, {
|
| 197 |
"cohere_key_present": bool(os.getenv("COHERE_API_KEY")),
|
| 198 |
"cohere_model": COHERE_MODEL_PRIMARY,
|
| 199 |
"open_fallbacks": USE_OPEN_FALLBACKS,
|
| 200 |
"timeout_s": COHERE_TIMEOUT_S
|
| 201 |
})
|
| 202 |
-
demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))
|
|
|
|
| 1 |
# app.py
|
| 2 |
+
# app.py
|
| 3 |
from __future__ import annotations
|
| 4 |
import os
|
| 5 |
import traceback
|
|
|
|
| 9 |
import gradio as gr
|
| 10 |
import pandas as pd
|
| 11 |
|
| 12 |
+
# New additions for data analysis agent
|
| 13 |
+
from langchain.agents.agent_types import AgentType
|
| 14 |
+
from langchain_community.chat_models import ChatCohere
|
| 15 |
+
from langchain_experimental.agents.agent_toolkits import create_pandas_dataframe_agent
|
| 16 |
+
|
| 17 |
# ---- Local modules
|
| 18 |
from settings import (
|
| 19 |
HEALTHCARE_SETTINGS, GENERAL_CONVERSATION_PROMPT, USE_SCENARIO_ENGINE, DEBUG_PLAN,
|
|
|
|
| 81 |
def handle(user_msg: str, history_messages: List[Dict[str, str]], files: list) -> Tuple[List[Dict[str, str]], str]:
|
| 82 |
"""
|
| 83 |
One entrypoint for both healthcare scenarios and general conversation.
|
| 84 |
+
- NEW: If files are uploaded, a data-aware agent is used to perform analysis.
|
| 85 |
+
- Scenario mode (no files): planner -> deterministic executor -> LLM narrative (Cohere).
|
| 86 |
+
- General mode: direct to Cohere with a light system prompt.
|
| 87 |
"""
|
| 88 |
try:
|
| 89 |
+
# Safety filter for user input
|
| 90 |
safe_in, blocked_in, reason_in = safety_filter(user_msg, mode="input")
|
| 91 |
if blocked_in:
|
| 92 |
reply = refusal_reply(reason_in)
|
|
|
|
| 94 |
new_hist = _append_msg(new_hist, "assistant", reply)
|
| 95 |
return new_hist, ""
|
| 96 |
|
|
|
|
| 97 |
file_paths: List[str] = [getattr(f, "name", None) or f for f in (files or [])]
|
| 98 |
|
| 99 |
+
# --- NEW LOGIC: Activate data agent if files are uploaded ---
|
| 100 |
+
if file_paths:
|
| 101 |
+
try:
|
| 102 |
+
# For this example, we'll load the first CSV file.
|
| 103 |
+
# This can be extended to handle multiple DataFrames.
|
| 104 |
+
df = pd.read_csv(file_paths[0])
|
| 105 |
+
|
| 106 |
+
# Initialize the Cohere Chat LLM for the agent
|
| 107 |
+
llm = ChatCohere(model=COHERE_MODEL_PRIMARY, temperature=0)
|
| 108 |
+
|
| 109 |
+
# Create the pandas DataFrame agent, powered by Cohere
|
| 110 |
+
agent = create_pandas_dataframe_agent(
|
| 111 |
+
llm,
|
| 112 |
+
df,
|
| 113 |
+
agent_type=AgentType.OPENAI_FUNCTIONS, # Recommended for reliability
|
| 114 |
+
verbose=True # Set to False in production
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
# Run the agent with the user's scenario text. The agent will
|
| 118 |
+
# write and execute code to answer the query based on the dataframe.
|
| 119 |
+
reply = agent.run(safe_in)
|
| 120 |
+
reply = _sanitize_text(reply)
|
| 121 |
+
|
| 122 |
+
except Exception as e:
|
| 123 |
+
tb = traceback.format_exc()
|
| 124 |
+
log_event("agent_error", None, {"err": str(e), "tb": tb})
|
| 125 |
+
reply = f"An error occurred while analyzing the data: {e}"
|
| 126 |
+
|
| 127 |
+
# --- ORIGINAL LOGIC: Fallback for scenarios without files or general chat ---
|
| 128 |
+
elif is_healthcare_scenario(safe_in, bool(file_paths)) and USE_SCENARIO_ENGINE:
|
| 129 |
+
# This block now primarily handles scenarios where no data files are provided,
|
| 130 |
+
# relying on the original deterministic analysis logic.
|
| 131 |
+
registry = DataRegistry() # This part might be simplified if files always trigger the agent
|
| 132 |
+
rag = RAGIndex()
|
| 133 |
try:
|
| 134 |
+
ing = extract_text_from_files(file_paths) # For text extraction from markdown/txt
|
| 135 |
+
rag.add(ing.get("chunks", []))
|
| 136 |
except Exception as e:
|
| 137 |
+
log_event("rag_ingest_error", None, {"err": str(e)})
|
| 138 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
analyzer = HealthcareAnalyzer(registry)
|
| 140 |
+
datasets = analyzer.comprehensive_analysis(safe_in)
|
| 141 |
catalog = _dataset_catalog(datasets)
|
|
|
|
|
|
|
| 142 |
plan = parse_to_plan(safe_in, catalog)
|
|
|
|
|
|
|
| 143 |
structured_md = ScenarioEngine.execute_plan(plan, datasets)
|
|
|
|
|
|
|
| 144 |
rag_hits = [txt for txt, _ in rag.retrieve(safe_in, k=6)]
|
| 145 |
narrative = generate_narrative(safe_in, structured_md, rag_hits)
|
| 146 |
|
|
|
|
| 147 |
if not narrative or "Unable to generate narrative" in narrative:
|
|
|
|
| 148 |
narrative = build_narrative(
|
| 149 |
+
scenario_text=safe_in, datasets=datasets, structured_tables=None,
|
|
|
|
|
|
|
| 150 |
metric_hints=["surgery_median", "consult_median", "wait", "median", "p90", "90th"],
|
| 151 |
group_hints=["facility", "specialty", "zone", "hospital", "city", "region"],
|
| 152 |
min_sample=5
|
| 153 |
)
|
| 154 |
|
| 155 |
+
debug_note = f"\n\n> **Planner note:** {getattr(plan, 'notes', '')}" if DEBUG_PLAN and getattr(plan, "notes", None) else ""
|
| 156 |
+
reply = _sanitize_text(f"{structured_md}\n\n# Narrative & Recommendations\n\n{narrative}{debug_note}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
else:
|
| 159 |
+
# General conversation mode (no files, not a structured scenario)
|
| 160 |
prompt = f"{GENERAL_CONVERSATION_PROMPT}\n\nUser: {safe_in}\nAssistant:"
|
| 161 |
reply = cohere_chat(prompt) or open_fallback_chat(prompt) or "How can I help further?"
|
| 162 |
reply = _sanitize_text(reply)
|
| 163 |
|
| 164 |
+
# Append interaction to chat history
|
| 165 |
new_hist = _append_msg(history_messages, "user", user_msg)
|
| 166 |
new_hist = _append_msg(new_hist, "assistant", reply)
|
| 167 |
return new_hist, ""
|
|
|
|
| 170 |
tb = traceback.format_exc()
|
| 171 |
log_event("app_error", None, {"err": str(e), "tb": tb})
|
| 172 |
new_hist = _append_msg(history_messages, "user", user_msg)
|
| 173 |
+
new_hist = _append_msg(new_hist, "assistant", f"A critical error occurred: {e}\n\n{tb}")
|
| 174 |
return new_hist, ""
|
| 175 |
|
| 176 |
|
|
|
|
| 179 |
gr.Markdown("## Canadian Healthcare AI • Cohere API • Scenario-Agnostic • Deterministic Analytics")
|
| 180 |
|
| 181 |
with gr.Row():
|
| 182 |
+
chat = gr.Chatbot(label="Chat History", type="messages", height=520)
|
|
|
|
| 183 |
files = gr.Files(
|
| 184 |
+
label="Upload Data Files (CSV recommended)",
|
| 185 |
file_count="multiple",
|
| 186 |
type="filepath",
|
| 187 |
file_types=HEALTHCARE_SETTINGS["supported_file_types"]
|
| 188 |
)
|
| 189 |
|
| 190 |
+
msg = gr.Textbox(label="Prompt", placeholder="Paste any scenario (Background / Situation / Tasks / Deliverables) or just chat.")
|
| 191 |
with gr.Row():
|
| 192 |
send = gr.Button("Send")
|
| 193 |
clear = gr.Button("Clear")
|
|
|
|
| 200 |
|
| 201 |
send.click(_on_send, inputs=[msg, chat, files], outputs=[chat, msg])
|
| 202 |
msg.submit(_on_send, inputs=[msg, chat, files], outputs=[chat, msg])
|
| 203 |
+
clear.click(lambda: ([], "", None), outputs=[chat, msg, files])
|
| 204 |
ping_btn.click(lambda: ping_cohere(), outputs=[ping_out])
|
| 205 |
|
| 206 |
if __name__ == "__main__":
|
| 207 |
+
# Ensure you have your COHERE_API_KEY set as an environment variable
|
| 208 |
+
if not os.getenv("COHERE_API_KEY"):
|
| 209 |
+
print("🔴 COHERE_API_KEY environment variable not set. Application may not function correctly.")
|
| 210 |
+
|
| 211 |
log_event("startup", None, {
|
| 212 |
"cohere_key_present": bool(os.getenv("COHERE_API_KEY")),
|
| 213 |
"cohere_model": COHERE_MODEL_PRIMARY,
|
| 214 |
"open_fallbacks": USE_OPEN_FALLBACKS,
|
| 215 |
"timeout_s": COHERE_TIMEOUT_S
|
| 216 |
})
|
| 217 |
+
demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))
|