File size: 7,367 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
"""
Response Validator - Ensures LLM responses follow quality standards
Shared validation logic for all agents
"""

from typing import Dict, List, Tuple

class ResponseValidator:
    """
    Base validator with common rules + agent-specific rules
    """
    
    # Common bad phrases across all agents
    COMMON_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",
        "Với tư cách",
        "Tôi là chuyên gia"
    ]
    
    @staticmethod
    def validate_common(response: str) -> Tuple[bool, List[str]]:
        """
        Common validation rules for all agents
        Returns: (is_valid, list_of_issues)
        """
        issues = []
        
        # Check for formal phrases
        for phrase in ResponseValidator.COMMON_BAD_PHRASES:
            if phrase.lower() in response.lower():
                issues.append(f"Formal phrase: '{phrase}'")
                break
        
        # Check for excessive length (>500 words)
        word_count = len(response.split())
        if word_count > 500:
            issues.append(f"Too long: {word_count} words (max 500)")
        
        # Check for empty response
        if len(response.strip()) < 10:
            issues.append("Response too short or empty")
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_symptom_response(response: str, context: Dict) -> Tuple[bool, List[str]]:
        """
        Symptom-specific validation
        """
        issues = []
        stage = context.get('conversation_stage', 0)
        
        # 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 (assessment phase)")
        
        # Check if both asking and advising
        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")
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_nutrition_response(response: str, context: Dict, chat_history: List) -> Tuple[bool, List[str]]:
        """
        Nutrition-specific validation
        """
        issues = []
        
        # Check if asking for info already provided
        if chat_history:
            all_user_text = " ".join([msg[0].lower() for msg in chat_history if msg[0]])
            
            # Check if asking for age when already provided
            if "tuổi" in all_user_text or "năm" in all_user_text:
                if "bao nhiêu tuổi" in response.lower() or "tuổi của bạn" in response.lower():
                    issues.append("Hỏi lại tuổi đã được cung cấp")
            
            # Check if asking for weight when already provided
            if "kg" in all_user_text or "cân nặng" in all_user_text:
                if "cân nặng" in response.lower() and "?" in response:
                    issues.append("Hỏi lại cân nặng đã được cung cấp")
        
        # Check for too theoretical (should be practical)
        theory_indicators = ["lý thuyết", "nghiên cứu cho thấy", "theo khoa học"]
        if any(ind in response.lower() for ind in theory_indicators):
            practical_indicators = ["bạn thử", "có thể", "ví dụ", "thực đơn"]
            if not any(ind in response.lower() for ind in practical_indicators):
                issues.append("Quá lý thuyết, thiếu practical advice")
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_exercise_response(response: str, context: Dict) -> Tuple[bool, List[str]]:
        """
        Exercise-specific validation
        """
        issues = []
        
        # Check if workout plan is too generic
        if "lịch tập" in response.lower() or "kế hoạch" in response.lower():
            # Should have specific days or progression
            has_specifics = any(word in response.lower() for word in [
                "thứ", "ngày", "tuần 1", "tuần 2", "tháng"
            ])
            if not has_specifics:
                issues.append("Lịch tập quá generic, thiếu chi tiết")
        
        # Check for progression
        if "tập" in response.lower():
            has_progression = any(word in response.lower() for word in [
                "tăng dần", "progression", "tuần 1", "giai đoạn"
            ])
            if not has_progression and len(response) > 200:
                issues.append("Thiếu hướng dẫn progression")
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_mental_health_response(response: str, context: Dict) -> Tuple[bool, List[str]]:
        """
        Mental health-specific validation
        """
        issues = []
        
        # Should have empathy/validation
        empathy_indicators = [
            "cảm giác", "hiểu", "bình thường", "nhiều người",
            "không phải lỗi của bạn"
        ]
        
        if len(response) > 100:  # Only check for longer responses
            has_empathy = any(ind in response.lower() for ind in empathy_indicators)
            if not has_empathy:
                issues.append("Thiếu empathy/validation")
        
        # Check for too clinical
        clinical_indicators = ["chẩn đoán", "bệnh", "rối loạn"]
        if any(ind in response.lower() for ind in clinical_indicators):
            if "không phải bác sĩ" not in response.lower():
                issues.append("Quá clinical, cần disclaimer")
        
        return len(issues) == 0, issues
    
    @staticmethod
    def validate_response(response: str, agent_type: str, context: Dict, chat_history: List = None) -> Tuple[bool, List[str]]:
        """
        Main validation method - routes to appropriate validator
        """
        # Common validation first
        is_valid_common, common_issues = ResponseValidator.validate_common(response)
        
        # Agent-specific validation
        agent_issues = []
        if agent_type == 'symptom':
            _, agent_issues = ResponseValidator.validate_symptom_response(response, context)
        elif agent_type == 'nutrition':
            _, agent_issues = ResponseValidator.validate_nutrition_response(response, context, chat_history or [])
        elif agent_type == 'exercise':
            _, agent_issues = ResponseValidator.validate_exercise_response(response, context)
        elif agent_type == 'mental_health':
            _, agent_issues = ResponseValidator.validate_mental_health_response(response, context)
        
        # Combine all issues
        all_issues = common_issues + agent_issues
        is_valid = len(all_issues) == 0
        
        return is_valid, all_issues