Spaces:
Sleeping
Sleeping
add
Browse files- src/config/prompts.py +65 -31
- src/core/core_classes.py +149 -18
src/config/prompts.py
CHANGED
|
@@ -2,47 +2,81 @@
|
|
| 2 |
|
| 3 |
# ===== CLASSIFIERS =====
|
| 4 |
|
| 5 |
-
SYSTEM_PROMPT_ENTRY_CLASSIFIER = """You are a message classification specialist for a medical chat system with lifestyle coaching capabilities.
|
| 6 |
|
| 7 |
TASK:
|
| 8 |
-
Classify the current patient message to determine the appropriate system mode.
|
| 9 |
-
|
| 10 |
-
CLASSIFICATION
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
DECISION LOGIC:
|
| 20 |
-
1.
|
| 21 |
-
2.
|
| 22 |
-
3.
|
| 23 |
-
4.
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
✅ "I want to start exercising" →
|
| 27 |
-
✅ "
|
| 28 |
-
✅ "
|
| 29 |
-
✅ "
|
| 30 |
-
✅ "
|
| 31 |
-
|
| 32 |
-
❌ "hello" → OFF (greeting)
|
| 33 |
-
⚡ "I want to exercise but my back hurts" → HYBRID (both)
|
| 34 |
|
| 35 |
CRITICAL RULES:
|
| 36 |
-
- IGNORE patient's medical history completely
|
| 37 |
- Focus ONLY on current message content
|
| 38 |
-
- Be
|
| 39 |
-
- Medical
|
|
|
|
| 40 |
|
| 41 |
OUTPUT FORMAT (JSON only):
|
| 42 |
{
|
| 43 |
-
"K": "
|
| 44 |
-
"
|
| 45 |
-
"
|
|
|
|
|
|
|
| 46 |
}"""
|
| 47 |
|
| 48 |
SYSTEM_PROMPT_TRIAGE_EXIT_CLASSIFIER = """You are a clinical triage specialist evaluating patient readiness for lifestyle coaching after medical assessment.
|
|
|
|
| 2 |
|
| 3 |
# ===== CLASSIFIERS =====
|
| 4 |
|
| 5 |
+
SYSTEM_PROMPT_ENTRY_CLASSIFIER = """You are a message classification specialist for a medical chat system with lifestyle coaching and spiritual health assessment capabilities.
|
| 6 |
|
| 7 |
TASK:
|
| 8 |
+
Classify the current patient message to determine the appropriate system mode(s). Analyze the message for medical concerns (K), lifestyle needs (L), and spiritual distress indicators (S).
|
| 9 |
+
|
| 10 |
+
CLASSIFICATION DIMENSIONS:
|
| 11 |
+
|
| 12 |
+
**K (Koncern - Medical):**
|
| 13 |
+
- "none": No medical concerns
|
| 14 |
+
- "minor": Minor medical questions or stable conditions
|
| 15 |
+
- "urgent": Active symptoms, pain, or medical issues requiring attention
|
| 16 |
+
|
| 17 |
+
**L (Lifestyle):**
|
| 18 |
+
- "off": No lifestyle/exercise/nutrition requests
|
| 19 |
+
- "on": Lifestyle, exercise, nutrition, rehabilitation requests
|
| 20 |
+
|
| 21 |
+
**S (Spiritual):**
|
| 22 |
+
- "off": No spiritual distress indicators
|
| 23 |
+
- "on": Spiritual/emotional distress, existential concerns, meaning/purpose questions
|
| 24 |
+
|
| 25 |
+
**T (Triage - Urgency):**
|
| 26 |
+
- "routine": Normal conversation, no urgency
|
| 27 |
+
- "urgent": Requires prompt attention but not emergency
|
| 28 |
+
- "emergency": Immediate medical attention needed
|
| 29 |
+
|
| 30 |
+
LIFESTYLE DETECTION KEYWORDS:
|
| 31 |
+
exercise, workout, training, fitness, sport, rehabilitation, nutrition, diet, physical, activity, movement, therapy
|
| 32 |
+
|
| 33 |
+
SPIRITUAL DISTRESS INDICATORS:
|
| 34 |
+
|
| 35 |
+
**Emotional Markers:**
|
| 36 |
+
- Anger, rage, frustration, irritability
|
| 37 |
+
- Sadness, depression, crying, grief
|
| 38 |
+
- Hopelessness, helplessness, despair
|
| 39 |
+
- Anxiety, fear, worry, panic
|
| 40 |
+
- Guilt, shame, regret
|
| 41 |
+
- Loneliness, isolation, abandonment
|
| 42 |
+
|
| 43 |
+
**Spiritual Markers:**
|
| 44 |
+
- Questions about meaning, purpose, "why me?"
|
| 45 |
+
- Loss of faith, questioning beliefs
|
| 46 |
+
- Feeling abandoned by God/higher power
|
| 47 |
+
- Existential concerns, life/death questions
|
| 48 |
+
- Moral distress, ethical dilemmas
|
| 49 |
+
- Loss of hope, future orientation
|
| 50 |
+
- Disconnection from spiritual community
|
| 51 |
+
- Inability to find peace or comfort
|
| 52 |
|
| 53 |
DECISION LOGIC:
|
| 54 |
+
1. Scan for medical symptoms → Set K level
|
| 55 |
+
2. Scan for lifestyle keywords → Set L (on/off)
|
| 56 |
+
3. Scan for emotional/spiritual markers → Set S (on/off)
|
| 57 |
+
4. Assess overall urgency → Set T level
|
| 58 |
+
|
| 59 |
+
EXAMPLES:
|
| 60 |
+
✅ "I want to start exercising" → K:none, L:on, S:off, T:routine
|
| 61 |
+
✅ "I have a headache" → K:minor, L:off, S:off, T:routine
|
| 62 |
+
✅ "Why is this happening to me? I feel so hopeless" → K:none, L:off, S:on, T:urgent
|
| 63 |
+
✅ "I want to exercise but feel so depressed" → K:none, L:on, S:on, T:routine
|
| 64 |
+
✅ "Severe chest pain" → K:urgent, L:off, S:off, T:emergency
|
| 65 |
+
✅ "I can't find meaning in life anymore" → K:none, L:off, S:on, T:urgent
|
|
|
|
|
|
|
| 66 |
|
| 67 |
CRITICAL RULES:
|
|
|
|
| 68 |
- Focus ONLY on current message content
|
| 69 |
+
- Be sensitive to subtle emotional/spiritual cues
|
| 70 |
+
- Medical safety is paramount (K and T take priority)
|
| 71 |
+
- Multiple dimensions can be active simultaneously
|
| 72 |
|
| 73 |
OUTPUT FORMAT (JSON only):
|
| 74 |
{
|
| 75 |
+
"K": "none|minor|urgent",
|
| 76 |
+
"L": "off|on",
|
| 77 |
+
"S": "off|on",
|
| 78 |
+
"T": "routine|urgent|emergency",
|
| 79 |
+
"reasoning": "Brief explanation of classification"
|
| 80 |
}"""
|
| 81 |
|
| 82 |
SYSTEM_PROMPT_TRIAGE_EXIT_CLASSIFIER = """You are a clinical triage specialist evaluating patient readiness for lifestyle coaching after medical assessment.
|
src/core/core_classes.py
CHANGED
|
@@ -19,10 +19,12 @@ import traceback
|
|
| 19 |
from datetime import datetime
|
| 20 |
from dataclasses import dataclass, asdict
|
| 21 |
from typing import Dict, List, Optional, Any, Union, Tuple, Callable, TypeVar, Type, TYPE_CHECKING
|
|
|
|
| 22 |
|
| 23 |
# Import AIClientManager for type hints
|
| 24 |
if TYPE_CHECKING:
|
| 25 |
from src.core.ai_client import AIClientManager
|
|
|
|
| 26 |
|
| 27 |
import re
|
| 28 |
|
|
@@ -80,6 +82,25 @@ except ImportError:
|
|
| 80 |
|
| 81 |
# ===== ENHANCED DATA STRUCTURES =====
|
| 82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
@dataclass
|
| 84 |
class ClinicalBackground:
|
| 85 |
"""Enhanced clinical background with composition context tracking"""
|
|
@@ -173,17 +194,27 @@ class ChatMessage:
|
|
| 173 |
|
| 174 |
@dataclass
|
| 175 |
class SessionState:
|
| 176 |
-
"""Enhanced session state with dynamic prompt context"""
|
| 177 |
-
current_mode: str
|
| 178 |
is_active_session: bool
|
| 179 |
session_start_time: Optional[str]
|
| 180 |
last_controller_decision: Dict
|
|
|
|
| 181 |
# Lifecycle management
|
| 182 |
lifestyle_session_length: int = 0
|
| 183 |
last_triage_summary: str = ""
|
| 184 |
entry_classification: Dict = None
|
| 185 |
|
| 186 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
current_prompt_composition_id: Optional[str] = None
|
| 188 |
composition_analytics: Dict = None
|
| 189 |
|
|
@@ -192,6 +223,47 @@ class SessionState:
|
|
| 192 |
self.entry_classification = {}
|
| 193 |
if self.composition_analytics is None:
|
| 194 |
self.composition_analytics = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
# ===== ENHANCED LIFESTYLE ASSISTANT WITH DYNAMIC PROMPTS =====
|
| 197 |
|
|
@@ -1183,7 +1255,18 @@ class EntryClassifier:
|
|
| 1183 |
self.api = api
|
| 1184 |
|
| 1185 |
def classify(self, user_message: str, clinical_background: ClinicalBackground) -> Dict:
|
| 1186 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1187 |
|
| 1188 |
system_prompt = SYSTEM_PROMPT_ENTRY_CLASSIFIER
|
| 1189 |
user_prompt = PROMPT_ENTRY_CLASSIFIER(clinical_background, user_message)
|
|
@@ -1198,26 +1281,74 @@ class EntryClassifier:
|
|
| 1198 |
try:
|
| 1199 |
classification = _extract_json_object(response)
|
| 1200 |
|
| 1201 |
-
# Валідація формату K/
|
| 1202 |
-
if not all(key in classification for key in ["K", "
|
| 1203 |
-
raise ValueError("Missing K/
|
| 1204 |
|
| 1205 |
-
|
| 1206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1207 |
|
| 1208 |
-
# Переважно підставляємо серверний timestamp (UTC), ігноруючи T з LLM
|
| 1209 |
-
from datetime import datetime, timezone
|
| 1210 |
-
classification["T"] = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
| 1211 |
return classification
|
| 1212 |
-
except:
|
| 1213 |
-
|
| 1214 |
return {
|
| 1215 |
-
"K": "
|
| 1216 |
-
"
|
| 1217 |
-
|
| 1218 |
-
"T":
|
|
|
|
|
|
|
| 1219 |
}
|
| 1220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1221 |
class TriageExitClassifier:
|
| 1222 |
"""Preserved Legacy Class - Triage Exit Assessment"""
|
| 1223 |
|
|
|
|
| 19 |
from datetime import datetime
|
| 20 |
from dataclasses import dataclass, asdict
|
| 21 |
from typing import Dict, List, Optional, Any, Union, Tuple, Callable, TypeVar, Type, TYPE_CHECKING
|
| 22 |
+
from enum import Enum
|
| 23 |
|
| 24 |
# Import AIClientManager for type hints
|
| 25 |
if TYPE_CHECKING:
|
| 26 |
from src.core.ai_client import AIClientManager
|
| 27 |
+
from src.core.spiritual_classes import DistressClassification, ReferralMessage
|
| 28 |
|
| 29 |
import re
|
| 30 |
|
|
|
|
| 82 |
|
| 83 |
# ===== ENHANCED DATA STRUCTURES =====
|
| 84 |
|
| 85 |
+
# ===== ASSISTANT MODE ENUM =====
|
| 86 |
+
|
| 87 |
+
class AssistantMode(Enum):
|
| 88 |
+
"""
|
| 89 |
+
Режими роботи асистента.
|
| 90 |
+
|
| 91 |
+
Визначає доступні режими для обробки повідомлень користувача:
|
| 92 |
+
- NONE: Режим не визначено
|
| 93 |
+
- MEDICAL: Медичний режим для обробки медичних питань
|
| 94 |
+
- LIFESTYLE: Режим lifestyle рекомендацій
|
| 95 |
+
- SPIRITUAL: Режим оцінки духовного дистресу
|
| 96 |
+
- COMBINED: Комбінований режим (Lifestyle + Spiritual)
|
| 97 |
+
"""
|
| 98 |
+
NONE = "none"
|
| 99 |
+
MEDICAL = "medical"
|
| 100 |
+
LIFESTYLE = "lifestyle"
|
| 101 |
+
SPIRITUAL = "spiritual"
|
| 102 |
+
COMBINED = "combined"
|
| 103 |
+
|
| 104 |
@dataclass
|
| 105 |
class ClinicalBackground:
|
| 106 |
"""Enhanced clinical background with composition context tracking"""
|
|
|
|
| 194 |
|
| 195 |
@dataclass
|
| 196 |
class SessionState:
|
| 197 |
+
"""Enhanced session state with dynamic prompt context and multi-mode support"""
|
| 198 |
+
current_mode: 'AssistantMode' # Changed from str to AssistantMode enum
|
| 199 |
is_active_session: bool
|
| 200 |
session_start_time: Optional[str]
|
| 201 |
last_controller_decision: Dict
|
| 202 |
+
|
| 203 |
# Lifecycle management
|
| 204 |
lifestyle_session_length: int = 0
|
| 205 |
last_triage_summary: str = ""
|
| 206 |
entry_classification: Dict = None
|
| 207 |
|
| 208 |
+
# Spiritual state (NEW)
|
| 209 |
+
spiritual_assessment: Optional['DistressClassification'] = None
|
| 210 |
+
spiritual_referral: Optional['ReferralMessage'] = None
|
| 211 |
+
spiritual_questions: List[str] = None
|
| 212 |
+
|
| 213 |
+
# Combined mode state (NEW)
|
| 214 |
+
combined_results: Dict[str, Any] = None
|
| 215 |
+
active_assistants: List[str] = None
|
| 216 |
+
|
| 217 |
+
# Dynamic prompt composition state
|
| 218 |
current_prompt_composition_id: Optional[str] = None
|
| 219 |
composition_analytics: Dict = None
|
| 220 |
|
|
|
|
| 223 |
self.entry_classification = {}
|
| 224 |
if self.composition_analytics is None:
|
| 225 |
self.composition_analytics = {}
|
| 226 |
+
if self.spiritual_questions is None:
|
| 227 |
+
self.spiritual_questions = []
|
| 228 |
+
if self.combined_results is None:
|
| 229 |
+
self.combined_results = {}
|
| 230 |
+
if self.active_assistants is None:
|
| 231 |
+
self.active_assistants = []
|
| 232 |
+
|
| 233 |
+
def reset(self):
|
| 234 |
+
"""Скидає стан сесії, очищуючи всі поля"""
|
| 235 |
+
self.is_active_session = False
|
| 236 |
+
self.session_start_time = None
|
| 237 |
+
self.last_controller_decision = {}
|
| 238 |
+
self.lifestyle_session_length = 0
|
| 239 |
+
self.last_triage_summary = ""
|
| 240 |
+
self.entry_classification = {}
|
| 241 |
+
self.spiritual_assessment = None
|
| 242 |
+
self.spiritual_referral = None
|
| 243 |
+
self.spiritual_questions = []
|
| 244 |
+
self.combined_results = {}
|
| 245 |
+
self.active_assistants = []
|
| 246 |
+
self.current_prompt_composition_id = None
|
| 247 |
+
self.composition_analytics = {}
|
| 248 |
+
|
| 249 |
+
def update_mode(self, new_mode: 'AssistantMode'):
|
| 250 |
+
"""Оновлює поточний режим роботи"""
|
| 251 |
+
self.current_mode = new_mode
|
| 252 |
+
# Оновлюємо список активних асистентів
|
| 253 |
+
if new_mode == AssistantMode.COMBINED:
|
| 254 |
+
self.active_assistants = ["lifestyle", "spiritual"]
|
| 255 |
+
elif new_mode == AssistantMode.LIFESTYLE:
|
| 256 |
+
self.active_assistants = ["lifestyle"]
|
| 257 |
+
elif new_mode == AssistantMode.SPIRITUAL:
|
| 258 |
+
self.active_assistants = ["spiritual"]
|
| 259 |
+
elif new_mode == AssistantMode.MEDICAL:
|
| 260 |
+
self.active_assistants = ["medical"]
|
| 261 |
+
else:
|
| 262 |
+
self.active_assistants = []
|
| 263 |
+
|
| 264 |
+
def get_active_assistants(self) -> List[str]:
|
| 265 |
+
"""Повертає список активних асистентів"""
|
| 266 |
+
return self.active_assistants.copy()
|
| 267 |
|
| 268 |
# ===== ENHANCED LIFESTYLE ASSISTANT WITH DYNAMIC PROMPTS =====
|
| 269 |
|
|
|
|
| 1255 |
self.api = api
|
| 1256 |
|
| 1257 |
def classify(self, user_message: str, clinical_background: ClinicalBackground) -> Dict:
|
| 1258 |
+
"""
|
| 1259 |
+
Класифікує повідомлення та повертає K/L/S/T формат.
|
| 1260 |
+
|
| 1261 |
+
Returns:
|
| 1262 |
+
Dict з полями:
|
| 1263 |
+
- K: "none" | "minor" | "urgent" (медичні індикатори)
|
| 1264 |
+
- L: "off" | "on" (lifestyle інди��атори)
|
| 1265 |
+
- S: "off" | "on" (spiritual індикатори)
|
| 1266 |
+
- T: "routine" | "urgent" | "emergency" (терміновість)
|
| 1267 |
+
- reasoning: str (пояснення класифікації)
|
| 1268 |
+
- recommended_mode: Optional[str] (рекомендований режим)
|
| 1269 |
+
"""
|
| 1270 |
|
| 1271 |
system_prompt = SYSTEM_PROMPT_ENTRY_CLASSIFIER
|
| 1272 |
user_prompt = PROMPT_ENTRY_CLASSIFIER(clinical_background, user_message)
|
|
|
|
| 1281 |
try:
|
| 1282 |
classification = _extract_json_object(response)
|
| 1283 |
|
| 1284 |
+
# Валідація формату K/L/S/T
|
| 1285 |
+
if not all(key in classification for key in ["K", "L", "S", "T"]):
|
| 1286 |
+
raise ValueError("Missing K/L/S/T keys")
|
| 1287 |
|
| 1288 |
+
# Валідація значень K
|
| 1289 |
+
if classification["K"] not in ["none", "minor", "urgent"]:
|
| 1290 |
+
classification["K"] = "none" # fallback
|
| 1291 |
+
|
| 1292 |
+
# Валідація значень L
|
| 1293 |
+
if classification["L"] not in ["on", "off"]:
|
| 1294 |
+
classification["L"] = "off" # fallback
|
| 1295 |
+
|
| 1296 |
+
# Валідація значень S
|
| 1297 |
+
if classification["S"] not in ["on", "off"]:
|
| 1298 |
+
classification["S"] = "off" # fallback
|
| 1299 |
+
|
| 1300 |
+
# Валідація значень T
|
| 1301 |
+
if classification["T"] not in ["routine", "urgent", "emergency"]:
|
| 1302 |
+
classification["T"] = "routine" # fallback
|
| 1303 |
+
|
| 1304 |
+
# Додаємо reasoning якщо немає
|
| 1305 |
+
if "reasoning" not in classification:
|
| 1306 |
+
classification["reasoning"] = "Classification completed"
|
| 1307 |
+
|
| 1308 |
+
# Визначаємо рекомендований режим
|
| 1309 |
+
classification["recommended_mode"] = self._determine_recommended_mode(classification)
|
| 1310 |
|
|
|
|
|
|
|
|
|
|
| 1311 |
return classification
|
| 1312 |
+
except Exception as e:
|
| 1313 |
+
# Fallback при помилці парсингу
|
| 1314 |
return {
|
| 1315 |
+
"K": "none",
|
| 1316 |
+
"L": "off",
|
| 1317 |
+
"S": "off",
|
| 1318 |
+
"T": "routine",
|
| 1319 |
+
"reasoning": f"Classification error: {str(e)}. Using safe defaults.",
|
| 1320 |
+
"recommended_mode": "medical"
|
| 1321 |
}
|
| 1322 |
|
| 1323 |
+
def _determine_recommended_mode(self, classification: Dict) -> str:
|
| 1324 |
+
"""
|
| 1325 |
+
Визначає рекомендований режим на основі класифікації.
|
| 1326 |
+
|
| 1327 |
+
Логіка:
|
| 1328 |
+
- K="urgent" → medical (пріоритет медичним питанням)
|
| 1329 |
+
- L="on" AND S="on" → combined (обидва типи підтримки)
|
| 1330 |
+
- L="on" AND S="off" → lifestyle
|
| 1331 |
+
- L="off" AND S="on" → spiritual
|
| 1332 |
+
- Інакше → medical (за замовчуванням)
|
| 1333 |
+
"""
|
| 1334 |
+
# Медичні питання мають найвищий пріоритет
|
| 1335 |
+
if classification.get("K") == "urgent":
|
| 1336 |
+
return "medical"
|
| 1337 |
+
|
| 1338 |
+
# Комбінований режим коли потрібні обидва типи підтримки
|
| 1339 |
+
if classification.get("L") == "on" and classification.get("S") == "on":
|
| 1340 |
+
return "combined"
|
| 1341 |
+
|
| 1342 |
+
# Окремі режими
|
| 1343 |
+
if classification.get("L") == "on":
|
| 1344 |
+
return "lifestyle"
|
| 1345 |
+
|
| 1346 |
+
if classification.get("S") == "on":
|
| 1347 |
+
return "spiritual"
|
| 1348 |
+
|
| 1349 |
+
# За замовчуванням медичний режим
|
| 1350 |
+
return "medical"
|
| 1351 |
+
|
| 1352 |
class TriageExitClassifier:
|
| 1353 |
"""Preserved Legacy Class - Triage Exit Assessment"""
|
| 1354 |
|