This commit is contained in:
jinli.yl 2026-05-17 00:26:50 +08:00
parent c583e06f3e
commit ba0900d60c
9 changed files with 57 additions and 49 deletions

View file

@ -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:

View file

@ -3,9 +3,10 @@
| 分类 | 能力 | 参数 |
|--------|---------|-----------------------------------------------------------|
| 通用 | help | |
| 通用 | start | |
| 通用 | start | 支持后台 |
| 通用 | restart | |
| 通用 | version | |
| 通用 | reindex | |
| search | search | query="search term" limit=10 tag="[]" score=0.1 copy=true |
@sen

View file

@ -5,5 +5,3 @@
5. meta信息存在一个地方
6. 测试一个完整的Service client的框架测试各种命令
7. config 默认改成default
8. todo reindex
9.

View file

@ -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)

View file

@ -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",
]

View file

@ -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

View file

@ -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__":

View file

@ -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")

View file