File size: 28,731 Bytes
eeb0f9c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
"""
Agent Router - Routes user requests to appropriate specialized agents

Supports two routing strategies:
1. Embedding-based routing (primary) - Automatic, scalable
2. LLM-based routing (fallback) - Manual, explicit
"""

from config.settings import client, MODEL
from typing import List, Dict, Tuple, Optional
import numpy as np

# Try to import embedding model (optional)
try:
    from sentence_transformers import SentenceTransformer
    from sklearn.metrics.pairwise import cosine_similarity
    EMBEDDINGS_AVAILABLE = True
except ImportError:
    EMBEDDINGS_AVAILABLE = False
    print("[WARNING] sentence-transformers not installed. Using LLM-based routing only.")
    print("Install with: pip install sentence-transformers scikit-learn")

# Define available functions/agents
AVAILABLE_FUNCTIONS = [
    {
        "name": "nutrition_agent",
        "description": """Tư vấn dinh dưỡng và chế độ ăn uống:
        - Tính BMI, calo, macro (protein/carb/fat)
        - Lập thực đơn, meal plan
        - Tư vấn thực phẩm nên ăn/tránh
        - Giảm cân, tăng cân, tăng cơ
        - Bổ sung dinh dưỡng, vitamin
        
        KHÔNG dùng cho: triệu chứng bệnh (đau bụng, buồn nôn, tiêu chảy)
        → Triệu chứng bệnh → dùng symptom_agent""",
        "parameters": {
            "type": "object",
            "properties": {
                "user_query": {
                    "type": "string",
                    "description": "Câu hỏi của người dùng về dinh dưỡng"
                },
                "user_data": {
                    "type": "object",
                    "description": "Thông tin người dùng (tuổi, giới tính, cân nặng, chiều cao, mục tiêu)",
                    "properties": {
                        "age": {"type": "integer"},
                        "gender": {"type": "string"},
                        "weight": {"type": "number"},
                        "height": {"type": "number"},
                        "goal": {"type": "string"}
                    }
                }
            },
            "required": ["user_query"]
        }
    },
    {
        "name": "exercise_agent",
        "description": "Tư vấn tập luyện, lịch tập gym, bài tập thể dục, kế hoạch tập luyện, yoga, cardio",
        "parameters": {
            "type": "object",
            "properties": {
                "user_query": {
                    "type": "string",
                    "description": "Câu hỏi của người dùng về tập luyện"
                },
                "user_data": {
                    "type": "object",
                    "description": "Thông tin người dùng (tuổi, giới tính, thể lực, mục tiêu, thời gian)",
                    "properties": {
                        "age": {"type": "integer"},
                        "gender": {"type": "string"},
                        "fitness_level": {"type": "string"},
                        "goal": {"type": "string"},
                        "available_time": {"type": "integer"}
                    }
                }
            },
            "required": ["user_query"]
        }
    },
    {
        "name": "symptom_agent",
        "description": """CLINICAL SYMPTOM ASSESSMENT - Đánh giá triệu chứng bệnh CỤ THỂ:
        
        ✅ USE FOR (Specific symptoms):
        - Pain: đau đầu, đau bụng, đau lưng, đau ngực, đau khớp
        - Fever/Infection: sốt, ho, cảm cúm, viêm họng, viêm phổi
        - Digestive: buồn nôn, nôn, tiêu chảy, táo bón, đầy hơi
        - Neurological: chóng mặt, đau nửa đầu, mất thăng bằng
        - Acute symptoms: triệu chứng đột ngột, bất thường, nghiêm trọng
        
        ✅ WHEN TO USE:
        - User describes SPECIFIC symptom: "Tôi bị đau bụng"
        - User feels sick/unwell: "Tôi không khỏe", "Tôi bị ốm"
        - Medical concern: "Tôi sợ bị bệnh X"
        
        ❌ DO NOT USE FOR:
        - General wellness: "Làm sao để khỏe?" → general_health_agent
        - Prevention: "Phòng ngừa bệnh" → general_health_agent  
        - Lifestyle: "Sống khỏe mạnh" → general_health_agent
        - Nutrition: "Nên ăn gì?" → nutrition_agent
        - Exercise: "Tập gì?" → exercise_agent""",
        "parameters": {
            "type": "object",
            "properties": {
                "user_query": {
                    "type": "string",
                    "description": "Mô tả triệu chứng của người dùng"
                },
                "symptom_data": {
                    "type": "object",
                    "description": "Thông tin triệu chứng (onset, location, severity, duration)",
                    "properties": {
                        "symptom_type": {"type": "string"},
                        "duration": {"type": "string"},
                        "severity": {"type": "integer"},
                        "location": {"type": "string"}
                    }
                }
            },
            "required": ["user_query"]
        }
    },
    {
        "name": "mental_health_agent",
        "description": "Tư vấn sức khỏe tinh thần, stress, lo âu, trầm cảm, burnout, giấc ngủ, cảm xúc",
        "parameters": {
            "type": "object",
            "properties": {
                "user_query": {
                    "type": "string",
                    "description": "Câu hỏi về sức khỏe tinh thần"
                },
                "context": {
                    "type": "object",
                    "description": "Ngữ cảnh (công việc, gia đình, stress level)",
                    "properties": {
                        "stress_level": {"type": "string"},
                        "duration": {"type": "string"},
                        "triggers": {"type": "array", "items": {"type": "string"}}
                    }
                }
            },
            "required": ["user_query"]
        }
    },
    {
        "name": "general_health_agent",
        "description": """GENERAL WELLNESS & LIFESTYLE - Tư vấn sức khỏe TỔNG QUÁT:
        
        ✅ USE FOR (General health & wellness):
        - Wellness: "Làm sao để khỏe mạnh?", "Sống khỏe"
        - Prevention: "Phòng ngừa bệnh", "Tăng sức đề kháng"
        - Lifestyle: "Lối sống lành mạnh", "Thói quen tốt"
        - General advice: "Tư vấn sức khỏe", "Chăm sóc sức khỏe"
        - Health education: "Kiến thức sức khỏe", "Hiểu về cơ thể"
        - Check-ups: "Khám sức khỏe định kỳ", "Xét nghiệm gì?"
        
        ✅ WHEN TO USE:
        - Broad health questions: "Tôi muốn khỏe hơn"
        - No specific symptom: "Tư vấn sức khỏe tổng quát"
        - Prevention focus: "Làm gì để không bị ốm?"
        - Lifestyle optimization: "Cải thiện sức khỏe"
        
        ❌ DO NOT USE FOR:
        - Specific symptoms: "Tôi bị đau bụng" → symptom_agent
        - Nutrition details: "Lập thực đơn" → nutrition_agent
        - Exercise plans: "Lịch tập gym" → exercise_agent
        - Mental health: "Stress, lo âu" → mental_health_agent""",
        "parameters": {
            "type": "object",
            "properties": {
                "user_query": {
                    "type": "string",
                    "description": "Câu hỏi chung về sức khỏe"
                }
            },
            "required": ["user_query"]
        }
    }
]

def route_to_agent(message, chat_history=None):
    """
    Route user message to appropriate specialized agent using function calling
    
    Args:
        message (str): User's message
        chat_history (list): Conversation history for context
    
    Returns:
        dict: {
            "agent": str,  # Agent name
            "parameters": dict,  # Extracted parameters
            "confidence": float  # Routing confidence (0-1)
        }
    """
    
    # Build context from chat history (increased from 3 to 10 for better context)
    context = ""
    last_agent = None
    
    if chat_history:
        recent_messages = chat_history[-10:]  # Last 10 exchanges (was 3)
        
        # Extract last agent from bot response
        if recent_messages:
            last_bot_msg = recent_messages[-1][1] if len(recent_messages[-1]) > 1 else ""
            # Try to detect agent from debug info
            if "Agent used:" in last_bot_msg:
                import re
                match = re.search(r'Agent used: `(\w+)`', last_bot_msg)
                if match:
                    last_agent = match.group(1)
        
        # Build context with turn numbers for clarity
        context_lines = []
        for i, (user_msg, bot_msg) in enumerate(recent_messages, 1):
            # Truncate long messages
            user_short = user_msg[:80] + "..." if len(user_msg) > 80 else user_msg
            bot_short = bot_msg[:80] + "..." if len(bot_msg) > 80 else bot_msg
            context_lines.append(f"Turn {i}:\n  User: {user_short}\n  Bot: {bot_short}")
        
        context = "\n".join(context_lines)
    
    # Create enhanced routing prompt with context awareness
    routing_prompt = f"""Phân tích câu hỏi của người dùng và xác định agent phù hợp nhất.

LỊCH SỬ HỘI THOẠI (10 exchanges gần nhất):
{context if context else "Đây là câu hỏi đầu tiên"}

AGENT TRƯỚC ĐÓ: {last_agent if last_agent else "Chưa có"}

CÂU HỎI HIỆN TẠI: {message}

HƯỚNG DẪN QUAN TRỌNG:

1. **TRIỆU CHỨNG BỆNH CỤ THỂ → symptom_agent (ưu tiên cao nhất)**
   - User MÔ TẢ triệu chứng CỤ THỂ: "tôi bị đau...", "tôi bị sốt", "buồn nôn"
   - Ví dụ: "đau bụng", "đau đầu", "sốt cao", "ho ra máu", "chóng mặt"
   - LUÔN ưu tiên symptom_agent khi có triệu chứng CỤ THỂ!
   
   ⚠️ EDGE CASES - KHÔNG PHẢI symptom_agent:
   - "Làm sao để KHÔNG bị đau đầu?" → general_health_agent (phòng ngừa)
   - "Ăn gì để hết đau bụng?" → nutrition_agent (dinh dưỡng)
   - "Tập gì để hết đau lưng?" → exercise_agent (tập luyện)
   - "Làm sao để khỏe?" → general_health_agent (tổng quát)

2. **DINH DƯỠNG → nutrition_agent**
   - Hỏi về thực phẩm, chế độ ăn, calo, BMI, thực đơn
   - KHÔNG phải triệu chứng bệnh
   - Ví dụ: "nên ăn gì", "giảm cân", "thực đơn"

3. **TẬP LUYỆN → exercise_agent**
   - Hỏi về bài tập, lịch tập, gym, cardio, dụng cụ tập
   - Follow-up về giáo án tập: "không có dụng cụ", "tập tại nhà", "không có tạ"
   - Ví dụ: "nên tập gì", "lịch tập gym", "không có dụng cụ gym"
   - **QUAN TRỌNG:** Nếu đang nói về tập luyện → TIẾP TỤC exercise_agent

4. **SỨC KHỎE TINH THẦN → mental_health_agent**
   - Stress, lo âu, trầm cảm, burnout, giấc ngủ
   - Ví dụ: "tôi stress", "lo âu", "mất ngủ"

5. **SỨC KHỎE TỔNG QUÁT → general_health_agent**
   - Câu hỏi CHUNG về sức khỏe, wellness, lifestyle
   - Phòng ngừa, tăng cường sức khỏe
   - Ví dụ: "làm sao để khỏe?", "phòng bệnh", "sống khỏe"
   
   ⚠️ EDGE CASES - Phân biệt với symptom_agent:
   - "Tôi BỊ đau bụng" → symptom_agent (có triệu chứng)
   - "Làm sao để KHÔNG bị đau bụng?" → general_health_agent (phòng ngừa)
   - "Tôi không khỏe" (mơ hồ) → general_health_agent (chung chung)
   - "Tôi bị sốt cao" → symptom_agent (triệu chứng cụ thể)

VÍ DỤ ROUTING (Bao gồm edge cases):

**Symptom Agent (có triệu chứng CỤ THỂ):**
✅ "Tôi bị đau bụng" → symptom_agent
✅ "Đau đầu từ sáng" → symptom_agent
✅ "Buồn nôn, muốn làm sao cho hết" → symptom_agent
✅ "Tôi bị sốt cao 39 độ" → symptom_agent

**General Health Agent (phòng ngừa, tổng quát):**
✅ "Làm sao để khỏe mạnh?" → general_health_agent
✅ "Phòng ngừa đau đầu" → general_health_agent (phòng ngừa!)
✅ "Tôi muốn sống khỏe hơn" → general_health_agent
✅ "Tư vấn sức khỏe tổng quát" → general_health_agent

**Nutrition Agent (dinh dưỡng):**
✅ "Tôi muốn giảm cân" → nutrition_agent
✅ "Nên ăn gì để khỏe?" → nutrition_agent
✅ "Ăn gì để hết đau bụng?" → nutrition_agent (dinh dưỡng!)

**Exercise Agent (tập luyện):**
✅ "Tôi nên tập gì?" → exercise_agent
✅ "Tập gì để hết đau lưng?" → exercise_agent (tập luyện!)
✅ "Không có dụng cụ gym thì sao?" (context: tập) → exercise_agent

**Mental Health Agent:**
✅ "Tôi stress quá" → mental_health_agent

**QUAN TRỌNG - CONTEXT AWARENESS:**
- Nếu last_agent = "exercise_agent" và câu hỏi về "dụng cụ", "tạ", "gym", "tại nhà"
  → TIẾP TỤC exercise_agent (đây là follow-up!)
- Nếu last_agent = "nutrition_agent" và câu hỏi về "món ăn", "thực đơn", "calo"
  → TIẾP TỤC nutrition_agent (đây là follow-up!)

Hãy chọn agent phù hợp nhất dựa trên CẢ câu hỏi hiện tại VÀ ngữ cảnh hội thoại."""

    try:
        response = client.chat.completions.create(
            model=MODEL,
            messages=[
                {
                    "role": "system",
                    "content": """Bạn là hệ thống định tuyến thông minh với khả năng HIỂU NGỮ CẢNH hội thoại.

NHIỆM VỤ: Phân tích câu hỏi trong NGỮCẢNH cuộc hội thoại và chọn agent phù hợp.

KỸ NĂNG QUAN TRỌNG:
1. Nhận biết câu hỏi follow-up (vậy, còn, thì sao, nữa)
2. Hiểu context từ lịch sử hội thoại
3. Phát hiện topic switching (chuyển đề rõ ràng)
4. Xử lý câu hỏi mơ hồ bằng cách xem context

NGUYÊN TẮC:
- Câu hỏi RÕ RÀNG → agent trực tiếp
- Câu hỏi MƠ HỒ → xem lịch sử, last agent
- Follow-up question → có thể tiếp tục agent cũ
- Topic switch rõ ràng → agent mới"""
                },
                {
                    "role": "user",
                    "content": routing_prompt
                }
            ],
            functions=AVAILABLE_FUNCTIONS,
            function_call="auto",
            temperature=0.3  # Lower temperature for more consistent routing
        )
        
        # Check if function was called
        if response.choices[0].message.function_call:
            function_call = response.choices[0].message.function_call
            
            import json
            parameters = json.loads(function_call.arguments)
            
            return {
                "agent": function_call.name,
                "parameters": parameters,
                "confidence": 0.9,  # High confidence when function is called
                "raw_response": response
            }
        else:
            # No function called, default to general health agent
            return {
                "agent": "general_health_agent",
                "parameters": {"user_query": message},
                "confidence": 0.5,
                "raw_response": response
            }
            
    except Exception as e:
        print(f"Routing error: {e}")
        # Fallback to general health agent
        return {
            "agent": "general_health_agent",
            "parameters": {"user_query": message},
            "confidence": 0.3,
            "error": str(e)
        }

def get_agent_description(agent_name):
    """Get description of an agent"""
    for func in AVAILABLE_FUNCTIONS:
        if func["name"] == agent_name:
            return func["description"]
    return "Unknown agent"


# ============================================================
# Embedding-Based Router (New, Scalable Approach)
# ============================================================

class EmbeddingRouter:
    """
    Embedding-based router that automatically matches queries to agents
    without manual rules. More scalable than LLM-based routing.
    """
    
    def __init__(self, use_embeddings=True):
        """
        Initialize router
        
        Args:
            use_embeddings: If False, falls back to LLM-based routing
        """
        self.use_embeddings = use_embeddings and EMBEDDINGS_AVAILABLE
        
        if self.use_embeddings:
            # Load embedding model
            self.embedder = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
            
            # Agent descriptions for embedding matching
            self.agent_descriptions = {
                "symptom_agent": """
                    Đánh giá triệu chứng bệnh khi BỊ ĐAU hoặc KHÔNG KHỎE: 
                    đau đầu, đau bụng, đau lưng, sốt, ho, buồn nôn, chóng mặt, 
                    mệt mỏi bất thường, khó thở, đau ngực, bị bệnh, cảm thấy đau, 
                    đau nhức, triệu chứng bệnh lý, không khỏe, ốm, bệnh, 
                    đang bị gì, bị gì vậy, triệu chứng gì
                """,
                "nutrition_agent": """
                    Tư vấn dinh dưỡng, ăn uống healthy, chế độ ăn: 
                    giảm cân, tăng cân, giảm mỡ, muốn gầy, muốn béo, 
                    ăn gì để giảm cân, ăn gì để tăng cân, calo, BMI, 
                    thực đơn, chế độ ăn kiêng, thực phẩm, protein, carb, fat, 
                    vitamin, khoáng chất, dinh dưỡng lành mạnh, ăn uống khoa học,
                    setup plan ăn uống, kế hoạch dinh dưỡng, healthy eating,
                    ăn healthy, ăn sạch, clean eating
                """,
                "exercise_agent": """
                    Tập luyện, gym, workout, fitness, thể hình:
                    tập luyện, luyện tập, gym, cardio, bài tập, lịch tập, 
                    dụng cụ tập, tạ, thanh đòn, tập tại nhà, không có dụng cụ, 
                    squat, push-up, plank, chạy bộ, yoga, thể dục, thể hình, 
                    rèn luyện cơ thể, tăng cơ, giảm mỡ, build muscle, lose fat,
                    setup plan tập luyện, kế hoạch tập luyện, lịch tập 7 ngày,
                    tập gym, tập thể hình, workout plan, fitness plan
                """,
                "mental_health_agent": """
                    Sức khỏe tinh thần, stress, lo âu, trầm cảm, burnout, 
                    mất ngủ, giấc ngủ, căng thẳng, áp lực, tâm lý, cảm xúc, 
                    buồn bã, mệt mỏi tinh thần
                """,
                "general_health_agent": """
                    Câu hỏi chung về sức khỏe, lời khuyên sức khỏe, 
                    phòng bệnh, chăm sóc sức khỏe, kiểm tra sức khỏe
                """
            }
            
            # Pre-compute agent embeddings
            print("[INFO] Pre-computing agent embeddings...")
            self.agent_embeddings = {
                agent: self.embedder.encode(desc)
                for agent, desc in self.agent_descriptions.items()
            }
            print("[INFO] Embedding router ready!")
        else:
            print("[INFO] Using LLM-based routing (embeddings not available)")
    
    def route(self, message: str, chat_history: List[Tuple[str, str]] = None) -> Dict:
        """
        Route message to appropriate agent
        
        Args:
            message: User message
            chat_history: Conversation history
        
        Returns:
            {
                "agent": agent_name,
                "parameters": {...},
                "confidence": float,
                "method": "embedding" or "llm"
            }
        """
        if self.use_embeddings:
            return self._route_with_embeddings(message, chat_history)
        else:
            # Fallback to LLM-based routing
            return route_to_agent(message, chat_history)
    
    def _route_with_embeddings(self, message: str, chat_history: List[Tuple[str, str]]) -> Dict:
        """Route using embedding similarity with topic change detection"""
        # Embed query
        query_embedding = self.embedder.encode(message)
        
        # Calculate similarity with each agent
        similarities = {}
        for agent, agent_embedding in self.agent_embeddings.items():
            similarity = cosine_similarity(
                query_embedding.reshape(1, -1),
                agent_embedding.reshape(1, -1)
            )[0][0]
            similarities[agent] = similarity
        
        # Detect topic change vs follow-up
        is_topic_change = self._detect_topic_change(message, chat_history)
        
        # Context boost ONLY for genuine follow-ups (NOT topic changes)
        if not is_topic_change and chat_history and len(chat_history) > 0:
            # Determine CURRENT context from recent conversation (not just last message)
            current_context_agent = self._get_current_context_agent(chat_history)
            
            # Simple heuristic: if query is short and has follow-up indicators
            if len(message.split()) < 10 and any(word in message.lower() for word in ["thì sao", "còn", "nữa", "thế", "vậy", "không", "khác", "nếu"]):
                # Boost ONLY the current context agent (not all agents)
                if current_context_agent and current_context_agent in similarities:
                    similarities[current_context_agent] += 0.15
                    print(f"[ROUTER] Boosting current context agent: {current_context_agent}")
        
        # Get best agent
        best_agent = max(similarities, key=similarities.get)
        confidence = float(similarities[best_agent])
        
        # Debug logging (disabled for cleaner output)
        # print(f"\n[ROUTER DEBUG] Message: {message[:50]}...")
        # print(f"[ROUTER DEBUG] Topic change detected: {is_topic_change}")
        # print(f"[ROUTER DEBUG] Similarities:")
        # for agent, score in sorted(similarities.items(), key=lambda x: x[1], reverse=True):
        #     print(f"  - {agent}: {score:.4f}")
        # print(f"[ROUTER DEBUG] Selected: {best_agent} (confidence: {confidence:.4f})\n")
        
        return {
            "agent": best_agent,
            "parameters": {"user_query": message},
            "confidence": confidence,
            "method": "embedding",
            "all_scores": {k: float(v) for k, v in similarities.items()},
            "topic_change": is_topic_change
        }
    
    def _get_current_context_agent(self, chat_history: List[Tuple[str, str]]) -> Optional[str]:
        """
        Determine which agent is handling the CURRENT context
        by analyzing recent conversation (last 3-5 turns)
        
        Returns:
            Agent name that's currently active, or None
        """
        if not chat_history or len(chat_history) == 0:
            return None
        
        # Check last 3-5 turns for dominant domain
        recent_turns = chat_history[-5:] if len(chat_history) >= 5 else chat_history
        
        domain_keywords = {
            'nutrition_agent': ['ăn', 'dinh dưỡng', 'thực đơn', 'calo', 'bmi', 'giảm cân', 'tăng cân', 'protein', 'carb', 'meal', 'bữa'],
            'exercise_agent': ['tập', 'luyện', 'gym', 'cardio', 'yoga', 'vận động', 'tăng cơ', 'giảm mỡ', 'workout', 'bài tập'],
            'symptom_agent': ['đau', 'bệnh', 'triệu chứng', 'khó chịu', 'buồn nôn', 'sốt', 'ốm'],
            'mental_health_agent': ['stress', 'lo âu', 'mất ngủ', 'trầm cảm', 'tâm lý']
        }
        
        # Count domain occurrences in recent turns
        domain_counts = {agent: 0 for agent in domain_keywords.keys()}
        
        for user_msg, bot_msg in recent_turns:
            combined = (user_msg + " " + bot_msg).lower()
            for agent, keywords in domain_keywords.items():
                for keyword in keywords:
                    if keyword in combined:
                        domain_counts[agent] += 1
                        break  # Count once per turn
        
        # Return agent with highest count (if significant)
        if domain_counts:
            max_agent = max(domain_counts, key=domain_counts.get)
            max_count = domain_counts[max_agent]
            
            # Need at least 2 occurrences in recent turns to be considered "current context"
            if max_count >= 2:
                return max_agent
        
        return None
    
    def _detect_topic_change(self, message: str, chat_history: List[Tuple[str, str]]) -> bool:
        """
        Detect if user is changing topics vs following up
        
        Topic change indicators:
        - Explicit new requests: "tôi muốn", "giúp tôi", "tư vấn về"
        - Different domain keywords: nutrition → exercise, symptom → nutrition
        - Long, detailed new questions
        
        Returns:
            bool: True if topic change, False if follow-up
        """
        msg_lower = message.lower()
        
        # Strong topic change indicators
        topic_change_phrases = [
            'tôi muốn', 'tôi cần', 'giúp tôi', 'tư vấn về', 'cho tôi',
            'bây giờ', 'còn về', 'chuyển sang', 'ngoài ra',
            'setup', 'tạo plan', 'lập kế hoạch'
        ]
        
        if any(phrase in msg_lower for phrase in topic_change_phrases):
            # Likely a new request
            return True
        
        # Check for domain-specific keywords that indicate topic change
        domain_keywords = {
            'nutrition': ['ăn', 'dinh dưỡng', 'thực đơn', 'calo', 'bmi', 'giảm cân', 'tăng cân'],
            'exercise': ['tập', 'luyện', 'gym', 'cardio', 'yoga', 'vận động', 'tăng cơ', 'giảm mỡ'],
            'symptom': ['đau', 'bệnh', 'triệu chứng', 'khó chịu', 'buồn nôn'],
            'mental': ['stress', 'lo âu', 'mất ngủ', 'trầm cảm', 'tâm lý']
        }
        
        # Detect current message domain
        current_domains = []
        for domain, keywords in domain_keywords.items():
            if any(kw in msg_lower for kw in keywords):
                current_domains.append(domain)
        
        # If no chat history, it's a new topic
        if not chat_history or len(chat_history) == 0:
            return True
        
        # Check last few messages for domain
        recent_messages = chat_history[-3:] if len(chat_history) >= 3 else chat_history
        previous_domains = []
        
        for user_msg, bot_msg in recent_messages:
            combined = (user_msg + " " + bot_msg).lower()
            for domain, keywords in domain_keywords.items():
                if any(kw in combined for kw in keywords):
                    if domain not in previous_domains:
                        previous_domains.append(domain)
        
        # If current domain is different from previous, it's a topic change
        if current_domains and previous_domains:
            # Check if there's overlap
            overlap = set(current_domains) & set(previous_domains)
            if not overlap:
                # No overlap = topic change
                return True
        
        # Long messages (>15 words) with new content are likely topic changes
        if len(message.split()) > 15:
            # Check if it's not just elaborating on previous topic
            follow_up_words = ['vì', 'do', 'bởi vì', 'là do', 'nghĩa là']
            if not any(word in msg_lower for word in follow_up_words):
                return True
        
        # Default: assume follow-up
        return False


# Global router instance (lazy initialization)
_router_instance = None

def get_router(use_embeddings=True, force_reload=False) -> EmbeddingRouter:
    """
    Get global router instance
    
    Args:
        use_embeddings: Use embedding-based routing
        force_reload: Force reload router (useful after updating agent descriptions)
    """
    global _router_instance
    if _router_instance is None or force_reload:
        _router_instance = EmbeddingRouter(use_embeddings=use_embeddings)
    return _router_instance