This commit is contained in:
jinli.yl 2026-05-16 22:52:07 +08:00
parent 3367d48ea1
commit 9d48a30fd6
14 changed files with 78 additions and 15 deletions

View file

@ -1,2 +1,7 @@
1. 完善mcp_servers config
2. 完善mcp/http的服务测试
2. 完善mcp/http的服务测试
3. [PosixPath('.reme')]
4. error
5. meta信息存在一个地方
6. 测试一个完整的Service client的框架测试各种命令
7. config 默认改成default

View file

@ -127,6 +127,13 @@ class BaseComponent(ABC):
return Path.cwd()
return Path(self.app_context.app_config.working_dir)
@property
def working_metadata_path(self) -> Path:
"""Resolved metadata directory: working_path / metadata_dir, or absolute metadata_dir."""
if self.app_context is None:
return Path.cwd() / "metadata"
return self.working_path / self.app_context.app_config.metadata_dir
# ----- Lifecycle -----------------------------------------------------
async def _start(self) -> None:

View file

@ -49,7 +49,7 @@ class BaseEmbeddingModel(BaseComponent):
@property
def cache_path(self) -> Path:
"""Disk path for the embedding cache file."""
return self.working_path / "embedding_cache" / f"{self.name}.npz"
return self.working_metadata_path / "embedding_cache" / f"{self.name}.npz"
async def _start(self) -> None:
"""Load cache from disk on startup."""

View file

@ -16,7 +16,7 @@ class BaseFileGraph(BaseComponent):
def __init__(self, graph_name: str = "default", **kwargs):
super().__init__(**kwargs)
self.graph_name: str = graph_name or self.name
self.graph_path: Path = self.working_path / self.component_type.value
self.graph_path: Path = self.working_metadata_path / self.component_type.value
self.graph_path.mkdir(parents=True, exist_ok=True)
# -- Node CRUD ---------------------------------------------------------

View file

@ -35,7 +35,7 @@ class BaseFileStore(BaseComponent):
self.embedding_model = self.bind(embedding_model, BaseEmbeddingModel, default_factory=OpenAIEmbeddingModel)
self.keyword_index = self.bind(keyword_index, BaseKeywordIndex, default_factory=BM25Index)
self.file_graph = self.bind(file_graph, BaseFileGraph, default_factory=LocalFileGraph)
self.store_path = self.working_path / self.component_type.value / store_name
self.store_path = self.working_metadata_path / self.component_type.value / store_name
self.store_path.mkdir(parents=True, exist_ok=True)
async def upsert_file(

View file

@ -50,7 +50,7 @@ class BaseFileWatcher(BaseComponent):
async def _start(self):
self._stop_event = asyncio.Event()
self._background_task = asyncio.create_task(self._background_run())
self.logger.info(f"Started watching: {self.watch_paths}")
self.logger.info(f"Started watching: {[str(p) for p in self.watch_paths]}")
async def _background_run(self):
"""Sync store then enter watch loop."""

View file

@ -34,10 +34,10 @@ class LiteFileWatcher(BaseFileWatcher):
invalid = set(self.watch_paths) - set(valid_paths)
if invalid:
self.logger.warning(f"Skipping invalid paths: {invalid}")
self.logger.warning(f"Skipping invalid paths: {[str(p) for p in invalid]}")
try:
self.logger.info(f"Watching: {valid_paths}")
self.logger.info(f"Watching: {[str(p) for p in valid_paths]}")
async for changes in awatch(
*valid_paths,
watch_filter=self.watch_filter,

View file

@ -18,7 +18,7 @@ class BaseKeywordIndex(BaseComponent):
from ..tokenizer import RegexTokenizer
self.tokenizer = self.bind(tokenizer, BaseTokenizer, default_factory=RegexTokenizer)
self.index_path = self.working_path / self.component_type.value
self.index_path = self.working_metadata_path / self.component_type.value
self.index_path.mkdir(parents=True, exist_ok=True)
async def _start(self) -> None:

View file

@ -182,6 +182,7 @@ def parse_args(*args, **kwargs) -> tuple[str, dict]:
action = first
configs: list[dict] = []
explicit_config = False
for raw in args[1:]:
arg = _strip_arg_dashes(raw)
@ -189,9 +190,16 @@ def parse_args(*args, **kwargs) -> tuple[str, dict]:
path = arg.split("=", 1)[1].strip()
if path:
configs.append(_load_config(path))
explicit_config = True
elif "=" in arg:
configs.append(parse_dot_notation([arg]))
if not explicit_config and "default" in _CONFIG_REGISTRY:
from ..utils import get_logger
get_logger().info("No config specified, using 'default'")
configs.insert(0, _load_config("default"))
configs.append(kwargs)
merged: dict = {}

View file

@ -23,44 +23,45 @@ jobs:
- backend: demo_echo_step2
components:
# 1. tokenizer — 无依赖
# 1. tokenizer — no dependencies
tokenizer:
default:
backend: regex
# 2. embedding_model — 无依赖
# 2. embedding_model — no dependencies
embedding_model:
default:
backend: openai
model_name: text-embedding-3-small
dimensions: 1536
# 3. file_graph — 无依赖
# 3. file_graph — no dependencies
file_graph:
default:
backend: local
# 4. file_parser — 无依赖
# 4. file_parser — no dependencies
file_parser:
default:
backend: default
# 5. keyword_index — 依赖 tokenizer
# 5. keyword_index — depends on tokenizer
keyword_index:
default:
backend: bm25
tokenizer: default
# 6. file_store — 依赖 embedding_model / keyword_index / file_graph
# 6. file_store — depends on embedding_model / keyword_index / file_graph
file_store:
default:
backend: local
store_name: default
embedding_model: default
# embedding_model: ""
keyword_index: default
file_graph: default
# 7. file_watcher — 依赖 file_store / file_parser
# 7. file_watcher — depends on file_store / file_parser
file_watcher:
default:
backend: lite

View file

@ -7,6 +7,9 @@ from .application import Application
from .components import R
from .config import parse_args
from .enumeration import ComponentEnum
from .utils import load_env
load_env()
class ReMe(Application):

View file

@ -29,6 +29,7 @@ class ApplicationConfig(BaseModel):
app_name: str = Field(default=os.getenv("APP_NAME", "ReMe"), description="Application display name")
working_dir: str = Field(default=".reme", description="Working directory for runtime files")
metadata_dir: str = Field(default="reme_metadata",description="Subdirectory for ReMe persistent state")
enable_logo: bool = Field(default=True, description="Show ASCII logo on startup")
language: str = Field(default="", description="Default language for LLM interactions")
log_to_console: bool = Field(default=True, description="Log to console")

View file

@ -1,6 +1,7 @@
"""Utility modules."""
from .common_utils import hash_text, execute_stream_task
from .env_utils import load_env
from .logger_utils import get_logger
from .logo_utils import print_logo
from .similarity_utils import cosine_similarity, batch_cosine_similarity
@ -8,6 +9,7 @@ from .similarity_utils import cosine_similarity, batch_cosine_similarity
__all__ = [
"hash_text",
"execute_stream_task",
"load_env",
"get_logger",
"print_logo",
"cosine_similarity",

36
reme4/utils/env_utils.py Normal file
View file

@ -0,0 +1,36 @@
"""Load .env files into os.environ (idempotent)."""
import os
from pathlib import Path
_LOADED = False
def _parse(path: Path) -> None:
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
os.environ[key.strip()] = value.strip().strip("'\"")
def load_env(path: str | Path | None = None) -> None:
"""Load .env from given path, or search cwd and up to 5 parents."""
global _LOADED
if _LOADED:
return
if path:
path = Path(path)
if path.exists():
_parse(path)
_LOADED = True
return
for directory in [Path.cwd(), *Path.cwd().parents[:5]]:
env_path = directory / ".env"
if env_path.exists():
_parse(env_path)
_LOADED = True
return