question_parser.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. # -*- coding: utf-8 -*-
  2. """问题解析与意图识别:将口语化问题转为结构化查询与类型标签
  3. 基于 jieba 分词 + 初中化学领域词库 + 规则进行轻量级解析,
  4. 为后续知识图谱检索与大模型提示词提供结构化信息。
  5. """
  6. from enum import Enum
  7. from typing import Any, Dict, List
  8. try:
  9. import jieba
  10. except ImportError: # 运行环境若未安装 jieba,则退化为纯规则模式
  11. jieba = None # type: ignore
  12. class QuestionType(str, Enum):
  13. """问题类型枚举:概念理解、实验操作、习题求解等"""
  14. CONCEPT = "concept"
  15. EXPERIMENT = "experiment"
  16. EXERCISE = "exercise"
  17. EQUATION = "equation" # 方程式书写/配平/离子方程式
  18. ION_COEXIST = "ion_coexist" # 离子共存/能否大量共存
  19. OTHER = "other"
  20. # 初中化学核心主题词与其关联的知识图谱扩展检索词
  21. _TOPIC_KG_EXPANSIONS: Dict[str, List[str]] = {
  22. # 复分解反应条件示例:扩展到沉淀/气体/水、离子共存规则、溶解性表等
  23. "复分解反应": [
  24. "复分解反应",
  25. "复分解反应条件",
  26. "生成沉淀",
  27. "生成气体",
  28. "生成水",
  29. "离子共存规则",
  30. "酸碱盐溶解性表",
  31. "离子反应",
  32. ],
  33. "离子反应": [
  34. "离子反应",
  35. "生成沉淀",
  36. "生成气体",
  37. "生成水",
  38. "离子共存规则",
  39. "酸碱盐溶解性表",
  40. ],
  41. "氧化还原反应": [
  42. "氧化还原反应",
  43. "化合价变化",
  44. "得电子",
  45. "失电子",
  46. "氧化剂",
  47. "还原剂",
  48. ],
  49. "中和反应": [
  50. "中和反应",
  51. "酸碱反应",
  52. "放热",
  53. "盐和水",
  54. "酸碱指示剂",
  55. "滴定",
  56. ],
  57. "酸碱指示剂": [
  58. "酸碱指示剂",
  59. "酚酞",
  60. "石蕊",
  61. "甲基橙",
  62. "指示剂变色范围",
  63. ],
  64. "物质的量": [
  65. "物质的量",
  66. "摩尔",
  67. "摩尔质量",
  68. "阿伏伽德罗常数",
  69. "气体摩尔体积",
  70. ],
  71. "溶液": [
  72. "溶液",
  73. "溶质",
  74. "溶剂",
  75. "质量分数",
  76. "溶解度",
  77. "饱和溶液",
  78. ],
  79. # 氧气及其制取与性质
  80. "氧气": [
  81. "氧气",
  82. "实验室制氧气",
  83. "高锰酸钾制氧气",
  84. "制取氧气的化学方程式",
  85. "氧气的物理性质",
  86. "氧气的化学性质",
  87. "氧气的制备和性质实验",
  88. "燃烧与灭火",
  89. ],
  90. # 二氧化碳及相关实验
  91. "二氧化碳": [
  92. "二氧化碳",
  93. "实验室制二氧化碳",
  94. "二氧化碳的物理性质",
  95. "二氧化碳的化学性质",
  96. "二氧化碳的制备和性质实验",
  97. "澄清石灰水",
  98. ],
  99. # 氧化还原的易错点补充:学生常说“氧化还原”而不是完整说“氧化还原反应”
  100. "氧化还原": [
  101. "氧化还原反应",
  102. "氧化还原",
  103. "化合价变化",
  104. "氧化剂",
  105. "还原剂",
  106. ],
  107. # 中和相关的常见混淆点
  108. "中和": [
  109. "中和反应",
  110. "酸碱中和",
  111. "酸碱指示剂",
  112. "盐和水",
  113. ],
  114. # 金属活动性顺序与置换反应
  115. "金属活动性顺序": [
  116. "金属活动性顺序",
  117. "置换反应",
  118. "金属与酸反应",
  119. "金属与盐溶液反应",
  120. ],
  121. # 气体制备与收集方法
  122. "气体制备与收集": [
  123. "实验室制氧气",
  124. "实验室制二氧化碳",
  125. "排水集气法",
  126. "向上排空气法",
  127. "向下排空气法",
  128. "气体的干燥",
  129. ],
  130. # 酸碱盐整体主题
  131. "酸碱盐": [
  132. "酸",
  133. "碱",
  134. "盐",
  135. "酸碱中和",
  136. "酸碱指示剂",
  137. "中和反应",
  138. ],
  139. }
  140. # 初中化学专业词汇词库(仅做识别,不直接决定题型)
  141. _DOMAIN_TERMS: Dict[str, str] = {
  142. # 物质构成
  143. "原子": "structure",
  144. "分子": "structure",
  145. "离子": "structure",
  146. "元素": "structure",
  147. "原子核": "structure",
  148. "核外电子": "structure",
  149. "氧气": "substance",
  150. "空气": "substance",
  151. # 氧化还原
  152. "氧化还原反应": "redox",
  153. "氧化剂": "redox",
  154. "还原剂": "redox",
  155. "化合价": "redox",
  156. "得电子": "redox",
  157. "失电子": "redox",
  158. "氧化还原": "redox",
  159. "氧化反应": "redox",
  160. "还原反应": "redox",
  161. # 电解质、电离
  162. "电解质": "electrolyte",
  163. "弱电解质": "electrolyte",
  164. "强电解质": "electrolyte",
  165. "电离": "electrolyte",
  166. "离子反应": "electrolyte",
  167. # 溶液
  168. "溶液": "solution",
  169. "溶质": "solution",
  170. "溶剂": "solution",
  171. "溶解度": "solution",
  172. "饱和溶液": "solution",
  173. "不饱和溶液": "solution",
  174. "质量分数": "solution",
  175. # 酸碱盐与指示剂
  176. "酸": "acid_base",
  177. "碱": "acid_base",
  178. "盐": "acid_base",
  179. "中和反应": "acid_base",
  180. "酸碱指示剂": "acid_base",
  181. "酚酞": "acid_base",
  182. "石蕊": "acid_base",
  183. "甲基橙": "acid_base",
  184. "中和": "acid_base",
  185. "酸碱中和": "acid_base",
  186. "酸性": "acid_base",
  187. "碱性": "acid_base",
  188. "pH": "acid_base",
  189. # 物质的量与计算
  190. "物质的量": "calc",
  191. "摩尔": "calc",
  192. "摩尔质量": "calc",
  193. "阿伏伽德罗常数": "calc",
  194. "气体摩尔体积": "calc",
  195. "溶液配制": "calc",
  196. # 常见实验与装置
  197. "高锰酸钾": "experiment",
  198. "高锰酸钾制氧气": "experiment",
  199. "实验室制氧气": "experiment",
  200. "实验室制二氧化碳": "experiment",
  201. "检验氧气": "experiment",
  202. "检验二氧化碳": "experiment",
  203. "酒精灯": "experiment",
  204. "量筒": "experiment",
  205. "滴定管": "experiment",
  206. "排水集气法": "experiment",
  207. "向上排空气法": "experiment",
  208. "向下排空气法": "experiment",
  209. "集气瓶": "experiment",
  210. "试管": "experiment",
  211. "烧杯": "experiment",
  212. "锥形瓶": "experiment",
  213. "坩埚": "experiment",
  214. "蒸发皿": "experiment",
  215. "漏斗": "experiment",
  216. "滤纸": "experiment",
  217. "玻璃棒": "experiment",
  218. "集气": "experiment",
  219. "过滤": "experiment",
  220. "蒸发": "experiment",
  221. "蒸馏": "experiment",
  222. # 反应类型与典型情景
  223. "化合反应": "reaction_type",
  224. "分解反应": "reaction_type",
  225. "置换反应": "reaction_type",
  226. "复分解反应": "reaction_type",
  227. "燃烧": "reaction_type",
  228. # 常见物质与实验现象
  229. "二氧化碳": "substance",
  230. "氢气": "substance",
  231. "氮气": "substance",
  232. "水": "substance",
  233. "石灰水": "substance",
  234. "澄清石灰水": "substance",
  235. # 典型初中化学实验情景
  236. "镁条燃烧": "experiment",
  237. "镁条在空气中燃烧": "experiment",
  238. "铁丝在氧气中燃烧": "experiment",
  239. "铁钉生锈": "experiment",
  240. "水的电解": "experiment",
  241. "锌和盐酸反应": "experiment",
  242. "锌粒与盐酸反应": "experiment",
  243. }
  244. _EXPERIMENT_KEYWORDS = [
  245. "实验",
  246. "装置",
  247. "步骤",
  248. "操作",
  249. "需要注意",
  250. "注意事项",
  251. "现象",
  252. "为什么要",
  253. "为啥要",
  254. "为啥要塞",
  255. "怎么做",
  256. "如何操作",
  257. "收集气体",
  258. "收集方式",
  259. "排水集气法",
  260. "向上排空气法",
  261. "向下排空气法",
  262. "装配",
  263. "组装",
  264. "点燃",
  265. "熄灭",
  266. "实验室制氧气",
  267. "实验室制二氧化碳",
  268. "检验氧气",
  269. "检验二氧化碳",
  270. ]
  271. _EXERCISE_KEYWORDS = [
  272. "求",
  273. "计算",
  274. "质量",
  275. "体积",
  276. "浓度",
  277. "配制",
  278. "生成多少",
  279. "生成的气体",
  280. "生成的沉淀",
  281. "x 的值",
  282. "已知",
  283. ]
  284. _CONCEPT_KEYWORDS = [
  285. "是什么",
  286. "定义",
  287. "本质",
  288. "特征",
  289. "条件",
  290. "意义",
  291. "原因",
  292. "道理",
  293. ]
  294. def _guess_question_type(q: str) -> QuestionType:
  295. """根据关键词粗略区分问题类型"""
  296. # 先识别方程式/离子共存类型(你选择先只打标签,求解交给大模型)
  297. if "化学方程式" in q or "离子方程式" in q or "配平" in q:
  298. return QuestionType.EQUATION
  299. if "能否大量共存" in q or "能否共存" in q or "是否能共存" in q:
  300. return QuestionType.ION_COEXIST
  301. for kw in _EXPERIMENT_KEYWORDS:
  302. if kw in q:
  303. return QuestionType.EXPERIMENT
  304. for kw in _EXERCISE_KEYWORDS:
  305. if kw in q:
  306. return QuestionType.EXERCISE
  307. for kw in _CONCEPT_KEYWORDS:
  308. if kw in q:
  309. return QuestionType.CONCEPT
  310. # 若包含“为什么/为啥”等多为概念或原理说明
  311. if "为什么" in q or "为啥" in q:
  312. return QuestionType.CONCEPT
  313. return QuestionType.OTHER
  314. def _extract_topic_keywords(q: str) -> List[str]:
  315. """基于规则提取与初中化学相关的核心主题/关键词
  316. 这里采用简单的子串匹配与规则扩展,后续可以接入分词与专业词库替换。
  317. """
  318. keywords: List[str] = []
  319. # 1) 使用 jieba 分词(若可用)从分词结果中匹配专业词汇
  320. if jieba is not None:
  321. for w in jieba.lcut(q):
  322. if w in _DOMAIN_TERMS and w not in keywords:
  323. keywords.append(w)
  324. # 2) 针对复分解反应等重点考点做显式主题识别(子串匹配兜底)
  325. if "复分解反应" in q and "复分解反应" not in keywords:
  326. keywords.append("复分解反应")
  327. if "离子反应" in q and "离子反应" not in keywords:
  328. keywords.append("离子反应")
  329. if "高锰酸钾" in q and "高锰酸钾" not in keywords:
  330. keywords.append("高锰酸钾")
  331. if "高锰酸钾制氧气" not in keywords:
  332. keywords.append("高锰酸钾制氧气")
  333. if "电解质" in q and "电解质" not in keywords:
  334. keywords.append("电解质")
  335. if "氧化还原反应" in q and "氧化还原反应" not in keywords:
  336. keywords.append("氧化还原反应")
  337. if "氧化还原" in q and "氧化还原反应" not in keywords:
  338. keywords.append("氧化还原反应")
  339. if "中和反应" in q and "中和反应" not in keywords:
  340. keywords.append("中和反应")
  341. if "中和" in q and "中和反应" not in keywords:
  342. keywords.append("中和反应")
  343. if ("物质的量" in q or "摩尔" in q or "mol" in q) and "物质的量" not in keywords:
  344. keywords.append("物质的量")
  345. if "酸碱指示剂" in q and "酸碱指示剂" not in keywords:
  346. keywords.append("酸碱指示剂")
  347. if ("溶液" in q or "溶解度" in q or "饱和溶液" in q) and "溶液" not in keywords:
  348. keywords.append("溶液")
  349. if "氧气" in q and "氧气" not in keywords:
  350. keywords.append("氧气")
  351. if "制氧气" in q and "实验室制氧气" not in keywords:
  352. keywords.append("实验室制氧气")
  353. if "氧气" not in keywords:
  354. keywords.append("氧气")
  355. if "二氧化碳" in q and "二氧化碳" not in keywords:
  356. keywords.append("二氧化碳")
  357. if "制二氧化碳" in q and "实验室制二氧化碳" not in keywords:
  358. keywords.append("实验室制二氧化碳")
  359. if "二氧化碳" not in keywords:
  360. keywords.append("二氧化碳")
  361. if "金属活动性顺序" in q and "金属活动性顺序" not in keywords:
  362. keywords.append("金属活动性顺序")
  363. if "空气" in q and "空气" not in keywords:
  364. keywords.append("空气")
  365. # 若未识别到任何主题,则用原始问题作为检索关键词
  366. if not keywords:
  367. keywords.append(q)
  368. # 去重保持顺序
  369. seen = set()
  370. deduped: List[str] = []
  371. for k in keywords:
  372. if k not in seen:
  373. seen.add(k)
  374. deduped.append(k)
  375. return deduped
  376. def _expand_kg_terms(base_keywords: List[str]) -> List[str]:
  377. """根据主题配置扩展知识图谱检索词"""
  378. terms: List[str] = []
  379. for kw in base_keywords:
  380. expanded = _TOPIC_KG_EXPANSIONS.get(kw, [kw])
  381. for t in expanded:
  382. if t not in terms:
  383. terms.append(t)
  384. return terms
  385. def analyze_question(text: str) -> Dict[str, Any]:
  386. """将学生的自然语言问题解析为结构化信息
  387. 返回内容示例:
  388. {
  389. "clean_text": "为啥高锰酸钾制氧要塞棉花",
  390. "type": "experiment",
  391. "keywords": ["高锰酸钾", "高锰酸钾制氧气"],
  392. "kg_terms": ["高锰酸钾制氧气", "高锰酸钾"]
  393. }
  394. """
  395. q = (text or "").strip()
  396. q = q.replace("?", "?").replace("!", "!").replace("。", ".")
  397. q_type = _guess_question_type(q)
  398. base_keywords = _extract_topic_keywords(q)
  399. kg_terms = _expand_kg_terms(base_keywords)
  400. return {
  401. "clean_text": q,
  402. "type": q_type.value,
  403. "keywords": base_keywords,
  404. "kg_terms": kg_terms,
  405. }