knowledge_graph.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # -*- coding: utf-8 -*-
  2. """知识图谱管理:Neo4j + Py2neo 构建初中化学知识点与关联关系的结构化网络"""
  3. from typing import List, Dict, Any, Optional
  4. from backend.config import get_settings
  5. try:
  6. from py2neo import Graph, Node, Relationship, NodeMatcher
  7. except ImportError:
  8. Graph = Node = Relationship = NodeMatcher = None # type: ignore
  9. def _get_graph() -> Optional["Graph"]:
  10. if Graph is None:
  11. return None
  12. s = get_settings()
  13. try:
  14. return Graph(s.NEO4J_URI, auth=(s.NEO4J_USER, s.NEO4J_PASSWORD))
  15. except Exception:
  16. return None
  17. _FALLBACK_TRIPLES = [
  18. {"subject": "电解质", "predicate": "定义", "obj": "在水溶液或熔融状态下能够导电的化合物"},
  19. {"subject": "电解质", "predicate": "包含", "obj": "酸、碱、盐"},
  20. {"subject": "氧化还原反应", "predicate": "特征", "obj": "有元素化合价发生变化的反应"},
  21. {"subject": "物质的量", "predicate": "单位", "obj": "mol(摩尔)"},
  22. {"subject": "离子反应", "predicate": "条件", "obj": "有沉淀、气体或弱电解质生成"},
  23. {"subject": "原子", "predicate": "结构", "obj": "原子核和核外电子"},
  24. ]
  25. def search_knowledge_graph(query: str, limit: int = 15) -> List[Dict[str, Any]]:
  26. """
  27. 知识图谱检索:
  28. - 优先从 Neo4j 任意节点/关系中,按 name/subject/object/relationship 等常见字段模糊匹配;
  29. - 若 Neo4j 中没有命中,则回退到内置兜底三元组(保证常见基础概念可用)。
  30. """
  31. q = (query or "").strip()
  32. if len(q) < 1:
  33. return []
  34. g = _get_graph()
  35. if g is not None:
  36. try:
  37. # 通用检索:兼容不同建模方式(KnowledgePoint 图 + 三元组节点等)
  38. cypher = """
  39. MATCH (s)-[r]->(o)
  40. WITH
  41. s, r, o,
  42. coalesce(
  43. s.name,
  44. s.subject,
  45. s.subject_name,
  46. s.formula,
  47. s.symbol,
  48. s.description,
  49. toString(s)
  50. ) AS subj,
  51. coalesce(
  52. o.name,
  53. o.object,
  54. o.object_name,
  55. o.formula,
  56. o.symbol,
  57. o.description,
  58. toString(o)
  59. ) AS obj,
  60. coalesce(
  61. r.name,
  62. r.relationship,
  63. toString(type(r))
  64. ) AS rel
  65. WHERE subj CONTAINS $q
  66. OR obj CONTAINS $q
  67. OR rel CONTAINS $q
  68. RETURN subj AS subject,
  69. rel AS predicate,
  70. obj AS obj
  71. LIMIT $limit
  72. """
  73. cursor = g.run(cypher, q=q, limit=limit)
  74. results = [
  75. {
  76. "subject": r["subject"],
  77. "predicate": r["predicate"],
  78. "obj": r["obj"],
  79. }
  80. for r in cursor
  81. if r.get("subject") and r.get("obj")
  82. ]
  83. if results:
  84. return results
  85. except Exception:
  86. # 连接或查询失败时,退回内置数据
  87. pass
  88. # 兜底:从内置基础三元组中模糊匹配
  89. results = [t for t in _FALLBACK_TRIPLES if q in t["subject"] or q in t["obj"] or q in t["predicate"]]
  90. return results[:limit]
  91. def get_graph_for_visualization(limit: int = 50) -> Dict[str, Any]:
  92. """Force-directed 布局可视化"""
  93. g = _get_graph()
  94. if g is not None:
  95. try:
  96. # 适配三元组节点模型:id, subject, predicate, object
  97. cursor = g.run(
  98. """
  99. MATCH (t)
  100. WHERE t.subject IS NOT NULL
  101. AND t.predicate IS NOT NULL
  102. AND t.object IS NOT NULL
  103. RETURN t.subject AS src,
  104. t.object AS tgt,
  105. t.predicate AS rel
  106. LIMIT $limit
  107. """,
  108. limit=limit,
  109. )
  110. nodes_set, links = set(), []
  111. for record in cursor:
  112. nodes_set.add(record["src"])
  113. nodes_set.add(record["tgt"])
  114. links.append({"source": record["src"], "target": record["tgt"], "value": record["rel"]})
  115. return {"nodes": [{"name": n} for n in nodes_set], "links": links}
  116. except Exception:
  117. pass
  118. # 兜底
  119. nodes_set = set()
  120. links = []
  121. for t in _FALLBACK_TRIPLES:
  122. nodes_set.add(t["subject"])
  123. nodes_set.add(t["obj"])
  124. links.append({"source": t["subject"], "target": t["obj"], "value": t["predicate"]})
  125. return {"nodes": [{"name": n} for n in nodes_set], "links": links}
  126. def init_neo4j_schema(graph: "Graph") -> None:
  127. """初始化 Neo4j 初中化学知识图谱结构(若为空)"""
  128. try:
  129. graph.run("""
  130. MERGE (a:KnowledgePoint {name: '电解质'})
  131. MERGE (b:KnowledgePoint {name: '在水溶液或熔融状态下能够导电的化合物'})
  132. MERGE (a)-[:定义]->(b)
  133. """)
  134. graph.run("""
  135. MERGE (a:KnowledgePoint {name: '电解质'})
  136. MERGE (b:KnowledgePoint {name: '酸、碱、盐'})
  137. MERGE (a)-[:包含]->(b)
  138. """)
  139. graph.run("""
  140. MERGE (a:KnowledgePoint {name: '氧化还原反应'})
  141. MERGE (b:KnowledgePoint {name: '有元素化合价发生变化的反应'})
  142. MERGE (a)-[:特征]->(b)
  143. """)
  144. graph.run("""
  145. MERGE (a:KnowledgePoint {name: '物质的量'})
  146. MERGE (b:KnowledgePoint {name: 'mol(摩尔)'})
  147. MERGE (a)-[:单位]->(b)
  148. """)
  149. graph.run("""
  150. MERGE (a:KnowledgePoint {name: '原子'})
  151. MERGE (b:KnowledgePoint {name: '原子核和核外电子'})
  152. MERGE (a)-[:结构]->(b)
  153. """)
  154. graph.run("""
  155. MERGE (a:KnowledgePoint {name: '原子核'})
  156. MERGE (b:KnowledgePoint {name: '质子和中子'})
  157. MERGE (a)-[:组成]->(b)
  158. """)
  159. except Exception:
  160. pass