This commit is contained in:
jinli.yl 2026-05-14 12:17:51 +08:00
parent d0aae68164
commit cca6cce0e0
5 changed files with 22 additions and 46 deletions

View file

@ -15,9 +15,7 @@ Contract — six abstract methods, two blocks:
from __future__ import annotations
import re
from abc import abstractmethod
from collections.abc import AsyncIterator
from pathlib import Path
from ..base_component import BaseComponent
@ -30,16 +28,11 @@ class BaseFileGraph(BaseComponent):
component_type = ComponentEnum.FILE_GRAPH
def __init__(self, store_name: str = "default", **kwargs):
def __init__(self, graph_name: str = "default", **kwargs):
super().__init__(**kwargs)
if not re.match(r"^[a-zA-Z0-9_]+$", store_name):
raise ValueError(
f"Invalid store name '{store_name}'. " f"Only alphanumeric and underscores allowed.",
)
self.store_name: str = store_name
self.working_dir: str = self.app_context.app_config.working_dir if self.app_context else ""
self.store_path: Path = Path(self.working_dir) / "file_graph" / store_name
self.store_path.mkdir(parents=True, exist_ok=True)
self.graph_name: str = graph_name or self.name
self.graph_path: Path = self.working_path / self.component_type.value / graph_name
self.graph_path.mkdir(parents=True, exist_ok=True)
# -- Node CRUD ---------------------------------------------------------
@ -58,15 +51,9 @@ class BaseFileGraph(BaseComponent):
# -- Link access -------------------------------------------------------
@abstractmethod
async def get_outlinks(
self,
path: str,
) -> list[tuple[FileNode, FileLink]]:
async def get_outlinks(self, path: str) -> list[FileLink]:
"""Resolved outgoing links from ``path``."""
@abstractmethod
async def get_inlinks(
self,
path: str,
) -> list[tuple[FileNode, FileLink]]:
async def get_inlinks(self, path: str) -> list[FileLink]:
"""Resolved incoming links to ``path``."""

View file

@ -26,7 +26,7 @@ from pathlib import Path
from .base_file_graph import BaseFileGraph
from ..component_registry import R
from ...schema import FileLink, FileNode
import networkx as nx
@R.register("local")
class LocalFileGraph(BaseFileGraph):
@ -35,21 +35,15 @@ class LocalFileGraph(BaseFileGraph):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._graph = None # networkx.MultiDiGraph; set in _start
self._graph_file: Path = self.store_path / "graph.pkl"
self._graph_file: Path = self.graph_path / f"{self.graph_name}.pkl"
# -- Lifecycle ---------------------------------------------------------
async def _start(self) -> None:
await super()._start()
try:
import networkx as nx
except ImportError as e:
raise ImportError(
"LocalFileGraph requires networkx. Install with `pip install networkx`.",
) from e
self._graph = self._load_graph(nx) or nx.MultiDiGraph()
self.logger.info(
f"LocalFileGraph '{self.store_name}' ready: "
f"LocalFileGraph '{self.graph_name}' ready: "
f"{self._graph.number_of_nodes()} nodes, "
f"{self._graph.number_of_edges()} edges",
)

View file

@ -83,7 +83,7 @@ class Neo4jFileGraph(BaseFileGraph):
"CREATE CONSTRAINT file_path_unique IF NOT EXISTS " "FOR (f:File) REQUIRE f.path IS UNIQUE",
)
self.logger.info(
f"Neo4jFileGraph '{self.store_name}' connected at " f"{self._uri}/{self._database}",
f"Neo4jFileGraph '{self.graph_name}' connected at " f"{self._uri}/{self._database}",
)
async def _close(self) -> None:

View file

@ -577,9 +577,8 @@ class LinkedFileParser(BaseFileParser):
when ``embed_toc``, otherwise just ``content``."""
text = _toc_join(before, content, after) if self.embed_toc else content
return FileChunk(
id=hash_text(text),
path=path,
start_line=start_line,
end_line=end_line,
text=text,
)
).set_hash_id()

View file

@ -1,4 +1,3 @@
import re
from abc import abstractmethod
from ..base_component import BaseComponent
@ -12,15 +11,13 @@ class BaseFileStore(BaseComponent):
component_type = ComponentEnum.FILE_STORE
def __init__(
self,
store_name: str,
embedding_model: str = "default",
keyword_index: str = "default",
**kwargs,
self,
store_name: str,
embedding_model: str = "default",
keyword_index: str = "default",
**kwargs,
):
super().__init__(**kwargs)
if not re.match(r"^[a-zA-Z0-9_]+$", store_name):
raise ValueError(f"Invalid store name '{store_name}'. Only alphanumeric and underscores allowed.")
self.store_name = store_name or self.name
self._embedding_model_name = embedding_model
self._keyword_index_name = keyword_index
@ -46,13 +43,15 @@ class BaseFileStore(BaseComponent):
self.keyword_index = None
await self.dump_file_nodes()
async def load_file_nodes(self): ...
async def load_file_nodes(self):
...
async def dump_file_nodes(self): ...
async def dump_file_nodes(self):
...
async def upsert_file(
self,
file: tuple[FileNode, list[FileChunk]] | list[tuple[FileNode, list[FileChunk]]],
self,
file: tuple[FileNode, list[FileChunk]] | list[tuple[FileNode, list[FileChunk]]],
) -> None:
"""Upsert a file and its chunks into the store."""
@ -69,6 +68,3 @@ class BaseFileStore(BaseComponent):
@abstractmethod
async def keyword_search(self, query: str, limit: int, search_filter: dict) -> list[FileChunk]:
"""Perform full-text keyword search."""
async def graph_search(self, query: str, limit: int, search_filter: dict) -> list[FileChunk] | None:
"""Perform graph search."""