isage-middleware 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of isage-middleware might be problematic. Click here for more details.

Files changed (191) hide show
  1. isage_middleware-0.1.0.dist-info/METADATA +424 -0
  2. isage_middleware-0.1.0.dist-info/RECORD +191 -0
  3. isage_middleware-0.1.0.dist-info/WHEEL +5 -0
  4. isage_middleware-0.1.0.dist-info/top_level.txt +1 -0
  5. sage/__init__.py +2 -0
  6. sage/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  7. sage/__pycache__/__init__.cpython-311.pyc +0 -0
  8. sage/middleware/__init__.py +83 -0
  9. sage/middleware/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  10. sage/middleware/__pycache__/__init__.cpython-311.pyc +0 -0
  11. sage/middleware/api/__init__.py +22 -0
  12. sage/middleware/api/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  13. sage/middleware/api/__pycache__/__init__.cpython-311.pyc +0 -0
  14. sage/middleware/api/__pycache__/graph_api.cpython-311.opt-2.pyc +0 -0
  15. sage/middleware/api/__pycache__/graph_api.cpython-311.pyc +0 -0
  16. sage/middleware/api/__pycache__/kv_api.cpython-311.opt-2.pyc +0 -0
  17. sage/middleware/api/__pycache__/kv_api.cpython-311.pyc +0 -0
  18. sage/middleware/api/__pycache__/memory_api.cpython-311.opt-2.pyc +0 -0
  19. sage/middleware/api/__pycache__/memory_api.cpython-311.pyc +0 -0
  20. sage/middleware/api/__pycache__/vdb_api.cpython-311.opt-2.pyc +0 -0
  21. sage/middleware/api/__pycache__/vdb_api.cpython-311.pyc +0 -0
  22. sage/middleware/api/graph_api.py +74 -0
  23. sage/middleware/api/kv_api.py +45 -0
  24. sage/middleware/api/memory_api.py +64 -0
  25. sage/middleware/api/vdb_api.py +60 -0
  26. sage/middleware/enterprise/__init__.py +75 -0
  27. sage/middleware/enterprise/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  28. sage/middleware/enterprise/__pycache__/__init__.cpython-311.pyc +0 -0
  29. sage/middleware/enterprise/sage_db/__init__.py +132 -0
  30. sage/middleware/enterprise/sage_db/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  31. sage/middleware/enterprise/sage_db/__pycache__/__init__.cpython-311.pyc +0 -0
  32. sage/middleware/enterprise/sage_db/__pycache__/sage_db.cpython-311.opt-2.pyc +0 -0
  33. sage/middleware/enterprise/sage_db/__pycache__/sage_db.cpython-311.pyc +0 -0
  34. sage/middleware/enterprise/sage_db/python/__init__.py +7 -0
  35. sage/middleware/enterprise/sage_db/python/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  36. sage/middleware/enterprise/sage_db/python/__pycache__/__init__.cpython-311.pyc +0 -0
  37. sage/middleware/enterprise/sage_db/python/__pycache__/sage_db.cpython-311.opt-2.pyc +0 -0
  38. sage/middleware/enterprise/sage_db/python/__pycache__/sage_db.cpython-311.pyc +0 -0
  39. sage/middleware/enterprise/sage_db/python/sage_db.py +44 -0
  40. sage/middleware/enterprise/sage_db/sage_db.py +395 -0
  41. sage/middleware/enterprise/sage_db/tests/__pycache__/test_python.cpython-311.opt-2.pyc +0 -0
  42. sage/middleware/enterprise/sage_db/tests/__pycache__/test_python.cpython-311.pyc +0 -0
  43. sage/middleware/enterprise/sage_db/tests/test_python.py +144 -0
  44. sage/middleware/examples/__pycache__/api_usage_tutorial.cpython-311.opt-2.pyc +0 -0
  45. sage/middleware/examples/__pycache__/api_usage_tutorial.cpython-311.pyc +0 -0
  46. sage/middleware/examples/__pycache__/dag_microservices_demo.cpython-311.opt-2.pyc +0 -0
  47. sage/middleware/examples/__pycache__/dag_microservices_demo.cpython-311.pyc +0 -0
  48. sage/middleware/examples/__pycache__/microservices_demo.cpython-311.opt-2.pyc +0 -0
  49. sage/middleware/examples/__pycache__/microservices_demo.cpython-311.pyc +0 -0
  50. sage/middleware/examples/__pycache__/microservices_integration_demo.cpython-311.opt-2.pyc +0 -0
  51. sage/middleware/examples/__pycache__/microservices_integration_demo.cpython-311.pyc +0 -0
  52. sage/middleware/examples/__pycache__/microservices_registration_demo.cpython-311.opt-2.pyc +0 -0
  53. sage/middleware/examples/__pycache__/microservices_registration_demo.cpython-311.pyc +0 -0
  54. sage/middleware/examples/api_usage_tutorial.py +339 -0
  55. sage/middleware/examples/dag_microservices_demo.py +220 -0
  56. sage/middleware/examples/microservices_demo.py +0 -0
  57. sage/middleware/examples/microservices_integration_demo.py +373 -0
  58. sage/middleware/examples/microservices_registration_demo.py +144 -0
  59. sage/middleware/py.typed +2 -0
  60. sage/middleware/services/graph/__init__.py +8 -0
  61. sage/middleware/services/graph/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  62. sage/middleware/services/graph/__pycache__/__init__.cpython-311.pyc +0 -0
  63. sage/middleware/services/graph/__pycache__/graph_index.cpython-311.opt-2.pyc +0 -0
  64. sage/middleware/services/graph/__pycache__/graph_index.cpython-311.pyc +0 -0
  65. sage/middleware/services/graph/__pycache__/graph_service.cpython-311.opt-2.pyc +0 -0
  66. sage/middleware/services/graph/__pycache__/graph_service.cpython-311.pyc +0 -0
  67. sage/middleware/services/graph/examples/__pycache__/graph_demo.cpython-311.opt-2.pyc +0 -0
  68. sage/middleware/services/graph/examples/__pycache__/graph_demo.cpython-311.pyc +0 -0
  69. sage/middleware/services/graph/examples/graph_demo.py +177 -0
  70. sage/middleware/services/graph/graph_index.py +194 -0
  71. sage/middleware/services/graph/graph_service.py +541 -0
  72. sage/middleware/services/graph/search_engine/__init__.py +0 -0
  73. sage/middleware/services/graph/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  74. sage/middleware/services/graph/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  75. sage/middleware/services/graph/search_engine/__pycache__/base_graph_index.cpython-311.opt-2.pyc +0 -0
  76. sage/middleware/services/graph/search_engine/__pycache__/base_graph_index.cpython-311.pyc +0 -0
  77. sage/middleware/services/graph/search_engine/base_graph_index.py +0 -0
  78. sage/middleware/services/kv/__init__.py +8 -0
  79. sage/middleware/services/kv/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  80. sage/middleware/services/kv/__pycache__/__init__.cpython-311.pyc +0 -0
  81. sage/middleware/services/kv/__pycache__/kv_service.cpython-311.opt-2.pyc +0 -0
  82. sage/middleware/services/kv/__pycache__/kv_service.cpython-311.pyc +0 -0
  83. sage/middleware/services/kv/examples/__pycache__/kv_demo.cpython-311.opt-2.pyc +0 -0
  84. sage/middleware/services/kv/examples/__pycache__/kv_demo.cpython-311.pyc +0 -0
  85. sage/middleware/services/kv/examples/kv_demo.py +213 -0
  86. sage/middleware/services/kv/kv_service.py +306 -0
  87. sage/middleware/services/kv/search_engine/__init__.py +0 -0
  88. sage/middleware/services/kv/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  89. sage/middleware/services/kv/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  90. sage/middleware/services/kv/search_engine/__pycache__/base_kv_index.cpython-311.opt-2.pyc +0 -0
  91. sage/middleware/services/kv/search_engine/__pycache__/base_kv_index.cpython-311.pyc +0 -0
  92. sage/middleware/services/kv/search_engine/__pycache__/bm25s_index.cpython-311.opt-2.pyc +0 -0
  93. sage/middleware/services/kv/search_engine/__pycache__/bm25s_index.cpython-311.pyc +0 -0
  94. sage/middleware/services/kv/search_engine/base_kv_index.py +75 -0
  95. sage/middleware/services/kv/search_engine/bm25s_index.py +238 -0
  96. sage/middleware/services/memory/__init__.py +12 -0
  97. sage/middleware/services/memory/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  98. sage/middleware/services/memory/__pycache__/__init__.cpython-311.pyc +0 -0
  99. sage/middleware/services/memory/__pycache__/memory_service.cpython-311.opt-2.pyc +0 -0
  100. sage/middleware/services/memory/__pycache__/memory_service.cpython-311.pyc +0 -0
  101. sage/middleware/services/memory/examples/__pycache__/dag_microservices_demo.cpython-311.opt-2.pyc +0 -0
  102. sage/middleware/services/memory/examples/__pycache__/dag_microservices_demo.cpython-311.pyc +0 -0
  103. sage/middleware/services/memory/examples/__pycache__/memory_demo.cpython-311.opt-2.pyc +0 -0
  104. sage/middleware/services/memory/examples/__pycache__/memory_demo.cpython-311.pyc +0 -0
  105. sage/middleware/services/memory/examples/dag_microservices_demo.py +220 -0
  106. sage/middleware/services/memory/examples/memory_demo.py +490 -0
  107. sage/middleware/services/memory/memory_collection/__pycache__/base_collection.cpython-311.opt-2.pyc +0 -0
  108. sage/middleware/services/memory/memory_collection/__pycache__/base_collection.cpython-311.pyc +0 -0
  109. sage/middleware/services/memory/memory_collection/__pycache__/graph_collection.cpython-311.opt-2.pyc +0 -0
  110. sage/middleware/services/memory/memory_collection/__pycache__/graph_collection.cpython-311.pyc +0 -0
  111. sage/middleware/services/memory/memory_collection/__pycache__/kv_collection.cpython-311.opt-2.pyc +0 -0
  112. sage/middleware/services/memory/memory_collection/__pycache__/kv_collection.cpython-311.pyc +0 -0
  113. sage/middleware/services/memory/memory_collection/__pycache__/vdb_collection.cpython-311.opt-2.pyc +0 -0
  114. sage/middleware/services/memory/memory_collection/__pycache__/vdb_collection.cpython-311.pyc +0 -0
  115. sage/middleware/services/memory/memory_collection/base_collection.py +0 -0
  116. sage/middleware/services/memory/memory_collection/graph_collection.py +0 -0
  117. sage/middleware/services/memory/memory_collection/kv_collection.py +0 -0
  118. sage/middleware/services/memory/memory_collection/vdb_collection.py +0 -0
  119. sage/middleware/services/memory/memory_service.py +474 -0
  120. sage/middleware/services/memory/utils/__init__.py +0 -0
  121. sage/middleware/services/memory/utils/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  122. sage/middleware/services/memory/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  123. sage/middleware/services/memory/utils/__pycache__/path_utils.cpython-311.opt-2.pyc +0 -0
  124. sage/middleware/services/memory/utils/__pycache__/path_utils.cpython-311.pyc +0 -0
  125. sage/middleware/services/memory/utils/path_utils.py +0 -0
  126. sage/middleware/services/vdb/__init__.py +8 -0
  127. sage/middleware/services/vdb/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  128. sage/middleware/services/vdb/__pycache__/__init__.cpython-311.pyc +0 -0
  129. sage/middleware/services/vdb/__pycache__/vdb_service.cpython-311.opt-2.pyc +0 -0
  130. sage/middleware/services/vdb/__pycache__/vdb_service.cpython-311.pyc +0 -0
  131. sage/middleware/services/vdb/examples/__pycache__/vdb_demo.cpython-311.opt-2.pyc +0 -0
  132. sage/middleware/services/vdb/examples/__pycache__/vdb_demo.cpython-311.pyc +0 -0
  133. sage/middleware/services/vdb/examples/vdb_demo.py +447 -0
  134. sage/middleware/services/vdb/search_engine/__init__.py +0 -0
  135. sage/middleware/services/vdb/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  136. sage/middleware/services/vdb/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  137. sage/middleware/services/vdb/search_engine/__pycache__/base_vdb_index.cpython-311.opt-2.pyc +0 -0
  138. sage/middleware/services/vdb/search_engine/__pycache__/base_vdb_index.cpython-311.pyc +0 -0
  139. sage/middleware/services/vdb/search_engine/__pycache__/faiss_index.cpython-311.opt-2.pyc +0 -0
  140. sage/middleware/services/vdb/search_engine/__pycache__/faiss_index.cpython-311.pyc +0 -0
  141. sage/middleware/services/vdb/search_engine/base_vdb_index.py +58 -0
  142. sage/middleware/services/vdb/search_engine/faiss_index.py +461 -0
  143. sage/middleware/services/vdb/vdb_service.py +433 -0
  144. sage/middleware/utils/__init__.py +5 -0
  145. sage/middleware/utils/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  146. sage/middleware/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  147. sage/middleware/utils/embedding/__init__.py +35 -0
  148. sage/middleware/utils/embedding/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
  149. sage/middleware/utils/embedding/__pycache__/__init__.cpython-311.pyc +0 -0
  150. sage/middleware/utils/embedding/__pycache__/_cohere.cpython-311.opt-2.pyc +0 -0
  151. sage/middleware/utils/embedding/__pycache__/_cohere.cpython-311.pyc +0 -0
  152. sage/middleware/utils/embedding/__pycache__/bedrock.cpython-311.opt-2.pyc +0 -0
  153. sage/middleware/utils/embedding/__pycache__/bedrock.cpython-311.pyc +0 -0
  154. sage/middleware/utils/embedding/__pycache__/embedding_api.cpython-311.opt-2.pyc +0 -0
  155. sage/middleware/utils/embedding/__pycache__/embedding_api.cpython-311.pyc +0 -0
  156. sage/middleware/utils/embedding/__pycache__/embedding_model.cpython-311.opt-2.pyc +0 -0
  157. sage/middleware/utils/embedding/__pycache__/embedding_model.cpython-311.pyc +0 -0
  158. sage/middleware/utils/embedding/__pycache__/hf.cpython-311.opt-2.pyc +0 -0
  159. sage/middleware/utils/embedding/__pycache__/hf.cpython-311.pyc +0 -0
  160. sage/middleware/utils/embedding/__pycache__/instructor.cpython-311.opt-2.pyc +0 -0
  161. sage/middleware/utils/embedding/__pycache__/instructor.cpython-311.pyc +0 -0
  162. sage/middleware/utils/embedding/__pycache__/jina.cpython-311.opt-2.pyc +0 -0
  163. sage/middleware/utils/embedding/__pycache__/jina.cpython-311.pyc +0 -0
  164. sage/middleware/utils/embedding/__pycache__/lollms.cpython-311.opt-2.pyc +0 -0
  165. sage/middleware/utils/embedding/__pycache__/lollms.cpython-311.pyc +0 -0
  166. sage/middleware/utils/embedding/__pycache__/mockembedder.cpython-311.opt-2.pyc +0 -0
  167. sage/middleware/utils/embedding/__pycache__/mockembedder.cpython-311.pyc +0 -0
  168. sage/middleware/utils/embedding/__pycache__/nvidia_openai.cpython-311.opt-2.pyc +0 -0
  169. sage/middleware/utils/embedding/__pycache__/nvidia_openai.cpython-311.pyc +0 -0
  170. sage/middleware/utils/embedding/__pycache__/ollama.cpython-311.opt-2.pyc +0 -0
  171. sage/middleware/utils/embedding/__pycache__/ollama.cpython-311.pyc +0 -0
  172. sage/middleware/utils/embedding/__pycache__/openai.cpython-311.opt-2.pyc +0 -0
  173. sage/middleware/utils/embedding/__pycache__/openai.cpython-311.pyc +0 -0
  174. sage/middleware/utils/embedding/__pycache__/siliconcloud.cpython-311.opt-2.pyc +0 -0
  175. sage/middleware/utils/embedding/__pycache__/siliconcloud.cpython-311.pyc +0 -0
  176. sage/middleware/utils/embedding/__pycache__/zhipu.cpython-311.opt-2.pyc +0 -0
  177. sage/middleware/utils/embedding/__pycache__/zhipu.cpython-311.pyc +0 -0
  178. sage/middleware/utils/embedding/_cohere.py +68 -0
  179. sage/middleware/utils/embedding/bedrock.py +174 -0
  180. sage/middleware/utils/embedding/embedding_api.py +12 -0
  181. sage/middleware/utils/embedding/embedding_model.py +150 -0
  182. sage/middleware/utils/embedding/hf.py +90 -0
  183. sage/middleware/utils/embedding/instructor.py +10 -0
  184. sage/middleware/utils/embedding/jina.py +115 -0
  185. sage/middleware/utils/embedding/lollms.py +100 -0
  186. sage/middleware/utils/embedding/mockembedder.py +46 -0
  187. sage/middleware/utils/embedding/nvidia_openai.py +97 -0
  188. sage/middleware/utils/embedding/ollama.py +97 -0
  189. sage/middleware/utils/embedding/openai.py +112 -0
  190. sage/middleware/utils/embedding/siliconcloud.py +133 -0
  191. sage/middleware/utils/embedding/zhipu.py +85 -0
@@ -0,0 +1,541 @@
1
+ """
2
+ Graph Service - 图数据库微服务
3
+ 提供图存储、知识图谱和图查询功能的服务任务,集成到SAGE DAG中
4
+ 支持Neo4j和内存图后端
5
+ """
6
+ from typing import Dict, Any, Optional, List, Union, Tuple, TYPE_CHECKING
7
+ from dataclasses import dataclass
8
+ import logging
9
+ import time
10
+ import uuid
11
+
12
+ from sage.core.api.service.base_service import BaseService
13
+
14
+ if TYPE_CHECKING:
15
+ from sage.core.factory.service_factory import ServiceFactory
16
+ from sage.kernel import ServiceContext
17
+
18
+
19
+ @dataclass
20
+ class GraphConfig:
21
+ """Graph服务配置"""
22
+ backend_type: str = "memory" # "memory" 或 "neo4j"
23
+ neo4j_uri: Optional[str] = None
24
+ neo4j_user: Optional[str] = None
25
+ neo4j_password: Optional[str] = None
26
+ max_nodes: int = 100000
27
+ max_relationships: int = 500000
28
+
29
+
30
+ @dataclass
31
+ class GraphNode:
32
+ """图节点"""
33
+ id: str
34
+ labels: List[str]
35
+ properties: Dict[str, Any]
36
+ created_at: float = None
37
+
38
+ def __post_init__(self):
39
+ if self.created_at is None:
40
+ self.created_at = time.time()
41
+
42
+
43
+ @dataclass
44
+ class GraphRelationship:
45
+ """图关系"""
46
+ id: str
47
+ from_node: str
48
+ to_node: str
49
+ rel_type: str
50
+ properties: Dict[str, Any]
51
+ created_at: float = None
52
+
53
+ def __post_init__(self):
54
+ if self.created_at is None:
55
+ self.created_at = time.time()
56
+
57
+
58
+ class MemoryGraphBackend:
59
+ """内存图数据库后端"""
60
+
61
+ def __init__(self, max_nodes: int = 100000, max_relationships: int = 500000):
62
+ self.nodes: Dict[str, GraphNode] = {}
63
+ self.relationships: Dict[str, GraphRelationship] = {}
64
+ self.node_relationships: Dict[str, List[str]] = {} # node_id -> [rel_ids]
65
+ self.max_nodes = max_nodes
66
+ self.max_relationships = max_relationships
67
+ self.logger = logging.getLogger(__name__)
68
+
69
+ def create_node(self, node: GraphNode) -> str:
70
+ """创建节点"""
71
+ if len(self.nodes) >= self.max_nodes:
72
+ raise RuntimeError(f"Maximum nodes limit reached: {self.max_nodes}")
73
+
74
+ if not node.id:
75
+ node.id = f"node_{uuid.uuid4()}"
76
+
77
+ self.nodes[node.id] = node
78
+ if node.id not in self.node_relationships:
79
+ self.node_relationships[node.id] = []
80
+
81
+ self.logger.debug(f"Created node: {node.id}")
82
+ return node.id
83
+
84
+ def create_relationship(self, rel: GraphRelationship) -> str:
85
+ """创建关系"""
86
+ if len(self.relationships) >= self.max_relationships:
87
+ raise RuntimeError(f"Maximum relationships limit reached: {self.max_relationships}")
88
+
89
+ if rel.from_node not in self.nodes or rel.to_node not in self.nodes:
90
+ raise ValueError("Both nodes must exist before creating relationship")
91
+
92
+ if not rel.id:
93
+ rel.id = f"rel_{uuid.uuid4()}"
94
+
95
+ self.relationships[rel.id] = rel
96
+
97
+ # 更新节点关系索引
98
+ if rel.from_node not in self.node_relationships:
99
+ self.node_relationships[rel.from_node] = []
100
+ if rel.to_node not in self.node_relationships:
101
+ self.node_relationships[rel.to_node] = []
102
+
103
+ self.node_relationships[rel.from_node].append(rel.id)
104
+ self.node_relationships[rel.to_node].append(rel.id)
105
+
106
+ self.logger.debug(f"Created relationship: {rel.id}")
107
+ return rel.id
108
+
109
+ def get_node(self, node_id: str) -> Optional[GraphNode]:
110
+ """获取节点"""
111
+ return self.nodes.get(node_id)
112
+
113
+ def get_relationship(self, rel_id: str) -> Optional[GraphRelationship]:
114
+ """获取关系"""
115
+ return self.relationships.get(rel_id)
116
+
117
+ def find_nodes(self, labels: Optional[List[str]] = None,
118
+ properties: Optional[Dict[str, Any]] = None) -> List[GraphNode]:
119
+ """查找节点"""
120
+ results = []
121
+ for node in self.nodes.values():
122
+ # 检查标签
123
+ if labels and not any(label in node.labels for label in labels):
124
+ continue
125
+
126
+ # 检查属性
127
+ if properties:
128
+ if not all(node.properties.get(k) == v for k, v in properties.items()):
129
+ continue
130
+
131
+ results.append(node)
132
+
133
+ return results
134
+
135
+ def get_node_relationships(self, node_id: str, direction: str = "both") -> List[GraphRelationship]:
136
+ """获取节点的关系"""
137
+ if node_id not in self.node_relationships:
138
+ return []
139
+
140
+ rel_ids = self.node_relationships[node_id]
141
+ relationships = []
142
+
143
+ for rel_id in rel_ids:
144
+ rel = self.relationships.get(rel_id)
145
+ if not rel:
146
+ continue
147
+
148
+ if direction == "outgoing" and rel.from_node != node_id:
149
+ continue
150
+ elif direction == "incoming" and rel.to_node != node_id:
151
+ continue
152
+
153
+ relationships.append(rel)
154
+
155
+ return relationships
156
+
157
+ def delete_node(self, node_id: str) -> bool:
158
+ """删除节点及其关系"""
159
+ if node_id not in self.nodes:
160
+ return False
161
+
162
+ # 删除相关的关系
163
+ if node_id in self.node_relationships:
164
+ rel_ids = list(self.node_relationships[node_id])
165
+ for rel_id in rel_ids:
166
+ self.delete_relationship(rel_id)
167
+
168
+ # 删除节点
169
+ del self.nodes[node_id]
170
+ if node_id in self.node_relationships:
171
+ del self.node_relationships[node_id]
172
+
173
+ return True
174
+
175
+ def delete_relationship(self, rel_id: str) -> bool:
176
+ """删除关系"""
177
+ if rel_id not in self.relationships:
178
+ return False
179
+
180
+ rel = self.relationships[rel_id]
181
+
182
+ # 从节点关系索引中移除
183
+ if rel.from_node in self.node_relationships:
184
+ self.node_relationships[rel.from_node] = [
185
+ r for r in self.node_relationships[rel.from_node] if r != rel_id
186
+ ]
187
+
188
+ if rel.to_node in self.node_relationships:
189
+ self.node_relationships[rel.to_node] = [
190
+ r for r in self.node_relationships[rel.to_node] if r != rel_id
191
+ ]
192
+
193
+ del self.relationships[rel_id]
194
+ return True
195
+
196
+ def count_nodes(self) -> int:
197
+ """获取节点数量"""
198
+ return len(self.nodes)
199
+
200
+ def count_relationships(self) -> int:
201
+ """获取关系数量"""
202
+ return len(self.relationships)
203
+
204
+ def clear(self) -> None:
205
+ """清空图数据"""
206
+ self.nodes.clear()
207
+ self.relationships.clear()
208
+ self.node_relationships.clear()
209
+
210
+
211
+ class Neo4jGraphBackend:
212
+ """Neo4j图数据库后端"""
213
+
214
+ def __init__(self, uri: str, user: str, password: str):
215
+ try:
216
+ from neo4j import GraphDatabase
217
+ self.driver = GraphDatabase.driver(uri, auth=(user, password))
218
+ self.logger = logging.getLogger(__name__)
219
+
220
+ # 测试连接
221
+ with self.driver.session() as session:
222
+ session.run("RETURN 1")
223
+
224
+ self.logger.info(f"Connected to Neo4j: {uri}")
225
+ except ImportError:
226
+ raise ImportError("Neo4j package not installed. Run: pip install neo4j")
227
+ except Exception as e:
228
+ raise ConnectionError(f"Failed to connect to Neo4j: {e}")
229
+
230
+ def create_node(self, node: GraphNode) -> str:
231
+ """创建节点"""
232
+ with self.driver.session() as session:
233
+ labels_str = ":".join(node.labels) if node.labels else ""
234
+ query = f"CREATE (n:{labels_str} $properties) RETURN n"
235
+
236
+ if not node.id:
237
+ node.id = f"node_{uuid.uuid4()}"
238
+
239
+ properties = {**node.properties, "id": node.id, "created_at": node.created_at}
240
+ result = session.run(query, properties=properties)
241
+
242
+ self.logger.debug(f"Created Neo4j node: {node.id}")
243
+ return node.id
244
+
245
+ def create_relationship(self, rel: GraphRelationship) -> str:
246
+ """创建关系"""
247
+ with self.driver.session() as session:
248
+ if not rel.id:
249
+ rel.id = f"rel_{uuid.uuid4()}"
250
+
251
+ query = """
252
+ MATCH (a {id: $from_node}), (b {id: $to_node})
253
+ CREATE (a)-[r:%s $properties]->(b)
254
+ RETURN r
255
+ """ % rel.rel_type
256
+
257
+ properties = {**rel.properties, "id": rel.id, "created_at": rel.created_at}
258
+ session.run(query,
259
+ from_node=rel.from_node,
260
+ to_node=rel.to_node,
261
+ properties=properties)
262
+
263
+ self.logger.debug(f"Created Neo4j relationship: {rel.id}")
264
+ return rel.id
265
+
266
+ def get_node(self, node_id: str) -> Optional[GraphNode]:
267
+ """获取节点"""
268
+ with self.driver.session() as session:
269
+ query = "MATCH (n {id: $node_id}) RETURN n, labels(n) as labels"
270
+ result = session.run(query, node_id=node_id)
271
+
272
+ record = result.single()
273
+ if not record:
274
+ return None
275
+
276
+ node_data = dict(record["n"])
277
+ labels = record["labels"]
278
+
279
+ return GraphNode(
280
+ id=node_data.pop("id"),
281
+ labels=labels,
282
+ properties=node_data,
283
+ created_at=node_data.get("created_at", time.time())
284
+ )
285
+
286
+ def find_nodes(self, labels: Optional[List[str]] = None,
287
+ properties: Optional[Dict[str, Any]] = None) -> List[GraphNode]:
288
+ """查找节点"""
289
+ with self.driver.session() as session:
290
+ where_clauses = []
291
+ params = {}
292
+
293
+ if labels:
294
+ label_str = ":".join(labels)
295
+ query = f"MATCH (n:{label_str})"
296
+ else:
297
+ query = "MATCH (n)"
298
+
299
+ if properties:
300
+ for i, (k, v) in enumerate(properties.items()):
301
+ param_name = f"prop_{i}"
302
+ where_clauses.append(f"n.{k} = ${param_name}")
303
+ params[param_name] = v
304
+
305
+ if where_clauses:
306
+ query += " WHERE " + " AND ".join(where_clauses)
307
+
308
+ query += " RETURN n, labels(n) as labels"
309
+
310
+ result = session.run(query, **params)
311
+ nodes = []
312
+
313
+ for record in result:
314
+ node_data = dict(record["n"])
315
+ labels = record["labels"]
316
+
317
+ nodes.append(GraphNode(
318
+ id=node_data.pop("id"),
319
+ labels=labels,
320
+ properties=node_data,
321
+ created_at=node_data.get("created_at", time.time())
322
+ ))
323
+
324
+ return nodes
325
+
326
+ def clear(self) -> None:
327
+ """清空数据库"""
328
+ with self.driver.session() as session:
329
+ session.run("MATCH (n) DETACH DELETE n")
330
+ self.logger.debug("Cleared Neo4j database")
331
+
332
+
333
+ class GraphService(BaseService):
334
+ """
335
+ Graph服务任务
336
+
337
+ 提供图存储、知识图谱和图查询功能,可以在SAGE DAG中作为服务节点使用
338
+ 支持内存和Neo4j后端
339
+ """
340
+
341
+ def __init__(self, service_factory: 'ServiceFactory', ctx: 'ServiceContext' = None):
342
+ super().__init__(service_factory, ctx)
343
+
344
+ # 从service_factory获取配置
345
+ self.config: GraphConfig = getattr(service_factory, 'config', GraphConfig())
346
+
347
+ # 初始化后端
348
+ if self.config.backend_type == "neo4j":
349
+ if not all([self.config.neo4j_uri, self.config.neo4j_user, self.config.neo4j_password]):
350
+ raise ValueError("Neo4j URI, user and password required for Neo4j backend")
351
+ self.backend = Neo4jGraphBackend(
352
+ self.config.neo4j_uri,
353
+ self.config.neo4j_user,
354
+ self.config.neo4j_password
355
+ )
356
+ self.logger.info(f"Graph Service '{self.service_name}' initialized with Neo4j backend")
357
+ else:
358
+ self.backend = MemoryGraphBackend(
359
+ self.config.max_nodes,
360
+ self.config.max_relationships
361
+ )
362
+ self.logger.info(f"Graph Service '{self.service_name}' initialized with memory backend")
363
+
364
+ def _start_service_instance(self):
365
+ """启动Graph服务实例"""
366
+ self.logger.info(f"Graph Service '{self.service_name}' started")
367
+
368
+ def _stop_service_instance(self):
369
+ """停止Graph服务实例"""
370
+ self.logger.info(f"Graph Service '{self.service_name}' stopped")
371
+
372
+ # Graph操作方法 - 这些方法可以通过服务调用机制被调用
373
+
374
+ def create_node(self, labels: List[str], properties: Dict[str, Any],
375
+ node_id: Optional[str] = None) -> str:
376
+ """创建节点"""
377
+ self.logger.debug(f"Creating node with labels: {labels}")
378
+
379
+ node = GraphNode(
380
+ id=node_id or "",
381
+ labels=labels,
382
+ properties=properties
383
+ )
384
+
385
+ result = self.backend.create_node(node)
386
+ self.logger.debug(f"Created node: {result}")
387
+ return result
388
+
389
+ def create_relationship(self, from_node: str, to_node: str, rel_type: str,
390
+ properties: Optional[Dict[str, Any]] = None,
391
+ rel_id: Optional[str] = None) -> str:
392
+ """创建关系"""
393
+ self.logger.debug(f"Creating relationship: {from_node} -[{rel_type}]-> {to_node}")
394
+
395
+ rel = GraphRelationship(
396
+ id=rel_id or "",
397
+ from_node=from_node,
398
+ to_node=to_node,
399
+ rel_type=rel_type,
400
+ properties=properties or {}
401
+ )
402
+
403
+ result = self.backend.create_relationship(rel)
404
+ self.logger.debug(f"Created relationship: {result}")
405
+ return result
406
+
407
+ def get_node(self, node_id: str) -> Optional[Dict[str, Any]]:
408
+ """获取节点"""
409
+ self.logger.debug(f"Getting node: {node_id}")
410
+
411
+ node = self.backend.get_node(node_id)
412
+ if node:
413
+ return {
414
+ "id": node.id,
415
+ "labels": node.labels,
416
+ "properties": node.properties,
417
+ "created_at": node.created_at
418
+ }
419
+ return None
420
+
421
+ def find_nodes(self, labels: Optional[List[str]] = None,
422
+ properties: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
423
+ """查找节点"""
424
+ self.logger.debug(f"Finding nodes with labels: {labels}, properties: {properties}")
425
+
426
+ nodes = self.backend.find_nodes(labels, properties)
427
+ results = []
428
+ for node in nodes:
429
+ results.append({
430
+ "id": node.id,
431
+ "labels": node.labels,
432
+ "properties": node.properties,
433
+ "created_at": node.created_at
434
+ })
435
+
436
+ self.logger.debug(f"Found {len(results)} nodes")
437
+ return results
438
+
439
+ def get_node_relationships(self, node_id: str, direction: str = "both") -> List[Dict[str, Any]]:
440
+ """获取节点的关系"""
441
+ self.logger.debug(f"Getting relationships for node: {node_id}, direction: {direction}")
442
+
443
+ if hasattr(self.backend, 'get_node_relationships'):
444
+ relationships = self.backend.get_node_relationships(node_id, direction)
445
+ results = []
446
+ for rel in relationships:
447
+ results.append({
448
+ "id": rel.id,
449
+ "from_node": rel.from_node,
450
+ "to_node": rel.to_node,
451
+ "rel_type": rel.rel_type,
452
+ "properties": rel.properties,
453
+ "created_at": rel.created_at
454
+ })
455
+ return results
456
+ return []
457
+
458
+ def delete_node(self, node_id: str) -> bool:
459
+ """删除节点"""
460
+ self.logger.debug(f"Deleting node: {node_id}")
461
+ result = self.backend.delete_node(node_id)
462
+ self.logger.debug(f"Delete node result: {result}")
463
+ return result
464
+
465
+ def delete_relationship(self, rel_id: str) -> bool:
466
+ """删除关系"""
467
+ self.logger.debug(f"Deleting relationship: {rel_id}")
468
+ result = self.backend.delete_relationship(rel_id)
469
+ self.logger.debug(f"Delete relationship result: {result}")
470
+ return result
471
+
472
+ def count_nodes(self) -> int:
473
+ """获取节点数量"""
474
+ result = self.backend.count_nodes()
475
+ self.logger.debug(f"Node count: {result}")
476
+ return result
477
+
478
+ def count_relationships(self) -> int:
479
+ """获取关系数量"""
480
+ result = self.backend.count_relationships()
481
+ self.logger.debug(f"Relationship count: {result}")
482
+ return result
483
+
484
+ def clear(self) -> None:
485
+ """清空图数据"""
486
+ self.logger.debug("Clearing graph data")
487
+ self.backend.clear()
488
+
489
+ def stats(self) -> Dict[str, Any]:
490
+ """获取服务统计信息"""
491
+ base_stats = self.get_statistics()
492
+ graph_stats = {
493
+ "backend_type": self.config.backend_type,
494
+ "node_count": self.backend.count_nodes(),
495
+ "relationship_count": self.backend.count_relationships(),
496
+ "max_nodes": getattr(self.config, 'max_nodes', None),
497
+ "max_relationships": getattr(self.config, 'max_relationships', None)
498
+ }
499
+ base_stats.update(graph_stats)
500
+ return base_stats
501
+
502
+
503
+ # 工厂函数,用于在DAG中创建Graph服务
504
+ def create_graph_service_factory(
505
+ service_name: str = "graph_service",
506
+ backend_type: str = "memory",
507
+ neo4j_uri: Optional[str] = None,
508
+ neo4j_user: Optional[str] = None,
509
+ neo4j_password: Optional[str] = None,
510
+ max_nodes: int = 100000,
511
+ max_relationships: int = 500000
512
+ ):
513
+ """
514
+ 创建Graph服务工厂
515
+
516
+ Args:
517
+ service_name: 服务名称
518
+ backend_type: 后端类型 ("memory" 或 "neo4j")
519
+ neo4j_uri: Neo4j连接URI
520
+ neo4j_user: Neo4j用户名
521
+ neo4j_password: Neo4j密码
522
+ max_nodes: 最大节点数 (仅内存后端)
523
+ max_relationships: 最大关系数 (仅内存后端)
524
+
525
+ Returns:
526
+ ServiceFactory: 可以用于注册到环境的服务工厂
527
+ """
528
+ from sage.core.factory.service_factory import ServiceFactory
529
+
530
+ config = GraphConfig(
531
+ backend_type=backend_type,
532
+ neo4j_uri=neo4j_uri,
533
+ neo4j_user=neo4j_user,
534
+ neo4j_password=neo4j_password,
535
+ max_nodes=max_nodes,
536
+ max_relationships=max_relationships
537
+ )
538
+
539
+ factory = ServiceFactory(service_name, GraphService)
540
+ factory.config = config
541
+ return factory
@@ -0,0 +1,8 @@
1
+ """
2
+ KV Service Module
3
+ 键值存储服务模块
4
+ """
5
+
6
+ from .kv_service import KVService, create_kv_service_factory
7
+
8
+ __all__ = ["KVService", "create_kv_service_factory"]