diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 97d48eab..263bd23a 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -1,22 +1,10 @@ -name: Tests (reme) +name: Tests ReMe on: push: - branches: [main, master, dev, develop, 'dev/**'] - paths: - - 'reme4/**' - - 'reme/reme2/**' - - 'tests4/**' - - 'pyproject.toml' - - '.github/workflows/tests4.yml' + branches: [main, master, dev, develop] pull_request: branches: [main, master, dev, develop] - paths: - - 'reme4/**' - - 'reme/reme2/**' - - 'tests4/**' - - 'pyproject.toml' - - '.github/workflows/tests4.yml' workflow_dispatch: concurrency: diff --git a/docs4/reme_design.md b/docs4/reme_design.md index 46ec739a..c382bfa1 100644 --- a/docs4/reme_design.md +++ b/docs4/reme_design.md @@ -3,9 +3,10 @@ | 分类 | 能力 | 参数 | |--------|---------|-----------------------------------------------------------| | 通用 | help | | -| 通用 | start | | +| 通用 | start | 支持后台 | | 通用 | restart | | | 通用 | version | | +| 通用 | reindex | | | search | search | query="search term" limit=10 tag="[]" score=0.1 copy=true | @sen diff --git a/docs4/todo.md b/docs4/todo.md index f98bb09a..938edcc1 100644 --- a/docs4/todo.md +++ b/docs4/todo.md @@ -5,5 +5,3 @@ 5. meta信息存在一个地方 6. 测试一个完整的Service client的框架,测试各种命令 7. config 默认改成default -8. todo reindex -9. \ No newline at end of file diff --git a/reme4/application.py b/reme4/application.py index b1d812e4..a06dadbd 100644 --- a/reme4/application.py +++ b/reme4/application.py @@ -19,6 +19,9 @@ class Application(BaseComponent): working_path = Path(self.config.working_dir).absolute() working_path.mkdir(parents=True, exist_ok=True) + (working_path / self.config.metadata_dir).mkdir(parents=True, exist_ok=True) + (working_path / self.config.daily_dir).mkdir(parents=True, exist_ok=True) + (working_path / self.config.knowledge_dir).mkdir(parents=True, exist_ok=True) if self.config.enable_logo: print_logo(self.config) @@ -168,5 +171,4 @@ class Application(BaseComponent): """Start the service and serve the application.""" if self.context.service is None: raise RuntimeError("Service not configured") - self.context.service.run_app(app=self) diff --git a/reme4/config/__init__.py b/reme4/config/__init__.py index b08107fe..c2903189 100644 --- a/reme4/config/__init__.py +++ b/reme4/config/__init__.py @@ -1,5 +1,8 @@ """Config""" -from .config_parser import parse_args +from .config_parser import parse_args, resolve_app_config -__all__ = ["parse_args"] +__all__ = [ + "parse_args", + "resolve_app_config", +] diff --git a/reme4/config/config_parser.py b/reme4/config/config_parser.py index 128ffe80..92fce784 100644 --- a/reme4/config/config_parser.py +++ b/reme4/config/config_parser.py @@ -167,11 +167,11 @@ def _strip_arg_dashes(arg: str) -> str: return arg -def parse_args(*args, **kwargs) -> tuple[str, dict]: - """Parse CLI args: first arg is action, rest are config overrides. +def parse_args(*args) -> tuple[str, dict]: + """Parse CLI args: first arg is action, rest are key=value pairs. Usage: reme app config=paw.yaml service.name=test - Returns: (action, merged_config_dict) + Returns: (action, parsed_kv_dict) """ if not args: raise ValueError("No arguments provided") @@ -180,25 +180,35 @@ def parse_args(*args, **kwargs) -> tuple[str, dict]: if "=" in first: raise ValueError(f"First argument must be action, got: {args[0]}") - action = first - configs: list[dict] = [] - explicit_config = False - + kvs: list[str] = [] for raw in args[1:]: arg = _strip_arg_dashes(raw) - if arg.startswith("config="): - 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 "=" in arg: + kvs.append(arg) - if not explicit_config and "default" in _CONFIG_REGISTRY: - from ..utils import get_logger + parsed = parse_dot_notation(kvs) if kvs else {} + return first, parsed - get_logger().info("No config specified, using 'default'") - configs.insert(0, _load_config("default")) + +def resolve_app_config(**kwargs) -> dict: + """Resolve full app-start config: load `config=path` file, fall back to + `default`, then deep-merge with the remaining kwargs as overrides. + """ + from ..utils import get_logger + + logger = get_logger() + configs: list[dict] = [] + + # `config=path` arrives as a string here; `config.foo=bar` arrives as a + # nested dict and is left in `kwargs` to be merged as a normal override. + config_value = kwargs.get("config") + if isinstance(config_value, str): + kwargs.pop("config") + logger.info(f"Loading config: {config_value}") + configs.append(_load_config(config_value)) + elif "default" in _CONFIG_REGISTRY: + logger.info("No config specified, loading 'default'") + configs.append(_load_config("default")) configs.append(kwargs) @@ -206,4 +216,4 @@ def parse_args(*args, **kwargs) -> tuple[str, dict]: for cfg in configs: merged = _deep_merge(merged, cfg) - return action, merged + return merged diff --git a/reme4/reme.py b/reme4/reme.py index cd23f0c5..a1dbf1df 100644 --- a/reme4/reme.py +++ b/reme4/reme.py @@ -5,28 +5,32 @@ import sys from .application import Application from .components import R -from .config import parse_args +from .config import parse_args, resolve_app_config from .enumeration import ComponentEnum from .utils import load_env -load_env() - class ReMe(Application): """ReMe memory management application.""" +async def call_server(action: str, **kwargs): + """Call the appropriate server component.""" + backend: str = kwargs.pop("backend", "http") + client_cls = R.get(ComponentEnum.CLIENT, backend) + async with client_cls(action=action, **kwargs) as client: + await client() + + def main(): """Parse CLI arguments and launch the appropriate mode.""" - action, config = parse_args(*sys.argv[1:]) + action, kwargs = parse_args(*sys.argv[1:]) if action == "start": - reme = ReMe(**config) - reme.run_app() + load_env() + kwargs = resolve_app_config(**kwargs) + ReMe(**kwargs).run_app() else: - backend: str = config.pop("backend", "http") - client_cls = R.get(ComponentEnum.CLIENT, backend) - client = client_cls(action=action, **config) - asyncio.run(client()) + asyncio.run(call_server(action, **kwargs)) if __name__ == "__main__": diff --git a/reme4/schema/application_config.py b/reme4/schema/application_config.py index 7b1bbda8..a1d78c5f 100644 --- a/reme4/schema/application_config.py +++ b/reme4/schema/application_config.py @@ -30,6 +30,8 @@ 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") + daily_dir: str = Field(default="daily", description="Subdirectory for daily memory") + knowledge_dir: str = Field(default="knowledge", description="Subdirectory for knowledge") 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") diff --git a/reme4/steps/common/__init__.py b/reme4/steps/common/__init__.py new file mode 100644 index 00000000..e69de29b