my-gradio-app / agents /specialized /symptom_agent.py
Nguyen Trong Lap
Recreate history without binary blobs
eeb0f9c
"""
Symptom Agent - Specialized agent for symptom assessment using OPQRST method
"""
from config.settings import client, MODEL
from health_data import HealthContext
from health_analysis import HealthAnalyzer
from rag.rag_integration import get_rag_integration
from agents.core.base_agent import BaseAgent
from agents.core.context_analyzer import ContextAnalyzer
from agents.core.response_validator import ResponseValidator
from typing import Dict, Any, List, Optional
from datetime import datetime
import re
class SymptomAgent(BaseAgent):
def __init__(self, memory=None):
super().__init__(memory)
self.health_context = None
self.analyzer = None
self.rag = get_rag_integration()
# Configure handoff triggers for symptom agent
self.handoff_triggers = {
'nutrition_agent': ['ăn gì', 'thực đơn', 'dinh dưỡng'],
'exercise_agent': ['tập luyện', 'vận động', 'phục hồi chức năng'],
'mental_health_agent': ['lo âu', 'stress', 'mất ngủ do lo'],
'general_health_agent': ['khám tổng quát', 'xét nghiệm', 'kiểm tra sức khỏe']
}
self.system_prompt = """Bạn là bác sĩ tư vấn chuyên nghiệp.
🩺 NHIỆM VỤ:
Thu thập thông tin triệu chứng một cách có hệ thống và chuyên nghiệp.
📋 PHƯƠNG PHÁP OPQRST (Hỏi tự nhiên, KHÔNG dùng template):
**Onset (Khởi phát):**
- Khi nào bắt đầu? Đột ngột hay từ từ?
- Ví dụ tự nhiên:
* Đau đầu: "Đau đầu từ khi nào rồi bạn? Đột ngột hay từ từ?"
* Đầy bụng: "Cảm giác đầy bụng này xuất hiện từ bao giờ? Sau khi ăn hay suốt ngày?"
**Quality (Đặc điểm):**
- Mô tả cảm giác như thế nào?
- Ví dụ tự nhiên:
* Đau đầu: "Đau kiểu gì? Đau nhói, tức, đập thình thình, hay nặng nề?"
* Đầy bụng: "Cảm giác đầy như thế nào? Căng cứng, khó tiêu, hay đau tức?"
**Region (Vị trí):**
- Ở đâu? Có lan ra không?
- Ví dụ tự nhiên:
* Đau đầu: "Đau ở đâu? Trán, thái dương, sau gáy, hay cả đầu?"
* Đầy bụng: "Đầy ở vùng nào? Trên rốn, dưới rốn, hay toàn bộ bụng?"
**Provocation/Palliation (Yếu tố ảnh hưởng):**
- Gì làm tệ/đỡ hơn?
- Ví dụ tự nhiên:
* Đau đầu: "Có gì làm đau nhiều hơn không? Ánh sáng, tiếng ồn, stress? Nghỉ ngơi có đỡ không?"
* Đầy bụng: "Ăn gì làm nặng hơn? Có loại thức ăn nào làm đỡ không?"
**Severity (Mức độ):**
- Mức độ và triệu chứng kèm theo?
- Ví dụ tự nhiên:
* Đau đầu: "Đau nhiều không? Có buồn nôn, nhìn mờ, hoặc sợ ánh sáng không?"
* Đầy bụng: "Đầy nhiều không? Có ợ hơi, buồn nôn, hoặc khó thở không?"
**Timing (Thời gian):**
- Khi nào xuất hiện? Liên tục hay từng đợt?
- Ví dụ tự nhiên:
* Đau đầu: "Đau suốt hay từng cơn? Thường xuất hiện lúc nào trong ngày?"
* Đầy bụng: "Đầy suốt ngày hay chỉ sau ăn? Kéo dài bao lâu?"
🎯 NGUYÊN TẮC QUAN TRỌNG:
1. **HỎI TỐI ĐA 3-4 CÂU:**
- Không hỏi mãi theo template OPQRST
- Hỏi 3-4 câu quan trọng nhất
- Nếu user không biết/không rõ → Chuyển sang đưa khuyến nghị
2. **ƯU TIÊN THÔNG TIN:**
- Câu 1: Thời gian xuất hiện (khi nào?)
- Câu 2: Đặc điểm (đau như thế nào?)
- Câu 3: Mức độ (có triệu chứng kèm theo?)
- Câu 4 (nếu cần): Yếu tố ảnh hưởng
3. **KHI USER KHÔNG BIẾT:**
- User nói "không biết", "không rõ", "không chắc"
- → DỪNG hỏi, chuyển sang đưa khuyến nghị
- Dựa trên thông tin ĐÃ CÓ để tư vấn
4. **ĐƯA KHUYẾN NGHỊ:**
- Tổng hợp thông tin đã thu thập
- Đưa ra các biện pháp tự chăm sóc phù hợp
- Khuyên gặp bác sĩ nếu cần
- KHÔNG hỏi thêm nữa
🚨 RED FLAGS - Khuyên gặp bác sĩ NGAY:
- Đau ngực + khó thở → Nghi ngờ tim
- Đau đầu dữ dội đột ngột + cứng gáy + sốt → Nghi ngờ màng não
- Yếu đột ngột một bên → Nghi ngờ đột quỵ
- Đau bụng dữ dội → Nghi ngờ ruột thừa/cấp cứu
- Ho/nôn ra máu
- Ý định tự tử
⚠️ AN TOÀN & GIỚI HẠN:
- KHÔNG chẩn đoán bệnh
- KHÔNG kê đơn thuốc
- KHÔNG tạo giáo án tập luyện (đó là việc của exercise_agent)
- KHÔNG tư vấn dinh dưỡng chi tiết (đó là việc của nutrition_agent)
- CHỈ tập trung vào ĐÁNH GIÁ TRIỆU CHỨNG
- Luôn khuyên gặp bác sĩ với triệu chứng nghiêm trọng
- Với red flags → khuyên đi cấp cứu NGAY
💬 PHONG CÁCH:
- Tự nhiên, conversational - như đang nói chuyện
- KHÔNG formal, KHÔNG "Dựa trên thông tin bạn cung cấp"
- Emoji tối thiểu (chỉ khi thật sự cần)
- Ngắn gọn, đi thẳng vấn đề
- KHÔNG vừa hỏi vừa khuyên trong cùng 1 response
🏥 KHI USER HỎI ĐỊA CHỈ BỆNH VIỆN:
- ĐỪNG lặp lại triệu chứng nếu đã nói rồi!
- Nếu user hỏi "tôi muốn đi khám", "bệnh viện nào tốt", "cho tôi địa chỉ"
→ ĐI THẲNG VÀO ĐỊA CHỈ, không cần nhắc lại "Triệu chứng đau đầu và mất ngủ..."
- Format địa chỉ bệnh viện:
**Bệnh viện/Phòng khám gần [địa điểm]:**
1. **Tên bệnh viện**
- Địa chỉ: [địa chỉ đầy đủ]
- Chuyên khoa: [chuyên khoa liên quan]
- SĐT: [nếu có]
**Khi nào cần đi khám:** [điều kiện]
- KHÔNG dùng "Giải pháp:" cho danh sách bệnh viện
- KHÔNG mix địa chỉ với home remedies (thiền, yoga) trong cùng list
📝 VÍ DỤ WORKFLOW:
**Tình huống: User đau bụng**
Turn 1:
User: "Tôi đau bụng"
Bot: "Bạn bắt đầu bị đau từ khi nào vậy?"
Turn 2:
User: "Mới xuất hiện, đau âm ỉ"
Bot: "Đau ở vị trí nào? Trên rốn, dưới rốn, hay toàn bộ bụng?"
Turn 3:
User: "Phía trên rốn"
Bot: "Có triệu chứng kèm theo như buồn nôn, ợ hơi, hoặc đầy bụng không?"
Turn 4:
User: "Không biết, giờ tôi muốn làm sao cho hết đau"
Bot: "Dựa trên thông tin bạn cung cấp (đau âm ỉ vùng thượng vị, mới xuất hiện),
đây có thể là triệu chứng của viêm dạ dày hoặc khó tiêu. Khuyến nghị:
1. Nghỉ ngơi, tránh căng thẳng
2. Ăn nhẹ, tránh thức ăn cay nóng, cà phê, rượu
3. Có thể dùng thuốc giảm acid (theo chỉ định)
Nếu đau không giảm sau 24h hoặc xuất hiện triệu chứng nặng hơn
(nôn ra máu, đau dữ dội), hãy đến bệnh viện ngay."
→ DỪNG hỏi, đưa khuyến nghị dựa trên thông tin có!
🎯 NGUYÊN TẮC QUAN TRỌNG:
1. **ƯU TIÊN GIẢI PHÁP KHI USER CẦN:**
- Nếu user nói "đau quá", "khó chịu", "làm sao" → Đưa giải pháp NGAY
- Không hỏi thêm khi user đang cần giúp đỡ khẩn cấp
- Cấu trúc: Giải pháp ngay → Thuốc (nếu cần) → Cảnh báo → Phòng ngừa
2. **CHỈ HỎI KHI CẦN THIẾT:**
- Tối đa 1-2 câu hỏi trong toàn bộ conversation
- Nếu đã có đủ info cơ bản → Đưa lời khuyên luôn
- Nếu user không muốn trả lời → Đưa lời khuyên chung
3. **EMOJI - Dùng tiết kiệm:**
- KHÔNG dùng 😔 cho mọi triệu chứng
- Chỉ dùng khi thực sự cần (trấn an, động viên)
- Có thể không dùng emoji nếu câu đã đủ ấm áp
4. **PHÂN TÍCH CONTEXT:**
- Nếu là câu hỏi ĐẦU TIÊN → có thể đồng cảm
- Nếu đang FOLLOW-UP → đi thẳng vào câu hỏi, không cần lặp lại đồng cảm
- Nếu user đã trả lời nhiều câu → cảm ơn họ, không cần đồng cảm nữa
VÍ DỤ CÁCH HỎI ĐA DẠNG:
❌ SAI (Lặp lại pattern):
Turn 1: "Đau đầu khó chịu lắm nhỉ 😔 Cho mình hỏi..."
Turn 2: "Đau nhói khó chịu quá 😔 Mà này..."
Turn 3: "Sợ ánh sáng khó chịu lắm 😔 Còn về..."
→ LẶP LẠI "khó chịu" + 😔 = MÁY MÓC!
✅ ĐÚNG (Đa dạng, tự nhiên):
Turn 1: "Mình hiểu rồi. Cho mình hỏi, bạn bị đau từ khi nào?"
Turn 2: "À, đau nhói từ 2 ngày trước nhỉ. Vậy đau ở đâu? Trán, thái dương, hay cả đầu?"
Turn 3: "Được rồi. Có gì làm đau nhiều hơn không? Ví dụ ánh sáng, tiếng ồn, hay stress?"
→ BIẾN ĐỔI, TỰ NHIÊN!
VÍ DỤ THEO TRIỆU CHỨNG:
**Đau đầu - Variations:**
- "Cho mình hỏi, bạn bị đau từ khi nào?"
- "Mình hiểu. Đau đầu xuất hiện đột ngột hay từ từ?"
- "Để mình giúp bạn tìm hiểu. Đau kiểu gì? Nhói, tức, hay đập thình thình?"
**Đầy bụng - Variations:**
- "Cảm giác đầy này xuất hiện từ bao giờ?"
- "Liên quan đến ăn uống không? Sau khi ăn hay suốt ngày?"
- "Có ợ hơi hoặc khó tiêu không?"
**Đau lưng - Variations:**
- "Bạn bị đau lưng từ khi nào?"
- "Có bị chấn thương hay làm gì nặng không?"
- "Đau ở vị trí nào? Lưng trên, giữa, hay dưới?"
QUAN TRỌNG:
- Mỗi triệu chứng cần cách hỏi KHÁC NHAU
- Mỗi TURN trong conversation cần cách diễn đạt KHÁC NHAU
- KHÔNG lặp lại patterns - hãy TỰ NHIÊN như người thật!"""
def set_health_context(self, health_context: HealthContext):
"""Inject health context and initialize health analyzer"""
self.health_context = health_context
self.analyzer = HealthAnalyzer(health_context)
def handle(self, parameters, chat_history=None):
"""
Handle symptom assessment request using LLM for natural conversation
Args:
parameters (dict): {"user_query": str}
chat_history (list): Conversation history
Returns:
str: Response message
"""
user_query = parameters.get("user_query", "")
# Check for red flags first
red_flag_response = self._check_red_flags(user_query, chat_history)
if red_flag_response:
# Persist red flag alert
if self.health_context:
self.health_context.add_health_record('symptom', {
'query': user_query,
'type': 'red_flag_alert',
'response': red_flag_response,
'timestamp': datetime.now().isoformat()
})
return red_flag_response
# Use LLM to naturally assess symptoms and ask questions
response = self._natural_symptom_assessment(user_query, chat_history)
# Analyze health risks if analyzer is available
if self.analyzer:
risks = self.analyzer.identify_health_risks()
predictions = self.analyzer.predict_disease_risk()
else:
risks = []
predictions = {}
# Persist symptom data to health context
if self.health_context:
self.health_context.add_health_record('symptom', {
'query': user_query,
'response': response,
'risks': risks,
'predictions': predictions,
'timestamp': datetime.now().isoformat()
})
return response
def _build_context_instruction(self, context, chat_history, user_query=""):
"""
Build clear instruction based on conversation stage
"""
stage = context.get('conversation_stage', 0)
urgency = context.get('urgency', 'medium')
is_vague = context.get('is_vague', False)
# PRIORITY: Handle vague/unclear queries first
if is_vague and stage == 0:
return """\n\nPHASE: LÀM RÕ Ý ĐỊNH (VỚI GỢI Ý)
User query không rõ ràng. Giúp user bằng GỢI Ý CỤ THỂ:
1. ACKNOWLEDGE + HỎI VỚI GỢI Ý:
Format: "Mình thấy bạn [cảm giác user nói]. Bạn có thể cho mình biết rõ hơn không? Ví dụ như:
• [Gợi ý 1 liên quan]
• [Gợi ý 2 liên quan]
• [Gợi ý 3 liên quan]
• Hoặc vấn đề khác?"
2. GỢI Ý DỰA VÀO TỪ KHÓA:
- "mệt" → gợi ý: mệt cơ thể, mệt tinh thần, mất ngủ, stress
- "không khỏe" → gợi ý: đau đầu, buồn nôn, chóng mặt, sốt
- "khó chịu" → gợi ý: đau bụng, khó tiêu, lo âu, căng thẳng
- "không ổn" → gợi ý: sức khỏe thể chất, tinh thần, dinh dưỡng
3. VÍ DỤ CỤ THỂ:
User: "tôi mệt"
Bot: "Mình thấy bạn đang cảm thấy mệt. Bạn có thể nói rõ hơn không? Ví dụ:
• Mệt cơ thể, không có sức?
• Mệt tinh thần, stress?
• Mất ngủ, ngủ không ngon?
• Hay vấn đề khác?"
QUAN TRỌNG:
- Dùng từ khóa user nói để tạo gợi ý phù hợp
- 3-4 gợi ý cụ thể
- Luôn có "hoặc vấn đề khác" để mở rộng
- Tự nhiên, không formal"""
# Assessment phase (first 1-2 turns)
if stage <= 1:
return """\n\nPHASE: ĐÁNH GIÁ TRIỆU CHỨNG
Hỏi 1-2 câu ngắn để hiểu rõ:
- Thời gian xuất hiện
- Vị trí đau
- Mức độ đau
CHỈ HỎi, KHÔNG đưa lời khuyên."""
# High urgency - skip to solutions
if urgency == 'high':
return """\n\nPHASE: GIẢI PHÁP KHẨN CẤP
User cần giúp NGAY. Đưa ra:
1. Giải pháp tức thời (2-3 điềm)
2. Thuốc có thể dùng + disclaimer
3. Cảnh báo khi nào đi khám
KHÔNG hỏi thêm."""
# Check if user is answering self-assessment questions
if chat_history and len(chat_history) > 0:
last_bot_msg = chat_history[-1][1] if len(chat_history[-1]) > 1 else ""
# More specific detection - must have "Câu hỏi tự kiểm tra" section
if ("Câu hỏi tự kiểm tra" in last_bot_msg or "### Câu hỏi tự kiểm tra" in last_bot_msg) and len(user_query) > 30:
return """\n\nPHASE: PHÂN TÍCH KẾT QUẢ TỰ KIỂM TRA
User vừa trả lời self-assessment. Phân tích THÔNG MINH dựa trên ĐÚNG CONTEXT:
QUAN TRỌNG:
- CHỈ phân tích dựa trên triệu chứng user VỪA NÓI
- KHÔNG dùng thông tin từ RAG không liên quan
- KHÔNG nhầm lẫn với các bệnh khác
1. NHẬN DIỆN PATTERN (dựa vào RAG knowledge):
- Đọc kỹ triệu chứng user mô tả
- So sánh với các bệnh có thể có (từ RAG)
- Tìm điểm KHÁC BIỆT quan trọng
- Đưa ra 1-2 khả năng phù hợp nhất
2. ĐÁNH GIÁ MỨC ĐỘ NGHIÊM TRỌNG:
- Có red flags? → "CẦN KHÁM NGAY"
- Triệu chứng nặng? → "Nên đi khám sớm"
- Triệu chứng nhẹ? → "Thử giải pháp, không đỡ thì khám"
3. LUÔN DISCLAIMER:
"Đây chỉ là đánh giá sơ bộ dựa trên triệu chứng. Bác sĩ sẽ chẩn đoán chính xác qua khám lâm sàng và xét nghiệm."
4. NEXT STEPS:
- Giải pháp tạm thời (nếu không nguy hiểm)
- Khi nào cần đi khám
- "Bạn muốn biết thêm về [bệnh nghi ngờ] không?"
QUAN TRỌNG: Phân tích GENERIC cho MỌI triệu chứng, KHÔNG hard-code."""
# Check if user asking "how to know" / differential diagnosis
if any(phrase in user_query.lower() for phrase in [
'làm sao biết', 'làm sao để biết', 'phân biệt',
'khác nhau thế nào', 'hay', 'hoặc'
]):
return """\n\nPHASE: HƯỚNG DẪN TỰ KIỂM TRA (GENERIC)
User muốn phân biệt các bệnh/tình trạng. Sử dụng RAG để:
1. XÁC ĐỊNH các bệnh có thể (từ user query):
- Trích xuất các bệnh user đề cập
- Hoặc tìm các bệnh liên quan đến triệu chứng
2. TẠO BẢNG SO SÁNH:
Format:
**[Bệnh A]:**
• Triệu chứng đặc trưng 1
• Triệu chứng đặc trưng 2
• Đặc điểm riêng
**[Bệnh B]:**
• Triệu chứng đặc trưng 1
• Triệu chứng đặc trưng 2
• Đặc điểm riêng
**Điểm khác biệt chính:** [Highlight key differences]
3. CÂU HỎI TỰ KIỂM TRA:
Tạo 3-5 câu hỏi giúp user tự đánh giá:
• Về thời gian xuất hiện
• Về đặc điểm triệu chứng
• Về yếu tố kích hoạt
• Về triệu chứng kèm theo
4. LUÔN DISCLAIMER:
"Tuy nhiên, chỉ bác sĩ mới chẩn đoán chính xác qua khám lâm sàng và xét nghiệm."
5. Kết thúc: "Sau khi tự kiểm tra, bạn có thể cho mình biết kết quả để mình phân tích nhé!"
QUAN TRỌNG: Dùng RAG knowledge, KHÔNG hard-code bệnh cụ thể."""
# Advice phase (have enough info)
return """\n\nPHASE: TƯ VẤN & PHÒNG NGỪÀ
Đưa ra:
1. Đánh giá ngắn (1 câu): Triệu chứng có thể là gì
2. Giải pháp (3-4 điểm cụ thể)
3. Khi nào cần đi khám
4. Kết thúc: "Có gì thắc mắc cứ hỏi mình nhé!"
KHÔNG nói "Dựa trên thông tin"."""
def _validate_response(self, response, context):
"""
Validate if LLM response follows instructions
Returns: (is_valid, list_of_issues)
"""
issues = []
stage = context.get('conversation_stage', 0)
# Check for bad formal phrases
bad_phrases = [
"Dựa trên thông tin bạn cung cấp",
"Dựa vào thông tin",
"Theo thông tin bạn đưa ra"
]
for phrase in bad_phrases:
if phrase.lower() in response.lower():
issues.append(f"Dùng cụm từ formal: '{phrase}'")
break
# Assessment phase: should ask, not advise
if stage <= 1:
advice_indicators = [
"khuyến nghị", "nên", "hãy", "bạn thử",
"giải pháp", "cách xử lý"
]
has_advice = any(ind in response.lower() for ind in advice_indicators)
has_question = '?' in response
if has_advice and not has_question:
issues.append("Đưa lời khuyên quá sớm (phase assessment)")
# Check if both asking and advising (bad)
if '?' in response:
advice_count = sum(1 for ind in ["khuyến nghị", "nên", "hãy thử"] if ind in response.lower())
if advice_count >= 2:
issues.append("Vừa hỏi vừa khuyên trong cùng response")
is_valid = len(issues) == 0
return is_valid, issues
def _post_process_response(self, response, context):
"""
Clean up LLM response to ensure quality
"""
# Remove formal phrases
bad_phrases = [
"Dựa trên thông tin bạn cung cấp",
"Dựa vào thông tin",
"Theo thông tin bạn đưa ra",
"Từ thông tin trên"
]
for phrase in bad_phrases:
response = response.replace(phrase, "")
response = response.replace(phrase.lower(), "")
# Clean up extra whitespace
response = "\n".join(line.strip() for line in response.split("\n") if line.strip())
return response
def _check_red_flags(self, user_query, chat_history):
"""Check for dangerous symptoms that need immediate medical attention"""
all_text = user_query.lower()
if chat_history:
all_text += " " + " ".join([msg[0].lower() for msg in chat_history if msg[0]])
red_flags = {
"heart_attack": {
"keywords": ["đau ngực", "khó thở", "chest pain", "đau tim"],
"message": """🚨 **CẢNH BÁO KHẨN CẤP**
Triệu chứng của bạn có thể liên quan đến **cơn đau tim**. Đây là tình huống khẩn cấp!
⚠️ **HÃY LÀM NGAY:**
1. **Gọi cấp cứu 115** hoặc đến bệnh viện GẤP
2. Ngồi nghỉ, không vận động
3. Nếu có aspirin, nhai 1 viên (nếu không dị ứng)
4. Thông báo cho người thân
🚑 **KHÔNG TỰ LÁI XE** - Gọi xe cấp cứu hoặc nhờ người khác đưa đi
Sức khỏe của bạn là ưu tiên số 1. Hãy đi khám NGAY nhé! 💙"""
},
"stroke": {
"keywords": ["yếu một bên", "méo miệng", "nói khó", "tê nửa người"],
"message": """🚨 **CẢNH BÁO KHẨN CẤP - NGUY CƠ ĐỘT QUỴ**
Triệu chứng của bạn có thể là **đột quỵ não**. Mỗi phút đều quan trọng!
⚠️ **HÃY LÀM NGAY:**
1. **Gọi cấp cứu 115 NGAY LẬP TỨC**
2. Ghi nhớ thời gian triệu chứng bắt đầu
3. Nằm nghỉ, đầu hơi cao
4. KHÔNG cho ăn uống gì
🚑 Đây là cấp cứu y tế. Hãy đi bệnh viện NGAY! Thời gian vàng chỉ có 3-4 giờ!"""
},
"meningitis": {
"keywords": ["đau đầu dữ dội", "cứng gáy", "sốt cao", "buồn nôn"],
"message": """🚨 **CẢNH BÁO - CẦN KHÁM NGAY**
Triệu chứng đau đầu dữ dội + cứng gáy + sốt có thể là **viêm màng não** - rất nguy hiểm!
⚠️ **HÃY LÀM NGAY:**
1. Đi bệnh viện hoặc gọi cấp cứu 115
2. Không trì hoãn
3. Thông báo bác sĩ về tất cả triệu chứng
Đây là tình huống nghiêm trọng. Hãy đi khám NGAY nhé! 🏥"""
},
"severe_abdominal": {
"keywords": ["đau bụng dữ dội", "đau bụng không chịu nổi", "đau bụng cấp"],
"message": """⚠️ **CẦN KHÁM BÁC SĨ NGAY**
Đau bụng dữ dội có thể là nhiều nguyên nhân nghiêm trọng (viêm ruột thừa, sỏi mật, thủng dạ dày...).
🏥 **Hãy đi khám ngay nếu:**
- Đau không giảm sau 1-2 giờ
- Kèm sốt, nôn, tiêu chảy
- Bụng cứng, đau khi ấn
- Có máu trong phân
Đừng chần chừ, hãy đi bệnh viện để được khám và xử lý kịp thời nhé!"""
}
}
for flag_type, flag_data in red_flags.items():
if any(keyword in all_text for keyword in flag_data["keywords"]):
return flag_data["message"]
return None
def _needs_rag_query(self, user_query, chat_history):
"""Determine if RAG query is needed for this question"""
# Simple questions don't need RAG
simple_patterns = [
'đau', 'bị', 'khó tiêu', 'mệt', 'chóng mặt', 'buồn nôn',
'sốt', 'ho', 'cảm', 'đau đầu', 'đau bụng', 'đau lưng'
]
# Check if it's a simple symptom report (first turn)
if not chat_history or len(chat_history) == 0:
# First message - usually just symptom report
if any(pattern in user_query.lower() for pattern in simple_patterns):
return False # Don't need RAG for initial symptom report
# Need RAG for complex questions or specific medical info
complex_patterns = [
'nguyên nhân', 'tại sao', 'làm sao', 'điều trị', 'thuốc',
'phòng ngừa', 'biến chứng', 'triệu chứng của', 'bệnh gì'
]
if any(pattern in user_query.lower() for pattern in complex_patterns):
return True
# Default: don't use RAG for conversational turns
return False
def _natural_symptom_assessment(self, user_query, chat_history):
"""Use LLM to naturally assess symptoms with context awareness"""
try:
# Analyze user context and intent
context = ContextAnalyzer.analyze_user_intent(user_query, chat_history)
response_structure = ContextAnalyzer.determine_response_structure(context)
# Smart RAG - only query when needed
rag_answer = ''
rag_sources = []
if self._needs_rag_query(user_query, chat_history):
# Build context-aware RAG query
# Include recent conversation context to get relevant results
if chat_history and len(chat_history) > 0:
last_exchange = chat_history[-1]
last_bot_msg = last_exchange[1] if len(last_exchange) > 1 else ""
# If answering self-assessment, include the diseases mentioned
if "Câu hỏi tự kiểm tra" in last_bot_msg:
# Extract diseases from last bot message
import re
diseases = re.findall(r'\*\*\[(.*?)\]:\*\*', last_bot_msg)
if diseases:
# Query specifically about those diseases
enhanced_query = f"{user_query} (liên quan đến: {', '.join(diseases)})"
rag_result = self.rag.query_health(enhanced_query)
else:
rag_result = self.rag.query_health(user_query)
else:
rag_result = self.rag.query_health(user_query)
else:
rag_result = self.rag.query_health(user_query)
rag_answer = rag_result.get('answer', '')
rag_sources = rag_result.get('source_docs', [])
# Build conversation context
messages = [{"role": "system", "content": self.system_prompt}]
# Add RAG context if available with explicit filtering instruction
if rag_answer:
# Add warning about context relevance
rag_instruction = f"""Thông tin tham khảo từ cơ sở dữ liệu:\n{rag_answer}
⚠️ QUAN TRỌNG:
- CHỈ sử dụng thông tin LIÊN QUAN đến triệu chứng user đang nói
- KHÔNG dùng thông tin về bệnh khác không liên quan
- Nếu thông tin RAG không match với triệu chứng user → BỎ QUA"""
messages.append({"role": "system", "content": rag_instruction})
# Add chat history (last 5 exchanges for context)
if chat_history:
recent_history = chat_history[-5:] if len(chat_history) > 5 else chat_history
for user_msg, bot_msg in recent_history:
if user_msg:
messages.append({"role": "user", "content": user_msg})
if bot_msg:
messages.append({"role": "assistant", "content": bot_msg})
# Build context-aware instruction
context_prompt = self._build_context_instruction(context, chat_history, user_query)
messages.append({"role": "user", "content": user_query + context_prompt})
# Get LLM response
response = client.chat.completions.create(
model=MODEL,
messages=messages,
temperature=0.7,
max_tokens=500
)
llm_response = response.choices[0].message.content
# CRITICAL: Check for context mismatch (e.g., talking about brain when discussing stomach)
if chat_history and len(chat_history) > 0:
# Get recent symptoms mentioned
recent_symptoms = []
for msg, _ in chat_history[-3:]:
if msg:
recent_symptoms.extend([
'đau bụng', 'dạ dày', 'tiêu hóa', 'ăn', 'buồn nôn', 'ợ'
] if any(w in msg.lower() for w in ['bụng', 'dạ dày', 'ăn', 'nôn', 'ợ']) else [])
# Check if response mentions completely unrelated conditions
unrelated_keywords = {
'stomach': ['viêm màng não', 'cứng gáy', 'não'],
'head': ['đau bụng', 'tiêu hóa', 'dạ dày'],
'respiratory': ['đau bụng', 'dạ dày']
}
# If discussing stomach but response mentions brain → REJECT
if recent_symptoms and any('bụng' in s or 'dạ dày' in s for s in recent_symptoms):
if any(keyword in llm_response.lower() for keyword in ['viêm màng não', 'cứng gáy', 'não', 'đầu dữ dội']):
print("⚠️ CONTEXT MISMATCH DETECTED: Response about brain when discussing stomach!")
# Force retry with explicit instruction
messages[-1]['content'] += "\n\n🚨 LỖI NGHIÊM TRỌNG: User đang nói về BỤng/DẠ DÀY, KHÔNG phải đầu/não! Phân tích lại ĐÚNG triệu chứng!"
retry_response = client.chat.completions.create(
model=MODEL,
messages=messages,
temperature=0.3, # Very low temp for accuracy
max_tokens=500
)
llm_response = retry_response.choices[0].message.content
# Validate response quality using shared validator
is_valid, issues = ResponseValidator.validate_response(
llm_response,
agent_type='symptom',
context=context,
chat_history=chat_history
)
# Retry if invalid (max 1 retry)
if not is_valid:
print(f"Response validation failed: {issues}. Retrying...")
# Add stronger instruction
messages[-1]['content'] += f"\n\nLỖI TRƯỚC: {', '.join(issues)}. HÃY SỬA LẠI!"
retry_response = client.chat.completions.create(
model=MODEL,
messages=messages,
temperature=0.5, # Lower temp for more control
max_tokens=500
)
llm_response = retry_response.choices[0].message.content
# Post-process to ensure quality
llm_response = self._post_process_response(llm_response, context)
# Add sources using RAG integration formatter (FIXED!)
if rag_sources:
formatted_response = self.rag.format_response_with_sources({
'answer': llm_response,
'source_docs': rag_sources
})
return formatted_response
return llm_response
except Exception as e:
return f"""Xin lỗi, mình gặp lỗi kỹ thuật. Bạn có thể:
1. Thử lại câu hỏi
2. Hoặc nếu triệu chứng nghiêm trọng, hãy gặp bác sĩ ngay nhé 🙏
Lỗi: {str(e)[:100]}"""
def _assess_opqrst_progress(self, chat_history):
"""Assess how much OPQRST data has been collected"""
if not chat_history:
return {'complete': False, 'next_step': 'onset', 'data': {}}
# Analyze conversation to see what's been asked
all_bot_messages = " ".join([msg[1].lower() for msg in chat_history if msg[1]])
all_user_messages = " ".join([msg[0].lower() for msg in chat_history if msg[0]])
opqrst_data = {
'onset': None,
'provocation': None,
'quality': None,
'region': None,
'severity': None,
'timing': None
}
# Check what's been asked
if "khi nào" in all_bot_messages or "bắt đầu" in all_bot_messages:
opqrst_data['onset'] = 'asked'
if "làm tệ hơn" in all_bot_messages or "làm đỡ" in all_bot_messages:
opqrst_data['provocation'] = 'asked'
if "mô tả cảm giác" in all_bot_messages or "đau kiểu gì" in all_bot_messages:
opqrst_data['quality'] = 'asked'
if "vị trí" in all_bot_messages or "ở đâu" in all_bot_messages:
opqrst_data['region'] = 'asked'
if "mức độ" in all_bot_messages or "1-10" in all_bot_messages:
opqrst_data['severity'] = 'asked'
if "lúc nào xuất hiện" in all_bot_messages or "liên tục" in all_bot_messages:
opqrst_data['timing'] = 'asked'
# Determine next step
for step, value in opqrst_data.items():
if value is None:
return {'complete': False, 'next_step': step, 'data': opqrst_data}
# All steps completed
return {'complete': True, 'next_step': None, 'data': opqrst_data}
def _ask_next_opqrst_question(self, next_step, user_query):
"""Ask the next OPQRST question"""
questions = {
'onset': """Mình hiểu rồi. Để đánh giá chính xác hơn, cho mình hỏi thêm nhé:
- Triệu chứng này bắt đầu từ khi nào? (hôm nay, mấy ngày, mấy tuần?)
- Nó xuất hiện đột ngột hay từ từ?""",
'quality': """À được rồi. Mà này:
- Bạn mô tả cảm giác đó như thế nào? (đau nhói, tức, nóng rát, tê, đập thình thình...)
- Mức độ từ 1-10 thì bao nhiêu? (1 = nhẹ, 10 = không chịu nổi)""",
'region': """Ừm, để mình hỏi thêm:
- Vị trí chính xác ở đâu? (chỉ rõ vùng cơ thể)
- Có lan ra chỗ khác không?""",
'provocation': """Bạn có nhận thấy:
- Có gì làm nó tệ hơn không? (vận động, ăn uống, stress, tư thế...)
- Có gì làm nó đỡ hơn không? (nghỉ ngơi, thuốc, chườm...)""",
'timing': """Quan trọng nhé:
- Nó xuất hiện lúc nào trong ngày? (sáng, chiều, tối, đêm?)
- Liên tục hay từng đợt? Mỗi đợt kéo dài bao lâu?""",
'severity': """Cuối cùng:
- Có kèm theo triệu chứng nào khác không? (sốt, buồn nôn, chóng mặt, mệt mỏi...)
- Có ảnh hưởng đến ăn uống, ngủ nghỉ, sinh hoạt không?
- Bạn có bệnh nền gì không? Đang uống thuốc gì không?"""
}
return questions.get(next_step, "Cho mình biết thêm về triệu chứng của bạn nhé?")
def _provide_assessment(self, opqrst_data, user_query):
"""Provide symptom assessment after collecting OPQRST data"""
# Use LLM to analyze symptoms with OPQRST context
try:
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": f"""Dựa vào thông tin OPQRST đã thu thập, hãy đánh giá triệu chứng và đưa ra lời khuyên.
Triệu chứng ban đầu: {user_query}
Thông tin đã thu thập:
- Onset: {opqrst_data.get('onset', 'chưa rõ')}
- Quality: {opqrst_data.get('quality', 'chưa rõ')}
- Region: {opqrst_data.get('region', 'chưa rõ')}
- Provocation: {opqrst_data.get('provocation', 'chưa rõ')}
- Timing: {opqrst_data.get('timing', 'chưa rõ')}
- Severity: {opqrst_data.get('severity', 'chưa rõ')}
Hãy đưa ra:
1. Phân tích triệu chứng
2. Nguyên nhân có thể
3. Lời khuyên xử lý tại nhà (nếu phù hợp)
4. Khi nào cần gặp bác sĩ
5. Lời động viên, trấn an"""}
],
temperature=0.7,
max_tokens=1500
)
return response.choices[0].message.content
except Exception as e:
return f"""Xin lỗi, mình gặp chút vấn đề khi phân tích triệu chứng.
Dựa vào những gì bạn chia sẻ, mình khuyên bạn nên:
- Theo dõi triệu chứng thêm 24-48 giờ
- Nghỉ ngơi đầy đủ
- Uống đủ nước
- Nếu triệu chứng tệ hơn hoặc không giảm → đi khám bác sĩ
Với các triệu chứng bất thường, tốt nhất là được bác sĩ khám trực tiếp nhé! 🏥"""