File size: 6,928 Bytes
8eaa451
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
DeepMed-AI — agents/planner.py
PlannerAgent: quyết định dùng RAG retriever hay LLM trực tiếp.
Nhận diện câu hỏi y tế, tên thuốc, và câu hỏi follow-up.
"""

import re

from app.core.logging_config import logger
from app.core.state import AgentState

# ── Từ khoá y tế — Tiếng Việt + Tiếng Anh ─────────────────────────────────────
MEDICAL_KEYWORDS_VI = [
    # Triệu chứng
    "sốt", "đau", "đau đầu", "buồn nôn", "nôn", "tiêu chảy", "ho",
    "mụn", "da", "phát ban", "ngứa", "cảm", "cúm", "khó thở",
    "đau ngực", "đau bụng", "đau lưng", "đau khớp", "đau cơ",
    "mệt mỏi", "yếu", "chóng mặt", "mất trí nhớ", "co giật",
    "tê", "sưng", "chảy máu", "bầm tím", "giảm cân", "tăng cân",
    "chán ăn", "mất ngủ", "khó ngủ", "hoa mắt",
    # Bệnh lý
    "ung thư", "tiểu đường", "huyết áp", "tim mạch", "đột quỵ", "hen suyễn",
    "viêm phổi", "viêm phế quản", "covid", "corona", "nhiễm trùng",
    "viêm", "vi khuẩn", "vi rút", "nấm", "viêm khớp", "loãng xương",
    "tuyến giáp", "thận", "gan", "viêm gan", "trầm cảm",
    "lo âu", "tâm thần", "alzheimer", "parkinson", "động kinh",
    "viêm ruột", "táo bón", "xuất huyết", "nhiễm khuẩn",
    # Điều trị / Thuốc / Tra cứu
    "điều trị", "thuốc", "phác đồ", "liều lượng", "tác dụng phụ",
    "chẩn đoán", "tiên lượng", "phẫu thuật", "thủ thuật",
    "xét nghiệm", "kết quả máu", "siêu âm", "x-quang", "mri",
    "ct scan", "sinh thiết", "tầm soát", "phòng ngừa", "vaccine",
    "tiêm phòng", "hồi phục", "mãn tính", "cấp tính",
    "hội chứng", "rối loạn", "triệu chứng", "bác sĩ", "bệnh viện",
    "y tế", "sức khỏe", "bệnh nhân", "khám", "toa thuốc",
    "kháng sinh", "giảm đau", "hạ sốt", "chống viêm",
    # *** Tra cứu thông tin thuốc (giá, danh mục, hãng sản xuất...) ***
    "giá", "giá thuốc", "đơn giá", "bao nhiêu tiền", "chi phí",
    "danh mục", "danh mục thuốc", "hãng sản xuất", "nhà sản xuất",
    "hoạt chất", "thành phần", "hàm lượng", "nồng độ", "quy cách",
    "chỉ định", "chống chỉ định", "liều dùng", "cách dùng",
    "tương tác", "bảo quản", "hạn dùng", "đường dùng",
    # Bộ phận cơ thể
    "tim", "phổi", "thận", "gan", "não", "dạ dày", "ruột",
    "máu", "xương", "cơ", "thần kinh", "mắt", "tai", "họng",
    "cổ", "cột sống", "khớp", "đầu", "ngực", "bụng", "chân", "tay",
    "răng", "miệng", "lưỡi", "mũi",
]

MEDICAL_KEYWORDS_EN = [
    "fever", "pain", "headache", "nausea", "vomiting", "diarrhea", "cough",
    "acne", "skin", "rash", "itch", "cold", "flu", "shortness of breath",
    "chest pain", "abdominal pain", "back pain", "joint pain", "muscle pain",
    "fatigue", "weakness", "dizziness", "confusion", "memory loss", "seizure",
    "numbness", "tingling", "swelling", "bleeding", "bruising",
    "cancer", "diabetes", "hypertension", "heart disease", "stroke", "asthma",
    "copd", "pneumonia", "bronchitis", "covid", "coronavirus",
    "infection", "virus", "bacteria", "fungal", "arthritis", "osteoporosis",
    "thyroid", "kidney disease", "liver disease", "hepatitis", "depression",
    "anxiety", "bipolar", "schizophrenia", "alzheimer", "parkinson", "epilepsy",
    "treatment", "therapy", "medication", "medicine", "prescription", "dosage",
    "side effects", "diagnosis", "prognosis", "surgery", "operation",
    "procedure", "test", "lab results", "blood test", "x-ray", "mri",
    "ct scan", "ultrasound", "biopsy", "screening", "prevention", "vaccine",
    "immunization", "rehabilitation", "recovery", "chronic", "acute",
    "syndrome", "disorder", "symptom", "cure", "remedy", "doctor", "hospital",
    "price", "cost", "how much",  # drug price lookups
    "heart", "lung", "kidney", "liver", "brain", "stomach", "intestine",
    "blood", "bone", "muscle", "nerve", "eye", "ear", "throat",
    "neck", "spine", "joint", "head", "chest", "abdomen", "leg", "arm",
]

ALL_MEDICAL_KEYWORDS = set(MEDICAL_KEYWORDS_VI + MEDICAL_KEYWORDS_EN)

# Drug suffix patterns for Title-case detection (Ceftriaxone, Amoxicillin, etc.)
_DRUG_SUFFIXES = re.compile(
    r'\b[A-Z][a-z]{3,}(?:in|ol|am|on|id|il|en|ax|im|an|ex|yl|ne|ate|ide|ine|cin|min|lin|one|fen|lol|tan|pin|pam|ban|mab|nib)\b'
)


def _has_drug_name_in_text(text: str) -> bool:
    """Detect drug names in text: ALLCAPS (MIDANTIN) or Title-case with drug suffix (Ceftriaxone)."""
    # ALL-CAPS words ≥ 3 chars: MIDANTIN, ACC, BFS
    if re.search(r'\b[A-Z][A-Z0-9\-]{2,}\b', text):
        return True
    # Title-case with pharmaceutical suffix: Ceftriaxone, Amoxicillin, Metoprolol
    if _DRUG_SUFFIXES.search(text):
        return True
    return False


def _conversation_mentions_drug(state: AgentState) -> bool:
    """Check if recent conversation history mentions a drug name.
    
    This handles follow-up questions like:
    User: "Ceftriaxone 2000 là gì?"
    User: "giá bao nhiêu?"  ← should still route to retriever!
    """
    for item in state.get("conversation_history", [])[-6:]:
        content = item.get("content", "")
        if _has_drug_name_in_text(content):
            return True
    return False


def PlannerAgent(state: AgentState) -> AgentState:
    """Quyết định dùng RAG retriever hay LLM trực tiếp.

    Logic (ưu tiên từ trên xuống):
    1. Câu hỏi chứa tên thuốc (ALLCAPS hoặc Title-case) → retriever
    2. Câu hỏi chứa từ khoá y tế/dược (giá, liều, thuốc...) → retriever
    3. Conversation history gần đây nhắc đến thuốc (follow-up) → retriever
    4. Ngược lại → LLM trực tiếp
    """
    question = state["question"]
    question_lower = question.lower()

    # Check 1: Drug name in current question
    has_drug = _has_drug_name_in_text(question)

    # Check 2: Medical/pharmaceutical keywords
    has_medical_kw = any(kw in question_lower for kw in ALL_MEDICAL_KEYWORDS)

    # Check 3: Follow-up — recent conversation mentioned a drug
    is_drug_followup = (not has_drug and not has_medical_kw
                        and _conversation_mentions_drug(state))

    use_retriever = has_drug or has_medical_kw or is_drug_followup
    state["current_tool"] = "retriever" if use_retriever else "llm_agent"
    state["retry_count"] = 0

    if is_drug_followup:
        logger.info("Planner: Follow-up detected — recent history has drug name → retriever")
    elif has_drug and not has_medical_kw:
        logger.info("Planner: Drug name '%s' detected → retriever", question[:50])

    return state