Update pages/2_Consult.py
Browse files- pages/2_Consult.py +110 -76
pages/2_Consult.py
CHANGED
@@ -1,23 +1,24 @@
|
|
1 |
# /home/user/app/pages/2_Consult.py
|
2 |
import streamlit as st
|
3 |
-
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage #
|
4 |
from datetime import datetime
|
5 |
from typing import List, Optional, Dict, Any
|
6 |
from sqlmodel import select
|
7 |
|
8 |
from config.settings import settings
|
9 |
-
from agent import get_agent_executor
|
10 |
from models import ChatMessage, ChatSession
|
11 |
from models.db import get_session_context
|
12 |
from services.logger import app_logger
|
13 |
from services.metrics import log_consultation_start
|
14 |
|
15 |
-
|
16 |
# --- Authentication Check ---
|
17 |
if not st.session_state.get("authenticated_user_id"):
|
18 |
st.warning("Please log in to access the consultation page.")
|
19 |
-
try:
|
20 |
-
|
|
|
|
|
21 |
st.stop()
|
22 |
|
23 |
authenticated_user_id = st.session_state.get("authenticated_user_id")
|
@@ -26,36 +27,44 @@ app_logger.info(f"User '{authenticated_username}' (ID: {authenticated_user_id})
|
|
26 |
|
27 |
# --- Initialize Agent ---
|
28 |
try:
|
29 |
-
agent_executor = get_agent_executor()
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
except Exception as e:
|
31 |
-
st.error(f"
|
32 |
-
app_logger.critical(f"AI Agent initialization
|
33 |
st.stop()
|
34 |
|
|
|
35 |
# --- Session State for Consult Page ---
|
36 |
if 'current_consult_patient_context' not in st.session_state:
|
37 |
-
st.session_state.current_consult_patient_context = {}
|
38 |
if 'consult_context_submitted' not in st.session_state:
|
39 |
st.session_state.consult_context_submitted = False
|
40 |
|
41 |
# --- Helper Functions ---
|
42 |
-
@st.cache_data(ttl=30, show_spinner=False)
|
43 |
-
def load_chat_history_for_agent(session_id: int) -> List:
|
44 |
messages = []
|
45 |
-
# ... (load_chat_history_for_agent from previous full rewrite of 2_Consult.py, using SQLModel select) ...
|
46 |
-
# This function should convert DB ChatMessages to LangChain HumanMessage/AIMessage
|
47 |
app_logger.debug(f"Loading agent chat history for session_id: {session_id}")
|
48 |
with get_session_context() as db:
|
49 |
statement = select(ChatMessage).where(ChatMessage.session_id == session_id).order_by(ChatMessage.timestamp)
|
50 |
db_messages = db.exec(statement).all()
|
51 |
for msg in db_messages:
|
52 |
-
if msg.role == "user":
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
54 |
return messages
|
55 |
|
56 |
-
|
57 |
def save_chat_message_to_db(session_id: int, role: str, content: str, tool_call_id: Optional[str]=None, tool_name: Optional[str]=None):
|
58 |
-
# ... (save_chat_message_to_db from previous full rewrite of 2_Consult.py) ...
|
59 |
app_logger.debug(f"Saving message to DB for session {session_id}: Role={role}")
|
60 |
with get_session_context() as db:
|
61 |
chat_message = ChatMessage(
|
@@ -65,8 +74,7 @@ def save_chat_message_to_db(session_id: int, role: str, content: str, tool_call_
|
|
65 |
db.add(chat_message) # Commit handled by context manager
|
66 |
app_logger.info(f"Message saved to DB for session {session_id}. Role: {role}.")
|
67 |
|
68 |
-
|
69 |
-
def update_chat_session_with_context(session_id: int, context_summary: str):
|
70 |
with get_session_context() as db:
|
71 |
session_to_update = db.get(ChatSession, session_id)
|
72 |
if session_to_update:
|
@@ -74,86 +82,95 @@ def update_chat_session_with_context(session_id: int, context_summary: str):
|
|
74 |
db.add(session_to_update) # Stage for commit
|
75 |
app_logger.info(f"Updated ChatSession {session_id} with patient context summary.")
|
76 |
else:
|
77 |
-
app_logger.error(f"Could not find ChatSession {session_id} to update with context.")
|
78 |
|
79 |
# --- Page Logic ---
|
80 |
st.title("AI Consultation Room")
|
81 |
st.markdown(f"Interacting as: **{authenticated_username}**")
|
82 |
-
st.info(settings.MAIN_DISCLAIMER_SHORT
|
83 |
|
84 |
chat_session_id = st.session_state.get("current_chat_session_id")
|
85 |
if not chat_session_id:
|
86 |
-
st.error("No active chat session. This
|
87 |
-
app_logger.error(f"User '{authenticated_username}' on Consult page with
|
88 |
-
# Attempt to create a new one if truly missing, or guide to re-login
|
89 |
-
# For now, stopping is safer if app.py is supposed to always create one.
|
90 |
st.stop()
|
91 |
|
92 |
# --- Patient Context Input Form ---
|
93 |
if not st.session_state.consult_context_submitted:
|
94 |
-
st.subheader("
|
95 |
-
with st.form(key="
|
96 |
st.markdown("**Reminder: Use only anonymized, simulated data for this demonstration.**")
|
97 |
-
age = st.number_input("Patient Age (Simulated)", min_value=0, max_value=120, step=1)
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
|
|
102 |
submit_context_button = st.form_submit_button("Start Consult with this Context")
|
103 |
|
104 |
if submit_context_button:
|
105 |
-
|
106 |
-
"age": age if age > 0 else
|
107 |
-
"gender": gender,
|
108 |
-
"chief_complaint": chief_complaint.strip()
|
109 |
-
"key_medical_history": key_history.strip()
|
110 |
-
"current_medications": current_meds.strip()
|
111 |
}
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
118 |
|
119 |
-
# Save context summary to ChatSession model
|
120 |
-
update_chat_session_with_context(chat_session_id, context_summary_for_agent)
|
121 |
-
|
122 |
-
# Prepend context to agent's chat history as a system message or initial user message
|
123 |
-
# For this example, let's add it as a system message to guide the AI
|
124 |
agent_history_key = f"agent_chat_history_{chat_session_id}"
|
125 |
if agent_history_key not in st.session_state: st.session_state[agent_history_key] = []
|
126 |
-
st.session_state[agent_history_key].insert(0, SystemMessage(content=context_summary_for_agent))
|
127 |
-
# Also save this "system" context message to DB for record keeping if desired
|
128 |
-
save_chat_message_to_db(chat_session_id, "system", context_summary_for_agent)
|
129 |
|
130 |
-
|
131 |
-
|
132 |
-
|
|
|
|
|
133 |
|
134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
agent_history_key = f"agent_chat_history_{chat_session_id}"
|
|
|
136 |
if agent_history_key not in st.session_state:
|
137 |
st.session_state[agent_history_key] = load_chat_history_for_agent(chat_session_id)
|
138 |
-
if not st.session_state[agent_history_key]: # If history is empty
|
139 |
try: log_consultation_start(user_id=authenticated_user_id, session_id=chat_session_id)
|
140 |
-
except Exception as e: app_logger.warning(f"Failed to log consultation start: {e}")
|
|
|
141 |
initial_ai_message_content = "Hello! I am your AI Health Navigator. How can I assist you today?"
|
|
|
|
|
|
|
142 |
st.session_state[agent_history_key].append(AIMessage(content=initial_ai_message_content))
|
143 |
save_chat_message_to_db(chat_session_id, "assistant", initial_ai_message_content)
|
|
|
144 |
|
145 |
-
# Display chat messages
|
146 |
with st.container():
|
147 |
with get_session_context() as db:
|
148 |
stmt = select(ChatMessage).where(ChatMessage.session_id == chat_session_id).order_by(ChatMessage.timestamp)
|
149 |
ui_messages = db.exec(stmt).all()
|
150 |
for msg in ui_messages:
|
151 |
-
if msg.role == "system": # Don't
|
152 |
-
continue
|
153 |
avatar = "π§ββοΈ" if msg.role == "assistant" else "π€"
|
154 |
-
if msg.role == "tool": avatar = "π οΈ"
|
155 |
with st.chat_message(msg.role, avatar=avatar):
|
156 |
-
st.markdown(msg.content) #
|
157 |
|
158 |
if prompt := st.chat_input("Ask the AI..."):
|
159 |
with st.chat_message("user", avatar="π€"): st.markdown(prompt)
|
@@ -163,24 +180,41 @@ if prompt := st.chat_input("Ask the AI..."):
|
|
163 |
with st.chat_message("assistant", avatar="π§ββοΈ"):
|
164 |
with st.spinner("AI is thinking..."):
|
165 |
try:
|
166 |
-
#
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
|
|
|
|
|
|
|
|
171 |
"input": prompt,
|
172 |
"chat_history": st.session_state[agent_history_key],
|
173 |
-
|
174 |
-
}
|
175 |
-
|
|
|
|
|
|
|
|
|
176 |
if not isinstance(ai_response_content, str): ai_response_content = str(ai_response_content)
|
177 |
|
178 |
-
|
|
|
179 |
save_chat_message_to_db(chat_session_id, "assistant", ai_response_content)
|
180 |
st.session_state[agent_history_key].append(AIMessage(content=ai_response_content))
|
|
|
181 |
except Exception as e:
|
182 |
app_logger.error(f"Error during agent invocation for session {chat_session_id}: {e}", exc_info=True)
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
# /home/user/app/pages/2_Consult.py
|
2 |
import streamlit as st
|
3 |
+
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage # Ensure SystemMessage is imported
|
4 |
from datetime import datetime
|
5 |
from typing import List, Optional, Dict, Any
|
6 |
from sqlmodel import select
|
7 |
|
8 |
from config.settings import settings
|
9 |
+
from agent import get_agent_executor # This now returns the Gemini-based agent
|
10 |
from models import ChatMessage, ChatSession
|
11 |
from models.db import get_session_context
|
12 |
from services.logger import app_logger
|
13 |
from services.metrics import log_consultation_start
|
14 |
|
|
|
15 |
# --- Authentication Check ---
|
16 |
if not st.session_state.get("authenticated_user_id"):
|
17 |
st.warning("Please log in to access the consultation page.")
|
18 |
+
try:
|
19 |
+
st.switch_page("app.py")
|
20 |
+
except st.errors.StreamlitAPIException:
|
21 |
+
st.info("Please navigate to the main login page.")
|
22 |
st.stop()
|
23 |
|
24 |
authenticated_user_id = st.session_state.get("authenticated_user_id")
|
|
|
27 |
|
28 |
# --- Initialize Agent ---
|
29 |
try:
|
30 |
+
agent_executor = get_agent_executor() # Gets the Gemini agent executor
|
31 |
+
app_logger.info("Gemini-based agent executor initialized for Consult page.")
|
32 |
+
except ValueError as e: # Catch specific error from get_agent_executor if API key is missing
|
33 |
+
st.error(f"AI Agent Initialization Error: {e}")
|
34 |
+
app_logger.critical(f"Fatal: AI Agent could not be initialized in Consult page: {e}", exc_info=True)
|
35 |
+
st.info("Please ensure the necessary API keys (e.g., Google API Key for Gemini) are configured in the application settings.")
|
36 |
+
st.stop()
|
37 |
except Exception as e:
|
38 |
+
st.error(f"An unexpected error occurred while initializing the AI Agent: {e}")
|
39 |
+
app_logger.critical(f"Fatal: Unexpected AI Agent initialization error: {e}", exc_info=True)
|
40 |
st.stop()
|
41 |
|
42 |
+
|
43 |
# --- Session State for Consult Page ---
|
44 |
if 'current_consult_patient_context' not in st.session_state:
|
45 |
+
st.session_state.current_consult_patient_context = {}
|
46 |
if 'consult_context_submitted' not in st.session_state:
|
47 |
st.session_state.consult_context_submitted = False
|
48 |
|
49 |
# --- Helper Functions ---
|
50 |
+
@st.cache_data(ttl=30, show_spinner=False)
|
51 |
+
def load_chat_history_for_agent(session_id: int) -> List: # List of LangChain messages
|
52 |
messages = []
|
|
|
|
|
53 |
app_logger.debug(f"Loading agent chat history for session_id: {session_id}")
|
54 |
with get_session_context() as db:
|
55 |
statement = select(ChatMessage).where(ChatMessage.session_id == session_id).order_by(ChatMessage.timestamp)
|
56 |
db_messages = db.exec(statement).all()
|
57 |
for msg in db_messages:
|
58 |
+
if msg.role == "user":
|
59 |
+
messages.append(HumanMessage(content=msg.content))
|
60 |
+
elif msg.role == "assistant":
|
61 |
+
messages.append(AIMessage(content=msg.content))
|
62 |
+
elif msg.role == "system": # Include system messages in agent history if they were saved
|
63 |
+
messages.append(SystemMessage(content=msg.content))
|
64 |
+
app_logger.debug(f"Loaded {len(messages)} messages for agent history for session {session_id}.")
|
65 |
return messages
|
66 |
|
|
|
67 |
def save_chat_message_to_db(session_id: int, role: str, content: str, tool_call_id: Optional[str]=None, tool_name: Optional[str]=None):
|
|
|
68 |
app_logger.debug(f"Saving message to DB for session {session_id}: Role={role}")
|
69 |
with get_session_context() as db:
|
70 |
chat_message = ChatMessage(
|
|
|
74 |
db.add(chat_message) # Commit handled by context manager
|
75 |
app_logger.info(f"Message saved to DB for session {session_id}. Role: {role}.")
|
76 |
|
77 |
+
def update_chat_session_with_context_summary(session_id: int, context_summary: str):
|
|
|
78 |
with get_session_context() as db:
|
79 |
session_to_update = db.get(ChatSession, session_id)
|
80 |
if session_to_update:
|
|
|
82 |
db.add(session_to_update) # Stage for commit
|
83 |
app_logger.info(f"Updated ChatSession {session_id} with patient context summary.")
|
84 |
else:
|
85 |
+
app_logger.error(f"Could not find ChatSession {session_id} to update with context summary.")
|
86 |
|
87 |
# --- Page Logic ---
|
88 |
st.title("AI Consultation Room")
|
89 |
st.markdown(f"Interacting as: **{authenticated_username}**")
|
90 |
+
st.info(f"{settings.MAIN_DISCLAIMER_SHORT} Remember to use only anonymized, simulated data.")
|
91 |
|
92 |
chat_session_id = st.session_state.get("current_chat_session_id")
|
93 |
if not chat_session_id:
|
94 |
+
st.error("No active chat session ID. This can occur if a session wasn't properly created on login. Please try logging out and then logging back in. If the problem persists, contact support.")
|
95 |
+
app_logger.error(f"User '{authenticated_username}' (ID: {authenticated_user_id}) on Consult page with NO current_chat_session_id.")
|
|
|
|
|
96 |
st.stop()
|
97 |
|
98 |
# --- Patient Context Input Form ---
|
99 |
if not st.session_state.consult_context_submitted:
|
100 |
+
st.subheader("Step 1: Provide Patient Context (Optional, Simulated Data Only)")
|
101 |
+
with st.form(key="patient_context_form_consult"):
|
102 |
st.markdown("**Reminder: Use only anonymized, simulated data for this demonstration.**")
|
103 |
+
age = st.number_input("Patient Age (Simulated)", min_value=0, max_value=120, step=1, value=None) # Default to None
|
104 |
+
gender_options = ["Not Specified", "Male", "Female", "Other"]
|
105 |
+
gender = st.selectbox("Patient Gender (Simulated)", gender_options, index=0)
|
106 |
+
chief_complaint = st.text_area("Chief Complaint / Reason for Consult (Simulated)", height=100, placeholder="e.g., Persistent cough for 2 weeks")
|
107 |
+
key_history = st.text_area("Key Medical History (Simulated)", height=100, placeholder="e.g., Type 2 Diabetes, Hypertension, Asthma")
|
108 |
+
current_meds = st.text_area("Current Medications (Simulated)", height=100, placeholder="e.g., Metformin 500mg BID, Lisinopril 10mg OD")
|
109 |
submit_context_button = st.form_submit_button("Start Consult with this Context")
|
110 |
|
111 |
if submit_context_button:
|
112 |
+
context_dict = {
|
113 |
+
"age": age if age is not None and age > 0 else None, # Store None if not specified
|
114 |
+
"gender": gender if gender != "Not Specified" else None,
|
115 |
+
"chief_complaint": chief_complaint.strip() or None,
|
116 |
+
"key_medical_history": key_history.strip() or None,
|
117 |
+
"current_medications": current_meds.strip() or None,
|
118 |
}
|
119 |
+
# Filter out None values for the summary string
|
120 |
+
valid_context_parts = {k: v for k, v in context_dict.items() if v is not None}
|
121 |
+
st.session_state.current_consult_patient_context = valid_context_parts # Store the filtered dict
|
122 |
+
|
123 |
+
if valid_context_parts:
|
124 |
+
context_summary_str_parts = [f"{k.replace('_', ' ').title()}: {v}" for k, v in valid_context_parts.items()]
|
125 |
+
context_summary_for_db_and_agent = "; ".join(context_summary_str_parts)
|
126 |
+
else:
|
127 |
+
context_summary_for_db_and_agent = "No specific patient context provided for this session."
|
128 |
+
|
129 |
+
update_chat_session_with_context_summary(chat_session_id, context_summary_for_db_and_agent)
|
130 |
|
|
|
|
|
|
|
|
|
|
|
131 |
agent_history_key = f"agent_chat_history_{chat_session_id}"
|
132 |
if agent_history_key not in st.session_state: st.session_state[agent_history_key] = []
|
|
|
|
|
|
|
133 |
|
134 |
+
# Don't add patient context as a SystemMessage if it's passed as a variable to invoke
|
135 |
+
# The agent's main system prompt will now include a placeholder for it.
|
136 |
+
# However, we save it to DB for record keeping.
|
137 |
+
if valid_context_parts: # Save a system message indicating context was provided
|
138 |
+
save_chat_message_to_db(chat_session_id, "system", f"Initial Patient Context Provided: {context_summary_for_db_and_agent}")
|
139 |
|
140 |
+
st.session_state.consult_context_submitted = True
|
141 |
+
app_logger.info(f"Patient context submitted for session {chat_session_id}: {context_summary_for_db_and_agent}")
|
142 |
+
st.rerun()
|
143 |
+
st.stop()
|
144 |
+
|
145 |
+
# --- Chat Interface (Shown after context is submitted or if skipped by some other logic not yet present) ---
|
146 |
+
st.subheader("Step 2: Interact with AI Health Navigator")
|
147 |
agent_history_key = f"agent_chat_history_{chat_session_id}"
|
148 |
+
|
149 |
if agent_history_key not in st.session_state:
|
150 |
st.session_state[agent_history_key] = load_chat_history_for_agent(chat_session_id)
|
151 |
+
if not st.session_state[agent_history_key]: # If history is empty
|
152 |
try: log_consultation_start(user_id=authenticated_user_id, session_id=chat_session_id)
|
153 |
+
except Exception as e: app_logger.warning(f"Failed to log consultation start metric: {e}")
|
154 |
+
|
155 |
initial_ai_message_content = "Hello! I am your AI Health Navigator. How can I assist you today?"
|
156 |
+
if st.session_state.get('current_consult_patient_context'):
|
157 |
+
initial_ai_message_content += " I have noted the patient context you provided."
|
158 |
+
|
159 |
st.session_state[agent_history_key].append(AIMessage(content=initial_ai_message_content))
|
160 |
save_chat_message_to_db(chat_session_id, "assistant", initial_ai_message_content)
|
161 |
+
app_logger.info(f"Initialized new consultation (session {chat_session_id}) with a greeting.")
|
162 |
|
163 |
+
# Display chat messages for UI
|
164 |
with st.container():
|
165 |
with get_session_context() as db:
|
166 |
stmt = select(ChatMessage).where(ChatMessage.session_id == chat_session_id).order_by(ChatMessage.timestamp)
|
167 |
ui_messages = db.exec(stmt).all()
|
168 |
for msg in ui_messages:
|
169 |
+
if msg.role == "system": continue # Don't display system context messages directly
|
|
|
170 |
avatar = "π§ββοΈ" if msg.role == "assistant" else "π€"
|
171 |
+
if msg.role == "tool": avatar = "π οΈ" # Assuming you might log tool calls this way
|
172 |
with st.chat_message(msg.role, avatar=avatar):
|
173 |
+
st.markdown(msg.content) # Potentially enhance to show sources/confidence if agent provides
|
174 |
|
175 |
if prompt := st.chat_input("Ask the AI..."):
|
176 |
with st.chat_message("user", avatar="π€"): st.markdown(prompt)
|
|
|
180 |
with st.chat_message("assistant", avatar="π§ββοΈ"):
|
181 |
with st.spinner("AI is thinking..."):
|
182 |
try:
|
183 |
+
# Prepare patient context string for the agent, if any was provided
|
184 |
+
patient_context_dict = st.session_state.get('current_consult_patient_context', {})
|
185 |
+
if patient_context_dict:
|
186 |
+
context_parts_for_invoke = [f"{k.replace('_', ' ').title()}: {v}" for k, v in patient_context_dict.items()]
|
187 |
+
patient_context_str_for_invoke = "; ".join(context_parts_for_invoke)
|
188 |
+
else:
|
189 |
+
patient_context_str_for_invoke = "No specific patient context was provided for this interaction."
|
190 |
+
|
191 |
+
invoke_payload = {
|
192 |
"input": prompt,
|
193 |
"chat_history": st.session_state[agent_history_key],
|
194 |
+
"patient_context": patient_context_str_for_invoke # Pass to agent
|
195 |
+
}
|
196 |
+
app_logger.debug(f"Invoking agent with payload: {invoke_payload}")
|
197 |
+
|
198 |
+
response = agent_executor.invoke(invoke_payload)
|
199 |
+
|
200 |
+
ai_response_content = response.get('output', "I could not generate a valid response.")
|
201 |
if not isinstance(ai_response_content, str): ai_response_content = str(ai_response_content)
|
202 |
|
203 |
+
app_logger.info(f"Agent response for session {chat_session_id}: '{ai_response_content[:100]}...'")
|
204 |
+
st.markdown(ai_response_content) # Display AI response
|
205 |
save_chat_message_to_db(chat_session_id, "assistant", ai_response_content)
|
206 |
st.session_state[agent_history_key].append(AIMessage(content=ai_response_content))
|
207 |
+
|
208 |
except Exception as e:
|
209 |
app_logger.error(f"Error during agent invocation for session {chat_session_id}: {e}", exc_info=True)
|
210 |
+
# The user-facing error was: "Sorry, an error occurred: ValidationError. Please try again."
|
211 |
+
# Let's try to be a bit more specific if we can, or keep it generic but log details.
|
212 |
+
error_type_name = type(e).__name__ # e.g., "ValidationError", "APIError"
|
213 |
+
user_friendly_error_message = f"Sorry, an error occurred ({error_type_name}). Please try rephrasing your query or contact support if the issue persists."
|
214 |
+
st.error(user_friendly_error_message)
|
215 |
+
|
216 |
+
# Save a representation of the error to DB for the assistant's turn
|
217 |
+
db_error_message = f"System encountered an error: {error_type_name} while processing user query. Details logged."
|
218 |
+
save_chat_message_to_db(chat_session_id, "assistant", db_error_message)
|
219 |
+
# Add error representation to agent history so it's aware for next turn (optional)
|
220 |
+
st.session_state[agent_history_key].append(AIMessage(content=f"Note to self: Encountered an error ({error_type_name}) on the previous turn."))
|