|
5 | 5 | import numpy as np |
6 | 6 |
|
7 | 7 |
|
8 | | -from lightrag.types import KnowledgeGraph |
| 8 | +from lightrag.types import KnowledgeGraph, KnowledgeGraphNode, KnowledgeGraphEdge |
9 | 9 | from lightrag.utils import ( |
10 | 10 | logger, |
11 | 11 | ) |
@@ -169,9 +169,118 @@ def remove_edges(self, edges: list[tuple[str, str]]): |
169 | 169 | self._graph.remove_edge(source, target) |
170 | 170 |
|
171 | 171 | async def get_all_labels(self) -> list[str]: |
172 | | - raise NotImplementedError |
| 172 | + """ |
| 173 | + Get all node labels in the graph |
| 174 | + Returns: |
| 175 | + [label1, label2, ...] # Alphabetically sorted label list |
| 176 | + """ |
| 177 | + labels = set() |
| 178 | + for node in self._graph.nodes(): |
| 179 | + labels.add(str(node)) # Add node id as a label |
| 180 | + |
| 181 | + # Return sorted list |
| 182 | + return sorted(list(labels)) |
173 | 183 |
|
174 | 184 | async def get_knowledge_graph( |
175 | 185 | self, node_label: str, max_depth: int = 5 |
176 | 186 | ) -> KnowledgeGraph: |
177 | | - raise NotImplementedError |
| 187 | + """ |
| 188 | + Get complete connected subgraph for specified node (including the starting node itself) |
| 189 | +
|
| 190 | + Args: |
| 191 | + node_label: Label of the starting node |
| 192 | + max_depth: Maximum depth of the subgraph |
| 193 | +
|
| 194 | + Returns: |
| 195 | + KnowledgeGraph object containing nodes and edges |
| 196 | + """ |
| 197 | + result = KnowledgeGraph() |
| 198 | + seen_nodes = set() |
| 199 | + seen_edges = set() |
| 200 | + |
| 201 | + # Handle special case for "*" label |
| 202 | + if node_label == "*": |
| 203 | + # For "*", return the entire graph including all nodes and edges |
| 204 | + subgraph = ( |
| 205 | + self._graph.copy() |
| 206 | + ) # Create a copy to avoid modifying the original graph |
| 207 | + else: |
| 208 | + # Find nodes with matching node id (partial match) |
| 209 | + nodes_to_explore = [] |
| 210 | + for n, attr in self._graph.nodes(data=True): |
| 211 | + if node_label in str(n): # Use partial matching |
| 212 | + nodes_to_explore.append(n) |
| 213 | + |
| 214 | + if not nodes_to_explore: |
| 215 | + logger.warning(f"No nodes found with label {node_label}") |
| 216 | + return result |
| 217 | + |
| 218 | + # Get subgraph using ego_graph |
| 219 | + subgraph = nx.ego_graph(self._graph, nodes_to_explore[0], radius=max_depth) |
| 220 | + |
| 221 | + # Check if number of nodes exceeds max_graph_nodes |
| 222 | + max_graph_nodes = 500 |
| 223 | + if len(subgraph.nodes()) > max_graph_nodes: |
| 224 | + origin_nodes = len(subgraph.nodes()) |
| 225 | + node_degrees = dict(subgraph.degree()) |
| 226 | + top_nodes = sorted(node_degrees.items(), key=lambda x: x[1], reverse=True)[ |
| 227 | + :max_graph_nodes |
| 228 | + ] |
| 229 | + top_node_ids = [node[0] for node in top_nodes] |
| 230 | + # Create new subgraph with only top nodes |
| 231 | + subgraph = subgraph.subgraph(top_node_ids) |
| 232 | + logger.info( |
| 233 | + f"Reduced graph from {origin_nodes} nodes to {max_graph_nodes} nodes (depth={max_depth})" |
| 234 | + ) |
| 235 | + |
| 236 | + # Add nodes to result |
| 237 | + for node in subgraph.nodes(): |
| 238 | + if str(node) in seen_nodes: |
| 239 | + continue |
| 240 | + |
| 241 | + node_data = dict(subgraph.nodes[node]) |
| 242 | + # Get entity_type as labels |
| 243 | + labels = [] |
| 244 | + if "entity_type" in node_data: |
| 245 | + if isinstance(node_data["entity_type"], list): |
| 246 | + labels.extend(node_data["entity_type"]) |
| 247 | + else: |
| 248 | + labels.append(node_data["entity_type"]) |
| 249 | + |
| 250 | + # Create node with properties |
| 251 | + node_properties = {k: v for k, v in node_data.items()} |
| 252 | + |
| 253 | + result.nodes.append( |
| 254 | + KnowledgeGraphNode( |
| 255 | + id=str(node), labels=[str(node)], properties=node_properties |
| 256 | + ) |
| 257 | + ) |
| 258 | + seen_nodes.add(str(node)) |
| 259 | + |
| 260 | + # Add edges to result |
| 261 | + for edge in subgraph.edges(): |
| 262 | + source, target = edge |
| 263 | + edge_id = f"{source}-{target}" |
| 264 | + if edge_id in seen_edges: |
| 265 | + continue |
| 266 | + |
| 267 | + edge_data = dict(subgraph.edges[edge]) |
| 268 | + |
| 269 | + # Create edge with complete information |
| 270 | + result.edges.append( |
| 271 | + KnowledgeGraphEdge( |
| 272 | + id=edge_id, |
| 273 | + type="DIRECTED", |
| 274 | + source=str(source), |
| 275 | + target=str(target), |
| 276 | + properties=edge_data, |
| 277 | + ) |
| 278 | + ) |
| 279 | + seen_edges.add(edge_id) |
| 280 | + |
| 281 | + # logger.info(result.edges) |
| 282 | + |
| 283 | + logger.info( |
| 284 | + f"Subgraph query successful | Node count: {len(result.nodes)} | Edge count: {len(result.edges)}" |
| 285 | + ) |
| 286 | + return result |
0 commit comments