Przeglądaj źródła

精简了所谓的渐进式的规则判断,减少了上下文对当前问题模式判断的影响。

wangxq 3 tygodni temu
rodzic
commit
0266aae652

+ 5 - 5
agent/citu_agent.py

@@ -84,19 +84,19 @@ class CituLangGraphAgent:
 
     
     def _classify_question_node(self, state: AgentState) -> AgentState:
-        """问题分类节点 - 支持渐进式分类策略"""
+        """问题分类节点 - 使用混合分类策略(规则+LLM)"""
         try:
             # 从state中获取路由模式,而不是从配置文件读取
             routing_mode = state.get("routing_mode", "hybrid")
             
             self.logger.info(f"开始分类问题: {state['question']}")
             
-            # 获取上下文类型(如果有的话
+            # 获取上下文类型(保留兼容性,但不在分类中使用
             context_type = state.get("context_type")
             if context_type:
                 self.logger.info(f"检测到上下文类型: {context_type}")
             
-            # 使用渐进式分类策略,传递路由模式
+            # 使用混合分类策略(规则+LLM),传递路由模式
             classification_result = self.classifier.classify(state["question"], context_type, routing_mode)
             
             # 更新状态
@@ -733,7 +733,7 @@ class CituLangGraphAgent:
         Args:
             question: 用户问题
             conversation_id: 对话ID
-            context_type: 上下文类型 ("DATABASE" 或 "CHAT"),用于渐进式分类
+            context_type: 上下文类型(保留兼容性参数,当前未使用)
             routing_mode: 路由模式,可选,用于覆盖配置文件设置
             
         Returns:
@@ -778,7 +778,7 @@ class CituLangGraphAgent:
             }
     
     def _create_initial_state(self, question: str, conversation_id: str = None, context_type: str = None, routing_mode: str = None) -> AgentState:
-        """创建初始状态 - 支持渐进式分类"""
+        """创建初始状态 - 支持兼容性参数"""
         # 确定使用的路由模式
         if routing_mode:
             effective_routing_mode = routing_mode

+ 70 - 211
agent/classifier.py

@@ -44,122 +44,59 @@ class QuestionClassifier:
             self.medium_confidence_threshold = 0.6
             self.logger.warning("配置文件不可用,使用默认分类器参数")
         
-        # 基于高速公路服务区业务的精准关键词
-        self.strong_business_keywords = {
-            "核心业务实体": [
-                "服务区", "档口", "商铺", "收费站", "高速公路",
-                "驿美", "驿购",  # 业务系统名称
-                "北区", "南区", "西区", "东区", "两区",  # 物理分区
-                "停车区", "公司", "管理公司", "运营公司", "驿美运营公司"  # 公司相关
-            ],
-            "支付业务": [
-                "微信支付", "支付宝支付", "现金支付", "行吧支付", "金豆支付",
-                "支付金额", "订单数量", "营业额", "收入", "营业收入",
-                "微信", "支付宝", "现金", "行吧", "金豆",  # 简化形式
-                "wx", "zfb", "rmb", "xs", "jd"  # 系统字段名
-            ],
-            "经营品类": [
-                "餐饮", "小吃", "便利店", "整体租赁",
-                "驿美餐饮", "品牌", "经营品类", "商业品类"
-            ],
-            "车流业务": [
-                "车流量", "车辆数量", "客车", "货车", 
-                "过境", "危化品", "城际", "车辆统计",
-                "流量统计", "车型分布"
-            ],
-            "地理路线": [
-                "大广", "昌金", "昌栗", "线路", "路段", "路线",
-                "高速线路", "公路线路"
-            ],
-            "系统查询指示词": [
-                "当前系统", "当前数据库", "当前数据", "数据库"
-                "本系统", "系统", "数据库中", "数据中",
-                "现有数据", "已有数据", "存储的数据",
-                "平台数据", "我们的数据库", "这个系统"
-            ]
-        }
-        
-        # 查询意图词(辅助判断)
-        self.query_intent_keywords = [
-            "统计", "查询", "分析", "排行", "排名",
-            "报表", "报告", "汇总", "计算", "对比",
-            "趋势", "占比", "百分比", "比例",
-            "最大", "最小", "最高", "最低", "平均",
-            "总计", "合计", "累计", "求和", "求平均",
-            "生成", "导出", "显示", "列出", "共有"
-        ]
-        
-        # 非业务实体词(包含则倾向CHAT)
-        self.non_business_keywords = [
-            # 农产品/食物
-            "荔枝", "苹果", "西瓜", "水果", "蔬菜", "大米", "小麦",
-            "橙子", "香蕉", "葡萄", "草莓", "樱桃", "桃子", "梨",
+        # 加载词典配置(新增逻辑)
+        self._load_dict_config()
+
+    def _load_dict_config(self):
+        """加载分类器词典配置"""
+        try:
+            from agent.config import get_classifier_dict_config
+            dict_config = get_classifier_dict_config()
             
-            # 技术概念  
-            "人工智能", "机器学习", "编程", "算法", "深度学习",
-            "AI", "神经网络", "模型训练", "数据挖掘",
+            # 加载关键词列表
+            self.strong_business_keywords = dict_config.strong_business_keywords
+            self.query_intent_keywords = dict_config.query_intent_keywords
+            self.non_business_keywords = dict_config.non_business_keywords
+            self.sql_patterns = dict_config.sql_patterns
+            self.chat_keywords = dict_config.chat_keywords
             
-            # 身份询问
-            "你是谁", "你是什么", "你叫什么", "你的名字", "你是什么AI",
-            "什么模型", "大模型", "AI助手", "助手", "机器人",
+            # 加载权重配置
+            self.weights = dict_config.weights
             
-            # 天气相关
-            "天气", "气温", "下雨", "晴天", "阴天", "温度",
-            "天气预报", "气候", "降雨", "雪天",
+            # 加载其他配置
+            self.metadata = dict_config.metadata
             
-            # 其他生活常识
-            "怎么做饭", "如何减肥", "健康", "医疗", "病症",
-            "历史", "地理", "文学", "电影", "音乐", "体育",
-            "娱乐", "游戏", "小说", "新闻", "政治", "战争",
-            "足球", "NBA", "篮球", "乒乓球", "冠军", "夺冠",
-            "高考",
+            total_keywords = (
+                sum(len(keywords) for keywords in self.strong_business_keywords.values()) +
+                len(self.query_intent_keywords) +
+                len(self.non_business_keywords) +
+                len(self.sql_patterns) +
+                len(self.chat_keywords)
+            )
+            
+            self.logger.info(f"从YAML配置文件加载词典完成,共加载 {total_keywords} 个关键词")
+            
+        except Exception as e:
+            self.logger.warning(f"加载YAML词典配置失败: {str(e)},使用代码中的备用配置")
+            self._load_default_dict()
 
-            # 旅游出行
-            "旅游","景点","门票","酒店","机票","航班","高铁","的士",
-            #情绪
-            "伤心","开心","无聊","生气","孤独","累了","烦恼","心情","难过","抑郁",
-            #商业
-            "股票","基金","理财","投资","经济","通货膨胀","上市",
-            #哲学
-            "人生意义","价值观","道德","信仰","宗教","爱情",
-            #地理
-            "全球","全国","亚洲","发展中","欧洲","美洲","东亚","东南亚","南美","非洲","大洋"
-        ]
+    def _load_default_dict(self):
+        """YAML配置加载失败时的处理"""
+        error_msg = "YAML词典配置文件加载失败,无法初始化分类器"
+        self.logger.error(error_msg)
         
-        # SQL关键词(技术层面的数据库操作)
-        # business_score +3
-        self.sql_patterns = [
-            r"\b(select|from|where|group by|order by|having|join|update)\b",
-            r"\b(数据库|表名|表|字段名|SQL|sql|database|table)\b"
-        ]
+        # 初始化空的weights字典,使用代码中的默认值
+        self.weights = {}
         
-        # 聊天关键词(平台功能和帮助)
-        self.chat_keywords = [
-            "你好啊", "谢谢", "再见", "怎么样", "如何", "为什么", "什么是",
-            "介绍", "解释", "说明", "帮助", "操作", "使用方法", "功能",
-            "教程", "指南", "手册","讲解"
-        ]
-        
-        # 追问关键词(用于检测追问型问题)
-        self.follow_up_keywords = [
-            "还有", "详细", "具体", "更多", "继续", "再", "也",
-            "那么", "另外", "其他", "以及", "还", "进一步",
-            "深入", "补充", "额外", "此外", "同时", "并且"
-        ]
-        
-        # 话题切换关键词(明显的话题转换)
-        self.topic_switch_keywords = [
-            "你好", "你是", "介绍", "功能", "帮助", "使用方法",
-            "平台", "系统", "AI", "助手", "谢谢", "再见"
-        ]
+        raise RuntimeError(error_msg)
 
     def classify(self, question: str, context_type: Optional[str] = None, routing_mode: Optional[str] = None) -> ClassificationResult:
         """
-        主分类方法:支持渐进式分类策略
+        主分类方法:简化为混合分类策略
         
         Args:
             question: 当前问题
-            context_type: 上下文类型 ("DATABASE" 或 "CHAT"),可选
+            context_type: 上下文类型(保留参数兼容性,但不使用)
             routing_mode: 路由模式,可选,用于覆盖配置文件设置
         """
         # 确定使用的路由模式
@@ -192,93 +129,8 @@ class QuestionClassifier:
         elif QUESTION_ROUTING_MODE == "llm_only":
             return self._enhanced_llm_classify(question)
         else:
-            # hybrid模式:使用渐进式分类策略
-            return self._progressive_classify(question, context_type)
-
-    def _progressive_classify(self, question: str, context_type: Optional[str] = None) -> ClassificationResult:
-        """
-        渐进式分类策略:
-        1. 首先只基于问题本身分类
-        2. 如果置信度不够且有上下文,考虑上下文辅助
-        3. 检测话题切换,避免错误继承
-        """
-        self.logger.info(f"渐进式分类 - 问题: {question}")
-        if context_type:
-            self.logger.info(f"上下文类型: {context_type}")
-        
-        # 第一步:只基于问题本身分类
-        primary_result = self._hybrid_classify(question)
-        self.logger.info(f"主分类结果: {primary_result.question_type}, 置信度: {primary_result.confidence}")
-        
-        # 如果没有上下文,直接返回主分类结果
-        if not context_type:
-            self.logger.debug("无上下文,使用主分类结果")
-            return primary_result
-        
-        # 如果置信度足够高,直接使用主分类结果
-        if primary_result.confidence >= self.high_confidence_threshold:
-            self.logger.info(f"高置信度({primary_result.confidence}≥{self.high_confidence_threshold}),使用主分类结果")
-            return primary_result
-        
-        # 检测明显的话题切换
-        if self._is_topic_switch(question):
-            self.logger.info("检测到话题切换,忽略上下文")
-            return primary_result
-        
-        # 如果置信度较低,考虑上下文辅助
-        if primary_result.confidence < self.medium_confidence_threshold:
-            self.logger.info(f"低置信度({primary_result.confidence}<{self.medium_confidence_threshold}),考虑上下文辅助")
-            
-            # 检测是否为追问型问题
-            if self._is_follow_up_question(question):
-                self.logger.info(f"检测到追问型问题,继承上下文类型: {context_type}")
-                return ClassificationResult(
-                    question_type=context_type,
-                    confidence=0.75,  # 给予中等置信度
-                    reason=f"追问型问题,继承上下文类型。原分类: {primary_result.reason}",
-                    method="progressive_context_inherit"
-                )
-        
-        # 中等置信度或其他情况,保持主分类结果
-        self.logger.debug("保持主分类结果")
-        return primary_result
-
-    def _is_follow_up_question(self, question: str) -> bool:
-        """检测是否为追问型问题"""
-        question_lower = question.lower()
-        
-        # 检查追问关键词
-        for keyword in self.follow_up_keywords:
-            if keyword in question_lower:
-                return True
-        
-        # 检查问号开头的短问题(通常是追问)
-        if question.strip().startswith(('还', '再', '那', '这', '有')) and len(question.strip()) < 15:
-            return True
-        
-        return False
-
-    def _is_topic_switch(self, question: str) -> bool:
-        """检测是否为明显的话题切换"""
-        question_lower = question.lower()
-        
-        # 检查话题切换关键词
-        for keyword in self.topic_switch_keywords:
-            if keyword in question_lower:
-                return True
-        
-        # 检查问候语模式
-        greeting_patterns = [
-            r"^(你好|您好|hi|hello)",
-            r"(你是|您是).*(什么|谁|哪)",
-            r"(介绍|说明).*(功能|平台|系统)"
-        ]
-        
-        for pattern in greeting_patterns:
-            if re.search(pattern, question_lower):
-                return True
-        
-        return False
+            # hybrid模式:直接使用混合分类策略(规则+LLM)
+            return self._hybrid_classify(question)
 
     def _hybrid_classify(self, question: str) -> ClassificationResult:
         """
@@ -292,7 +144,7 @@ class QuestionClassifier:
         if rule_result.confidence >= self.high_confidence_threshold:
             return rule_result
         
-        # 第二步:使用增强的LLM分类
+        # 否则:使用增强的LLM分类
         llm_result = self._enhanced_llm_classify(question)
         
         # 选择置信度更高的结果
@@ -327,12 +179,13 @@ class QuestionClassifier:
                 current_start = question.find("\n[CURRENT]\n")
                 if current_start != -1:
                     current_question = question[current_start + len("\n[CURRENT]\n"):].strip()
-                    self.logger.debug(f"规则分类提取到当前问题: {current_question}")
+                    self.logger.info(f"规则分类从[CURRENT]标签提取到问题: {current_question}")
                     return current_question
             
             # 如果不是enhanced_question格式,直接返回原问题
-            self.logger.debug("未检测到[CURRENT]标签,使用完整问题进行规则分类")
-            return question.strip()
+            stripped_question = question.strip()
+            self.logger.info(f"规则分类未找到[CURRENT]标签,使用完整问题: {stripped_question}")
+            return stripped_question
             
         except Exception as e:
             self.logger.warning(f"提取当前问题失败: {str(e)},返回空字符串")
@@ -344,8 +197,6 @@ class QuestionClassifier:
         current_question = self._extract_current_question_for_rule_classification(question)
         question_lower = current_question.lower()
         
-        self.logger.debug(f"规则分类使用问题: {current_question}")
-        
         # 检查非业务实体词
         non_business_matched = []
         for keyword in self.non_business_keywords:
@@ -356,7 +207,7 @@ class QuestionClassifier:
         if non_business_matched:
             return ClassificationResult(
                 question_type="CHAT",
-                confidence=0.85,
+                confidence=self.weights.get('non_business_confidence', 0.85),  # 使用YAML配置的置信度
                 reason=f"包含非业务实体词: {non_business_matched}",
                 method="rule_based_non_business"
             )
@@ -370,7 +221,7 @@ class QuestionClassifier:
                 continue
             for keyword in keywords:
                 if keyword in question_lower:
-                    business_score += 2  # 业务实体词权重更高
+                    business_score += self.weights.get('business_entity', 2)  # 使用YAML配置的权重
                     business_matched.append(f"{category}:{keyword}")
         
         # 检查系统查询指示词
@@ -378,7 +229,7 @@ class QuestionClassifier:
         system_matched = []
         for keyword in self.strong_business_keywords.get("系统查询指示词", []):
             if keyword in question_lower:
-                system_indicator_score += 1
+                system_indicator_score += self.weights.get('system_indicator', 1)  # 使用YAML配置的权重
                 system_matched.append(f"系统查询指示词:{keyword}")
         
         # 检查查询意图词
@@ -386,14 +237,14 @@ class QuestionClassifier:
         intent_matched = []
         for keyword in self.query_intent_keywords:
             if keyword in question_lower:
-                intent_score += 1
+                intent_score += self.weights.get('query_intent', 1)  # 使用YAML配置的权重
                 intent_matched.append(keyword)
         
         # 检查SQL模式
         sql_patterns_matched = []
         for pattern in self.sql_patterns:
             if re.search(pattern, question_lower, re.IGNORECASE):
-                business_score += 3  # SQL模式权重最高
+                business_score += self.weights.get('sql_pattern', 3)  # 使用YAML配置的权重
                 sql_patterns_matched.append(pattern)
         
         # 检查聊天关键词
@@ -401,25 +252,29 @@ class QuestionClassifier:
         chat_matched = []
         for keyword in self.chat_keywords:
             if keyword in question_lower:
-                chat_score += 1
+                chat_score += self.weights.get('chat_keyword', 1)  # 使用YAML配置的权重
                 chat_matched.append(keyword)
         
         # 系统指示词组合评分逻辑
         if system_indicator_score > 0 and business_score > 0:
             # 系统指示词 + 业务实体 = 强组合效应
-            business_score += 3  # 组合加分
+            business_score += self.weights.get('combination_bonus', 3)  # 使用YAML配置的组合加分权重
             business_matched.extend(system_matched)
         elif system_indicator_score > 0:
             # 仅有系统指示词 = 中等业务倾向
-            business_score += 1
+            business_score += self.weights.get('system_indicator', 1)  # 使用YAML配置的权重
             business_matched.extend(system_matched)
         
         # 分类决策逻辑
         total_business_score = business_score + intent_score
         
         # 强业务特征:包含业务实体 + 查询意图
-        if business_score >= 2 and intent_score >= 1:
-            confidence = min(self.max_confidence, 0.8 + (total_business_score * 0.05))
+        min_business_score = self.weights.get('strong_business_min_score', 2)
+        min_intent_score = self.weights.get('strong_business_min_intent', 1)
+        if business_score >= min_business_score and intent_score >= min_intent_score:
+            base_conf = self.weights.get('strong_business_base', 0.8)
+            increment = self.weights.get('strong_business_increment', 0.05)
+            confidence = min(self.max_confidence, base_conf + (total_business_score * increment))
             return ClassificationResult(
                 question_type="DATABASE",
                 confidence=confidence,
@@ -428,8 +283,10 @@ class QuestionClassifier:
             )
         
         # 中等业务特征:包含多个业务实体词
-        elif business_score >= 4:
-            confidence = min(self.max_confidence, 0.7 + (business_score * 0.03))
+        elif business_score >= self.weights.get('medium_business_min_score', 4):
+            base_conf = self.weights.get('medium_business_base', 0.7)
+            increment = self.weights.get('medium_business_increment', 0.03)
+            confidence = min(self.max_confidence, base_conf + (business_score * increment))
             return ClassificationResult(
                 question_type="DATABASE", 
                 confidence=confidence,
@@ -438,8 +295,10 @@ class QuestionClassifier:
             )
         
         # 聊天特征
-        elif chat_score >= 1 and business_score == 0:
-            confidence = min(self.max_confidence, self.base_confidence + (chat_score * self.confidence_increment))
+        elif chat_score >= self.weights.get('chat_min_score', 1) and business_score == 0:
+            base_conf = self.weights.get('chat_base_confidence', 0.4)
+            increment = self.weights.get('chat_confidence_increment', 0.08)
+            confidence = min(self.max_confidence, base_conf + (chat_score * increment))
             return ClassificationResult(
                 question_type="CHAT",
                 confidence=confidence,
@@ -556,7 +415,7 @@ class QuestionClassifier:
             self.logger.error(f"LLM分类失败,业务上下文不可用: {str(e)}")
             return ClassificationResult(
                 question_type="CHAT",  # 失败时默认为CHAT,更安全
-                confidence=0.1,  # 很低的置信度表示分类不可靠
+                confidence=self.weights.get('llm_error_confidence', 0.1),  # 使用YAML配置的低置信度
                 reason=f"业务上下文加载失败,无法进行准确分类: {str(e)}",
                 method="llm_context_error"
             )

+ 422 - 0
agent/classifier_dict.yaml

@@ -0,0 +1,422 @@
+# agent/classifier_dict.yaml
+# 问题分类器词典配置文件
+# 版本: v1.0
+# 最后更新: 2024-12-21
+
+# ===========================================
+# 配置元信息
+# ===========================================
+metadata:
+  version: "1.0"
+  description: "Citu智能数据问答平台问题分类器关键词配置"
+  last_updated: "2024-12-21"
+  author: "系统管理员"
+
+# ===========================================
+# 权重配置
+# ===========================================
+weights:
+  # ===========================================
+  # 关键词权重配置
+  # ===========================================
+  
+  # 业务实体词权重(强业务关键词中除系统指示词外的部分)
+  business_entity: 2
+  
+  # 系统指示词权重(强业务关键词中的系统查询指示词)
+  system_indicator: 1
+  
+  # 查询意图词权重
+  query_intent: 1
+  
+  # SQL模式权重(最高权重)
+  sql_pattern: 3
+  
+  # 聊天关键词权重
+  chat_keyword: 1
+  
+  # 组合加分权重(系统指示词+业务实体词)
+  combination_bonus: 3
+
+  # ===========================================
+  # 置信度计算配置
+  # ===========================================
+  
+  # 非业务词固定置信度(匹配非业务关键词时直接返回此置信度)
+  non_business_confidence: 0.85
+  
+  # 强业务特征置信度配置(业务实体≥2分 且 查询意图≥1分)
+  strong_business_base: 0.8        # 强业务特征基础置信度
+  strong_business_increment: 0.05  # 每增加1分总分的置信度增量
+  
+  # 中等业务特征置信度配置(业务实体≥4分)
+  medium_business_base: 0.7        # 中等业务特征基础置信度
+  medium_business_increment: 0.03  # 每增加1分业务分的置信度增量
+  
+  # 聊天特征置信度配置(聊天分≥1 且 业务分=0)
+  chat_base_confidence: 0.4        # 聊天特征基础置信度(对应base_confidence)
+  chat_confidence_increment: 0.08  # 每增加1分聊天分的置信度增量
+  
+  # 分类阈值配置
+  strong_business_min_score: 2     # 强业务特征最低业务分要求
+  strong_business_min_intent: 1    # 强业务特征最低意图分要求
+  medium_business_min_score: 4     # 中等业务特征最低业务分要求
+  chat_min_score: 1               # 聊天特征最低聊天分要求
+
+# ===========================================
+# 强业务关键词(字典结构,保持原有层次)
+# ===========================================
+strong_business_keywords:
+  核心业务实体:
+    description: "高速公路服务区基础设施和业务系统"
+    keywords:
+      - 服务区
+      - 档口
+      - 商铺
+      - 收费站
+      - 高速公路
+      - 驿美          # 业务系统名称
+      - 驿购          # 业务系统名称
+      - 北区          # 物理分区
+      - 南区
+      - 西区
+      - 东区
+      - 两区
+      - 停车区
+      - 公司
+      - 管理公司
+      - 运营公司
+    
+  支付业务:
+    description: "支付方式、金额、订单等支付相关业务"
+    keywords:
+      # 支付方式全称
+      - 微信支付
+      - 支付宝支付
+      - 现金支付
+      - 行吧支付
+      - 金豆支付
+      
+      # 业务指标
+      - 支付金额
+      - 订单数量
+      - 营业额
+      - 收入
+      - 营业收入
+      
+      # 简化形式
+      - 微信
+      - 支付宝
+      - 现金
+      - 行吧
+      - 金豆
+      
+      # 系统字段名
+      - wx
+      - zfb
+      - rmb
+      - xs
+      - jd
+    
+  经营品类:
+    description: "经营类型、品牌、商业品类"
+    keywords:
+      - 餐饮
+      - 小吃
+      - 便利店
+      - 整体租赁
+      - 驿美餐饮
+      - 品牌
+      - 经营品类
+      - 商业品类
+    
+  车流业务:
+    description: "车辆流量、车型统计等车流相关业务"
+    keywords:
+      # 流量概念
+      - 车流量
+      - 车辆数量
+      - 客车
+      - 货车
+      - 过境
+      - 危化品
+      - 城际
+      - 车辆统计
+      - 流量统计
+      - 车型分布
+    
+  地理路线:
+    description: "高速线路、路段等地理位置信息"
+    keywords:
+      # 具体线路
+      - 大广
+      - 昌金
+      - 昌栗
+      
+      # 概念词
+      - 线路
+      - 路段
+      - 路线
+      - 高速线路
+      - 公路线路
+    
+  系统查询指示词:
+    description: "系统、数据库等查询指示词(特殊权重处理)"
+    weight: 1  # 特殊标记:权重低于其他业务实体词
+    keywords:
+      # 系统指示
+      - 当前系统
+      - 当前数据库
+      - 当前数据
+      - 数据库
+      - 本系统
+      - 系统
+      
+      # 数据指示
+      - 数据库中
+      - 数据中
+      - 现有数据
+      - 已有数据
+      - 存储的数据
+      
+      # 平台指示
+      - 平台数据
+      - 我们的数据库
+      - 这个系统
+
+# ===========================================
+# 查询意图关键词
+# ===========================================
+query_intent_keywords:
+  description: "用于识别数据查询意图的关键词"
+  keywords:
+    # 统计分析
+    - 统计
+    - 查询
+    - 分析
+    - 排行
+    - 排名
+    - 报表
+    - 报告
+    - 汇总
+    - 计算
+    - 对比
+    - 趋势
+    - 占比
+    - 百分比
+    - 比例
+    
+    # 聚合函数
+    - 最大
+    - 最小
+    - 最高
+    - 最低
+    - 平均
+    - 总计
+    - 合计
+    - 累计
+    - 求和
+    - 求平均
+    
+    # 输出动作
+    - 生成
+    - 导出
+    - 显示
+    - 列出
+    - 共有
+
+# ===========================================
+# 非业务实体词(一旦匹配立即分类为CHAT)
+# ===========================================
+non_business_keywords:
+  description: "明确的非业务领域问题,最高优先级直接分类"
+  
+  农产品食物:
+    - 荔枝
+    - 苹果
+    - 西瓜
+    - 水果
+    - 蔬菜
+    - 大米
+    - 小麦
+    - 橙子
+    - 香蕉
+    - 葡萄
+    - 草莓
+    - 樱桃
+    - 桃子
+    - 梨
+    
+  技术概念:
+    - 人工智能
+    - 机器学习
+    - 编程
+    - 算法
+    - 深度学习
+    - AI
+    - 神经网络
+    - 模型训练
+    - 数据挖掘
+    
+  身份询问:
+    - 你是谁
+    - 你是什么
+    - 你叫什么
+    - 你的名字
+    - 你是什么AI
+    - 什么模型
+    - 大模型
+    - AI助手
+    - 助手
+    - 机器人
+    
+  天气相关:
+    - 天气
+    - 气温
+    - 下雨
+    - 晴天
+    - 阴天
+    - 温度
+    - 天气预报
+    - 气候
+    - 降雨
+    - 雪天
+    
+  生活常识:
+    - 怎么做饭
+    - 如何减肥
+    - 健康
+    - 医疗
+    - 病症
+    - 历史
+    - 地理
+    - 文学
+    - 电影
+    - 音乐
+    - 体育
+    - 娱乐
+    - 游戏
+    - 小说
+    - 新闻
+    - 政治
+    - 战争
+    - 足球
+    - NBA
+    - 篮球
+    - 乒乓球
+    - 冠军
+    - 夺冠
+    - 高考
+    
+  旅游出行:
+    - 旅游
+    - 景点
+    - 门票
+    - 酒店
+    - 机票
+    - 航班
+    - 高铁
+    - 的士
+    
+  情绪表达:
+    - 伤心
+    - 开心
+    - 无聊
+    - 生气
+    - 孤独
+    - 累了
+    - 烦恼
+    - 心情
+    - 难过
+    - 抑郁
+    
+  商业金融:
+    - 股票
+    - 基金
+    - 理财
+    - 投资
+    - 经济
+    - 通货膨胀
+    - 上市
+    
+  哲学思考:
+    - 人生意义
+    - 价值观
+    - 道德
+    - 信仰
+    - 宗教
+    - 爱情
+    
+  地理范围:
+    - 全球
+    - 全国
+    - 亚洲
+    - 发展中
+    - 欧洲
+    - 美洲
+    - 东亚
+    - 东南亚
+    - 南美
+    - 非洲
+    - 大洋
+
+# ===========================================
+# SQL模式(正则表达式)
+# ===========================================
+sql_patterns:
+  description: "用于识别SQL语句特征的正则表达式"
+  patterns:
+    - pattern: "\\b(select|from|where|group by|order by|having|join|update)\\b"
+      description: "SQL关键字匹配"
+      case_sensitive: false
+      
+    - pattern: "\\b(数据库|表名|表|字段名|SQL|sql|database|table)\\b"
+      description: "数据库概念词匹配"
+      case_sensitive: false
+
+# ===========================================
+# 聊天关键词
+# ===========================================
+chat_keywords:
+  description: "倾向于聊天分类的关键词"
+  keywords:
+    # 问候语
+    - 你好啊
+    - 谢谢
+    - 再见
+    
+    # 疑问词
+    - 怎么样
+    - 如何
+    - 为什么
+    - 什么是
+    
+    # 帮助请求
+    - 介绍
+    - 解释
+    - 说明
+    - 帮助
+    - 操作
+    - 使用方法
+    - 功能
+    - 教程
+    - 指南
+    - 手册
+    - 讲解
+
+# ===========================================
+# 配置验证规则
+# ===========================================
+validation:
+  required_sections:
+    - strong_business_keywords
+    - query_intent_keywords
+    - non_business_keywords
+    - sql_patterns
+    - chat_keywords
+  
+  min_keywords_count:
+    strong_business_keywords: 50
+    query_intent_keywords: 20
+    non_business_keywords: 70
+    chat_keywords: 15 

+ 39 - 1
agent/config.py

@@ -160,4 +160,42 @@ def get_current_config() -> dict:
         此函数返回的是配置的引用,修改返回值会影响全局配置
         如需修改配置,建议创建副本后再修改
     """
-    return AGENT_CONFIG 
+    return AGENT_CONFIG
+
+# ==================== 分类器词典配置加载 ====================
+
+try:
+    from .dict_loader import load_classifier_dict_config, get_dict_loader
+    
+    def get_classifier_dict_config(force_reload: bool = False):
+        """
+        获取分类器词典配置
+        
+        Args:
+            force_reload: 是否强制重新加载
+            
+        Returns:
+            ClassifierDictConfig: 词典配置对象
+        """
+        return load_classifier_dict_config(force_reload)
+    
+    def reload_classifier_dict_config():
+        """重新加载分类器词典配置"""
+        return load_classifier_dict_config(force_reload=True)
+    
+    # 导出词典配置函数
+    __all__ = [
+        'get_current_config', 
+        'get_nested_config', 
+        'AGENT_CONFIG',
+        'get_classifier_dict_config',
+        'reload_classifier_dict_config'
+    ]
+    
+except ImportError as e:
+    # 如果dict_loader模块不存在,提供空实现
+    def get_classifier_dict_config(force_reload: bool = False):
+        raise ImportError("词典加载器模块不可用,请检查dict_loader.py是否存在")
+    
+    def reload_classifier_dict_config():
+        raise ImportError("词典加载器模块不可用,请检查dict_loader.py是否存在") 

+ 216 - 0
agent/dict_loader.py

@@ -0,0 +1,216 @@
+# agent/dict_loader.py
+"""
+分类器词典配置加载器
+负责从YAML文件加载分类器词典配置,并提供数据转换和验证功能
+"""
+
+import yaml
+import os
+import re
+from typing import Dict, Any, List, Optional
+from dataclasses import dataclass
+from core.logging import get_agent_logger
+
+# 初始化日志 [[memory:3840221]]
+logger = get_agent_logger("DictLoader")
+
+@dataclass
+class ClassifierDictConfig:
+    """分类器词典配置数据类"""
+    strong_business_keywords: Dict[str, List[str]]
+    query_intent_keywords: List[str]
+    non_business_keywords: List[str]
+    sql_patterns: List[str]
+    chat_keywords: List[str]
+    weights: Dict[str, float]
+    metadata: Dict[str, Any]
+
+class DictLoader:
+    """分类器词典配置加载器"""
+    
+    def __init__(self, dict_file: str = None):
+        """
+        初始化加载器
+        
+        Args:
+            dict_file: 词典配置文件路径,默认为agent/classifier_dict.yaml
+        """
+        if dict_file is None:
+            current_dir = os.path.dirname(os.path.abspath(__file__))
+            dict_file = os.path.join(current_dir, "classifier_dict.yaml")
+        
+        self.dict_file = dict_file
+        self.config_cache = None
+    
+    def load_config(self, force_reload: bool = False) -> ClassifierDictConfig:
+        """
+        加载词典配置
+        
+        Args:
+            force_reload: 是否强制重新加载,默认使用缓存
+            
+        Returns:
+            ClassifierDictConfig: 词典配置对象
+            
+        Raises:
+            FileNotFoundError: 配置文件不存在
+            ValueError: 配置文件格式错误
+        """
+        if self.config_cache is not None and not force_reload:
+            return self.config_cache
+        
+        try:
+            logger.info(f"加载词典配置文件: {self.dict_file}")
+            
+            with open(self.dict_file, 'r', encoding='utf-8') as f:
+                yaml_data = yaml.safe_load(f)
+            
+            # 验证配置文件
+            self._validate_config(yaml_data)
+            
+            # 转换数据格式
+            config = self._convert_config(yaml_data)
+            
+            # 缓存配置
+            self.config_cache = config
+            
+            logger.info("词典配置加载成功")
+            return config
+            
+        except FileNotFoundError:
+            error_msg = f"词典配置文件不存在: {self.dict_file}"
+            logger.error(error_msg)
+            raise FileNotFoundError(error_msg)
+        except yaml.YAMLError as e:
+            error_msg = f"词典配置文件YAML格式错误: {str(e)}"
+            logger.error(error_msg)
+            raise ValueError(error_msg)
+        except Exception as e:
+            error_msg = f"词典配置加载失败: {str(e)}"
+            logger.error(error_msg)
+            raise ValueError(error_msg)
+    
+    def _validate_config(self, yaml_data: Dict[str, Any]) -> None:
+        """验证配置文件格式和必要字段"""
+        required_sections = [
+            'strong_business_keywords',
+            'query_intent_keywords', 
+            'non_business_keywords',
+            'sql_patterns',
+            'chat_keywords',
+            'weights'
+        ]
+        
+        for section in required_sections:
+            if section not in yaml_data:
+                raise ValueError(f"配置文件缺少必要部分: {section}")
+        
+        # 验证权重配置
+        required_weights = [
+            'business_entity',
+            'system_indicator', 
+            'query_intent',
+            'sql_pattern',
+            'chat_keyword',
+            'non_business_confidence'
+        ]
+        
+        for weight in required_weights:
+            if weight not in yaml_data['weights']:
+                raise ValueError(f"权重配置缺少: {weight}")
+        
+        logger.debug("配置文件验证通过")
+    
+    def _convert_config(self, yaml_data: Dict[str, Any]) -> ClassifierDictConfig:
+        """将YAML数据转换为ClassifierDictConfig对象"""
+        
+        # 转换强业务关键词(保持字典结构)
+        strong_business_keywords = {}
+        for category, data in yaml_data['strong_business_keywords'].items():
+            if isinstance(data, dict) and 'keywords' in data:
+                strong_business_keywords[category] = data['keywords']
+            else:
+                # 兼容简单格式
+                strong_business_keywords[category] = data
+        
+        # 转换查询意图关键词
+        query_intent_data = yaml_data['query_intent_keywords']
+        if isinstance(query_intent_data, dict) and 'keywords' in query_intent_data:
+            query_intent_keywords = query_intent_data['keywords']
+        else:
+            query_intent_keywords = query_intent_data
+        
+        # 转换非业务实体词(展平为列表)
+        non_business_keywords = self._flatten_non_business_keywords(
+            yaml_data['non_business_keywords']
+        )
+        
+        # 转换SQL模式
+        sql_patterns = []
+        patterns_data = yaml_data['sql_patterns']
+        if isinstance(patterns_data, dict) and 'patterns' in patterns_data:
+            for pattern_info in patterns_data['patterns']:
+                if isinstance(pattern_info, dict):
+                    sql_patterns.append(pattern_info['pattern'])
+                else:
+                    sql_patterns.append(pattern_info)
+        else:
+            sql_patterns = patterns_data
+        
+        # 转换其他关键词列表
+        chat_keywords = self._extract_keywords_list(yaml_data['chat_keywords'])
+        
+        return ClassifierDictConfig(
+            strong_business_keywords=strong_business_keywords,
+            query_intent_keywords=query_intent_keywords,
+            non_business_keywords=non_business_keywords,
+            sql_patterns=sql_patterns,
+            chat_keywords=chat_keywords,
+            weights=yaml_data['weights'],
+            metadata=yaml_data.get('metadata', {})
+        )
+    
+    def _flatten_non_business_keywords(self, non_business_data: Dict[str, Any]) -> List[str]:
+        """将分类的非业务词展平为列表"""
+        flattened = []
+        
+        # 跳过description字段
+        for category, keywords in non_business_data.items():
+            if category == 'description':
+                continue
+            if isinstance(keywords, list):
+                flattened.extend(keywords)
+        
+        return flattened
+    
+    def _extract_keywords_list(self, data: Any) -> List[str]:
+        """从可能包含description的数据中提取关键词列表"""
+        if isinstance(data, dict) and 'keywords' in data:
+            return data['keywords']
+        elif isinstance(data, list):
+            return data
+        else:
+            return []
+
+# 全局加载器实例
+_dict_loader = None
+
+def get_dict_loader() -> DictLoader:
+    """获取全局词典加载器实例"""
+    global _dict_loader
+    if _dict_loader is None:
+        _dict_loader = DictLoader()
+    return _dict_loader
+
+def load_classifier_dict_config(force_reload: bool = False) -> ClassifierDictConfig:
+    """
+    加载分类器词典配置(便捷函数)
+    
+    Args:
+        force_reload: 是否强制重新加载
+        
+    Returns:
+        ClassifierDictConfig: 词典配置对象
+    """
+    loader = get_dict_loader()
+    return loader.load_config(force_reload) 

+ 1 - 1
agent/state.py

@@ -10,7 +10,7 @@ class AgentState(TypedDict):
     conversation_id: Optional[str]
     
     # 上下文信息
-    context_type: Optional[str]  # 上下文类型 ("DATABASE" 或 "CHAT")
+    context_type: Optional[str]  # 上下文类型(保留兼容性字段,当前未使用)
     
     # 分类结果
     question_type: Literal["DATABASE", "CHAT", "UNCERTAIN"]

+ 3 - 3
app_config.py

@@ -37,13 +37,13 @@ API_DEEPSEEK_CONFIG = {
 API_QIANWEN_CONFIG = {
     "api_key": os.getenv("QWEN_API_KEY"),  # 从环境变量读取API密钥
     "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",  # 千问API地址
-    "model": "qwen-plus-latest",
+    "model": "qwen3-235b-a22b",
     "allow_llm_to_see_data": True,
     "temperature": 0.6,
     "n_results": 6,
     "language": "Chinese",
-    "stream": False,  # 是否使用流式模式
-    "enable_thinking": False  # 是否启用思考功能(要求stream=True)
+    "stream": True,  # 是否使用流式模式
+    "enable_thinking": True  # 是否启用思考功能(要求stream=True)
 }
 #qwen3-30b-a3b
 #qwen3-235b-a22b

+ 1 - 5
citu_app.py

@@ -524,7 +524,7 @@ def ask_agent():
         if context:
             try:
                 # 获取最后一条助手消息的metadata
-                messages = redis_conversation_manager.get_messages(conversation_id, limit=10)
+                messages = redis_conversation_manager.get_conversation_messages(conversation_id, limit=10)
                 for message in reversed(messages):  # 从最新的开始找
                     if message.get("role") == "assistant":
                         metadata = message.get("metadata", {})
@@ -587,11 +587,9 @@ def ask_agent():
                 execution_path=cached_answer.get("execution_path", []),
                 classification_info=cached_answer.get("classification_info", {}),
                 user_id=user_id,
-                is_guest_user=(user_id == DEFAULT_ANONYMOUS_USER),
                 context_used=bool(context),
                 from_cache=True,
                 conversation_status=conversation_status["status"],
-                conversation_message=conversation_status["message"],
                 requested_conversation_id=conversation_status.get("requested_id")
             ))
         
@@ -696,11 +694,9 @@ def ask_agent():
                 execution_path=execution_path,
                 classification_info=classification_info,
                 user_id=user_id,
-                is_guest_user=(user_id == DEFAULT_ANONYMOUS_USER),
                 context_used=bool(context),
                 from_cache=False,
                 conversation_status=conversation_status["status"],
-                conversation_message=conversation_status["message"],
                 requested_conversation_id=conversation_status.get("requested_id"),
                 routing_mode_used=effective_routing_mode,  # 新增:实际使用的路由模式
                 routing_mode_source="api" if api_routing_mode else "config"  # 新增:路由模式来源

+ 3 - 3
common/redis_conversation_manager.py

@@ -81,7 +81,7 @@ class RedisConversationManager:
         Returns:
             tuple: (conversation_id, status_info)
             status_info包含:
-            - status: "existing" | "new" | "invalid_id_new"
+            - status: "continue" | "new" | "invalid_id_new"
             - message: 状态说明
             - requested_id: 原始请求的ID(如果有)
         """
@@ -91,7 +91,7 @@ class RedisConversationManager:
             if self._is_valid_conversation(conversation_id_input, user_id):
                 self.logger.debug(f"使用指定对话: {conversation_id_input}")
                 return conversation_id_input, {
-                    "status": "existing",
+                    "status": "continue",
                     "message": "继续已有对话"
                 }
             else:
@@ -109,7 +109,7 @@ class RedisConversationManager:
             if recent_conversation:
                 self.logger.debug(f"继续最近对话: {recent_conversation}")
                 return recent_conversation, {
-                    "status": "existing",
+                    "status": "continue",
                     "message": "继续最近对话"
                 }
         

+ 15 - 15
customllm/base_llm_chat.py

@@ -62,18 +62,18 @@ class BaseLLMChat(VannaBase, ABC):
         # 将Vanna的log输出转换为项目的日志格式
         if title == "SQL Prompt":
             # 对于SQL Prompt,使用debug级别,避免输出过长的内容
-            # 将列表格式转换为字符串,只显示前200个字符
+            # 将列表格式转换为字符串,只显示前500个字符
             if isinstance(message, list):
-                message_str = str(message)[:200] + "..." if len(str(message)) > 200 else str(message)
+                message_str = str(message)[:500] + "..." if len(str(message)) > 500 else str(message)
             else:
-                message_str = str(message)[:200] + "..." if len(str(message)) > 200 else str(message)
+                message_str = str(message)[:500] + "..." if len(str(message)) > 500 else str(message)
             self.logger.debug(f"[Vanna] {title}: {message_str}")
         elif title == "LLM Response":
             # 对于LLM响应,记录但不显示全部内容
             if isinstance(message, str):
-                message_str = message[:200] + "..." if len(message) > 200 else message
+                message_str = message[:500] + "..." if len(message) > 500 else message
             else:
-                message_str = str(message)[:200] + "..." if len(str(message)) > 200 else str(message)
+                message_str = str(message)[:500] + "..." if len(str(message)) > 500 else str(message)
             self.logger.debug(f"[Vanna] {title}: {message_str}")
         elif title == "Extracted SQL":
             # 对于提取的SQL,使用info级别
@@ -162,19 +162,19 @@ class BaseLLMChat(VannaBase, ABC):
 
         initial_prompt += self.prompt_loader.get_sql_response_guidelines(self.dialect)
 
-        message_log = [self.system_message(initial_prompt)]
+        sql_prompt_messages = [self.system_message(initial_prompt)]
 
         for example in question_sql_list:
             if example is None:
                 self.logger.warning("example is None")
             else:
                 if example is not None and "question" in example and "sql" in example:
-                    message_log.append(self.user_message(example["question"]))
-                    message_log.append(self.assistant_message(example["sql"]))
+                    sql_prompt_messages.append(self.user_message(example["question"]))
+                    sql_prompt_messages.append(self.assistant_message(example["sql"]))
 
-        message_log.append(self.user_message(question))
-        
-        return message_log
+        sql_prompt_messages.append(self.user_message(question))
+        # 实际发送给LLM的内容,当前做了格式化处理       
+        return sql_prompt_messages
 
     def generate_plotly_code(self, question: str = None, sql: str = None, df_metadata: str = None, **kwargs) -> str:
         """
@@ -190,13 +190,13 @@ class BaseLLMChat(VannaBase, ABC):
         # 构建用户消息
         user_msg = self.prompt_loader.get_chart_user_message()
 
-        message_log = [
+        chart_prompt_messages = [
             self.system_message(system_msg),
             self.user_message(user_msg),
         ]
 
         # 调用submit_prompt方法,并清理结果
-        plotly_code = self.submit_prompt(message_log, **kwargs)
+        plotly_code = self.submit_prompt(chart_prompt_messages, **kwargs)
         
         # 根据 DISPLAY_RESULT_THINKING 参数处理thinking内容
         if not DISPLAY_RESULT_THINKING:
@@ -485,12 +485,12 @@ class BaseLLMChat(VannaBase, ABC):
             # 构建用户消息,强调中文思考和回答
             user_content = self.prompt_loader.get_summary_user_instructions()
             
-            message_log = [
+            summary_prompt_messages = [
                 self.system_message(system_content),
                 self.user_message(user_content)
             ]
             
-            summary = self.submit_prompt(message_log, **kwargs)
+            summary = self.submit_prompt(summary_prompt_messages, **kwargs)
             
             # 检查是否需要隐藏 thinking 内容
             display_thinking = kwargs.get("display_result_thinking", DISPLAY_RESULT_THINKING)

+ 18 - 14
docs/redis_conversation_improvement_example.py

@@ -19,7 +19,7 @@ class ImprovedRedisConversationManager:
         Returns:
             tuple: (conversation_id, status_info)
             status_info包含:
-            - status: "existing" | "new" | "invalid_id_new" | "no_permission"
+            - status: "continue" | "new" | "invalid_id_new" | "no_permission"
             - message: 状态说明
             - requested_id: 原始请求的ID(如果有)
         """
@@ -31,7 +31,7 @@ class ImprovedRedisConversationManager:
             if validation_result["valid"]:
                 print(f"[REDIS_CONV] 使用指定对话: {conversation_id_input}")
                 return conversation_id_input, {
-                    "status": "existing",
+                    "status": "continue",
                     "message": "继续已有对话"
                 }
             else:
@@ -59,11 +59,11 @@ class ImprovedRedisConversationManager:
         if continue_conversation:
             recent_conversation = self._get_recent_conversation(user_id)
             if recent_conversation:
-                print(f"[REDIS_CONV] 继续最近对话: {recent_conversation}")
-                return recent_conversation, {
-                    "status": "existing",
-                    "message": "继续最近对话"
-                }
+                        print(f"[REDIS_CONV] 继续最近对话: {recent_conversation}")
+        return recent_conversation, {
+            "status": "continue",
+            "message": "继续最近对话"
+        }
         
         # 3. 创建新对话
         new_conversation_id = self.create_conversation(user_id)
@@ -183,7 +183,6 @@ def enhanced_ask_agent_response(conversation_status: Dict) -> Dict:
     # 添加对话状态信息
     response["data"].update({
         "conversation_status": conversation_status["status"],
-        "conversation_message": conversation_status["message"],
         "requested_conversation_id": conversation_status.get("requested_id")
     })
     
@@ -199,29 +198,34 @@ def frontend_handling_example():
         {
             "data": {
                 "conversation_status": "invalid_id_new",
-                "conversation_message": "您请求的对话不存在或已过期,已为您创建新对话",
                 "requested_conversation_id": "conv_old_123"
             }
         },
         {
             "data": {
                 "conversation_status": "no_permission",
-                "conversation_message": "您没有权限访问该对话,已为您创建新对话",
                 "requested_conversation_id": "conv_other_user"
             }
         },
         {
             "data": {
-                "conversation_status": "existing",
-                "conversation_message": "继续已有对话"
+                "conversation_status": "continue"
             }
         }
     ]
     
+    # 状态消息映射(支持本地化)
+    status_messages = {
+        "invalid_id_new": "您请求的对话不存在或已过期,已为您创建新对话",
+        "no_permission": "您没有权限访问该对话,已为您创建新对话", 
+        "continue": "继续已有对话",
+        "new": "创建新对话"
+    }
+    
     # 处理不同状态
     for response in api_responses:
         status = response["data"]["conversation_status"]
-        message = response["data"]["conversation_message"]
+        message = status_messages.get(status, "未知状态")
         
         if status == "invalid_id_new":
             print(f"⚠️ 警告通知: {message}")
@@ -233,7 +237,7 @@ def frontend_handling_example():
             print(f"  原请求ID: {response['data'].get('requested_conversation_id')}")
             print("  [清除本地无效的conversation_id]")
             
-        elif status == "existing":
+        elif status == "continue":
             print(f"✅ 成功: {message}")
             
         print()

+ 1098 - 0
docs/问题分类器关键词YAML化改造方案.md

@@ -0,0 +1,1098 @@
+# 问题分类器关键词YAML化改造方案
+
+## 📋 改造目标
+
+将问题分类器中硬编码的关键词提取到独立的YAML配置文件中,实现关键词与代码的分离,提高系统的可维护性和灵活性。
+
+## 🎯 改造背景
+
+### 当前问题
+1. **维护困难**: 关键词硬编码在`agent/classifier.py`中,修改需要改动代码
+2. **业务隔离**: 业务人员无法直接维护关键词,需要开发人员参与
+3. **版本管理**: 关键词变更难以独立追踪和回滚
+4. **环境配置**: 不同环境难以使用不同的关键词配置
+
+### 改造收益
+1. **业务自主**: 业务人员可直接编辑YAML文件维护关键词
+2. **热更新**: 支持重启生效的配置热更新
+3. **版本控制**: 关键词变更可独立进行Git版本管理
+4. **环境隔离**: 支持开发/测试/生产环境的差异化配置
+5. **备用机制**: 保留代码备用,确保系统稳定性
+
+## 📊 关键词类型分析
+
+根据`agent/classifier.py`代码分析,共有**8种关键词类型**需要迁移:
+
+| 序号 | 关键词类型 | 当前位置 | 数据结构 | 权重/作用 | 数量 |
+|------|------------|----------|----------|-----------|------|
+| 1 | 强业务关键词 | `classifier.py:49-79` | 字典(6个子类别) | 混合权重 | 65个 |
+| 2 | 查询意图关键词 | `classifier.py:81-87` | 列表 | +1分/词 | 25个 |
+| 3 | 非业务实体词 | `classifier.py:91-122` | 列表 | 立即CHAT(0.85) | ~80个 |
+| 4 | SQL模式 | `classifier.py:126-129` | 正则表达式列表 | +3分/匹配 | 2个 |
+| 5 | 聊天关键词 | `classifier.py:132-136` | 列表 | +1分/词 | 17个 |
+| 6 | 追问关键词 | `classifier.py:139-143` | 列表 | 上下文判断 | 16个 |
+| 7 | 话题切换关键词 | `classifier.py:146-150` | 列表 | 上下文判断 | 12个 |
+| 8 | 业务上下文文件 | `tools/db_query_decision_prompt.txt` | 外部文本 | LLM分类辅助 | 1个文件 |
+
+## 🏗️ 文件结构设计
+
+### 推荐方案:独立YAML配置文件
+
+```
+agent/
+├── config.py              # 现有配置文件(保持不变)
+├── classifier_dict.yaml   # 新增:分类器词典配置文件
+├── dict_loader.py         # 新增:词典加载器
+├── classifier.py          # 修改:使用YAML配置
+└── tools/
+    └── db_query_decision_prompt.txt  # 保持不变
+```
+
+### 文件职责分工
+
+| 文件 | 职责 | 变更类型 |
+|------|------|----------|
+| `classifier_dict.yaml` | 存储所有分类器词典配置 | 新增 |
+| `dict_loader.py` | 词典加载逻辑 | 新增 |
+| `config.py` | 导出词典加载函数 | 轻微修改 |
+| `classifier.py` | 使用YAML配置初始化关键词 | 中等修改 |
+
+## 📝 YAML配置文件设计
+
+### 文件路径
+```
+agent/classifier_dict.yaml
+```
+
+### 文件结构设计原则
+1. **层次化组织**: 保持原有的分类层次结构
+2. **权重配置**: 单独配置区域,便于调优
+3. **注释完整**: 每个配置项都有详细说明
+4. **版本标识**: 包含配置版本信息
+
+### 完整YAML配置文件
+
+```yaml
+# agent/classifier_dict.yaml
+# 问题分类器词典配置文件
+# 版本: v1.0
+# 最后更新: 2024-12-XX
+
+# ===========================================
+# 配置元信息
+# ===========================================
+metadata:
+  version: "1.0"
+  description: "Citu智能数据问答平台问题分类器关键词配置"
+  last_updated: "2024-12-XX"
+  author: "系统管理员"
+
+# ===========================================
+# 权重配置
+# ===========================================
+weights:
+  # 业务实体词权重(强业务关键词中除系统指示词外的部分)
+  business_entity: 2
+  
+  # 系统指示词权重(强业务关键词中的系统查询指示词)
+  system_indicator: 1
+  
+  # 查询意图词权重
+  query_intent: 1
+  
+  # SQL模式权重(最高权重)
+  sql_pattern: 3
+  
+  # 聊天关键词权重
+  chat_keyword: 1
+  
+  # 非业务词固定置信度
+  non_business_confidence: 0.85
+  
+  # 组合加分权重(系统指示词+业务实体词)
+  combination_bonus: 3
+
+# ===========================================
+# 强业务关键词(字典结构,保持原有层次)
+# ===========================================
+strong_business_keywords:
+  核心业务实体:
+    description: "高速公路服务区基础设施和业务系统"
+    keywords:
+      - 服务区
+      - 档口
+      - 商铺
+      - 收费站
+      - 高速公路
+      - 驿美          # 业务系统名称
+      - 驿购          # 业务系统名称
+      - 北区          # 物理分区
+      - 南区
+      - 西区
+      - 东区
+      - 两区
+      - 停车区
+      - 公司
+      - 管理公司
+      - 运营公司
+      - 驿美运营公司
+    
+  支付业务:
+    description: "支付方式、金额、订单等支付相关业务"
+    keywords:
+      # 支付方式全称
+      - 微信支付
+      - 支付宝支付
+      - 现金支付
+      - 行吧支付
+      - 金豆支付
+      
+      # 业务指标
+      - 支付金额
+      - 订单数量
+      - 营业额
+      - 收入
+      - 营业收入
+      
+      # 简化形式
+      - 微信
+      - 支付宝
+      - 现金
+      - 行吧
+      - 金豆
+      
+      # 系统字段名
+      - wx
+      - zfb
+      - rmb
+      - xs
+      - jd
+    
+  经营品类:
+    description: "经营类型、品牌、商业品类"
+    keywords:
+      - 餐饮
+      - 小吃
+      - 便利店
+      - 整体租赁
+      - 驿美餐饮
+      - 品牌
+      - 经营品类
+      - 商业品类
+    
+  车流业务:
+    description: "车辆流量、车型统计等车流相关业务"
+    keywords:
+      # 流量概念
+      - 车流量
+      - 车辆数量
+      - 车辆统计
+      - 流量统计
+      
+      # 车型分类
+      - 客车
+      - 货车
+      - 过境
+      - 危化品
+      - 城际
+      
+      # 分析概念
+      - 车型分布
+    
+  地理路线:
+    description: "高速线路、路段等地理位置信息"
+    keywords:
+      # 具体线路
+      - 大广
+      - 昌金
+      - 昌栗
+      
+      # 概念词
+      - 线路
+      - 路段
+      - 路线
+      - 高速线路
+      - 公路线路
+    
+  系统查询指示词:
+    description: "系统、数据库等查询指示词(特殊权重处理)"
+    weight: 1  # 特殊标记:权重低于其他业务实体词
+    keywords:
+      # 系统指示
+      - 当前系统
+      - 当前数据库
+      - 当前数据
+      - 数据库
+      - 本系统
+      - 系统
+      
+      # 数据指示
+      - 数据库中
+      - 数据中
+      - 现有数据
+      - 已有数据
+      - 存储的数据
+      
+      # 平台指示
+      - 平台数据
+      - 我们的数据库
+      - 这个系统
+
+# ===========================================
+# 查询意图关键词
+# ===========================================
+query_intent_keywords:
+  description: "用于识别数据查询意图的关键词"
+  keywords:
+    # 统计分析
+    - 统计
+    - 查询
+    - 分析
+    - 报表
+    - 报告
+    - 汇总
+    - 计算
+    - 对比
+    
+    # 排序概念
+    - 排行
+    - 排名
+    - 趋势
+    - 占比
+    - 百分比
+    - 比例
+    
+    # 聚合函数
+    - 最大
+    - 最小
+    - 最高
+    - 最低
+    - 平均
+    - 总计
+    - 合计
+    - 累计
+    - 求和
+    - 求平均
+    
+    # 输出动作
+    - 生成
+    - 导出
+    - 显示
+    - 列出
+    - 共有
+
+# ===========================================
+# 非业务实体词(一旦匹配立即分类为CHAT)
+# ===========================================
+non_business_keywords:
+  description: "明确的非业务领域问题,最高优先级直接分类"
+  
+  农产品食物:
+    - 荔枝
+    - 苹果
+    - 西瓜
+    - 水果
+    - 蔬菜
+    - 大米
+    - 小麦
+    - 橙子
+    - 香蕉
+    - 葡萄
+    - 草莓
+    - 樱桃
+    - 桃子
+    - 梨
+    
+  技术概念:
+    - 人工智能
+    - 机器学习
+    - 编程
+    - 算法
+    - 深度学习
+    - AI
+    - 神经网络
+    - 模型训练
+    - 数据挖掘
+    
+  身份询问:
+    - 你是谁
+    - 你是什么
+    - 你叫什么
+    - 你的名字
+    - 你是什么AI
+    - 什么模型
+    - 大模型
+    - AI助手
+    - 助手
+    - 机器人
+    
+  天气相关:
+    - 天气
+    - 气温
+    - 下雨
+    - 晴天
+    - 阴天
+    - 温度
+    - 天气预报
+    - 气候
+    - 降雨
+    - 雪天
+    
+  生活常识:
+    - 怎么做饭
+    - 如何减肥
+    - 健康
+    - 医疗
+    - 病症
+    - 历史
+    - 地理
+    - 文学
+    - 电影
+    - 音乐
+    - 体育
+    - 娱乐
+    - 游戏
+    - 小说
+    - 新闻
+    - 政治
+    - 战争
+    - 足球
+    - NBA
+    - 篮球
+    - 乒乓球
+    - 冠军
+    - 夺冠
+    - 高考
+    
+  旅游出行:
+    - 旅游
+    - 景点
+    - 门票
+    - 酒店
+    - 机票
+    - 航班
+    - 高铁
+    - 的士
+    
+  情绪表达:
+    - 伤心
+    - 开心
+    - 无聊
+    - 生气
+    - 孤独
+    - 累了
+    - 烦恼
+    - 心情
+    - 难过
+    - 抑郁
+    
+  商业金融:
+    - 股票
+    - 基金
+    - 理财
+    - 投资
+    - 经济
+    - 通货膨胀
+    - 上市
+    
+  哲学思考:
+    - 人生意义
+    - 价值观
+    - 道德
+    - 信仰
+    - 宗教
+    - 爱情
+    
+  地理范围:
+    - 全球
+    - 全国
+    - 亚洲
+    - 发展中
+    - 欧洲
+    - 美洲
+    - 东亚
+    - 东南亚
+    - 南美
+    - 非洲
+    - 大洋
+
+# ===========================================
+# SQL模式(正则表达式)
+# ===========================================
+sql_patterns:
+  description: "用于识别SQL语句特征的正则表达式"
+  patterns:
+    - pattern: "\\b(select|from|where|group by|order by|having|join|update)\\b"
+      description: "SQL关键字匹配"
+      case_sensitive: false
+      
+    - pattern: "\\b(数据库|表名|表|字段名|SQL|sql|database|table)\\b"
+      description: "数据库概念词匹配"
+      case_sensitive: false
+
+# ===========================================
+# 聊天关键词
+# ===========================================
+chat_keywords:
+  description: "倾向于聊天分类的关键词"
+  keywords:
+    # 问候语
+    - 你好啊
+    - 谢谢
+    - 再见
+    
+    # 疑问词
+    - 怎么样
+    - 如何
+    - 为什么
+    - 什么是
+    
+    # 帮助请求
+    - 介绍
+    - 解释
+    - 说明
+    - 帮助
+    - 操作
+    - 使用方法
+    - 功能
+    - 教程
+    - 指南
+    - 手册
+    - 讲解
+
+# ===========================================
+# 追问关键词(用于上下文判断)
+# ===========================================
+follow_up_keywords:
+  description: "用于检测追问型问题的关键词"
+  keywords:
+    # 延续词
+    - 还有
+    - 详细
+    - 具体
+    - 更多
+    - 继续
+    - 再
+    - 也
+    
+    # 连接词
+    - 那么
+    - 另外
+    - 其他
+    - 以及
+    - 还
+    - 进一步
+    
+    # 补充词
+    - 深入
+    - 补充
+    - 额外
+    - 此外
+    - 同时
+    - 并且
+
+# ===========================================
+# 话题切换关键词(用于上下文判断)
+# ===========================================
+topic_switch_keywords:
+  description: "用于检测明显话题转换的关键词"
+  keywords:
+    # 问候开场
+    - 你好
+    - 你是
+    - 谢谢
+    - 再见
+    
+    # 功能询问
+    - 介绍
+    - 功能
+    - 帮助
+    - 使用方法
+    
+    # 系统询问
+    - 平台
+    - 系统
+    - AI
+    - 助手
+
+# ===========================================
+# 配置验证规则
+# ===========================================
+validation:
+  required_sections:
+    - strong_business_keywords
+    - query_intent_keywords
+    - non_business_keywords
+    - sql_patterns
+    - chat_keywords
+    - follow_up_keywords
+    - topic_switch_keywords
+  
+  min_keywords_count:
+    strong_business_keywords: 50
+    query_intent_keywords: 20
+    non_business_keywords: 70
+    chat_keywords: 15
+```
+
+## 🔧 技术实现方案
+
+### 1. 关键词加载器设计
+
+创建 `agent/dict_loader.py`:
+
+```python
+# agent/dict_loader.py
+"""
+分类器词典配置加载器
+负责从YAML文件加载分类器词典配置,并提供数据转换和验证功能
+"""
+
+import yaml
+import os
+import re
+from typing import Dict, Any, List, Optional
+from dataclasses import dataclass
+from core.logging import get_agent_logger
+
+# 初始化日志
+logger = get_agent_logger("KeywordsLoader")
+
+@dataclass
+class ClassifierDictConfig:
+    """分类器词典配置数据类"""
+    strong_business_keywords: Dict[str, List[str]]
+    query_intent_keywords: List[str]
+    non_business_keywords: List[str]
+    sql_patterns: List[str]
+    chat_keywords: List[str]
+    follow_up_keywords: List[str]
+    topic_switch_keywords: List[str]
+    weights: Dict[str, float]
+    metadata: Dict[str, Any]
+
+class DictLoader:
+    """分类器词典配置加载器"""
+    
+    def __init__(self, dict_file: str = None):
+        """
+        初始化加载器
+        
+        Args:
+            dict_file: 词典配置文件路径,默认为agent/classifier_dict.yaml
+        """
+        if dict_file is None:
+            current_dir = os.path.dirname(os.path.abspath(__file__))
+            dict_file = os.path.join(current_dir, "classifier_dict.yaml")
+        
+        self.dict_file = dict_file
+        self.config_cache = None
+    
+    def load_config(self, force_reload: bool = False) -> ClassifierDictConfig:
+        """
+        加载词典配置
+        
+        Args:
+            force_reload: 是否强制重新加载,默认使用缓存
+            
+        Returns:
+            ClassifierDictConfig: 词典配置对象
+            
+        Raises:
+            FileNotFoundError: 配置文件不存在
+            ValueError: 配置文件格式错误
+        """
+        if self.config_cache is not None and not force_reload:
+            return self.config_cache
+        
+        try:
+            logger.info(f"加载词典配置文件: {self.dict_file}")
+            
+            with open(self.dict_file, 'r', encoding='utf-8') as f:
+                yaml_data = yaml.safe_load(f)
+            
+            # 验证配置文件
+            self._validate_config(yaml_data)
+            
+            # 转换数据格式
+            config = self._convert_config(yaml_data)
+            
+            # 缓存配置
+            self.config_cache = config
+            
+            logger.info("词典配置加载成功")
+            return config
+            
+        except FileNotFoundError:
+            error_msg = f"词典配置文件不存在: {self.dict_file}"
+            logger.error(error_msg)
+            raise FileNotFoundError(error_msg)
+        except yaml.YAMLError as e:
+            error_msg = f"词典配置文件YAML格式错误: {str(e)}"
+            logger.error(error_msg)
+            raise ValueError(error_msg)
+        except Exception as e:
+            error_msg = f"词典配置加载失败: {str(e)}"
+            logger.error(error_msg)
+            raise ValueError(error_msg)
+    
+    def _validate_config(self, yaml_data: Dict[str, Any]) -> None:
+        """验证配置文件格式和必要字段"""
+        required_sections = [
+            'strong_business_keywords',
+            'query_intent_keywords', 
+            'non_business_keywords',
+            'sql_patterns',
+            'chat_keywords',
+            'follow_up_keywords',
+            'topic_switch_keywords',
+            'weights'
+        ]
+        
+        for section in required_sections:
+            if section not in yaml_data:
+                raise ValueError(f"配置文件缺少必要部分: {section}")
+        
+        # 验证权重配置
+        required_weights = [
+            'business_entity',
+            'system_indicator', 
+            'query_intent',
+            'sql_pattern',
+            'chat_keyword',
+            'non_business_confidence'
+        ]
+        
+        for weight in required_weights:
+            if weight not in yaml_data['weights']:
+                raise ValueError(f"权重配置缺少: {weight}")
+        
+        logger.debug("配置文件验证通过")
+    
+    def _convert_config(self, yaml_data: Dict[str, Any]) -> ClassifierDictConfig:
+        """将YAML数据转换为ClassifierDictConfig对象"""
+        
+        # 转换强业务关键词(保持字典结构)
+        strong_business_keywords = {}
+        for category, data in yaml_data['strong_business_keywords'].items():
+            if isinstance(data, dict) and 'keywords' in data:
+                strong_business_keywords[category] = data['keywords']
+            else:
+                # 兼容简单格式
+                strong_business_keywords[category] = data
+        
+        # 转换查询意图关键词
+        query_intent_data = yaml_data['query_intent_keywords']
+        if isinstance(query_intent_data, dict) and 'keywords' in query_intent_data:
+            query_intent_keywords = query_intent_data['keywords']
+        else:
+            query_intent_keywords = query_intent_data
+        
+        # 转换非业务实体词(展平为列表)
+        non_business_keywords = self._flatten_non_business_keywords(
+            yaml_data['non_business_keywords']
+        )
+        
+        # 转换SQL模式
+        sql_patterns = []
+        patterns_data = yaml_data['sql_patterns']
+        if isinstance(patterns_data, dict) and 'patterns' in patterns_data:
+            for pattern_info in patterns_data['patterns']:
+                if isinstance(pattern_info, dict):
+                    sql_patterns.append(pattern_info['pattern'])
+                else:
+                    sql_patterns.append(pattern_info)
+        else:
+            sql_patterns = patterns_data
+        
+        # 转换其他关键词列表
+        chat_keywords = self._extract_keywords_list(yaml_data['chat_keywords'])
+        follow_up_keywords = self._extract_keywords_list(yaml_data['follow_up_keywords'])
+        topic_switch_keywords = self._extract_keywords_list(yaml_data['topic_switch_keywords'])
+        
+        return ClassifierDictConfig(
+            strong_business_keywords=strong_business_keywords,
+            query_intent_keywords=query_intent_keywords,
+            non_business_keywords=non_business_keywords,
+            sql_patterns=sql_patterns,
+            chat_keywords=chat_keywords,
+            follow_up_keywords=follow_up_keywords,
+            topic_switch_keywords=topic_switch_keywords,
+            weights=yaml_data['weights'],
+            metadata=yaml_data.get('metadata', {})
+        )
+    
+    def _flatten_non_business_keywords(self, non_business_data: Dict[str, Any]) -> List[str]:
+        """将分类的非业务词展平为列表"""
+        flattened = []
+        
+        # 跳过description字段
+        for category, keywords in non_business_data.items():
+            if category == 'description':
+                continue
+            if isinstance(keywords, list):
+                flattened.extend(keywords)
+        
+        return flattened
+    
+    def _extract_keywords_list(self, data: Any) -> List[str]:
+        """从可能包含description的数据中提取关键词列表"""
+        if isinstance(data, dict) and 'keywords' in data:
+            return data['keywords']
+        elif isinstance(data, list):
+            return data
+        else:
+            return []
+
+# 全局加载器实例
+_dict_loader = None
+
+def get_dict_loader() -> DictLoader:
+    """获取全局词典加载器实例"""
+    global _dict_loader
+    if _dict_loader is None:
+        _dict_loader = DictLoader()
+    return _dict_loader
+
+def load_classifier_dict_config(force_reload: bool = False) -> ClassifierDictConfig:
+    """
+    加载分类器词典配置(便捷函数)
+    
+    Args:
+        force_reload: 是否强制重新加载
+        
+    Returns:
+        ClassifierDictConfig: 词典配置对象
+    """
+    loader = get_dict_loader()
+    return loader.load_config(force_reload)
+```
+
+### 2. config.py 修改方案
+
+在 `agent/config.py` 中添加关键词加载函数:
+
+```python
+# 在 agent/config.py 文件末尾添加
+
+# ==================== 关键词配置加载 ====================
+
+try:
+    from .dict_loader import load_classifier_dict_config, get_dict_loader
+    
+    def get_classifier_dict_config(force_reload: bool = False):
+        """
+        获取分类器词典配置
+        
+        Args:
+            force_reload: 是否强制重新加载
+            
+        Returns:
+            ClassifierDictConfig: 词典配置对象
+        """
+        return load_classifier_dict_config(force_reload)
+    
+    def reload_classifier_dict_config():
+        """重新加载分类器词典配置"""
+        return load_classifier_dict_config(force_reload=True)
+    
+    # 导出词典配置函数
+    __all__ = [
+        'get_current_config', 
+        'get_nested_config', 
+        'AGENT_CONFIG',
+        'get_classifier_dict_config',
+        'reload_classifier_dict_config'
+    ]
+    
+except ImportError as e:
+    # 如果dict_loader模块不存在,提供空实现
+    def get_classifier_dict_config(force_reload: bool = False):
+        raise ImportError("词典加载器模块不可用,请检查dict_loader.py是否存在")
+    
+    def reload_classifier_dict_config():
+        raise ImportError("词典加载器模块不可用,请检查dict_loader.py是否存在")
+```
+
+### 3. classifier.py 修改方案
+
+修改 `QuestionClassifier.__init__` 方法:
+
+```python
+# 在 QuestionClassifier.__init__ 方法中的修改
+
+def __init__(self):
+    # 初始化日志
+    self.logger = get_agent_logger("Classifier")
+    
+    # 加载配置参数(保持现有逻辑)
+    try:
+        from agent.config import get_current_config, get_nested_config
+        config = get_current_config()
+        self.high_confidence_threshold = get_nested_config(config, "classification.high_confidence_threshold", 0.7)
+        # ... 其他配置参数加载保持不变
+        self.logger.info("从配置文件加载分类器参数完成")
+    except ImportError:
+        # ... 现有的默认配置逻辑保持不变
+        self.logger.warning("配置文件不可用,使用默认分类器参数")
+    
+    # 加载词典配置(新增逻辑)
+    self._load_dict_config()
+
+def _load_dict_config(self):
+    """加载分类器词典配置"""
+    try:
+        from agent.config import get_classifier_dict_config
+        dict_config = get_classifier_dict_config()
+        
+        # 加载强业务关键词
+        self.strong_business_keywords = dict_config.strong_business_keywords
+        
+        # 加载其他关键词列表
+        self.query_intent_keywords = dict_config.query_intent_keywords
+        self.non_business_keywords = dict_config.non_business_keywords
+        self.chat_keywords = dict_config.chat_keywords
+        self.follow_up_keywords = dict_config.follow_up_keywords
+        self.topic_switch_keywords = dict_config.topic_switch_keywords
+        
+        # 加载SQL模式
+        self.sql_patterns = dict_config.sql_patterns
+        
+        # 记录加载的关键词数量
+        total_keywords = (
+            sum(len(keywords) for keywords in self.strong_business_keywords.values()) +
+            len(self.query_intent_keywords) +
+            len(self.non_business_keywords) +
+            len(self.chat_keywords) +
+            len(self.follow_up_keywords) +
+            len(self.topic_switch_keywords)
+        )
+        
+        self.logger.info(f"从YAML配置文件加载词典完成,共加载 {total_keywords} 个关键词")
+        
+    except Exception as e:
+        self.logger.warning(f"加载YAML词典配置失败: {str(e)},使用代码中的备用配置")
+        self._load_default_dict()
+
+def _load_default_dict(self):
+    """加载代码中的备用词典配置"""
+    self.logger.info("使用代码中的默认词典配置作为备用")
+    
+    # 保留原有的硬编码关键词作为备用
+    self.strong_business_keywords = {
+        "核心业务实体": [
+            "服务区", "档口", "商铺", "收费站", "高速公路",
+            "驿美", "驿购",
+            "北区", "南区", "西区", "东区", "两区",
+            "停车区", "公司", "管理公司", "运营公司", "驿美运营公司"
+        ],
+        # ... 其他关键词类别的备用配置
+    }
+    
+    # ... 其他关键词的备用配置
+    
+    self.logger.info("默认词典配置加载完成")
+```
+
+## 🧪 测试验证方案
+
+### 1. 单元测试设计
+
+创建 `test/test_dict_loader.py`:
+
+```python
+# test/test_dict_loader.py
+import unittest
+import tempfile
+import os
+import yaml
+from agent.dict_loader import DictLoader, ClassifierDictConfig
+
+class TestDictLoader(unittest.TestCase):
+    """词典加载器测试"""
+    
+    def setUp(self):
+        """测试前准备"""
+        self.test_yaml_content = {
+            'metadata': {'version': '1.0'},
+            'weights': {
+                'business_entity': 2,
+                'system_indicator': 1,
+                'query_intent': 1,
+                'sql_pattern': 3,
+                'chat_keyword': 1,
+                'non_business_confidence': 0.85
+            },
+            'strong_business_keywords': {
+                '核心业务实体': {
+                    'keywords': ['服务区', '档口']
+                }
+            },
+            'query_intent_keywords': {
+                'keywords': ['统计', '查询']
+            },
+            'non_business_keywords': {
+                '农产品食物': ['苹果', '香蕉']
+            },
+            'sql_patterns': {
+                'patterns': [
+                    {'pattern': '\\bselect\\b', 'description': 'SQL关键字'}
+                ]
+            },
+            'chat_keywords': {
+                'keywords': ['你好', '谢谢']
+            },
+            'follow_up_keywords': {
+                'keywords': ['还有', '详细']
+            },
+            'topic_switch_keywords': {
+                'keywords': ['你好', '你是']
+            }
+        }
+    
+    def test_load_valid_config(self):
+        """测试加载有效配置"""
+        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
+            yaml.dump(self.test_yaml_content, f)
+            temp_file = f.name
+        
+        try:
+            loader = DictLoader(temp_file)
+            config = loader.load_config()
+            
+            self.assertIsInstance(config, ClassifierDictConfig)
+            self.assertEqual(config.weights['business_entity'], 2)
+            self.assertIn('服务区', config.strong_business_keywords['核心业务实体'])
+            self.assertIn('苹果', config.non_business_keywords)
+            
+        finally:
+            os.unlink(temp_file)
+    
+    def test_load_missing_file(self):
+        """测试加载不存在的文件"""
+        loader = DictLoader('nonexistent.yaml')
+        with self.assertRaises(FileNotFoundError):
+            loader.load_config()
+    
+    def test_load_invalid_yaml(self):
+        """测试加载无效YAML"""
+        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
+            f.write("invalid: yaml: content: [")
+            temp_file = f.name
+        
+        try:
+            loader = DictLoader(temp_file)
+            with self.assertRaises(ValueError):
+                loader.load_config()
+        finally:
+            os.unlink(temp_file)
+
+if __name__ == '__main__':
+    unittest.main()
+```
+
+### 2. 集成测试设计
+
+创建 `test/test_classifier_yaml_integration.py`:
+
+```python
+# test/test_classifier_yaml_integration.py
+import unittest
+from agent.classifier import QuestionClassifier
+
+class TestClassifierYamlIntegration(unittest.TestCase):
+    """分类器YAML集成测试"""
+    
+    def setUp(self):
+        """测试前准备"""
+        self.classifier = QuestionClassifier()
+    
+    def test_yaml_dict_loaded(self):
+        """测试YAML词典是否正确加载"""
+        # 验证强业务关键词
+        self.assertIsInstance(self.classifier.strong_business_keywords, dict)
+        self.assertIn('核心业务实体', self.classifier.strong_business_keywords)
+        
+        # 验证其他关键词列表
+        self.assertIsInstance(self.classifier.query_intent_keywords, list)
+        self.assertIsInstance(self.classifier.non_business_keywords, list)
+        self.assertIsInstance(self.classifier.chat_keywords, list)
+    
+    def test_classification_still_works(self):
+        """测试分类功能仍然正常工作"""
+        # 测试业务查询
+        result = self.classifier.classify("统计服务区的微信支付金额")
+        self.assertEqual(result.question_type, "DATABASE")
+        
+        # 测试非业务查询
+        result = self.classifier.classify("苹果什么时候成熟")
+        self.assertEqual(result.question_type, "CHAT")
+        
+        # 测试聊天查询
+        result = self.classifier.classify("你好,请问如何使用")
+        self.assertEqual(result.question_type, "CHAT")
+
+if __name__ == '__main__':
+    unittest.main()
+```
+
+## 📋 实施步骤
+
+### 阶段一:基础设施搭建(1-2天)
+1. ✅ 创建 `agent/classifier_dict.yaml` 配置文件
+2. ✅ 创建 `agent/dict_loader.py` 加载器
+3. ✅ 修改 `agent/config.py` 添加加载函数
+4. ✅ 编写单元测试
+
+### 阶段二:代码改造(1天)
+1. ✅ 修改 `QuestionClassifier.__init__` 方法
+2. ✅ 添加备用关键词加载逻辑
+3. ✅ 编写集成测试
+
+### 阶段三:测试验证(1天)
+1. ✅ 运行单元测试和集成测试
+2. ✅ 验证分类功能正确性
+3. ✅ 测试异常情况处理
+
+### 阶段四:部署上线(0.5天)
+1. ✅ 部署配置文件到生产环境
+2. ✅ 验证系统运行正常
+3. ✅ 监控分类效果
+
+## 🎯 预期效果
+
+### 立即收益
+1. **词典维护便利化**: 业务人员可直接编辑YAML文件
+2. **配置版本化管理**: 词典变更可进行Git版本控制
+3. **系统稳定性保障**: 备用机制确保配置失败时系统正常运行
+
+### 长期收益
+1. **快速业务适配**: 新业务场景的词典快速添加
+2. **A/B测试支持**: 不同环境使用不同词典配置
+3. **数据驱动优化**: 基于分类效果数据调整词典权重
+
+## ⚠️ 风险控制
+
+### 潜在风险
+1. **配置文件错误**: YAML格式错误导致系统启动失败
+2. **词典缺失**: 关键词遗漏影响分类准确性
+3. **权重配置错误**: 权重设置不当影响分类效果
+
+### 风险控制措施
+1. **格式验证**: 加载器进行严格的YAML格式和必要字段验证
+2. **备用机制**: 保留代码中的默认词典作为备用
+3. **渐进式部署**: 先在测试环境验证,再逐步推广到生产环境
+4. **监控告警**: 添加词典加载失败的监控和告警
+5. **文档说明**: 提供详细的配置文件编辑指南
+
+## 📈 后续优化方向
+
+1. **热更新机制**: 实现运行时重载词典配置,无需重启
+2. **可视化管理**: 开发Web界面管理词典配置
+3. **智能推荐**: 基于用户查询日志推荐新关键词
+4. **效果分析**: 统计各关键词的命中率和分类准确性
+5. **多环境支持**: 支持开发/测试/生产环境的差异化配置
+
+---
+
+*本方案基于当前系统架构设计,遵循最小变更原则,确保改造过程中系统稳定运行。* 

+ 503 - 0
docs/问题分类器规则判断逻辑详解.md

@@ -0,0 +1,503 @@
+# 问题分类器规则判断逻辑详解
+
+## 概述
+
+问题分类器(`QuestionClassifier`)是Citu智能数据问答平台的核心组件,负责将用户问题分类为`DATABASE`(数据库查询)或`CHAT`(聊天对话)类型。本文档详细解释其基于规则的分类逻辑和评分机制。
+
+## 分类流程概览
+
+```mermaid
+graph TD
+    A[用户问题输入] --> B[提取当前问题]
+    B --> C[检查非业务实体词]
+    C --> D{包含非业务词?}
+    D -->|是| E[直接分类为CHAT<br/>置信度0.85]
+    D -->|否| F[计算各类评分]
+    F --> G[业务实体评分]
+    F --> H[系统指示词评分]
+    F --> I[查询意图评分]
+    F --> J[SQL模式评分]
+    F --> K[聊天关键词评分]
+    G --> L[组合评分逻辑]
+    H --> L
+    I --> L
+    J --> L
+    K --> L
+    L --> M[分类决策]
+    M --> N[返回分类结果]
+```
+
+## 1. 关键词定义体系
+
+### 1.0 关键词类型总览
+
+问题分类器定义了 **8种关键词类型**,用于不同的分类判断:
+
+| 序号 | 关键词类型 | 数据结构 | 权重/作用 | 数量 | 定义位置 |
+|------|------------|----------|-----------|------|----------|
+| 1 | **强业务关键词** | 字典(6个子类别) | 混合权重 | 65个 | `classifier.py:49-79` |
+| 2 | **查询意图关键词** | 列表 | +1分/词 | 25个 | `classifier.py:81-87` |
+| 3 | **非业务实体词** | 列表 | 立即CHAT(0.85) | ~80个 | `classifier.py:91-122` |
+| 4 | **SQL模式** | 正则表达式列表 | +3分/匹配 | 2个 | `classifier.py:126-129` |
+| 5 | **聊天关键词** | 列表 | +1分/词 | 17个 | `classifier.py:132-136` |
+| 6 | **追问关键词** | 列表 | 上下文判断 | 16个 | `classifier.py:139-143` |
+| 7 | **话题切换关键词** | 列表 | 上下文判断 | 12个 | `classifier.py:146-150` |
+| 8 | **业务上下文文件** | 外部文本 | LLM分类辅助 | 1个文件 | `tools/db_query_decision_prompt.txt` |
+
+### 1.1 强业务关键词与业务实体词的关系
+
+**关键概念区分**:
+
+```mermaid
+graph TD
+    A[强业务关键词<br/>strong_business_keywords] --> B[业务实体词<br/>+2分/词<br/>5个子类别]
+    A --> C[系统查询指示词<br/>+1分/词<br/>特殊处理]
+    
+    B --> D[核心业务实体]
+    B --> E[支付业务] 
+    B --> F[经营品类]
+    B --> G[车流业务]
+    B --> H[地理路线]
+    
+    C --> I[系统指示<br/>数据指示<br/>平台指示]
+    
+    style B fill:#e1f5fe
+    style C fill:#fff3e0
+```
+
+**包含关系说明**:
+- **强业务关键词** = 总概念(6个子类别)
+- **业务实体词** = 强业务关键词 - 系统查询指示词(5个子类别)
+- **权重差异**:业务实体词(+2分/词) > 系统查询指示词(+1分/词)
+
+**代码实现逻辑**:
+```python
+# 在 _rule_based_classify 方法中
+for category, keywords in self.strong_business_keywords.items():
+    if category == "系统查询指示词":  # 系统指示词单独处理
+        continue
+    for keyword in keywords:
+        if keyword in question_lower:
+            business_score += 2  # 业务实体词权重更高
+```
+
+### 1.2 强业务关键词详细定义 (strong_business_keywords)
+
+这是分类器的核心词库,分为6个业务类别:
+
+#### 核心业务实体 (权重: +2分/词)
+- **基础设施**: 服务区、档口、商铺、收费站、高速公路、停车区
+- **业务系统**: 驿美、驿购
+- **物理分区**: 北区、南区、西区、东区、两区
+- **公司相关**: 公司、管理公司、运营公司、驿美运营公司
+
+#### 支付业务 (权重: +2分/词)
+- **支付方式全称**: 微信支付、支付宝支付、现金支付、行吧支付、金豆支付
+- **业务指标**: 支付金额、订单数量、营业额、收入、营业收入
+- **简化形式**: 微信、支付宝、现金、行吧、金豆
+- **系统字段**: wx、zfb、rmb、xs、jd
+
+#### 经营品类 (权重: +2分/词)
+- **品类**: 餐饮、小吃、便利店、整体租赁
+- **品牌**: 驿美餐饮、品牌、经营品类、商业品类
+
+#### 车流业务 (权重: +2分/词)
+- **流量概念**: 车流量、车辆数量、车辆统计、流量统计
+- **车型分类**: 客车、货车、过境、危化品、城际
+- **分析概念**: 车型分布
+
+#### 地理路线 (权重: +2分/词)
+- **具体线路**: 大广、昌金、昌栗
+- **概念词**: 线路、路段、路线、高速线路、公路线路
+
+#### 系统查询指示词 (权重: +1分/词,特殊处理)
+- **系统指示**: 当前系统、当前数据库、当前数据、数据库、本系统、系统
+- **数据指示**: 数据库中、数据中、现有数据、已有数据、存储的数据
+- **平台指示**: 平台数据、我们的数据库、这个系统
+
+### 1.2 查询意图关键词 (query_intent_keywords)
+
+用于识别数据查询意图,权重: +1分/词
+
+- **统计分析**: 统计、查询、分析、报表、报告、汇总、计算、对比
+- **排序概念**: 排行、排名、趋势、占比、百分比、比例
+- **聚合函数**: 最大、最小、最高、最低、平均、总计、合计、累计、求和、求平均
+- **输出动作**: 生成、导出、显示、列出、共有
+
+### 1.3 SQL模式匹配 (sql_patterns)
+
+使用正则表达式匹配SQL语句特征,权重: +3分/匹配
+
+- **SQL关键字**: `select|from|where|group by|order by|having|join|update`
+- **数据库概念**: `数据库|表名|表|字段名|SQL|sql|database|table`
+
+### 1.4 非业务实体词 (non_business_keywords)
+
+**定义位置**: `agent/classifier.py` 第91-122行
+
+**处理机制**: 一旦匹配,直接分类为CHAT,置信度0.85(**最高优先级判断**)
+
+**具体分类**:
+- **农产品/食物**: 荔枝、苹果、西瓜、水果、蔬菜、大米、小麦、橙子、香蕉、葡萄、草莓、樱桃、桃子、梨
+- **技术概念**: 人工智能、机器学习、编程、算法、深度学习、AI、神经网络、模型训练、数据挖掘
+- **身份询问**: 你是谁、你是什么、你叫什么、你的名字、你是什么AI、什么模型、大模型、AI助手、助手、机器人
+- **天气相关**: 天气、气温、下雨、晴天、阴天、温度、天气预报、气候、降雨、雪天
+- **生活常识**: 怎么做饭、如何减肥、健康、医疗、病症、历史、地理、文学、电影、音乐、体育、娱乐、游戏、小说、新闻、政治、战争、足球、NBA、篮球、乒乓球、冠军、夺冠、高考
+- **旅游出行**: 旅游、景点、门票、酒店、机票、航班、高铁、的士
+- **情绪表达**: 伤心、开心、无聊、生气、孤独、累了、烦恼、心情、难过、抑郁
+- **商业金融**: 股票、基金、理财、投资、经济、通货膨胀、上市
+- **哲学思考**: 人生意义、价值观、道德、信仰、宗教、爱情
+- **地理范围**: 全球、全国、亚洲、发展中、欧洲、美洲、东亚、东南亚、南美、非洲、大洋
+
+### 1.5 聊天关键词 (chat_keywords)
+
+**定义位置**: `agent/classifier.py` 第132-136行
+
+**处理机制**: 倾向于聊天分类,权重: +1分/词
+
+**具体分类**:
+- **问候语**: 你好啊、谢谢、再见
+- **疑问词**: 怎么样、如何、为什么、什么是
+- **帮助请求**: 介绍、解释、说明、帮助、操作、使用方法、功能、教程、指南、手册、讲解
+
+### 1.6 追问关键词 (follow_up_keywords)
+
+**定义位置**: `agent/classifier.py` 第139-143行
+
+**处理机制**: 用于检测追问型问题,在渐进式分类中起上下文判断作用
+
+**具体分类**:
+- **延续词**: 还有、详细、具体、更多、继续、再、也
+- **连接词**: 那么、另外、其他、以及、还、进一步
+- **补充词**: 深入、补充、额外、此外、同时、并且
+
+### 1.7 话题切换关键词 (topic_switch_keywords)
+
+**定义位置**: `agent/classifier.py` 第146-150行
+
+**处理机制**: 检测明显的话题转换,避免错误继承上下文类型
+
+**具体分类**:
+- **问候开场**: 你好、你是、谢谢、再见
+- **功能询问**: 介绍、功能、帮助、使用方法
+- **系统询问**: 平台、系统、AI、助手
+
+### 1.8 业务上下文文件 (外部文本)
+
+**定义位置**: `agent/tools/db_query_decision_prompt.txt`
+
+**处理机制**: 为LLM分类提供详细的业务范围描述
+
+**内容概要**:
+- 核心业务实体定义
+- 关键业务指标说明  
+- 高速线路信息
+- 数据库业务范围界定
+
+## 2. 评分机制详解
+
+### 2.1 评分计算流程
+
+```python
+# 1. 业务实体评分 (business_score)
+for 每个业务类别:
+    if 类别 != "系统查询指示词":
+        for 每个关键词:
+            if 关键词 in 问题:
+                business_score += 2
+
+# 2. 系统指示词评分 (system_indicator_score)
+for 每个系统查询指示词:
+    if 关键词 in 问题:
+        system_indicator_score += 1
+
+# 3. 查询意图评分 (intent_score)
+for 每个查询意图词:
+    if 关键词 in 问题:
+        intent_score += 1
+
+# 4. SQL模式评分
+for 每个SQL正则模式:
+    if 模式匹配:
+        business_score += 3
+
+# 5. 聊天关键词评分 (chat_score)
+for 每个聊天关键词:
+    if 关键词 in 问题:
+        chat_score += 1
+```
+
+### 2.2 组合评分逻辑
+
+系统指示词具有特殊的组合效应:
+
+```python
+if system_indicator_score > 0 and business_score > 0:
+    # 系统指示词 + 业务实体 = 强组合效应
+    business_score += 3  # 组合加分
+elif system_indicator_score > 0:
+    # 仅有系统指示词 = 中等业务倾向
+    business_score += 1
+```
+
+**设计理念**: 
+- 当用户说"当前系统有哪些服务区"时,"当前系统"(+1) + "服务区"(+2) + 组合加分(+3) = 总计6分
+- 仅有"当前系统"时,只加1分,表示轻微的数据查询倾向
+
+## 3. 分类决策规则
+
+### 3.1 优先级决策顺序
+
+1. **非业务实体词检查** (最高优先级)
+   - 条件: 包含任何非业务实体词
+   - 结果: `CHAT`, 置信度=0.85
+   - 理由: 明确的非业务领域问题
+
+2. **强业务特征** (次高优先级)
+   - 条件: `business_score ≥ 2` AND `intent_score ≥ 1`
+   - 结果: `DATABASE`
+   - 置信度计算: `min(max_confidence, 0.8 + (total_business_score * 0.05))`
+   - 理由: 既有业务实体,又有查询意图
+
+3. **中等业务特征**
+   - 条件: `business_score ≥ 4`
+   - 结果: `DATABASE`
+   - 置信度计算: `min(max_confidence, 0.7 + (business_score * 0.03))`
+   - 理由: 包含多个业务实体词
+
+4. **聊天特征**
+   - 条件: `chat_score ≥ 1` AND `business_score = 0`
+   - 结果: `CHAT`
+   - 置信度计算: `min(max_confidence, base_confidence + (chat_score * confidence_increment))`
+   - 理由: 有聊天意图且无业务特征
+
+5. **不确定情况** (最低优先级)
+   - 条件: 不满足以上任何条件
+   - 结果: `UNCERTAIN`
+   - 置信度: `uncertain_confidence` (默认0.2)
+   - 理由: 规则无法明确判断
+
+### 3.2 置信度计算公式
+
+#### 强业务特征置信度
+```
+confidence = min(max_confidence, 0.8 + (total_business_score * 0.05))
+其中: total_business_score = business_score + intent_score
+```
+
+#### 中等业务特征置信度
+```
+confidence = min(max_confidence, 0.7 + (business_score * 0.03))
+```
+
+#### 聊天特征置信度
+```
+confidence = min(max_confidence, base_confidence + (chat_score * confidence_increment))
+默认: base_confidence=0.4, confidence_increment=0.08
+```
+
+## 4. 置信度阈值决策机制
+
+### 4.1 决策流程图
+
+```mermaid
+graph TD
+    A[规则分类完成] --> B{非业务词匹配?}
+    B -->|是| C[直接CHAT<br/>置信度=0.85<br/>🔴王炸优先级]
+    B -->|否| D{置信度 ≥ 0.7?}
+    D -->|是| E[🟢毫不犹豫<br/>直接使用规则结果<br/>不调用LLM]
+    D -->|否| F[🟡调用LLM分类<br/>进行二次判断]
+    F --> G[比较两个置信度]
+    G --> H[选择置信度更高的结果]
+```
+
+### 4.2 关键阈值说明
+
+| 置信度范围 | 决策行为 | 代码位置 | 说明 |
+|------------|----------|----------|------|
+| **非业务词匹配** | 🔴 直接CHAT,置信度=0.85 | `classifier.py:354-361` | 最高优先级,立即决策 |
+| **≥ 0.7** | 🟢 毫不犹豫使用规则结果 | `classifier.py:291-292` | 高置信度,不调用LLM |
+| **0.4 - 0.69** | 🟡 规则+LLM双重判断 | `classifier.py:294-301` | 取置信度更高者 |
+| **< 0.4** | 🟡 规则+LLM双重判断 | `classifier.py:294-301` | 取置信度更高者 |
+
+### 4.3 决策示例分析
+
+#### 示例1:毫不犹豫决策 (≥0.7)
+```
+问题: "统计服务区的微信支付金额"
+规则分类: DATABASE, 置信度=0.9
+决策: 直接使用规则结果,不调用LLM ✓
+```
+
+#### 示例2:双重判断 (<0.7)
+```
+问题: "服务区情况"
+规则分类: DATABASE, 置信度=0.6
+LLM分类: DATABASE, 置信度=0.8
+决策: 选择LLM结果 (0.8 > 0.6) ✓
+```
+
+#### 示例3:非业务词王炸
+```
+问题: "苹果什么时候成熟"
+非业务词: 苹果 ✓
+决策: 直接CHAT,置信度=0.85,跳过所有其他判断 ✓
+```
+
+### 4.4 设计理念
+
+1. **成本控制**: 0.7是经济平衡点,避免过度调用LLM
+2. **准确性保障**: 低置信度时用LLM作为"第二意见"
+3. **强制优先级**: 非业务词具有绝对优先权
+4. **智能选择**: 总是选择置信度更高的分类结果
+
+### 4.5 混合分类模式说明
+
+**代码位置**: `agent/classifier.py:283-301` (`_hybrid_classify`方法)
+
+```python
+def _hybrid_classify(self, question: str) -> ClassificationResult:
+    # 第一步:规则预筛选
+    rule_result = self._rule_based_classify(question)
+    
+    # 如果规则分类有高置信度,直接使用
+    if rule_result.confidence >= self.high_confidence_threshold:  # 0.7
+        return rule_result  # 毫不犹豫使用规则结果
+    
+    # 第二步:使用增强的LLM分类
+    llm_result = self._enhanced_llm_classify(question)
+    
+    # 选择置信度更高的结果
+    if llm_result.confidence > rule_result.confidence:
+        return llm_result
+    else:
+        return rule_result
+```
+
+**关键特点**:
+- 没有"必须交给LLM"的阈值下限
+- 即使规则置信度很低(如0.2),如果LLM置信度更低,仍会选择规则结果
+- 这样避免了强制依赖LLM,保持了系统的鲁棒性
+
+## 5. 配置参数说明
+
+### 5.1 核心阈值参数
+
+| 参数名 | 默认值 | 范围 | 说明 |
+|--------|--------|------|------|
+| `high_confidence_threshold` | 0.7 | 0.7-0.9 | 高置信度阈值,超过则直接使用规则结果 |
+| `low_confidence_threshold` | 0.4 | 0.2-0.5 | 低置信度阈值,低于则启用LLM辅助 |
+| `max_confidence` | 0.9 | 0.8-1.0 | 最大置信度上限,防止过度自信 |
+| `base_confidence` | 0.4 | 0.3-0.6 | 基础置信度,聊天分类的起始值 |
+| `confidence_increment` | 0.08 | 0.05-0.2 | 置信度增量步长 |
+| `uncertain_confidence` | 0.2 | 0.1-0.3 | 不确定分类的置信度 |
+
+### 5.2 评分权重体系
+
+| 关键词类型 | 权重 | 用途 | 说明 |
+|------------|------|------|------|
+| **业务实体词** | +2分/词 | 规则评分 | 核心业务概念,强业务关键词的主要部分 |
+| **系统指示词** | +1分/词 | 规则评分 | 系统查询指示,权重低于业务实体词 |
+| **查询意图词** | +1分/词 | 规则评分 | 数据查询意图,辅助判断 |
+| **SQL模式** | +3分/匹配 | 规则评分 | 技术查询特征,权重最高 |
+| **聊天关键词** | +1分/词 | 规则评分 | 聊天交互意图 |
+| **非业务实体词** | 立即CHAT(0.85) | 直接分类 | 最高优先级,跳过所有评分 |
+| **追问关键词** | 无直接权重 | 上下文判断 | 检测追问型问题 |
+| **话题切换关键词** | 无直接权重 | 上下文判断 | 检测话题转换 |
+| **组合加分** | +3分 | 特殊逻辑 | 系统词+业务词组合效应 |
+
+## 6. 典型分类示例
+
+### 6.1 DATABASE分类示例
+
+#### 强业务特征 (business_score≥2 + intent_score≥1)
+```
+问题: "统计服务区的微信支付金额"
+匹配:
+- 业务实体: 服务区(+2), 微信支付(+2) → business_score=4
+- 查询意图: 统计(+1) → intent_score=1
+- 总分: 4+1=5
+结果: DATABASE, 置信度=min(0.9, 0.8+5*0.05)=0.9
+```
+
+#### 中等业务特征 (business_score≥4)
+```
+问题: "驿美运营公司档口数量"
+匹配:
+- 业务实体: 驿美运营公司(+2), 档口(+2) → business_score=4
+- 查询意图: 无 → intent_score=0
+结果: DATABASE, 置信度=min(0.9, 0.7+4*0.03)=0.82
+```
+
+### 6.2 CHAT分类示例
+
+#### 非业务实体词
+```
+问题: "苹果什么时候成熟"
+匹配: 苹果(非业务词)
+结果: CHAT, 置信度=0.85
+```
+
+#### 聊天特征
+```
+问题: "怎么使用这个平台"
+匹配:
+- 聊天关键词: 怎么(+1), 使用方法(+1) → chat_score=2
+- 业务实体: 无 → business_score=0
+结果: CHAT, 置信度=min(0.9, 0.4+2*0.08)=0.56
+```
+
+### 6.3 UNCERTAIN分类示例
+
+```
+问题: "请问一下"
+匹配: 无关键词匹配
+结果: UNCERTAIN, 置信度=0.2
+```
+
+## 7. 优化建议
+
+### 7.1 当前逻辑的优势
+1. **完整的关键词体系**: 8种关键词类型覆盖了规则评分、直接分类、上下文判断等不同维度
+2. **明确的优先级**: 非业务词 > 强业务 > 中等业务 > 聊天 > 不确定
+3. **精细的权重设计**: 业务实体词(+2) > 系统指示词(+1),体现业务相关性差异
+4. **组合效应**: 系统指示词与业务词的协同加分机制
+5. **置信度区分**: 不同条件下的差异化置信度计算
+6. **上下文感知**: 追问关键词和话题切换关键词支持渐进式分类
+7. **可配置性**: 所有阈值和权重都可调整
+
+### 7.2 潜在优化方向
+1. **关键词扩展**: 根据实际业务场景补充关键词库
+2. **权重调优**: 基于分类效果数据调整各类词的权重
+3. **组合规则**: 增加更多的关键词组合判断逻辑
+4. **上下文考虑**: 增强上下文相关性的判断机制
+5. **动态阈值**: 根据历史分类准确率动态调整阈值
+
+### 7.3 监控指标
+1. **分类准确率**: 各类别的分类正确率
+2. **置信度分布**: 高、中、低置信度的分布情况
+3. **关键词命中率**: 各关键词的实际使用频率
+4. **边界案例**: 接近阈值的分类案例分析
+
+## 8. 技术实现细节
+
+### 8.1 问题预处理
+```python
+def _extract_current_question_for_rule_classification(self, question: str) -> str:
+    """提取当前问题用于规则分类,避免上下文干扰"""
+    if "\n[CURRENT]\n" in question:
+        current_start = question.find("\n[CURRENT]\n")
+        current_question = question[current_start + len("\n[CURRENT]\n"):].strip()
+        return current_question
+    return question.strip()
+```
+
+### 8.2 大小写处理
+所有关键词匹配都转换为小写进行,确保大小写不敏感。
+
+### 8.3 正则表达式
+SQL模式使用正则表达式匹配,支持单词边界检查,避免子字符串误匹配。
+
+---
+
+*本文档基于 agent/classifier.py 代码分析生成,版本日期: 2024年* 

+ 9 - 2
test/redis_conversation_demo.py

@@ -49,7 +49,7 @@ class ConversationDemo:
             
             print(f"[结果] 对话ID: {self.conversation_id}")
             print(f"[结果] 用户ID: {self.user_id}")
-            print(f"[结果] 是否为Guest用户: {data['data'].get('is_guest_user')}")
+            print(f"[结果] 是否为Guest用户: {data['data']['user_id'] == 'guest'}")
             print(f"[结果] 回答: {data['data'].get('response', '')[:100]}...")
         else:
             print(f"[错误] 响应码: {response.status_code}")
@@ -198,7 +198,14 @@ class ConversationDemo:
         if response.status_code == 200:
             data = response.json()
             print(f"[结果] 对话状态: {data['data'].get('conversation_status')}")
-            print(f"[结果] 状态消息: {data['data'].get('conversation_message')}")
+            # 根据状态显示对应消息(本地化处理)
+            status = data['data'].get('conversation_status')
+            status_messages = {
+                'new': '创建新对话',
+                'continue': '继续已有对话', 
+                'invalid_id_new': '您请求的对话不存在或无权访问,已为您创建新对话'
+            }
+            print(f"[结果] 状态消息: {status_messages.get(status, '未知状态')}")
             print(f"[结果] 请求的ID: {data['data'].get('requested_conversation_id')}")
             print(f"[结果] 新创建的ID: {data['data'].get('conversation_id')}")
     

+ 1 - 1
test/test_ask_agent_redis_integration.py

@@ -254,7 +254,7 @@ class TestAskAgentRedisIntegration(unittest.TestCase):
             if response.status_code == 200:
                 data = response.json()
                 user_id = data['data']['user_id']
-                is_guest = data['data'].get('is_guest_user', False)
+                is_guest = user_id == "guest"  # 直接通过user_id判断
                 
                 print(f"[TEST] 生成的用户ID: {user_id}")
                 print(f"[TEST] 是否为guest用户: {is_guest}")

+ 1 - 5
unified_api.py

@@ -779,7 +779,7 @@ def ask_agent():
         if context:
             try:
                 # 获取最后一条助手消息的metadata
-                messages = redis_conversation_manager.get_messages(conversation_id, limit=10)
+                messages = redis_conversation_manager.get_conversation_messages(conversation_id, limit=10)
                 for message in reversed(messages):  # 从最新的开始找
                     if message.get("role") == "assistant":
                         metadata = message.get("metadata", {})
@@ -842,11 +842,9 @@ def ask_agent():
                 execution_path=cached_answer.get("execution_path", []),
                 classification_info=cached_answer.get("classification_info", {}),
                 user_id=user_id,
-                is_guest_user=(user_id == DEFAULT_ANONYMOUS_USER),
                 context_used=bool(context),
                 from_cache=True,
                 conversation_status=conversation_status["status"],
-                conversation_message=conversation_status["message"],
                 requested_conversation_id=conversation_status.get("requested_id")
             ))
         
@@ -944,11 +942,9 @@ def ask_agent():
                 execution_path=execution_path,
                 classification_info=classification_info,
                 user_id=user_id,
-                is_guest_user=(user_id == DEFAULT_ANONYMOUS_USER),
                 context_used=bool(context),
                 from_cache=False,
                 conversation_status=conversation_status["status"],
-                conversation_message=conversation_status["message"],
                 requested_conversation_id=conversation_status.get("requested_id"),
                 routing_mode_used=effective_routing_mode,  # 新增:实际使用的路由模式
                 routing_mode_source="api" if api_routing_mode else "config"  # 新增:路由模式来源