hermes-hy-memory 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hermes_hy_memory-0.1.2/PKG-INFO +223 -0
- hermes_hy_memory-0.1.2/README.md +201 -0
- hermes_hy_memory-0.1.2/__init__.py +15 -0
- hermes_hy_memory-0.1.2/cli.py +259 -0
- hermes_hy_memory-0.1.2/hermes_hy_memory.egg-info/PKG-INFO +223 -0
- hermes_hy_memory-0.1.2/hermes_hy_memory.egg-info/SOURCES.txt +17 -0
- hermes_hy_memory-0.1.2/hermes_hy_memory.egg-info/dependency_links.txt +1 -0
- hermes_hy_memory-0.1.2/hermes_hy_memory.egg-info/entry_points.txt +5 -0
- hermes_hy_memory-0.1.2/hermes_hy_memory.egg-info/requires.txt +5 -0
- hermes_hy_memory-0.1.2/hermes_hy_memory.egg-info/top_level.txt +1 -0
- hermes_hy_memory-0.1.2/plugin.yaml +50 -0
- hermes_hy_memory-0.1.2/provider.py +631 -0
- hermes_hy_memory-0.1.2/pyproject.toml +71 -0
- hermes_hy_memory-0.1.2/setup.cfg +4 -0
- hermes_hy_memory-0.1.2/tests/test_provider.py +576 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hermes-hy-memory
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: HY Memory provider plugin for Hermes Agent (native, 100% passive injection)
|
|
5
|
+
Author: alvinfei
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: hermes,memory,llm,agent,plugin
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: hy-memory>=1.2.17
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# HY Memory Provider for Hermes
|
|
24
|
+
|
|
25
|
+
Hermes Agent 的原生 Memory Provider 插件 —— **第一梯队 100% 被动注入**。
|
|
26
|
+
|
|
27
|
+
## 工作原理
|
|
28
|
+
|
|
29
|
+
Hermes 框架在每次 LLM 调用前自动触发 `prefetch(query)`,本 Provider:
|
|
30
|
+
1. 用 query 在 HY Memory 里搜(chat 链路三路召回)
|
|
31
|
+
2. 把命中结果按 layer 格式化(`§ [profile]` / `§ [intent]` / `§`)
|
|
32
|
+
3. Hermes 把这块文本注入 system prompt
|
|
33
|
+
|
|
34
|
+
**用户无需任何额外操作**,记忆自动工作。
|
|
35
|
+
|
|
36
|
+
每轮对话结束 Hermes 调 `sync_turn(user, assistant)`,Provider 提交到内部线程池异步写入,不阻塞主流程。
|
|
37
|
+
|
|
38
|
+
## 安装
|
|
39
|
+
|
|
40
|
+
> SDK 包 `hy-memory` 已发布到**公网 PyPI**(pypi.org),直接 `pip install` 即可,无需任何额外源或凭证。
|
|
41
|
+
|
|
42
|
+
> **chroma 后端需 `sqlite3 >= 3.35`**(默认 `MEMORY_VECTOR_STORE=chroma`)。系统 sqlite 过低
|
|
43
|
+
> (旧版 CentOS/Linux 会报 `unsupported version of sqlite3`)时:`pip install pysqlite3-binary`
|
|
44
|
+
> 并在进程入口 swap(`import sys; sys.modules["sqlite3"]=__import__("pysqlite3")`),
|
|
45
|
+
> 或改用 `MEMORY_VECTOR_STORE=qdrant` / `faiss`。
|
|
46
|
+
|
|
47
|
+
### 方式 A:通过 `hermes memory setup`(推荐)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
hermes memory setup hy-memory
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Hermes 会按 `plugin.yaml` 的 `pip_dependencies` 自动安装 `hy-memory`,并启动配置向导询问 `HY_MEMORY_USER_ID` / `HY_MEMORY_AGENT_ID` / mode。
|
|
54
|
+
|
|
55
|
+
### 方式 B:手动 pip install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 装这个 plugin(自动拉 hy-memory 作为依赖)
|
|
59
|
+
pip install hermes-hy-memory
|
|
60
|
+
|
|
61
|
+
# 让 Hermes 启用
|
|
62
|
+
hermes config set memory.provider hy-memory
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 方式 C:folder-drop(开发调试)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cp -r plugins/native/hermes ~/.hermes/plugins/memory/hy-memory
|
|
69
|
+
pip install "hy-memory>=1.2.17"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 内网安装(腾讯内部,可选)
|
|
73
|
+
|
|
74
|
+
腾讯内网用内部包 `hy-memory-internal`(与公网 `hy-memory` 同源,import 同为 `hy_memory`),
|
|
75
|
+
通过腾讯镜像源安装,无需凭证:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install "hy-memory-internal>=0.1.5.14" --index-url https://mirrors.tencent.com/pypi/simple/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 配置
|
|
82
|
+
|
|
83
|
+
### 环境变量
|
|
84
|
+
|
|
85
|
+
| 变量 | 必填 | 默认 | 说明 |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `HY_MEMORY_USER_ID` | ✅ | — | 一级隔离 key(你的记忆命名空间) |
|
|
88
|
+
| `HY_MEMORY_AGENT_ID` | ❌ | `hermes` | 二级隔离 key |
|
|
89
|
+
| `HY_MEMORY_MODE` | ❌ | `pro` | 处理模式:`lite` / `pro` / `ultra` |
|
|
90
|
+
| `HY_MEMORY_PREFETCH_MAX_CHARS` | ❌ | `2000` | prefetch 注入文本总字符数上限 |
|
|
91
|
+
| `HY_MEMORY_SYNC_WORKERS` | ❌ | `2` | sync_turn 后台线程池大小 |
|
|
92
|
+
| `HY_MEMORY_SHUTDOWN_GRACE_SEC` | ❌ | `10` | shutdown 等 in-flight 写入的最长秒数 |
|
|
93
|
+
| `OPENAI_API_KEY`(或对应 LLM/embedder key) | ✅* | — | `pro`/`ultra` 必须;`lite` 仅 embed 用得到 |
|
|
94
|
+
|
|
95
|
+
*HY Memory SDK 自身的 LLM/Embedder 配置(详见 `hy-memory-internal` 文档)。
|
|
96
|
+
|
|
97
|
+
### `~/.hermes/config.yaml`
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
memory:
|
|
101
|
+
provider: hy-memory
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
环境变量优先级最高,可在 `~/.hermes/.env` 里持久化(`hermes memory setup` 自动写)。
|
|
105
|
+
|
|
106
|
+
## 处理模式(`HY_MEMORY_MODE`)
|
|
107
|
+
|
|
108
|
+
| 模式 | 写入流程 | 速度 | 召回质量 |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| `lite` | 纯 embedding 写入(不调 LLM) | 最快 | 仅向量相似 |
|
|
111
|
+
| `pro`(默认) | LLM 抽取事实/身份 + reconcile 演化 | 中 | 含画像 + 事实层 |
|
|
112
|
+
| `ultra` | pro + System2 异步认知(Schema/Intention) | 最完整 | + 跨域归纳 + 前瞻意图 |
|
|
113
|
+
|
|
114
|
+
> ⚠️ **`lite` 模式不适用于 Hermes 被动注入 / 检索。** lite 只做 embedding、不经 LLM 抽取,
|
|
115
|
+
> 记忆停留在 `L1_RAW` 层;而 SDK 的 `list` / `search` 会过滤掉 `L1_RAW`,因此 lite 写入的记忆
|
|
116
|
+
> **无法被 `prefetch` 召回**(表现为写入成功但检索恒为空)。Hermes 集成请使用 `pro`(默认)或
|
|
117
|
+
> `ultra`;`lite` 仅适合“只写、不依赖语义召回”的特殊场景。
|
|
118
|
+
|
|
119
|
+
## CLI
|
|
120
|
+
|
|
121
|
+
`pip install` 后可用 `hermes-hy-memory` 子命令做体检和手动操作(脱离 Hermes 主进程):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# 体检:env 齐 / Client 能否构造 / list 能否跑通(只读)
|
|
125
|
+
hermes-hy-memory doctor
|
|
126
|
+
|
|
127
|
+
# 手动写入
|
|
128
|
+
hermes-hy-memory add "我喜欢 K-Pop 但更喜欢 Jazz"
|
|
129
|
+
|
|
130
|
+
# 手动搜索
|
|
131
|
+
hermes-hy-memory search "音乐口味" --limit 5
|
|
132
|
+
|
|
133
|
+
# 列出最近 20 条
|
|
134
|
+
hermes-hy-memory list
|
|
135
|
+
|
|
136
|
+
# 跨用户测试(覆盖 env)
|
|
137
|
+
hermes-hy-memory search "x" --user-id other_user --agent-id test
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
被 Hermes 主 CLI 加载后也可:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
hermes hy-memory doctor
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Hooks
|
|
147
|
+
|
|
148
|
+
| Hook | 触发时机 | 行为 |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `prefetch(query)` | 每次 LLM 调用前 | 搜记忆 → 注入 system prompt |
|
|
151
|
+
| `sync_turn(user, ast)` | 每轮对话结束 | 提交线程池异步写入 |
|
|
152
|
+
| `on_session_end(msgs)` | 会话结束 | 等 in-flight 完成 + 尾部补提取 |
|
|
153
|
+
| `on_pre_compress(msgs)` | 上下文压缩前 | 同 `on_session_end`,保住即将被裁的内容 |
|
|
154
|
+
| `on_memory_write(action, target, content)` | Hermes 内置 memory 命令 | `add` 同步到 HY Memory;`delete` 跳过(target ID 不互通) |
|
|
155
|
+
|
|
156
|
+
## Tools(LLM 主动调用,可选)
|
|
157
|
+
|
|
158
|
+
| Tool | 用途 |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `memory_search(query, limit)` | 搜索记忆 |
|
|
161
|
+
| `memory_add(content)` | 写入一条记忆 |
|
|
162
|
+
| `memory_delete(memory_id)` | 删除一条记忆 |
|
|
163
|
+
| `memory_list(limit)` | 列出当前 user/agent 的记忆 |
|
|
164
|
+
|
|
165
|
+
即使关掉 tools,prefetch 的被动注入也保证每次 LLM 调用都能看到相关记忆。
|
|
166
|
+
|
|
167
|
+
## 故障排查
|
|
168
|
+
|
|
169
|
+
### `provider not initialized` 或所有 hook 静默 no-op
|
|
170
|
+
|
|
171
|
+
跑一遍体检:
|
|
172
|
+
```bash
|
|
173
|
+
hermes-hy-memory doctor
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
常见原因:
|
|
177
|
+
1. `HY_MEMORY_USER_ID` 没设
|
|
178
|
+
2. embed/LLM key 没设(`OPENAI_API_KEY` 等)→ `HyMemoryClient(mode="pro")` 构造失败
|
|
179
|
+
3. SDK 没装:`pip install hy-memory`(内网:`pip install hy-memory-internal --index-url https://mirrors.tencent.com/pypi/simple/`)
|
|
180
|
+
|
|
181
|
+
### prefetch 没注入任何记忆
|
|
182
|
+
|
|
183
|
+
- 库里**真没相关记忆** —— 用 `hermes-hy-memory list` 确认
|
|
184
|
+
- query 太短(< 3 字符)或在跳过列表里(`ok`/`好的`/`thanks` 等)—— 设计如此
|
|
185
|
+
- 中文 query 命中率低?SDK ≥ 0.1.5.11.dev2 已修过 BM25 中文分词不对称的 bug,确认版本
|
|
186
|
+
|
|
187
|
+
### sync_turn 似乎没写入
|
|
188
|
+
|
|
189
|
+
- 默认 daemon 线程,主进程退出会被 kill。生产部署用 `hermes` daemon 模式
|
|
190
|
+
- 提高 `HY_MEMORY_SHUTDOWN_GRACE_SEC` 让 shutdown 多等几秒
|
|
191
|
+
- 检查日志 `[hermes] sync_turn failed: ...`
|
|
192
|
+
|
|
193
|
+
### `cross-loop` 错误(多 client 场景)
|
|
194
|
+
|
|
195
|
+
如果你的 Hermes 部署同时跑了多个 `HyMemoryClient` 实例(比如多租户 server),需要 SDK ≥ 0.1.5.11.dev2 + 用 `SharedRuntime`:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from hy_memory import HyMemoryClient, SharedRuntime
|
|
199
|
+
runtime = await SharedRuntime.create(base_config)
|
|
200
|
+
client = HyMemoryClient(cfg, runtime=runtime)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
本 plugin 单 Hermes 进程默认走 solo mode 的 client,不需要这个。
|
|
204
|
+
|
|
205
|
+
## 与 Mem0 风格的对比
|
|
206
|
+
|
|
207
|
+
[Mem0 的 Hermes 集成](https://github.com/mem0ai/mem0)依赖 **TypeScript SDK**,本插件用 **Python SDK**。HY Memory 提供 lite/pro/ultra 三档处理深度(lite 不调 LLM,pro 标准抽取,ultra 加 System2 认知);Mem0 是单档 LLM 抽取。
|
|
208
|
+
|
|
209
|
+
## 开发
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
cd plugins/native/hermes
|
|
213
|
+
python -m pytest tests/ -v
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
测试 mock 掉 `HyMemoryClient`,无外部依赖(不需要 OPENAI_API_KEY、不需要起 Qdrant)。
|
|
217
|
+
|
|
218
|
+
## 版本
|
|
219
|
+
|
|
220
|
+
| Plugin | SDK 依赖 | 备注 |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| 0.1.2 | `hy-memory>=1.2.17`(内网 `hy-memory-internal>=0.1.5.14`) | 发布到公网 PyPI(`hermes-hy-memory`);channel-dict flatten 修复 |
|
|
223
|
+
| 0.1.0 | `hy-memory-internal>=0.1.5.11.dev2` | 含 SharedRuntime / BM25 中文修复 / Chroma keyword_search / stdout 日志 |
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# HY Memory Provider for Hermes
|
|
2
|
+
|
|
3
|
+
Hermes Agent 的原生 Memory Provider 插件 —— **第一梯队 100% 被动注入**。
|
|
4
|
+
|
|
5
|
+
## 工作原理
|
|
6
|
+
|
|
7
|
+
Hermes 框架在每次 LLM 调用前自动触发 `prefetch(query)`,本 Provider:
|
|
8
|
+
1. 用 query 在 HY Memory 里搜(chat 链路三路召回)
|
|
9
|
+
2. 把命中结果按 layer 格式化(`§ [profile]` / `§ [intent]` / `§`)
|
|
10
|
+
3. Hermes 把这块文本注入 system prompt
|
|
11
|
+
|
|
12
|
+
**用户无需任何额外操作**,记忆自动工作。
|
|
13
|
+
|
|
14
|
+
每轮对话结束 Hermes 调 `sync_turn(user, assistant)`,Provider 提交到内部线程池异步写入,不阻塞主流程。
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
> SDK 包 `hy-memory` 已发布到**公网 PyPI**(pypi.org),直接 `pip install` 即可,无需任何额外源或凭证。
|
|
19
|
+
|
|
20
|
+
> **chroma 后端需 `sqlite3 >= 3.35`**(默认 `MEMORY_VECTOR_STORE=chroma`)。系统 sqlite 过低
|
|
21
|
+
> (旧版 CentOS/Linux 会报 `unsupported version of sqlite3`)时:`pip install pysqlite3-binary`
|
|
22
|
+
> 并在进程入口 swap(`import sys; sys.modules["sqlite3"]=__import__("pysqlite3")`),
|
|
23
|
+
> 或改用 `MEMORY_VECTOR_STORE=qdrant` / `faiss`。
|
|
24
|
+
|
|
25
|
+
### 方式 A:通过 `hermes memory setup`(推荐)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
hermes memory setup hy-memory
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Hermes 会按 `plugin.yaml` 的 `pip_dependencies` 自动安装 `hy-memory`,并启动配置向导询问 `HY_MEMORY_USER_ID` / `HY_MEMORY_AGENT_ID` / mode。
|
|
32
|
+
|
|
33
|
+
### 方式 B:手动 pip install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# 装这个 plugin(自动拉 hy-memory 作为依赖)
|
|
37
|
+
pip install hermes-hy-memory
|
|
38
|
+
|
|
39
|
+
# 让 Hermes 启用
|
|
40
|
+
hermes config set memory.provider hy-memory
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 方式 C:folder-drop(开发调试)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cp -r plugins/native/hermes ~/.hermes/plugins/memory/hy-memory
|
|
47
|
+
pip install "hy-memory>=1.2.17"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 内网安装(腾讯内部,可选)
|
|
51
|
+
|
|
52
|
+
腾讯内网用内部包 `hy-memory-internal`(与公网 `hy-memory` 同源,import 同为 `hy_memory`),
|
|
53
|
+
通过腾讯镜像源安装,无需凭证:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install "hy-memory-internal>=0.1.5.14" --index-url https://mirrors.tencent.com/pypi/simple/
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 配置
|
|
60
|
+
|
|
61
|
+
### 环境变量
|
|
62
|
+
|
|
63
|
+
| 变量 | 必填 | 默认 | 说明 |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| `HY_MEMORY_USER_ID` | ✅ | — | 一级隔离 key(你的记忆命名空间) |
|
|
66
|
+
| `HY_MEMORY_AGENT_ID` | ❌ | `hermes` | 二级隔离 key |
|
|
67
|
+
| `HY_MEMORY_MODE` | ❌ | `pro` | 处理模式:`lite` / `pro` / `ultra` |
|
|
68
|
+
| `HY_MEMORY_PREFETCH_MAX_CHARS` | ❌ | `2000` | prefetch 注入文本总字符数上限 |
|
|
69
|
+
| `HY_MEMORY_SYNC_WORKERS` | ❌ | `2` | sync_turn 后台线程池大小 |
|
|
70
|
+
| `HY_MEMORY_SHUTDOWN_GRACE_SEC` | ❌ | `10` | shutdown 等 in-flight 写入的最长秒数 |
|
|
71
|
+
| `OPENAI_API_KEY`(或对应 LLM/embedder key) | ✅* | — | `pro`/`ultra` 必须;`lite` 仅 embed 用得到 |
|
|
72
|
+
|
|
73
|
+
*HY Memory SDK 自身的 LLM/Embedder 配置(详见 `hy-memory-internal` 文档)。
|
|
74
|
+
|
|
75
|
+
### `~/.hermes/config.yaml`
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
memory:
|
|
79
|
+
provider: hy-memory
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
环境变量优先级最高,可在 `~/.hermes/.env` 里持久化(`hermes memory setup` 自动写)。
|
|
83
|
+
|
|
84
|
+
## 处理模式(`HY_MEMORY_MODE`)
|
|
85
|
+
|
|
86
|
+
| 模式 | 写入流程 | 速度 | 召回质量 |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `lite` | 纯 embedding 写入(不调 LLM) | 最快 | 仅向量相似 |
|
|
89
|
+
| `pro`(默认) | LLM 抽取事实/身份 + reconcile 演化 | 中 | 含画像 + 事实层 |
|
|
90
|
+
| `ultra` | pro + System2 异步认知(Schema/Intention) | 最完整 | + 跨域归纳 + 前瞻意图 |
|
|
91
|
+
|
|
92
|
+
> ⚠️ **`lite` 模式不适用于 Hermes 被动注入 / 检索。** lite 只做 embedding、不经 LLM 抽取,
|
|
93
|
+
> 记忆停留在 `L1_RAW` 层;而 SDK 的 `list` / `search` 会过滤掉 `L1_RAW`,因此 lite 写入的记忆
|
|
94
|
+
> **无法被 `prefetch` 召回**(表现为写入成功但检索恒为空)。Hermes 集成请使用 `pro`(默认)或
|
|
95
|
+
> `ultra`;`lite` 仅适合“只写、不依赖语义召回”的特殊场景。
|
|
96
|
+
|
|
97
|
+
## CLI
|
|
98
|
+
|
|
99
|
+
`pip install` 后可用 `hermes-hy-memory` 子命令做体检和手动操作(脱离 Hermes 主进程):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# 体检:env 齐 / Client 能否构造 / list 能否跑通(只读)
|
|
103
|
+
hermes-hy-memory doctor
|
|
104
|
+
|
|
105
|
+
# 手动写入
|
|
106
|
+
hermes-hy-memory add "我喜欢 K-Pop 但更喜欢 Jazz"
|
|
107
|
+
|
|
108
|
+
# 手动搜索
|
|
109
|
+
hermes-hy-memory search "音乐口味" --limit 5
|
|
110
|
+
|
|
111
|
+
# 列出最近 20 条
|
|
112
|
+
hermes-hy-memory list
|
|
113
|
+
|
|
114
|
+
# 跨用户测试(覆盖 env)
|
|
115
|
+
hermes-hy-memory search "x" --user-id other_user --agent-id test
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
被 Hermes 主 CLI 加载后也可:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
hermes hy-memory doctor
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Hooks
|
|
125
|
+
|
|
126
|
+
| Hook | 触发时机 | 行为 |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `prefetch(query)` | 每次 LLM 调用前 | 搜记忆 → 注入 system prompt |
|
|
129
|
+
| `sync_turn(user, ast)` | 每轮对话结束 | 提交线程池异步写入 |
|
|
130
|
+
| `on_session_end(msgs)` | 会话结束 | 等 in-flight 完成 + 尾部补提取 |
|
|
131
|
+
| `on_pre_compress(msgs)` | 上下文压缩前 | 同 `on_session_end`,保住即将被裁的内容 |
|
|
132
|
+
| `on_memory_write(action, target, content)` | Hermes 内置 memory 命令 | `add` 同步到 HY Memory;`delete` 跳过(target ID 不互通) |
|
|
133
|
+
|
|
134
|
+
## Tools(LLM 主动调用,可选)
|
|
135
|
+
|
|
136
|
+
| Tool | 用途 |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `memory_search(query, limit)` | 搜索记忆 |
|
|
139
|
+
| `memory_add(content)` | 写入一条记忆 |
|
|
140
|
+
| `memory_delete(memory_id)` | 删除一条记忆 |
|
|
141
|
+
| `memory_list(limit)` | 列出当前 user/agent 的记忆 |
|
|
142
|
+
|
|
143
|
+
即使关掉 tools,prefetch 的被动注入也保证每次 LLM 调用都能看到相关记忆。
|
|
144
|
+
|
|
145
|
+
## 故障排查
|
|
146
|
+
|
|
147
|
+
### `provider not initialized` 或所有 hook 静默 no-op
|
|
148
|
+
|
|
149
|
+
跑一遍体检:
|
|
150
|
+
```bash
|
|
151
|
+
hermes-hy-memory doctor
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
常见原因:
|
|
155
|
+
1. `HY_MEMORY_USER_ID` 没设
|
|
156
|
+
2. embed/LLM key 没设(`OPENAI_API_KEY` 等)→ `HyMemoryClient(mode="pro")` 构造失败
|
|
157
|
+
3. SDK 没装:`pip install hy-memory`(内网:`pip install hy-memory-internal --index-url https://mirrors.tencent.com/pypi/simple/`)
|
|
158
|
+
|
|
159
|
+
### prefetch 没注入任何记忆
|
|
160
|
+
|
|
161
|
+
- 库里**真没相关记忆** —— 用 `hermes-hy-memory list` 确认
|
|
162
|
+
- query 太短(< 3 字符)或在跳过列表里(`ok`/`好的`/`thanks` 等)—— 设计如此
|
|
163
|
+
- 中文 query 命中率低?SDK ≥ 0.1.5.11.dev2 已修过 BM25 中文分词不对称的 bug,确认版本
|
|
164
|
+
|
|
165
|
+
### sync_turn 似乎没写入
|
|
166
|
+
|
|
167
|
+
- 默认 daemon 线程,主进程退出会被 kill。生产部署用 `hermes` daemon 模式
|
|
168
|
+
- 提高 `HY_MEMORY_SHUTDOWN_GRACE_SEC` 让 shutdown 多等几秒
|
|
169
|
+
- 检查日志 `[hermes] sync_turn failed: ...`
|
|
170
|
+
|
|
171
|
+
### `cross-loop` 错误(多 client 场景)
|
|
172
|
+
|
|
173
|
+
如果你的 Hermes 部署同时跑了多个 `HyMemoryClient` 实例(比如多租户 server),需要 SDK ≥ 0.1.5.11.dev2 + 用 `SharedRuntime`:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from hy_memory import HyMemoryClient, SharedRuntime
|
|
177
|
+
runtime = await SharedRuntime.create(base_config)
|
|
178
|
+
client = HyMemoryClient(cfg, runtime=runtime)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
本 plugin 单 Hermes 进程默认走 solo mode 的 client,不需要这个。
|
|
182
|
+
|
|
183
|
+
## 与 Mem0 风格的对比
|
|
184
|
+
|
|
185
|
+
[Mem0 的 Hermes 集成](https://github.com/mem0ai/mem0)依赖 **TypeScript SDK**,本插件用 **Python SDK**。HY Memory 提供 lite/pro/ultra 三档处理深度(lite 不调 LLM,pro 标准抽取,ultra 加 System2 认知);Mem0 是单档 LLM 抽取。
|
|
186
|
+
|
|
187
|
+
## 开发
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
cd plugins/native/hermes
|
|
191
|
+
python -m pytest tests/ -v
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
测试 mock 掉 `HyMemoryClient`,无外部依赖(不需要 OPENAI_API_KEY、不需要起 Qdrant)。
|
|
195
|
+
|
|
196
|
+
## 版本
|
|
197
|
+
|
|
198
|
+
| Plugin | SDK 依赖 | 备注 |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| 0.1.2 | `hy-memory>=1.2.17`(内网 `hy-memory-internal>=0.1.5.14`) | 发布到公网 PyPI(`hermes-hy-memory`);channel-dict flatten 修复 |
|
|
201
|
+
| 0.1.0 | `hy-memory-internal>=0.1.5.11.dev2` | 含 SharedRuntime / BM25 中文修复 / Chroma keyword_search / stdout 日志 |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HY Memory Provider for Hermes Agent
|
|
3
|
+
|
|
4
|
+
第一梯队原生插件 — 实现 Hermes MemoryProvider 接口,
|
|
5
|
+
每次请求自动 prefetch 相关记忆注入 system prompt(100% 被动注入)。
|
|
6
|
+
|
|
7
|
+
安装:pip install hy-mem-internal
|
|
8
|
+
配置:~/.hermes/config.yaml → memory.provider: hy-memory
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
|
|
13
|
+
from .provider import HyMemoryProvider, register
|
|
14
|
+
|
|
15
|
+
__all__ = ["HyMemoryProvider", "register"]
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
HY Memory CLI for Hermes — `hermes hy-memory <subcommand>`
|
|
4
|
+
|
|
5
|
+
子命令:
|
|
6
|
+
doctor 连通性 + 配置体检(不写不删,只读)
|
|
7
|
+
add <text> 手动写入一条记忆
|
|
8
|
+
search <query> 手动搜索
|
|
9
|
+
list 列出最近 N 条记忆
|
|
10
|
+
|
|
11
|
+
Hermes 在 plugin 加载时调用 register_cli(subparser) 把以上 subcommand
|
|
12
|
+
挂到 hermes 主 CLI 上;只有当 provider 已配置(HY_MEMORY_USER_ID 设置)
|
|
13
|
+
时才激活。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def register_cli(subparser: argparse._SubParsersAction) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Hermes 插件 CLI 注册入口。
|
|
28
|
+
|
|
29
|
+
会被 hermes 主 CLI 在加载插件时调用:
|
|
30
|
+
` hermes hy-memory doctor / add / search / list `
|
|
31
|
+
"""
|
|
32
|
+
p = subparser.add_parser(
|
|
33
|
+
"hy-memory",
|
|
34
|
+
help="HY Memory plugin CLI (doctor / add / search / list)",
|
|
35
|
+
)
|
|
36
|
+
sub = p.add_subparsers(dest="hy_memory_cmd", required=True)
|
|
37
|
+
|
|
38
|
+
p_doctor = sub.add_parser("doctor", help="Health check (read-only diagnostic)")
|
|
39
|
+
p_doctor.set_defaults(func=_cmd_doctor)
|
|
40
|
+
|
|
41
|
+
p_add = sub.add_parser("add", help="Manually add a memory")
|
|
42
|
+
p_add.add_argument("text", help="Memory content")
|
|
43
|
+
p_add.add_argument("--user-id", help="Override HY_MEMORY_USER_ID")
|
|
44
|
+
p_add.add_argument("--agent-id", help="Override HY_MEMORY_AGENT_ID")
|
|
45
|
+
p_add.set_defaults(func=_cmd_add)
|
|
46
|
+
|
|
47
|
+
p_search = sub.add_parser("search", help="Search memories")
|
|
48
|
+
p_search.add_argument("query", help="Search query")
|
|
49
|
+
p_search.add_argument("--limit", type=int, default=10)
|
|
50
|
+
p_search.add_argument("--user-id", help="Override HY_MEMORY_USER_ID")
|
|
51
|
+
p_search.add_argument("--agent-id", help="Override HY_MEMORY_AGENT_ID")
|
|
52
|
+
p_search.set_defaults(func=_cmd_search)
|
|
53
|
+
|
|
54
|
+
p_list = sub.add_parser("list", help="List recent memories")
|
|
55
|
+
p_list.add_argument("--limit", type=int, default=20)
|
|
56
|
+
p_list.add_argument("--user-id", help="Override HY_MEMORY_USER_ID")
|
|
57
|
+
p_list.add_argument("--agent-id", help="Override HY_MEMORY_AGENT_ID")
|
|
58
|
+
p_list.set_defaults(func=_cmd_list)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ================================================================
|
|
62
|
+
# Helpers
|
|
63
|
+
# ================================================================
|
|
64
|
+
|
|
65
|
+
def _resolve_ids(args) -> tuple[str, str, str]:
|
|
66
|
+
"""从 args + env 解析 user_id / agent_id / mode(顺序:CLI flag > env)"""
|
|
67
|
+
user_id = (getattr(args, "user_id", None) or os.environ.get("HY_MEMORY_USER_ID", "")).strip()
|
|
68
|
+
agent_id = (getattr(args, "agent_id", None) or os.environ.get("HY_MEMORY_AGENT_ID", "hermes")).strip() or "hermes"
|
|
69
|
+
mode = os.environ.get("HY_MEMORY_MODE", "pro").strip() or "pro"
|
|
70
|
+
return user_id, agent_id, mode
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _make_client(mode: str):
|
|
74
|
+
try:
|
|
75
|
+
from hy_memory import HyMemoryClient
|
|
76
|
+
except ImportError as e:
|
|
77
|
+
print(f"ERROR: hy-memory SDK not installed: {e}", file=sys.stderr)
|
|
78
|
+
print("Hint: pip install hy-memory (内网: pip install hy-memory-internal --index-url https://mirrors.tencent.com/pypi/simple/)", file=sys.stderr)
|
|
79
|
+
sys.exit(2)
|
|
80
|
+
try:
|
|
81
|
+
return HyMemoryClient(mode=mode)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"ERROR: HyMemoryClient init failed: {e}", file=sys.stderr)
|
|
84
|
+
sys.exit(2)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _print_kv(label: str, value, ok: Optional[bool] = None) -> None:
|
|
88
|
+
"""[ok]/[fail]/[--] label: value"""
|
|
89
|
+
if ok is True:
|
|
90
|
+
prefix = " ✓ "
|
|
91
|
+
elif ok is False:
|
|
92
|
+
prefix = " ✗ "
|
|
93
|
+
else:
|
|
94
|
+
prefix = " "
|
|
95
|
+
print(f"{prefix}{label}: {value}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ================================================================
|
|
99
|
+
# Subcommands
|
|
100
|
+
# ================================================================
|
|
101
|
+
|
|
102
|
+
def _cmd_doctor(args) -> int:
|
|
103
|
+
"""Health check — 完全只读。报:env 是否齐 / Client 能否构造 / list 能否跑通。"""
|
|
104
|
+
print("HY Memory Provider — Doctor")
|
|
105
|
+
print("=" * 60)
|
|
106
|
+
|
|
107
|
+
user_id, agent_id, mode = _resolve_ids(args)
|
|
108
|
+
|
|
109
|
+
# 1) Env vars
|
|
110
|
+
print("\n[1/3] Environment")
|
|
111
|
+
_print_kv("HY_MEMORY_USER_ID", user_id or "(unset)", ok=bool(user_id))
|
|
112
|
+
_print_kv("HY_MEMORY_AGENT_ID", agent_id, ok=True)
|
|
113
|
+
_print_kv("HY_MEMORY_MODE", mode, ok=mode in ("lite", "pro", "ultra"))
|
|
114
|
+
|
|
115
|
+
embed_keys = [
|
|
116
|
+
"OPENAI_API_KEY",
|
|
117
|
+
"MEMORY_EMBEDDER_API_KEY",
|
|
118
|
+
"AZURE_OPENAI_API_KEY",
|
|
119
|
+
]
|
|
120
|
+
has_embed = any(os.environ.get(k) for k in embed_keys)
|
|
121
|
+
_print_kv(
|
|
122
|
+
"Embedder API key",
|
|
123
|
+
"set" if has_embed else "(none of " + "/".join(embed_keys) + " set)",
|
|
124
|
+
ok=has_embed,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if not user_id:
|
|
128
|
+
print("\nFAIL: HY_MEMORY_USER_ID is required.", file=sys.stderr)
|
|
129
|
+
return 2
|
|
130
|
+
|
|
131
|
+
# 2) Client init
|
|
132
|
+
print("\n[2/3] Client init")
|
|
133
|
+
try:
|
|
134
|
+
from hy_memory import HyMemoryClient
|
|
135
|
+
client = HyMemoryClient(mode=mode)
|
|
136
|
+
_print_kv("HyMemoryClient", "ready", ok=True)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
_print_kv("HyMemoryClient", f"FAIL: {e}", ok=False)
|
|
139
|
+
return 2
|
|
140
|
+
|
|
141
|
+
# 3) Probe list
|
|
142
|
+
print("\n[3/3] List probe")
|
|
143
|
+
try:
|
|
144
|
+
result = client.list_memories(user_id=user_id, agent_id=agent_id, limit=1)
|
|
145
|
+
vdb = result.get("vdb", {}) or {}
|
|
146
|
+
total = vdb.get("total", 0)
|
|
147
|
+
_print_kv(f"list_memories(user={user_id}, agent={agent_id})", f"total={total}", ok=True)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
_print_kv("list_memories probe", f"FAIL: {e}", ok=False)
|
|
150
|
+
client.close()
|
|
151
|
+
return 2
|
|
152
|
+
|
|
153
|
+
client.close()
|
|
154
|
+
print("\nAll checks passed.")
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _cmd_add(args) -> int:
|
|
159
|
+
user_id, agent_id, mode = _resolve_ids(args)
|
|
160
|
+
if not user_id:
|
|
161
|
+
print("ERROR: HY_MEMORY_USER_ID required", file=sys.stderr)
|
|
162
|
+
return 2
|
|
163
|
+
|
|
164
|
+
client = _make_client(mode)
|
|
165
|
+
try:
|
|
166
|
+
result = client.add(
|
|
167
|
+
args.text,
|
|
168
|
+
user_id=user_id,
|
|
169
|
+
agent_id=agent_id,
|
|
170
|
+
session_id="hermes_cli",
|
|
171
|
+
)
|
|
172
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
173
|
+
return 0 if result.get("success") else 1
|
|
174
|
+
finally:
|
|
175
|
+
client.close()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _flatten_memories(memories):
|
|
179
|
+
"""把 search() 返回统一拍平成 list。
|
|
180
|
+
|
|
181
|
+
SDK search() chat 路径返回按通道分组的 dict
|
|
182
|
+
{'profile': [...], 'proactive': [...], 'normal': [...]};旧契约为扁平 list。
|
|
183
|
+
三路 layer 互斥,无需去重;顺序 profile→proactive→normal。
|
|
184
|
+
"""
|
|
185
|
+
if isinstance(memories, dict):
|
|
186
|
+
out = []
|
|
187
|
+
for ch in ("profile", "proactive", "normal"):
|
|
188
|
+
out.extend(memories.get(ch) or [])
|
|
189
|
+
return out
|
|
190
|
+
return memories or []
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _cmd_search(args) -> int:
|
|
194
|
+
user_id, agent_id, mode = _resolve_ids(args)
|
|
195
|
+
if not user_id:
|
|
196
|
+
print("ERROR: HY_MEMORY_USER_ID required", file=sys.stderr)
|
|
197
|
+
return 2
|
|
198
|
+
|
|
199
|
+
client = _make_client(mode)
|
|
200
|
+
try:
|
|
201
|
+
result = client.search(
|
|
202
|
+
args.query,
|
|
203
|
+
user_ids=[user_id],
|
|
204
|
+
agent_ids=[agent_id],
|
|
205
|
+
limit=args.limit,
|
|
206
|
+
)
|
|
207
|
+
memories = _flatten_memories(result.get("memories"))
|
|
208
|
+
print(f"Found {len(memories)} memories for query={args.query!r}")
|
|
209
|
+
for i, m in enumerate(memories, 1):
|
|
210
|
+
score = m.get("score", 0)
|
|
211
|
+
layer = m.get("layer", "")
|
|
212
|
+
content = m.get("content", "")
|
|
213
|
+
print(f"\n[{i}] score={score:.3f} layer={layer}")
|
|
214
|
+
print(f" {content}")
|
|
215
|
+
return 0
|
|
216
|
+
finally:
|
|
217
|
+
client.close()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _cmd_list(args) -> int:
|
|
221
|
+
user_id, agent_id, mode = _resolve_ids(args)
|
|
222
|
+
if not user_id:
|
|
223
|
+
print("ERROR: HY_MEMORY_USER_ID required", file=sys.stderr)
|
|
224
|
+
return 2
|
|
225
|
+
|
|
226
|
+
client = _make_client(mode)
|
|
227
|
+
try:
|
|
228
|
+
result = client.list_memories(
|
|
229
|
+
user_id=user_id, agent_id=agent_id, limit=args.limit,
|
|
230
|
+
)
|
|
231
|
+
vdb = result.get("vdb", {}) or {}
|
|
232
|
+
memories = vdb.get("memories") or []
|
|
233
|
+
total = vdb.get("total", 0)
|
|
234
|
+
print(f"Listing {len(memories)}/{total} memories (user={user_id} agent={agent_id})")
|
|
235
|
+
for i, m in enumerate(memories, 1):
|
|
236
|
+
mid = m.get("memory_id", "")
|
|
237
|
+
layer = m.get("layer", "")
|
|
238
|
+
content = m.get("content", "")
|
|
239
|
+
print(f"\n[{i}] {mid} layer={layer}")
|
|
240
|
+
print(f" {content}")
|
|
241
|
+
return 0
|
|
242
|
+
finally:
|
|
243
|
+
client.close()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ================================================================
|
|
247
|
+
# Standalone entry — 当 plugin 不通过 hermes 加载时也能直接 python -m
|
|
248
|
+
# ================================================================
|
|
249
|
+
|
|
250
|
+
def _main_standalone() -> int:
|
|
251
|
+
parser = argparse.ArgumentParser(prog="hermes-hy-memory")
|
|
252
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
253
|
+
register_cli(sub)
|
|
254
|
+
args = parser.parse_args()
|
|
255
|
+
return args.func(args)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
if __name__ == "__main__":
|
|
259
|
+
sys.exit(_main_standalone())
|