From b2f75d79e0bd79a9b41ca821440ab3ff85c1f80b Mon Sep 17 00:00:00 2001 From: weiyang Date: Wed, 12 Aug 2026 12:19:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(memory):=20update=20README=E3=80=81remove?= =?UTF-8?q?=20cli.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agentarts-memory-hermes/README.en.md | 36 +-- .../agentarts-memory-hermes/README.md | 33 +-- .../agentarts-memory-hermes/cli.py | 132 --------- tests/agentarts-memory-hermes/test_cli.py | 251 ------------------ 4 files changed, 20 insertions(+), 432 deletions(-) delete mode 100644 agentarts-memory-plugins/agentarts-memory-hermes/cli.py delete mode 100644 tests/agentarts-memory-hermes/test_cli.py diff --git a/agentarts-memory-plugins/agentarts-memory-hermes/README.en.md b/agentarts-memory-plugins/agentarts-memory-hermes/README.en.md index c36045e..e63fdd1 100644 --- a/agentarts-memory-plugins/agentarts-memory-hermes/README.en.md +++ b/agentarts-memory-plugins/agentarts-memory-hermes/README.en.md @@ -20,36 +20,20 @@ A Hermes Memory Provider plugin that uses Huawei Cloud AgentArts Memory as the l | Parameter | Description | |---------------------------------|------------------------------------| | `AGENTARTS_MEMORY_SPACE_ID` | AgentArts memory space ID | -| `HUAWEICLOUD_SDK_MEMORY_API_KEY`| AgentArts memory space API Key | +| `HUAWEICLOUD_SDK_MEMORY_API_KEY`| AgentArts memory space API Key | | `HUAWEICLOUD_SDK_REGION` | Region (default `cn-southwest-2`) | ## Installation -There are two installation methods — choose either one. - -### Option 1: Install as a memory provider - -Copy the plugin directory to Hermes' memory provider plugin path: +Copy the plugin directory `agentarts-memory-hermes` to Hermes' memory provider plugin path and rename it to `agentarts-memory`: ```bash -cp -r agentarts-memory-hermes ~/.hermes/hermes-agent/plugins/memory/ +cp -r agentarts-memory-hermes ~/.hermes/hermes-agent/plugins/memory/agentarts-memory ``` -Configure interactively via `hermes memory setup`, or manually set the environment variables above. Follow the prompts to select `agentarts_memory` and complete configuration. - -### Option 2: Install as a general plugin - -Copy the plugin directory to Hermes' general plugin path and register via the `hermes plugins` command: - -```bash -cp -r agentarts-memory-hermes ~/.hermes/plugins/ -``` - -Configure interactively via `hermes plugins`, or manually set the environment variables above. Follow the prompts to select `agentarts_memory` and complete configuration. - ## Configuration -During configuration, you will be prompted to enter the API Key, Space ID, etc. Sensitive fields (API Key) are written to `.env`, while non-sensitive config (`space_id`, `region`) is written to `$HERMES_HOME/agentarts.json`. +Configure interactively via the `hermes memory setup` command. Follow the prompts to select `agentarts-memory`, then enter the correct parameters to complete configuration. ## Tools @@ -74,11 +58,11 @@ Get a list of AgentArts memory summaries. After the plugin is registered, the following CLI subcommands are available (only when the provider is active): -| Command | Description | -|---|---| -| `hermes agentarts_memory status` | Show provider status and environment variable configuration | -| `hermes agentarts_memory config` | Show saved non-sensitive configuration | -| `hermes agentarts_memory test` | Test provider connectivity | +| Command | Description | +|------------------------|----------------------------| +| `hermes memory status` | Show provider status | +| `hermes memory setup` | Configure the provider | +| `hermes memory off` | Disable the provider | ## Architecture @@ -116,5 +100,5 @@ Check: ```bash pip install -e ".[dev]" -pytest tests/unit/ -v +pytest tests/agentarts-memory-hermes/ -v ``` diff --git a/agentarts-memory-plugins/agentarts-memory-hermes/README.md b/agentarts-memory-plugins/agentarts-memory-hermes/README.md index c1e8f1a..f66ba82 100644 --- a/agentarts-memory-plugins/agentarts-memory-hermes/README.md +++ b/agentarts-memory-plugins/agentarts-memory-hermes/README.md @@ -26,30 +26,16 @@ Hermes Memory Provider 插件,将华为云 AgentArts Memory 作为 Hermes Agen ## 安装 -有两种安装方式,任选其一。 - -### 方式一:作为 memory provider 安装 - -将插件目录复制到 Hermes 的 memory provider 插件路径: - -```bash -cp -r agentarts-memory-hermes ~/.hermes/hermes-agent/plugins/memory/ -``` -通过 `hermes memory setup` 交互式配置,或手动设置上述环境变量。 按提示选择 `agentarts_memory` 并完成配置。 - -### 方式二:作为通用插件安装 - -将插件目录复制到 Hermes 的通用插件路径,通过 `hermes plugins` 命令注册: +将插件目录agentarts-memory-hermes复制到 Hermes 的 memory provider 插件路径并重命名为agentarts-memory: ```bash -cp -r agentarts-memory-hermes ~/.hermes/plugins/ +cp -r agentarts-memory-hermes ~/.hermes/hermes-agent/plugins/memory/agentarts-memory ``` -通过 `hermes plugins` 交互式配置,或手动设置上述环境变量。 按提示选择 `agentarts_memory` 并完成配置。 ## 配置 -配置过程中会提示输入 API Key、Space ID 等。敏感字段(API Key)写入 `.env`,非敏感配置(`space_id`、`region`)写入 `$HERMES_HOME/agentarts.json`。 +通过命令 `hermes memory setup` 进行交互式配置,按提示选择 `agentarts-memory` 后,输入正确的参数完成配置。 ## 工具说明 @@ -74,11 +60,12 @@ cp -r agentarts-memory-hermes ~/.hermes/plugins/ 插件注册后提供以下 CLI 子命令(仅在 provider 活跃时可用): -| 命令 | 说明 | -|---|---| -| `hermes agentarts_memory status` | 显示 provider 状态和环境变量配置 | -| `hermes agentarts_memory config` | 显示已保存的非敏感配置 | -| `hermes agentarts_memory test` | 测试 provider 连通性 | +| 命令 | 说明 | +|-------------------------|------------------| +| `hermes memory status` | 查看 provider 状态 | +| `hermes memory setup` | 配置 provider | +| `hermes memory off` | 禁用 provider | + ## 架构说明 @@ -116,5 +103,5 @@ AgentArts Memory 从对话消息生成记忆需要时间(约 30 秒)。`sync ```bash pip install -e ".[dev]" -pytest tests/unit/ -v +pytest tests/agentarts-memory-hermes/ -v ``` diff --git a/agentarts-memory-plugins/agentarts-memory-hermes/cli.py b/agentarts-memory-plugins/agentarts-memory-hermes/cli.py deleted file mode 100644 index 022a636..0000000 --- a/agentarts-memory-plugins/agentarts-memory-hermes/cli.py +++ /dev/null @@ -1,132 +0,0 @@ -"""CLI subcommands for the agentarts_memory provider. - -This module registers ``hermes agentarts_memory `` commands via -the ``register_cli(subparser)`` convention. The commands are only available -when this provider is the active ``memory.provider`` in Hermes configuration. -""" - -import json -import os - -from provider import ( - ENV_API_KEY, - ENV_REGION, - ENV_SPACE_ID, - AgentArtsMemoryProvider, -) - - -def _status(args) -> None: - """Show provider status: availability, env vars, and active config.""" - provider = AgentArtsMemoryProvider() - available = provider.is_available() - - print("AgentArts Memory Provider — Status") - print(f" Provider name : {provider.name}") - print(f" Available : {'yes' if available else 'no'}") - print() - print("Environment variables:") - - env_info = [ - ("HUAWEICLOUD_SDK_MEMORY_API_KEY", ENV_API_KEY, False), - ("AGENTARTS_MEMORY_SPACE_ID", ENV_SPACE_ID, False), - ("HUAWEICLOUD_SDK_REGION", ENV_REGION, True), - ] - - all_set = True - for display_name, env_var, optional in env_info: - value = os.getenv(env_var) - if value: - if optional: - print(f" {display_name} = {value}") - else: - print(f" {display_name} = {'*' * min(len(value), 8)}…({len(value)} chars)") - else: - label = "(optional)" if optional else "(MISSING)" - print(f" {display_name} = {label}") - if not optional: - all_set = False - - if not all_set: - print() - print("Warning: Some required environment variables are not set.") - print("Run 'hermes memory setup' to configure the provider.") - - -def _config(args) -> None: - """Show the non-secret configuration saved to agentarts.json.""" - hermes_home = os.environ.get("HERMES_HOME", "") - if not hermes_home: - print("HERMES_HOME is not set. Cannot locate configuration file.") - print("Run this command from within a Hermes session context.") - return - - config_path = os.path.join(hermes_home, "agentarts.json") - if not os.path.exists(config_path): - print(f"No configuration file found at: {config_path}") - print("Run 'hermes memory setup' to configure the provider.") - return - - with open(config_path, encoding="utf-8") as f: - data = json.load(f) - - print("AgentArts Memory Provider — Configuration") - print(f" Config file: {config_path}") - print() - if data: - for key, value in data.items(): - print(f" {key} = {value}") - else: - print(" (empty — no non-secret values saved)") - - -def _test_connection(args) -> None: - """Test connectivity by initializing the provider and checking the client.""" - provider = AgentArtsMemoryProvider() - - if not provider.is_available(): - print("AgentArts Memory Provider — Connection Test: FAILED") - print("Required environment variables are not set.") - print("Run 'hermes memory setup' to configure the provider.") - return - - print("AgentArts Memory Provider — Connection Test") - print(" Environment variables: OK") - print(" Initializing MemoryClient...") - - try: - provider.initialize("cli-test-session", hermes_home=os.environ.get("HERMES_HOME", "")) - print(" MemoryClient initialized: OK") - print(" Memory session created: OK") - print() - print("Connection test: PASSED") - provider.shutdown() - except Exception as e: - print(f" Error: {e}") - print() - print("Connection test: FAILED") - - -def _handle_command(args) -> None: - """Dispatch to the appropriate subcommand handler.""" - sub = getattr(args, "agentarts_memory_command", None) - if sub == "status": - _status(args) - elif sub == "config": - _config(args) - elif sub == "test": - _test_connection(args) - else: - print("Usage: hermes agentarts_memory ") - - -def register_cli(subparser) -> None: - """Build the argparse tree for 'hermes agentarts_memory' commands. - - Called by discover_plugin_cli_commands() during argparse initialization. - """ - subs = subparser.add_subparsers(dest="agentarts_memory_command") - subs.add_parser("status", help="Show provider status and environment variables") - subs.add_parser("config", help="Show saved non-secret configuration") - subs.add_parser("test", help="Test provider connectivity") - subparser.set_defaults(func=_handle_command) diff --git a/tests/agentarts-memory-hermes/test_cli.py b/tests/agentarts-memory-hermes/test_cli.py deleted file mode 100644 index d0e0b5b..0000000 --- a/tests/agentarts-memory-hermes/test_cli.py +++ /dev/null @@ -1,251 +0,0 @@ -"""Tests for the agentarts_memory CLI subcommands.""" - -import argparse -import json -from unittest.mock import MagicMock, patch - -import pytest -from cli import _config, _handle_command, _status, _test_connection, register_cli -from provider import AgentArtsMemoryProvider - -ENV_VARS = { - "HUAWEICLOUD_SDK_MEMORY_API_KEY": "test-api-key", - "HUAWEICLOUD_SDK_REGION": "cn-southwest-2", - "AGENTARTS_MEMORY_SPACE_ID": "test-space-id", -} - - -@pytest.fixture -def env_vars(monkeypatch): - for key, val in ENV_VARS.items(): - monkeypatch.setenv(key, val) - - -@pytest.fixture -def no_env_vars(monkeypatch): - for key in ENV_VARS: - monkeypatch.delenv(key, raising=False) - - -# ── register_cli ── - - -class TestRegisterCli: - def test_creates_subcommands(self): - parser = argparse.ArgumentParser(prog="hermes") - subparser = parser.add_subparsers(dest="agentarts_memory") - agentarts_parser = subparser.add_parser("agentarts_memory") - register_cli(agentarts_parser) - - args = parser.parse_args(["agentarts_memory", "status"]) - assert args.agentarts_memory_command == "status" - - args = parser.parse_args(["agentarts_memory", "config"]) - assert args.agentarts_memory_command == "config" - - args = parser.parse_args(["agentarts_memory", "test"]) - assert args.agentarts_memory_command == "test" - - def test_sets_default_func(self): - parser = argparse.ArgumentParser(prog="hermes") - subparser = parser.add_subparsers(dest="agentarts_memory") - agentarts_parser = subparser.add_parser("agentarts_memory") - register_cli(agentarts_parser) - - args = parser.parse_args(["agentarts_memory", "status"]) - assert hasattr(args, "func") - assert callable(args.func) - - def test_no_subcommand(self): - parser = argparse.ArgumentParser(prog="hermes") - subparser = parser.add_subparsers(dest="agentarts_memory") - agentarts_parser = subparser.add_parser("agentarts_memory") - register_cli(agentarts_parser) - - args = parser.parse_args(["agentarts_memory"]) - assert args.agentarts_memory_command is None - - def test_func_is_handle_command(self): - parser = argparse.ArgumentParser(prog="hermes") - subparser = parser.add_subparsers(dest="agentarts_memory") - agentarts_parser = subparser.add_parser("agentarts_memory") - register_cli(agentarts_parser) - - args = parser.parse_args(["agentarts_memory", "status"]) - assert args.func is _handle_command - - -# ── _handle_command dispatch ── - - -class TestHandleCommandDispatch: - def test_dispatches_status(self, env_vars, capsys): - args = argparse.Namespace(agentarts_memory_command="status") - _handle_command(args) - out = capsys.readouterr().out - assert "Status" in out - - def test_dispatches_config(self, env_vars, capsys, tmp_path, monkeypatch): - config_path = tmp_path / "agentarts.json" - config_path.write_text(json.dumps({"space_id": "s1", "region": "r1"}), encoding="utf-8") - monkeypatch.setenv("HERMES_HOME", str(tmp_path)) - - args = argparse.Namespace(agentarts_memory_command="config") - _handle_command(args) - out = capsys.readouterr().out - assert "Configuration" in out - assert "s1" in out - - def test_dispatches_test(self, env_vars, capsys): - mock_provider = MagicMock(spec=AgentArtsMemoryProvider) - mock_provider.is_available.return_value = True - - with patch("cli.AgentArtsMemoryProvider", return_value=mock_provider): - args = argparse.Namespace(agentarts_memory_command="test") - _handle_command(args) - out = capsys.readouterr().out - assert "PASSED" in out - mock_provider.initialize.assert_called_once() - mock_provider.shutdown.assert_called_once() - - def test_no_subcommand_prints_usage(self, capsys): - args = argparse.Namespace(agentarts_memory_command=None) - _handle_command(args) - out = capsys.readouterr().out - assert "Usage" in out - assert "status" in out - assert "config" in out - assert "test" in out - - -# ── _status ── - - -class TestStatusCommand: - def test_shows_available_when_env_set(self, env_vars, capsys): - _status(argparse.Namespace()) - out = capsys.readouterr().out - assert "Available" in out - assert "yes" in out - - def test_shows_unavailable_when_env_missing(self, no_env_vars, capsys): - _status(argparse.Namespace()) - out = capsys.readouterr().out - assert "Available" in out - assert "no" in out - - def test_lists_all_env_vars(self, env_vars, capsys): - _status(argparse.Namespace()) - out = capsys.readouterr().out - assert "HUAWEICLOUD_SDK_MEMORY_API_KEY" in out - assert "AGENTARTS_MEMORY_SPACE_ID" in out - assert "HUAWEICLOUD_SDK_REGION" in out - - def test_shows_warning_for_missing_vars(self, no_env_vars, capsys): - _status(argparse.Namespace()) - out = capsys.readouterr().out - assert "MISSING" in out - assert "Warning" in out - - def test_masks_secret_values(self, env_vars, capsys): - _status(argparse.Namespace()) - out = capsys.readouterr().out - assert "test-api-key" not in out - - def test_shows_region_in_plaintext(self, env_vars, capsys): - _status(argparse.Namespace()) - out = capsys.readouterr().out - assert "cn-southwest-2" in out - - def test_missing_optional_var_shows_optional_label(self, monkeypatch, capsys): - for key, val in ENV_VARS.items(): - if key == "HUAWEICLOUD_SDK_REGION": - continue - monkeypatch.setenv(key, val) - monkeypatch.delenv("HUAWEICLOUD_SDK_REGION", raising=False) - - _status(argparse.Namespace()) - out = capsys.readouterr().out - assert "optional" in out - - -# ── _config ── - - -class TestConfigCommand: - def test_shows_config_file_content(self, env_vars, tmp_path, monkeypatch, capsys): - config_path = tmp_path / "agentarts.json" - config_path.write_text( - json.dumps({"space_id": "space-xyz", "region": "cn-north-4"}), encoding="utf-8" - ) - monkeypatch.setenv("HERMES_HOME", str(tmp_path)) - - _config(argparse.Namespace()) - out = capsys.readouterr().out - assert "Configuration" in out - assert "space-xyz" in out - assert "cn-north-4" in out - - def test_no_hermes_home(self, monkeypatch, capsys): - monkeypatch.delenv("HERMES_HOME", raising=False) - _config(argparse.Namespace()) - out = capsys.readouterr().out - assert "HERMES_HOME is not set" in out - - def test_no_config_file(self, tmp_path, monkeypatch, capsys): - monkeypatch.setenv("HERMES_HOME", str(tmp_path)) - _config(argparse.Namespace()) - out = capsys.readouterr().out - assert "No configuration file found" in out - - def test_empty_config_file(self, tmp_path, monkeypatch, capsys): - config_path = tmp_path / "agentarts.json" - config_path.write_text("{}", encoding="utf-8") - monkeypatch.setenv("HERMES_HOME", str(tmp_path)) - - _config(argparse.Namespace()) - out = capsys.readouterr().out - assert "empty" in out - - -# ── _test_connection ── - - -class TestConnectionCommand: - def test_passes_when_available(self, env_vars, capsys): - mock_provider = MagicMock(spec=AgentArtsMemoryProvider) - mock_provider.is_available.return_value = True - - with patch("cli.AgentArtsMemoryProvider", return_value=mock_provider): - _test_connection(argparse.Namespace()) - out = capsys.readouterr().out - assert "PASSED" in out - assert "OK" in out - mock_provider.initialize.assert_called_once() - mock_provider.shutdown.assert_called_once() - - def test_fails_when_not_available(self, no_env_vars, capsys): - _test_connection(argparse.Namespace()) - out = capsys.readouterr().out - assert "FAILED" in out - assert "not set" in out - - def test_fails_on_init_error(self, env_vars, capsys): - mock_provider = MagicMock(spec=AgentArtsMemoryProvider) - mock_provider.is_available.return_value = True - mock_provider.initialize.side_effect = RuntimeError("connection refused") - - with patch("cli.AgentArtsMemoryProvider", return_value=mock_provider): - _test_connection(argparse.Namespace()) - out = capsys.readouterr().out - assert "FAILED" in out - assert "connection refused" in out - - def test_calls_shutdown_even_on_error(self, env_vars, capsys): - mock_provider = MagicMock(spec=AgentArtsMemoryProvider) - mock_provider.is_available.return_value = True - mock_provider.initialize.side_effect = RuntimeError("init failed") - - with patch("cli.AgentArtsMemoryProvider", return_value=mock_provider): - _test_connection(argparse.Namespace()) - mock_provider.initialize.assert_called_once()