DocUA commited on
Commit
f5f8487
·
1 Parent(s): 4e7f9c7
Files changed (2) hide show
  1. src/config/prompts.py +65 -31
  2. 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. Focus ONLY on the message content, completely ignoring patient's medical history.
9
-
10
- CLASSIFICATION MODES:
11
- - **ON**: Lifestyle, exercise, nutrition, rehabilitation requests
12
- - **OFF**: Medical complaints, symptoms, greetings, general questions
13
- - **HYBRID**: Messages containing BOTH lifestyle requests AND current medical complaints
14
-
15
- AGGRESSIVE LIFESTYLE DETECTION:
16
- If the message contains ANY of these terms, classify as ON regardless of medical history:
17
- - Keywords: exercise, workout, training, fitness, sport, rehabilitation, nutrition, diet, physical, activity, movement, therapy
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  DECISION LOGIC:
20
- 1. **Scan for lifestyle keywords**If found without medical complaints → ON
21
- 2. **Check for medical symptoms**If found without lifestyle content → OFF
22
- 3. **Both present**HYBRID
23
- 4. **Neither present** (greetings, social) OFF
24
-
25
- CLEAR EXAMPLES:
26
- ✅ "I want to start exercising" → ON (sports request)
27
- ✅ "Let's do some exercises" → ON (exercise request)
28
- ✅ "What exercises are suitable for me" → ON (exercise inquiry)
29
- ✅ "Let's talk about rehabilitation" → ON (rehabilitation)
30
- ✅ "want to start working out" → ON (fitness motivation)
31
- "I have a headache" → OFF (medical symptom)
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 aggressive in detecting lifestyle intent
39
- - Medical history does NOT override lifestyle classification
 
40
 
41
  OUTPUT FORMAT (JSON only):
42
  {
43
- "K": "Lifestyle Mode",
44
- "V": "on|off|hybrid",
45
- "T": "YYYY-MM-DDTHH:MM:SSZ"
 
 
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 symptomsSet K level
55
+ 2. Scan for lifestyle keywordsSet L (on/off)
56
+ 3. Scan for emotional/spiritual markers Set S (on/off)
57
+ 4. Assess overall urgencySet 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
- # NEW: Dynamic prompt composition state
 
 
 
 
 
 
 
 
 
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
- """Класифікує повідомлення та повертає K/V/T формат"""
 
 
 
 
 
 
 
 
 
 
 
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/V/T
1202
- if not all(key in classification for key in ["K", "V", "T"]):
1203
- raise ValueError("Missing K/V/T keys")
1204
 
1205
- if classification["V"] not in ["on", "off", "hybrid"]:
1206
- classification["V"] = "off" # fallback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- from datetime import datetime
1214
  return {
1215
- "K": "Lifestyle Mode",
1216
- "V": "off",
1217
- # Використовуємо UTC timestamp у fallback
1218
- "T": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
 
 
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