# -*- coding: utf-8 -*- """问题解析与意图识别:将口语化问题转为结构化查询与类型标签 基于 jieba 分词 + 初中化学领域词库 + 规则进行轻量级解析, 为后续知识图谱检索与大模型提示词提供结构化信息。 """ from enum import Enum from typing import Any, Dict, List try: import jieba except ImportError: # 运行环境若未安装 jieba,则退化为纯规则模式 jieba = None # type: ignore class QuestionType(str, Enum): """问题类型枚举:概念理解、实验操作、习题求解等""" CONCEPT = "concept" EXPERIMENT = "experiment" EXERCISE = "exercise" EQUATION = "equation" # 方程式书写/配平/离子方程式 ION_COEXIST = "ion_coexist" # 离子共存/能否大量共存 OTHER = "other" # 初中化学核心主题词与其关联的知识图谱扩展检索词 _TOPIC_KG_EXPANSIONS: Dict[str, List[str]] = { # 复分解反应条件示例:扩展到沉淀/气体/水、离子共存规则、溶解性表等 "复分解反应": [ "复分解反应", "复分解反应条件", "生成沉淀", "生成气体", "生成水", "离子共存规则", "酸碱盐溶解性表", "离子反应", ], "离子反应": [ "离子反应", "生成沉淀", "生成气体", "生成水", "离子共存规则", "酸碱盐溶解性表", ], "氧化还原反应": [ "氧化还原反应", "化合价变化", "得电子", "失电子", "氧化剂", "还原剂", ], "中和反应": [ "中和反应", "酸碱反应", "放热", "盐和水", "酸碱指示剂", "滴定", ], "酸碱指示剂": [ "酸碱指示剂", "酚酞", "石蕊", "甲基橙", "指示剂变色范围", ], "物质的量": [ "物质的量", "摩尔", "摩尔质量", "阿伏伽德罗常数", "气体摩尔体积", ], "溶液": [ "溶液", "溶质", "溶剂", "质量分数", "溶解度", "饱和溶液", ], # 氧气及其制取与性质 "氧气": [ "氧气", "实验室制氧气", "高锰酸钾制氧气", "制取氧气的化学方程式", "氧气的物理性质", "氧气的化学性质", "氧气的制备和性质实验", "燃烧与灭火", ], # 二氧化碳及相关实验 "二氧化碳": [ "二氧化碳", "实验室制二氧化碳", "二氧化碳的物理性质", "二氧化碳的化学性质", "二氧化碳的制备和性质实验", "澄清石灰水", ], # 氧化还原的易错点补充:学生常说“氧化还原”而不是完整说“氧化还原反应” "氧化还原": [ "氧化还原反应", "氧化还原", "化合价变化", "氧化剂", "还原剂", ], # 中和相关的常见混淆点 "中和": [ "中和反应", "酸碱中和", "酸碱指示剂", "盐和水", ], # 金属活动性顺序与置换反应 "金属活动性顺序": [ "金属活动性顺序", "置换反应", "金属与酸反应", "金属与盐溶液反应", ], # 气体制备与收集方法 "气体制备与收集": [ "实验室制氧气", "实验室制二氧化碳", "排水集气法", "向上排空气法", "向下排空气法", "气体的干燥", ], # 酸碱盐整体主题 "酸碱盐": [ "酸", "碱", "盐", "酸碱中和", "酸碱指示剂", "中和反应", ], } # 初中化学专业词汇词库(仅做识别,不直接决定题型) _DOMAIN_TERMS: Dict[str, str] = { # 物质构成 "原子": "structure", "分子": "structure", "离子": "structure", "元素": "structure", "原子核": "structure", "核外电子": "structure", "氧气": "substance", "空气": "substance", # 氧化还原 "氧化还原反应": "redox", "氧化剂": "redox", "还原剂": "redox", "化合价": "redox", "得电子": "redox", "失电子": "redox", "氧化还原": "redox", "氧化反应": "redox", "还原反应": "redox", # 电解质、电离 "电解质": "electrolyte", "弱电解质": "electrolyte", "强电解质": "electrolyte", "电离": "electrolyte", "离子反应": "electrolyte", # 溶液 "溶液": "solution", "溶质": "solution", "溶剂": "solution", "溶解度": "solution", "饱和溶液": "solution", "不饱和溶液": "solution", "质量分数": "solution", # 酸碱盐与指示剂 "酸": "acid_base", "碱": "acid_base", "盐": "acid_base", "中和反应": "acid_base", "酸碱指示剂": "acid_base", "酚酞": "acid_base", "石蕊": "acid_base", "甲基橙": "acid_base", "中和": "acid_base", "酸碱中和": "acid_base", "酸性": "acid_base", "碱性": "acid_base", "pH": "acid_base", # 物质的量与计算 "物质的量": "calc", "摩尔": "calc", "摩尔质量": "calc", "阿伏伽德罗常数": "calc", "气体摩尔体积": "calc", "溶液配制": "calc", # 常见实验与装置 "高锰酸钾": "experiment", "高锰酸钾制氧气": "experiment", "实验室制氧气": "experiment", "实验室制二氧化碳": "experiment", "检验氧气": "experiment", "检验二氧化碳": "experiment", "酒精灯": "experiment", "量筒": "experiment", "滴定管": "experiment", "排水集气法": "experiment", "向上排空气法": "experiment", "向下排空气法": "experiment", "集气瓶": "experiment", "试管": "experiment", "烧杯": "experiment", "锥形瓶": "experiment", "坩埚": "experiment", "蒸发皿": "experiment", "漏斗": "experiment", "滤纸": "experiment", "玻璃棒": "experiment", "集气": "experiment", "过滤": "experiment", "蒸发": "experiment", "蒸馏": "experiment", # 反应类型与典型情景 "化合反应": "reaction_type", "分解反应": "reaction_type", "置换反应": "reaction_type", "复分解反应": "reaction_type", "燃烧": "reaction_type", # 常见物质与实验现象 "二氧化碳": "substance", "氢气": "substance", "氮气": "substance", "水": "substance", "石灰水": "substance", "澄清石灰水": "substance", # 典型初中化学实验情景 "镁条燃烧": "experiment", "镁条在空气中燃烧": "experiment", "铁丝在氧气中燃烧": "experiment", "铁钉生锈": "experiment", "水的电解": "experiment", "锌和盐酸反应": "experiment", "锌粒与盐酸反应": "experiment", } _EXPERIMENT_KEYWORDS = [ "实验", "装置", "步骤", "操作", "需要注意", "注意事项", "现象", "为什么要", "为啥要", "为啥要塞", "怎么做", "如何操作", "收集气体", "收集方式", "排水集气法", "向上排空气法", "向下排空气法", "装配", "组装", "点燃", "熄灭", "实验室制氧气", "实验室制二氧化碳", "检验氧气", "检验二氧化碳", ] _EXERCISE_KEYWORDS = [ "求", "计算", "质量", "体积", "浓度", "配制", "生成多少", "生成的气体", "生成的沉淀", "x 的值", "已知", ] _CONCEPT_KEYWORDS = [ "是什么", "定义", "本质", "特征", "条件", "意义", "原因", "道理", ] def _guess_question_type(q: str) -> QuestionType: """根据关键词粗略区分问题类型""" # 先识别方程式/离子共存类型(你选择先只打标签,求解交给大模型) if "化学方程式" in q or "离子方程式" in q or "配平" in q: return QuestionType.EQUATION if "能否大量共存" in q or "能否共存" in q or "是否能共存" in q: return QuestionType.ION_COEXIST for kw in _EXPERIMENT_KEYWORDS: if kw in q: return QuestionType.EXPERIMENT for kw in _EXERCISE_KEYWORDS: if kw in q: return QuestionType.EXERCISE for kw in _CONCEPT_KEYWORDS: if kw in q: return QuestionType.CONCEPT # 若包含“为什么/为啥”等多为概念或原理说明 if "为什么" in q or "为啥" in q: return QuestionType.CONCEPT return QuestionType.OTHER def _extract_topic_keywords(q: str) -> List[str]: """基于规则提取与初中化学相关的核心主题/关键词 这里采用简单的子串匹配与规则扩展,后续可以接入分词与专业词库替换。 """ keywords: List[str] = [] # 1) 使用 jieba 分词(若可用)从分词结果中匹配专业词汇 if jieba is not None: for w in jieba.lcut(q): if w in _DOMAIN_TERMS and w not in keywords: keywords.append(w) # 2) 针对复分解反应等重点考点做显式主题识别(子串匹配兜底) if "复分解反应" in q and "复分解反应" not in keywords: keywords.append("复分解反应") if "离子反应" in q and "离子反应" not in keywords: keywords.append("离子反应") if "高锰酸钾" in q and "高锰酸钾" not in keywords: keywords.append("高锰酸钾") if "高锰酸钾制氧气" not in keywords: keywords.append("高锰酸钾制氧气") if "电解质" in q and "电解质" not in keywords: keywords.append("电解质") if "氧化还原反应" in q and "氧化还原反应" not in keywords: keywords.append("氧化还原反应") if "氧化还原" in q and "氧化还原反应" not in keywords: keywords.append("氧化还原反应") if "中和反应" in q and "中和反应" not in keywords: keywords.append("中和反应") if "中和" in q and "中和反应" not in keywords: keywords.append("中和反应") if ("物质的量" in q or "摩尔" in q or "mol" in q) and "物质的量" not in keywords: keywords.append("物质的量") if "酸碱指示剂" in q and "酸碱指示剂" not in keywords: keywords.append("酸碱指示剂") if ("溶液" in q or "溶解度" in q or "饱和溶液" in q) and "溶液" not in keywords: keywords.append("溶液") if "氧气" in q and "氧气" not in keywords: keywords.append("氧气") if "制氧气" in q and "实验室制氧气" not in keywords: keywords.append("实验室制氧气") if "氧气" not in keywords: keywords.append("氧气") if "二氧化碳" in q and "二氧化碳" not in keywords: keywords.append("二氧化碳") if "制二氧化碳" in q and "实验室制二氧化碳" not in keywords: keywords.append("实验室制二氧化碳") if "二氧化碳" not in keywords: keywords.append("二氧化碳") if "金属活动性顺序" in q and "金属活动性顺序" not in keywords: keywords.append("金属活动性顺序") if "空气" in q and "空气" not in keywords: keywords.append("空气") # 若未识别到任何主题,则用原始问题作为检索关键词 if not keywords: keywords.append(q) # 去重保持顺序 seen = set() deduped: List[str] = [] for k in keywords: if k not in seen: seen.add(k) deduped.append(k) return deduped def _expand_kg_terms(base_keywords: List[str]) -> List[str]: """根据主题配置扩展知识图谱检索词""" terms: List[str] = [] for kw in base_keywords: expanded = _TOPIC_KG_EXPANSIONS.get(kw, [kw]) for t in expanded: if t not in terms: terms.append(t) return terms def analyze_question(text: str) -> Dict[str, Any]: """将学生的自然语言问题解析为结构化信息 返回内容示例: { "clean_text": "为啥高锰酸钾制氧要塞棉花", "type": "experiment", "keywords": ["高锰酸钾", "高锰酸钾制氧气"], "kg_terms": ["高锰酸钾制氧气", "高锰酸钾"] } """ q = (text or "").strip() q = q.replace("?", "?").replace("!", "!").replace("。", ".") q_type = _guess_question_type(q) base_keywords = _extract_topic_keywords(q) kg_terms = _expand_kg_terms(base_keywords) return { "clean_text": q, "type": q_type.value, "keywords": base_keywords, "kg_terms": kg_terms, }