Spaces:
Runtime error
Runtime error
File size: 36,798 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 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 |
"""
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é! 🏥"""
|