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.
- isage_middleware-0.1.0.dist-info/METADATA +424 -0
- isage_middleware-0.1.0.dist-info/RECORD +191 -0
- isage_middleware-0.1.0.dist-info/WHEEL +5 -0
- isage_middleware-0.1.0.dist-info/top_level.txt +1 -0
- sage/__init__.py +2 -0
- sage/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/__init__.py +83 -0
- sage/middleware/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/api/__init__.py +22 -0
- sage/middleware/api/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/api/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/api/__pycache__/graph_api.cpython-311.opt-2.pyc +0 -0
- sage/middleware/api/__pycache__/graph_api.cpython-311.pyc +0 -0
- sage/middleware/api/__pycache__/kv_api.cpython-311.opt-2.pyc +0 -0
- sage/middleware/api/__pycache__/kv_api.cpython-311.pyc +0 -0
- sage/middleware/api/__pycache__/memory_api.cpython-311.opt-2.pyc +0 -0
- sage/middleware/api/__pycache__/memory_api.cpython-311.pyc +0 -0
- sage/middleware/api/__pycache__/vdb_api.cpython-311.opt-2.pyc +0 -0
- sage/middleware/api/__pycache__/vdb_api.cpython-311.pyc +0 -0
- sage/middleware/api/graph_api.py +74 -0
- sage/middleware/api/kv_api.py +45 -0
- sage/middleware/api/memory_api.py +64 -0
- sage/middleware/api/vdb_api.py +60 -0
- sage/middleware/enterprise/__init__.py +75 -0
- sage/middleware/enterprise/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/enterprise/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/enterprise/sage_db/__init__.py +132 -0
- sage/middleware/enterprise/sage_db/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/enterprise/sage_db/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/enterprise/sage_db/__pycache__/sage_db.cpython-311.opt-2.pyc +0 -0
- sage/middleware/enterprise/sage_db/__pycache__/sage_db.cpython-311.pyc +0 -0
- sage/middleware/enterprise/sage_db/python/__init__.py +7 -0
- sage/middleware/enterprise/sage_db/python/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/enterprise/sage_db/python/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/enterprise/sage_db/python/__pycache__/sage_db.cpython-311.opt-2.pyc +0 -0
- sage/middleware/enterprise/sage_db/python/__pycache__/sage_db.cpython-311.pyc +0 -0
- sage/middleware/enterprise/sage_db/python/sage_db.py +44 -0
- sage/middleware/enterprise/sage_db/sage_db.py +395 -0
- sage/middleware/enterprise/sage_db/tests/__pycache__/test_python.cpython-311.opt-2.pyc +0 -0
- sage/middleware/enterprise/sage_db/tests/__pycache__/test_python.cpython-311.pyc +0 -0
- sage/middleware/enterprise/sage_db/tests/test_python.py +144 -0
- sage/middleware/examples/__pycache__/api_usage_tutorial.cpython-311.opt-2.pyc +0 -0
- sage/middleware/examples/__pycache__/api_usage_tutorial.cpython-311.pyc +0 -0
- sage/middleware/examples/__pycache__/dag_microservices_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/examples/__pycache__/dag_microservices_demo.cpython-311.pyc +0 -0
- sage/middleware/examples/__pycache__/microservices_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/examples/__pycache__/microservices_demo.cpython-311.pyc +0 -0
- sage/middleware/examples/__pycache__/microservices_integration_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/examples/__pycache__/microservices_integration_demo.cpython-311.pyc +0 -0
- sage/middleware/examples/__pycache__/microservices_registration_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/examples/__pycache__/microservices_registration_demo.cpython-311.pyc +0 -0
- sage/middleware/examples/api_usage_tutorial.py +339 -0
- sage/middleware/examples/dag_microservices_demo.py +220 -0
- sage/middleware/examples/microservices_demo.py +0 -0
- sage/middleware/examples/microservices_integration_demo.py +373 -0
- sage/middleware/examples/microservices_registration_demo.py +144 -0
- sage/middleware/py.typed +2 -0
- sage/middleware/services/graph/__init__.py +8 -0
- sage/middleware/services/graph/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/graph/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/services/graph/__pycache__/graph_index.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/graph/__pycache__/graph_index.cpython-311.pyc +0 -0
- sage/middleware/services/graph/__pycache__/graph_service.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/graph/__pycache__/graph_service.cpython-311.pyc +0 -0
- sage/middleware/services/graph/examples/__pycache__/graph_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/graph/examples/__pycache__/graph_demo.cpython-311.pyc +0 -0
- sage/middleware/services/graph/examples/graph_demo.py +177 -0
- sage/middleware/services/graph/graph_index.py +194 -0
- sage/middleware/services/graph/graph_service.py +541 -0
- sage/middleware/services/graph/search_engine/__init__.py +0 -0
- sage/middleware/services/graph/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/graph/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/services/graph/search_engine/__pycache__/base_graph_index.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/graph/search_engine/__pycache__/base_graph_index.cpython-311.pyc +0 -0
- sage/middleware/services/graph/search_engine/base_graph_index.py +0 -0
- sage/middleware/services/kv/__init__.py +8 -0
- sage/middleware/services/kv/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/kv/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/services/kv/__pycache__/kv_service.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/kv/__pycache__/kv_service.cpython-311.pyc +0 -0
- sage/middleware/services/kv/examples/__pycache__/kv_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/kv/examples/__pycache__/kv_demo.cpython-311.pyc +0 -0
- sage/middleware/services/kv/examples/kv_demo.py +213 -0
- sage/middleware/services/kv/kv_service.py +306 -0
- sage/middleware/services/kv/search_engine/__init__.py +0 -0
- sage/middleware/services/kv/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/kv/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/services/kv/search_engine/__pycache__/base_kv_index.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/kv/search_engine/__pycache__/base_kv_index.cpython-311.pyc +0 -0
- sage/middleware/services/kv/search_engine/__pycache__/bm25s_index.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/kv/search_engine/__pycache__/bm25s_index.cpython-311.pyc +0 -0
- sage/middleware/services/kv/search_engine/base_kv_index.py +75 -0
- sage/middleware/services/kv/search_engine/bm25s_index.py +238 -0
- sage/middleware/services/memory/__init__.py +12 -0
- sage/middleware/services/memory/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/services/memory/__pycache__/memory_service.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/__pycache__/memory_service.cpython-311.pyc +0 -0
- sage/middleware/services/memory/examples/__pycache__/dag_microservices_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/examples/__pycache__/dag_microservices_demo.cpython-311.pyc +0 -0
- sage/middleware/services/memory/examples/__pycache__/memory_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/examples/__pycache__/memory_demo.cpython-311.pyc +0 -0
- sage/middleware/services/memory/examples/dag_microservices_demo.py +220 -0
- sage/middleware/services/memory/examples/memory_demo.py +490 -0
- sage/middleware/services/memory/memory_collection/__pycache__/base_collection.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/memory_collection/__pycache__/base_collection.cpython-311.pyc +0 -0
- sage/middleware/services/memory/memory_collection/__pycache__/graph_collection.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/memory_collection/__pycache__/graph_collection.cpython-311.pyc +0 -0
- sage/middleware/services/memory/memory_collection/__pycache__/kv_collection.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/memory_collection/__pycache__/kv_collection.cpython-311.pyc +0 -0
- sage/middleware/services/memory/memory_collection/__pycache__/vdb_collection.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/memory_collection/__pycache__/vdb_collection.cpython-311.pyc +0 -0
- sage/middleware/services/memory/memory_collection/base_collection.py +0 -0
- sage/middleware/services/memory/memory_collection/graph_collection.py +0 -0
- sage/middleware/services/memory/memory_collection/kv_collection.py +0 -0
- sage/middleware/services/memory/memory_collection/vdb_collection.py +0 -0
- sage/middleware/services/memory/memory_service.py +474 -0
- sage/middleware/services/memory/utils/__init__.py +0 -0
- sage/middleware/services/memory/utils/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/utils/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/services/memory/utils/__pycache__/path_utils.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/memory/utils/__pycache__/path_utils.cpython-311.pyc +0 -0
- sage/middleware/services/memory/utils/path_utils.py +0 -0
- sage/middleware/services/vdb/__init__.py +8 -0
- sage/middleware/services/vdb/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/vdb/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/services/vdb/__pycache__/vdb_service.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/vdb/__pycache__/vdb_service.cpython-311.pyc +0 -0
- sage/middleware/services/vdb/examples/__pycache__/vdb_demo.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/vdb/examples/__pycache__/vdb_demo.cpython-311.pyc +0 -0
- sage/middleware/services/vdb/examples/vdb_demo.py +447 -0
- sage/middleware/services/vdb/search_engine/__init__.py +0 -0
- sage/middleware/services/vdb/search_engine/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/vdb/search_engine/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/services/vdb/search_engine/__pycache__/base_vdb_index.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/vdb/search_engine/__pycache__/base_vdb_index.cpython-311.pyc +0 -0
- sage/middleware/services/vdb/search_engine/__pycache__/faiss_index.cpython-311.opt-2.pyc +0 -0
- sage/middleware/services/vdb/search_engine/__pycache__/faiss_index.cpython-311.pyc +0 -0
- sage/middleware/services/vdb/search_engine/base_vdb_index.py +58 -0
- sage/middleware/services/vdb/search_engine/faiss_index.py +461 -0
- sage/middleware/services/vdb/vdb_service.py +433 -0
- sage/middleware/utils/__init__.py +5 -0
- sage/middleware/utils/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__init__.py +35 -0
- sage/middleware/utils/embedding/__pycache__/__init__.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/__init__.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/_cohere.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/_cohere.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/bedrock.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/bedrock.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/embedding_api.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/embedding_api.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/embedding_model.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/embedding_model.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/hf.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/hf.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/instructor.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/instructor.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/jina.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/jina.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/lollms.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/lollms.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/mockembedder.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/mockembedder.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/nvidia_openai.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/nvidia_openai.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/ollama.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/ollama.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/openai.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/openai.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/siliconcloud.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/siliconcloud.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/zhipu.cpython-311.opt-2.pyc +0 -0
- sage/middleware/utils/embedding/__pycache__/zhipu.cpython-311.pyc +0 -0
- sage/middleware/utils/embedding/_cohere.py +68 -0
- sage/middleware/utils/embedding/bedrock.py +174 -0
- sage/middleware/utils/embedding/embedding_api.py +12 -0
- sage/middleware/utils/embedding/embedding_model.py +150 -0
- sage/middleware/utils/embedding/hf.py +90 -0
- sage/middleware/utils/embedding/instructor.py +10 -0
- sage/middleware/utils/embedding/jina.py +115 -0
- sage/middleware/utils/embedding/lollms.py +100 -0
- sage/middleware/utils/embedding/mockembedder.py +46 -0
- sage/middleware/utils/embedding/nvidia_openai.py +97 -0
- sage/middleware/utils/embedding/ollama.py +97 -0
- sage/middleware/utils/embedding/openai.py +112 -0
- sage/middleware/utils/embedding/siliconcloud.py +133 -0
- 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
|
|
File without changes
|
|
Binary file
|
sage/middleware/services/graph/search_engine/__pycache__/base_graph_index.cpython-311.opt-2.pyc
ADDED
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|