kb-memory 2.1.2__tar.gz → 2.2.0__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.
Files changed (96) hide show
  1. {kb_memory-2.1.2 → kb_memory-2.2.0}/PKG-INFO +52 -20
  2. {kb_memory-2.1.2 → kb_memory-2.2.0}/README.md +51 -19
  3. kb_memory-2.2.0/kb/__init__.py +2 -0
  4. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/api.py +71 -0
  5. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/cli.py +215 -26
  6. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/config.py +7 -0
  7. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/embedder.py +88 -3
  8. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/i18n.py +2 -0
  9. kb_memory-2.2.0/kb/links.py +167 -0
  10. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/mcp.py +70 -5
  11. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/scopes.py +13 -1
  12. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/service.py +205 -2
  13. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb_memory.egg-info/PKG-INFO +52 -20
  14. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb_memory.egg-info/SOURCES.txt +4 -0
  15. {kb_memory-2.1.2 → kb_memory-2.2.0}/pyproject.toml +1 -1
  16. kb_memory-2.2.0/tests/test_forget_upgrade.py +216 -0
  17. kb_memory-2.2.0/tests/test_low_memory_tuning.py +107 -0
  18. kb_memory-2.2.0/tests/test_memory_links.py +210 -0
  19. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n12_mcp.py +3 -2
  20. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_skill_package.py +8 -4
  21. kb_memory-2.1.2/kb/__init__.py +0 -2
  22. {kb_memory-2.1.2 → kb_memory-2.2.0}/LICENSE +0 -0
  23. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/__main__.py +0 -0
  24. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/audit.py +0 -0
  25. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/bm25.py +0 -0
  26. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/consolidation.py +0 -0
  27. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/discovery.py +0 -0
  28. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/eval.py +0 -0
  29. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/governance.py +0 -0
  30. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/idle.py +0 -0
  31. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/ingest.py +0 -0
  32. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/llm.py +0 -0
  33. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/logging_setup.py +0 -0
  34. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/models.py +0 -0
  35. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/monitor.py +0 -0
  36. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/reranker.py +0 -0
  37. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/retriever.py +0 -0
  38. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/sparse.py +0 -0
  39. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/storage.py +0 -0
  40. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb/watcher.py +0 -0
  41. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb_memory.egg-info/dependency_links.txt +0 -0
  42. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb_memory.egg-info/entry_points.txt +0 -0
  43. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb_memory.egg-info/requires.txt +0 -0
  44. {kb_memory-2.1.2 → kb_memory-2.2.0}/kb_memory.egg-info/top_level.txt +0 -0
  45. {kb_memory-2.1.2 → kb_memory-2.2.0}/setup.cfg +0 -0
  46. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_agent_isolation.py +0 -0
  47. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_api_auth.py +0 -0
  48. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_charset_json.py +0 -0
  49. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_consolidation.py +0 -0
  50. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_embed_model_resolution.py +0 -0
  51. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_hotfix_validation.py +0 -0
  52. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_improvements_bm25_batch.py +0 -0
  53. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_improvements_concurrency.py +0 -0
  54. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_improvements_isolation_recall.py +0 -0
  55. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n01_config.py +0 -0
  56. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n02_models.py +0 -0
  57. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n03_embedder.py +0 -0
  58. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n04_storage.py +0 -0
  59. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n05_bm25.py +0 -0
  60. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n06_retriever_service.py +0 -0
  61. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n07_api.py +0 -0
  62. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n08_api_ext.py +0 -0
  63. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n09_llm.py +0 -0
  64. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n10_ask.py +0 -0
  65. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n11_routing.py +0 -0
  66. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n13_ingest.py +0 -0
  67. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n13_ingest_extra.py +0 -0
  68. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n14_web.py +0 -0
  69. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n15_watcher.py +0 -0
  70. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n16_regression.py +0 -0
  71. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n17_logging.py +0 -0
  72. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n18_logs_api.py +0 -0
  73. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n18_logs_doc.py +0 -0
  74. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n18_monitor.py +0 -0
  75. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n19_nav.py +0 -0
  76. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n21_decay.py +0 -0
  77. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n21_metadata.py +0 -0
  78. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n22_dedup.py +0 -0
  79. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n22_governance.py +0 -0
  80. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n23_audit.py +0 -0
  81. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n23_cli.py +0 -0
  82. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n24_reranker.py +0 -0
  83. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n25_sparse.py +0 -0
  84. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n26_eval.py +0 -0
  85. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n27_perf.py +0 -0
  86. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n28_cli.py +0 -0
  87. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n29_security.py +0 -0
  88. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n30_mcp_stdio.py +0 -0
  89. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n31_serve_probe.py +0 -0
  90. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_n32_hybrid_fusion_recall.py +0 -0
  91. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_user_experience_fixes.py +0 -0
  92. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_v21_bootstrap.py +0 -0
  93. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_v21_i18n.py +0 -0
  94. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_v21_packaging.py +0 -0
  95. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_v21_runtime.py +0 -0
  96. {kb_memory-2.1.2 → kb_memory-2.2.0}/tests/test_watcher_missing_dir.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kb-memory
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: Local-first agent memory & knowledge service: hybrid retrieval + memory governance (decay / dedup / freshness), REST + MCP dual protocol, fully usable offline without an LLM
5
5
  Author: fish827-08
6
6
  License-Expression: Apache-2.0
@@ -60,7 +60,7 @@ Dynamic: license-file
60
60
 
61
61
  <!-- GitHub Topics: mcp, memory-service, rag, ai-agent, knowledge-base, local-first, llm, claude-code, hybrid-search, embedding -->
62
62
 
63
- **kb** 是本地优先、完全免费的 Agent 记忆与知识服务(v2.1.2)。
63
+ **kb** 是本地优先、完全免费的 Agent 记忆与知识服务(v2.2.0)。
64
64
 
65
65
  > 开源协议:[Apache-2.0](LICENSE)(含专利授权,可商用)。
66
66
 
@@ -94,20 +94,37 @@ Cursor / TraeWork / 自建 Agent 提供记忆写入、文档与网页入库、
94
94
  - **隐私护栏**:敏感 namespace 强制本地回答不出网;`/ask` 智能路由(本地优先,难题可选云端)
95
95
  - **断网可用**:模型与数据全部落本地,无网络时存取与检索功能完整
96
96
 
97
- ## 快速开始(Windows / Linux / macOS)
97
+ ## 快速开始(Linux / Windows / macOS)
98
98
 
99
- **从 PyPI 安装(推荐):**
99
+ **Linux(推荐,含 2 核 2G 小服务器):先装 CPU 版 torch,再装 kb。**
100
100
 
101
101
  ```bash
102
- pip install "kb-memory[ingest]" # 核心 + 文档/网页摄取;[llm]=云端问答,[full]=全部
102
+ pip install torch --index-url https://download.pytorch.org/whl/cpu # CPU 轮子约 196 MB
103
+ pip install "kb-memory[ingest]"
103
104
  python -m kb serve
104
105
  ```
105
106
 
106
- **Linux 无显卡机器先装 CPU 版 torch**(PyPI 默认拉 CUDA 轮子,体积大得多):
107
+ > 不先装 CPU 版 torch 时,PyPI 默认会拉 CUDA 版本(torch + cudnn + nccl 等合计 **2–3 GB**),
108
+ > 这是 Linux 新用户最大的下载坑;只做记忆存取与检索不需要显卡。
109
+
110
+ **2 核 2G 服务器建议配置**(写入 `.env` 或环境变量):
111
+
112
+ ```ini
113
+ KB_DEVICE=cpu
114
+ KB_LLM_MODE=off
115
+ KB_TORCH_THREADS=2 # 0=自动(按 cgroup/affinity 配额,最多 4)
116
+ KB_EMBED_BATCH_SIZE=16 # 默认 32;内存紧张降到 8–16
117
+ KB_IDLE_UNLOAD_SECONDS=300 # 空闲 5 分钟卸载嵌入模型
118
+ ```
119
+
120
+ 启动阶段不加载嵌入模型(百 MB 级);首次检索/写入才加载默认 `bge-small`(约 92 MB),
121
+ 加载后常驻通常 < 1 GB,检索为毫秒级,2 核 2G 可运行。LLM 问答(`/ask`)在 2G 上
122
+ 建议保持关闭或使用云端。
123
+
124
+ **Windows / macOS:**
107
125
 
108
126
  ```bash
109
- pip install torch --index-url https://download.pytorch.org/whl/cpu
110
- pip install "kb-memory[ingest]"
127
+ pip install "kb-memory[ingest]" # 核心 + 文档/网页摄取;[llm]=云端问答,[full]=全部
111
128
  python -m kb serve
112
129
  ```
113
130
 
@@ -122,12 +139,14 @@ python -m kb serve
122
139
  ```
123
140
 
124
141
  > **首次安装要下载什么、大概多久**(先看一眼,避免以为卡死了):
125
- > - 核心 Python 依赖约 **400–600 MB**(Windows CPU 版 `torch` 约 124 MB 为最大单体;
126
- > Linux 先装 CPU 轮子可避免默认 CUDA 轮子带来的数 GB 下载);
142
+ > - Linux 先装 CPU 版 `torch`(约 **196 MB**)后,整条核心依赖约 **0.5–0.7 GB**;
143
+ > Windows / macOS 核心依赖约 **400–600 MB**(Windows CPU 版 `torch` 约 124 MB 为最大单体);
127
144
  > - 可选 `[ingest]` 额外约几十 MB(PDF/DOCX/Office 解析、网页提取、目录监听);
128
145
  > - **首次写入/检索时**才加载并下载嵌入模型(懒加载,启动本身很轻),**默认 `bge-small-zh-v1.5` 约 92 MB**(若显式配了 `KB_EMBED_MODEL=BAAI/bge-m3`,则约 2.2 GB);
129
- > - 国内网络建议加 pip 镜像(实测阿里云可用、清华可能 403):
130
- > `pip install "kb-memory[ingest]" -i https://mirrors.aliyun.com/pypi/simple/`
146
+ > - 国内网络可加 pip 镜像加速(实测阿里云可用、清华可能 403),但镜像同步新包有延迟
147
+ > (实测阿里云对 `kb-memory` 返回过 404);建议同时挂官方源兜底:
148
+ > `pip install "kb-memory[ingest]" -i https://mirrors.aliyun.com/pypi/simple/ --extra-index-url https://pypi.org/simple/`
149
+ > 若仍报 `No matching distribution`,改用官方源:`pip install "kb-memory[ingest]" -i https://pypi.org/simple/`
131
150
  > - 模型下载走 HuggingFace 镜像:`$env:HF_ENDPOINT='https://hf-mirror.com'`(Windows)/ `export HF_ENDPOINT=https://hf-mirror.com`(Linux、macOS)。
132
151
  >
133
152
  > 重要:用源码 venv 安装时 `kb` 命令只装在虚拟环境内。**每个新终端都要先激活虚拟环境**
@@ -148,9 +167,9 @@ curl http://127.0.0.1:8000/api/v1/healthz
148
167
  > `/healthz` 的 `embedder` 字段会显示 `unloaded/loading/ok/error`,模型加载失败时
149
168
  > 写/查请求返回 **503 `EMBED_MODEL_UNAVAILABLE`**(含模型名与修复建议),不再裸 500。
150
169
  >
151
- > **CPU 即可运行,不占显卡**:全新安装默认拿到 CPU 版 torch(`device=cpu`),
152
- > 不占用 GPU 显存,无独立显卡的机器也能完整使用。有显卡想加速(大库、批量入库)
153
- > 可另装 CUDA 版 torch 后显式 `KB_DEVICE=cuda` 启用 GPU 加速。
170
+ > **CPU 即可运行,不占显卡**:Windows / macOS 安装默认使用 CPU 版 torch;
171
+ > Linux 按上面的命令先装 CPU 轮子即可,无独立显卡的机器也能完整使用。
172
+ > 有显卡想加速(大库、批量入库)可另装 CUDA 版 torch 后显式 `KB_DEVICE=cuda`。
154
173
  >
155
174
  > **LLM 默认关闭(`KB_LLM_MODE=off`)**:服务启动不会探测/加载/调用任何大模型,
156
175
  > 零显存、零成本、纯离线;记忆写入、文档入库、混合检索完整可用。
@@ -300,8 +319,9 @@ claude mcp add --transport http kb http://127.0.0.1:8000/mcp/
300
319
  }
301
320
  ```
302
321
 
303
- 挂载后可用的 MCP 工具:`write_memory` / `search_memory` / `read_memory` /
304
- `update_memory` / `delete_memory` / `add_document` / `add_webpage` / `ask_kb`。
322
+ 挂载后可用的 MCP 工具(11 个):`write_memory` / `search_memory` / `read_memory` /
323
+ `update_memory` / `delete_memory` / `link_memory` / `unlink_memory` / `list_links` /
324
+ `add_document` / `add_webpage` / `ask_kb`。
305
325
 
306
326
  > 启用 `KB_API_KEY` 鉴权后,MCP 客户端需在连接配置加 `headers`(`Authorization: Bearer <key>`);
307
327
  > 仓库内 `.mcp.json` 模板不含真实 key(JSON 不支持注释),配法见 [USER_GUIDE §5.2](docs/USER_GUIDE.md#52-客户端适配n20启用鉴权后各客户端如何带-key)。
@@ -315,6 +335,9 @@ claude mcp add --transport http kb http://127.0.0.1:8000/mcp/
315
335
  | GET | `/api/v1/memories/{id}` | 读取单条记忆 |
316
336
  | PATCH | `/api/v1/memories/{id}` | 更新内容或标签 |
317
337
  | DELETE | `/api/v1/memories/{id}` | 删除单条记忆 |
338
+ | POST | `/api/v1/memories/{id}/links` | 建立记忆关联 `{to_id, relation?}`;relation: `related/supersedes/depends_on/part_of/contradicts` |
339
+ | GET | `/api/v1/memories/{id}/links` | 列一跳关联(含内容/方向;`?project=&relation=`) |
340
+ | DELETE | `/api/v1/memories/{id}/links/{to_id}` | 取消关联(任一方向;删除后同步清理) |
318
341
  | POST | `/api/v1/search` | 混合检索 `{query, top_k?, mode?, type?, tag?}`,mode: `hybrid/vector/keyword` |
319
342
  | POST | `/api/v1/documents` | 文档入库:multipart `file` 字段或 JSON `{"path": "本地路径"}` |
320
343
  | GET | `/api/v1/documents` | 已入库文档列表(按 source 聚合) |
@@ -327,6 +350,11 @@ claude mcp add --transport http kb http://127.0.0.1:8000/mcp/
327
350
  | GET | `/api/v1/governance/config` | 治理配置:衰减+新鲜度开关与参数(只读) |
328
351
  | POST | `/api/v1/memories` → **409** | 启用语义去重(`KB_DEDUP_ENABLED=true`)后写入命中重复返回 409:`{"error":"DUPLICATE","duplicate_of":"<已有记录id>","similarity":<相似度>}`(不写入) |
329
352
 
353
+ > 记忆关联默认启用一跳检索扩展:命中的 memory 会带出直接关联的邻居(结果附
354
+ > `via: {id, relation}`),无关联数据时与基础检索完全一致;开关与上限见
355
+ > `KB_LINK_EXPAND_ENABLED` / `KB_LINK_EXPAND_LIMIT`。设计见
356
+ > [memory-links spec](docs/superpowers/specs/2026-10-22-kb-memory-links-design.md)。
357
+ >
330
358
  > 记忆治理(去重/衰减/新鲜度)均默认关闭、零行为变化;用法见 [USER_GUIDE §3.5](docs/USER_GUIDE.md#35-记忆治理a3语义去重--衰减--新鲜度)。
331
359
 
332
360
  示例(Windows PowerShell 可直接复制;中文必须按 UTF-8 字节发送):
@@ -364,11 +392,13 @@ curl -X POST http://127.0.0.1:8000/api/v1/memories \
364
392
  | 配置项 | 默认值 | 说明 |
365
393
  |---|---|---|
366
394
  | `KB_LLM_MODE` | `off` | LLM 模式:`off`(默认,不加载/不调用 LLM,零显存零成本)/ `local`(仅本地 Ollama)/ `auto`(本地优先,云端降级)/ `cloud` |
367
- | `KB_DEVICE` | 空 | 嵌入模型设备:空=自动检测,可显式设 `cpu` / `cuda` |
395
+ | `KB_DEVICE` | 空 | 嵌入模型设备:空或 `auto`=自动检测(非交互启动回退 `cpu`),可显式设 `cpu` / `cuda` |
368
396
  | `KB_WATCH_DIR` | `data` | serve 模式监听目录,文件变动自动入库;空串或 `.` = 不启动 |
369
397
  | `KB_DATA_DIR` | `kb_data` | 运行数据根目录(ChromaDB、运行时状态等) |
370
398
  | `KB_API_HOST` / `KB_API_PORT` | `127.0.0.1` / `8000` | REST 与 MCP 监听地址 |
371
399
  | `KB_EMBED_MODEL` | `BAAI/bge-small-zh-v1.5` | 嵌入模型(轻量默认,约 92 MB);可换 `BAAI/bge-m3`(约 2.2 GB,更强),见「嵌入模型怎么选」 |
400
+ | `KB_TORCH_THREADS` / `KB_EMBED_BATCH_SIZE` | `0` / `32` | 低配 CPU 调优:线程数 `0`=自动(按 cgroup/affinity,最多 4);2G 内存可把批大小降到 8–16 |
401
+ | `KB_IDLE_UNLOAD_SECONDS` | `600` | 嵌入模型空闲 N 秒后卸载;`0`=不卸载 |
372
402
  | `KB_LLM_MODEL` | 空 | 本地 Ollama 模型名(默认空=不配;配 `KB_LLM_MODE=local/auto` 时须按自己电脑选模型,以 `ollama list` 为准) |
373
403
  | `KB_OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama 端点 |
374
404
  | `KB_LLM_API_KEY` / `KB_LLM_BASE_URL` / `KB_LLM_CLOUD_MODEL` | 空 | 云端 LLM(可选):**任意 OpenAI 兼容服务商**(DeepSeek / OpenAI / 通义 / 硅基流动等),仅填在本机 `.env` |
@@ -377,6 +407,8 @@ curl -X POST http://127.0.0.1:8000/api/v1/memories \
377
407
  | `KB_API_KEY` | 空 | 空=不鉴权(本地回环零摩擦);非空=启用 Bearer/X-API-Key 鉴权 |
378
408
  | `KB_RERANK_ENABLED` / `KB_RERANK_MODEL` / `KB_RERANK_TOP_N` | `false` / `BAAI/bge-reranker-v2-m3` / `20` | 检索精排(A3.5):融合候选送 CrossEncoder 重排,默认关 |
379
409
  | `KB_SPARSE_ENABLED` | `false` | 稀疏第三路(A3.5):BGE-M3 稀疏向量 + 倒排索引参与归一化加权融合,默认关 |
410
+ | `KB_LINK_EXPAND_ENABLED` / `KB_LINK_EXPAND_LIMIT` / `KB_LINK_EXPAND_SCORE_RATIO` | `true` / `3` / `0.5` | 记忆关联一跳扩展:每次最多追加邻居数、邻居分数相对种子的比例 |
411
+ | `KB_LINK_MAX_PER_RECORD` | `50` | 单条记忆的关联边上限(防止单点成为巨型枢纽) |
380
412
 
381
413
  ## CLI 速查(无需启动服务)
382
414
 
@@ -387,7 +419,7 @@ python -m kb stats # 统计:类型分布 / 访问
387
419
  python -m kb ask "问题" --client TraeWork # 终端 RAG 问答(LLM 不可用时输出检索命中)
388
420
  python -m kb audit --client TraeWork --days 7 # 查某客户端/项目存过/读过什么
389
421
  python -m kb eval --file tests/eval_zh_50.jsonl # 检索质量评测(Recall@1/@5 + MRR)
390
- python -m kb forget --stale --days 90 # 清理超期未命中记忆
422
+ python -m kb forget --stale --days 90 --dry-run # 全作用域预览(默认 dry-run,删除前自动备份)
391
423
  python -m kb dedup --threshold 0.92 # 语义去重
392
424
  ```
393
425
 
@@ -398,7 +430,7 @@ python -m kb dedup --threshold 0.92 # 语义去重
398
430
  ```
399
431
  kb/ kb 服务源码(config / models / embedder / storage / bm25 / retriever /
400
432
  service / llm / ingest / watcher / api / mcp / cli + reranker / sparse / eval)
401
- tests/ kb 验收测试(425 项通过 / 1 跳过,含 eval_zh_50.jsonl 检索评测数据集)
433
+ tests/ kb 验收测试(462 项通过 / 1 跳过,含 eval_zh_50.jsonl 检索评测数据集)
402
434
  docs/ 用户指南、开发者手册与设计文档
403
435
  kb_data/ kb 运行数据(gitignore)
404
436
  ```
@@ -10,7 +10,7 @@
10
10
 
11
11
  <!-- GitHub Topics: mcp, memory-service, rag, ai-agent, knowledge-base, local-first, llm, claude-code, hybrid-search, embedding -->
12
12
 
13
- **kb** 是本地优先、完全免费的 Agent 记忆与知识服务(v2.1.2)。
13
+ **kb** 是本地优先、完全免费的 Agent 记忆与知识服务(v2.2.0)。
14
14
 
15
15
  > 开源协议:[Apache-2.0](LICENSE)(含专利授权,可商用)。
16
16
 
@@ -44,20 +44,37 @@ Cursor / TraeWork / 自建 Agent 提供记忆写入、文档与网页入库、
44
44
  - **隐私护栏**:敏感 namespace 强制本地回答不出网;`/ask` 智能路由(本地优先,难题可选云端)
45
45
  - **断网可用**:模型与数据全部落本地,无网络时存取与检索功能完整
46
46
 
47
- ## 快速开始(Windows / Linux / macOS)
47
+ ## 快速开始(Linux / Windows / macOS)
48
48
 
49
- **从 PyPI 安装(推荐):**
49
+ **Linux(推荐,含 2 核 2G 小服务器):先装 CPU 版 torch,再装 kb。**
50
50
 
51
51
  ```bash
52
- pip install "kb-memory[ingest]" # 核心 + 文档/网页摄取;[llm]=云端问答,[full]=全部
52
+ pip install torch --index-url https://download.pytorch.org/whl/cpu # CPU 轮子约 196 MB
53
+ pip install "kb-memory[ingest]"
53
54
  python -m kb serve
54
55
  ```
55
56
 
56
- **Linux 无显卡机器先装 CPU 版 torch**(PyPI 默认拉 CUDA 轮子,体积大得多):
57
+ > 不先装 CPU 版 torch 时,PyPI 默认会拉 CUDA 版本(torch + cudnn + nccl 等合计 **2–3 GB**),
58
+ > 这是 Linux 新用户最大的下载坑;只做记忆存取与检索不需要显卡。
59
+
60
+ **2 核 2G 服务器建议配置**(写入 `.env` 或环境变量):
61
+
62
+ ```ini
63
+ KB_DEVICE=cpu
64
+ KB_LLM_MODE=off
65
+ KB_TORCH_THREADS=2 # 0=自动(按 cgroup/affinity 配额,最多 4)
66
+ KB_EMBED_BATCH_SIZE=16 # 默认 32;内存紧张降到 8–16
67
+ KB_IDLE_UNLOAD_SECONDS=300 # 空闲 5 分钟卸载嵌入模型
68
+ ```
69
+
70
+ 启动阶段不加载嵌入模型(百 MB 级);首次检索/写入才加载默认 `bge-small`(约 92 MB),
71
+ 加载后常驻通常 < 1 GB,检索为毫秒级,2 核 2G 可运行。LLM 问答(`/ask`)在 2G 上
72
+ 建议保持关闭或使用云端。
73
+
74
+ **Windows / macOS:**
57
75
 
58
76
  ```bash
59
- pip install torch --index-url https://download.pytorch.org/whl/cpu
60
- pip install "kb-memory[ingest]"
77
+ pip install "kb-memory[ingest]" # 核心 + 文档/网页摄取;[llm]=云端问答,[full]=全部
61
78
  python -m kb serve
62
79
  ```
63
80
 
@@ -72,12 +89,14 @@ python -m kb serve
72
89
  ```
73
90
 
74
91
  > **首次安装要下载什么、大概多久**(先看一眼,避免以为卡死了):
75
- > - 核心 Python 依赖约 **400–600 MB**(Windows CPU 版 `torch` 约 124 MB 为最大单体;
76
- > Linux 先装 CPU 轮子可避免默认 CUDA 轮子带来的数 GB 下载);
92
+ > - Linux 先装 CPU 版 `torch`(约 **196 MB**)后,整条核心依赖约 **0.5–0.7 GB**;
93
+ > Windows / macOS 核心依赖约 **400–600 MB**(Windows CPU 版 `torch` 约 124 MB 为最大单体);
77
94
  > - 可选 `[ingest]` 额外约几十 MB(PDF/DOCX/Office 解析、网页提取、目录监听);
78
95
  > - **首次写入/检索时**才加载并下载嵌入模型(懒加载,启动本身很轻),**默认 `bge-small-zh-v1.5` 约 92 MB**(若显式配了 `KB_EMBED_MODEL=BAAI/bge-m3`,则约 2.2 GB);
79
- > - 国内网络建议加 pip 镜像(实测阿里云可用、清华可能 403):
80
- > `pip install "kb-memory[ingest]" -i https://mirrors.aliyun.com/pypi/simple/`
96
+ > - 国内网络可加 pip 镜像加速(实测阿里云可用、清华可能 403),但镜像同步新包有延迟
97
+ > (实测阿里云对 `kb-memory` 返回过 404);建议同时挂官方源兜底:
98
+ > `pip install "kb-memory[ingest]" -i https://mirrors.aliyun.com/pypi/simple/ --extra-index-url https://pypi.org/simple/`
99
+ > 若仍报 `No matching distribution`,改用官方源:`pip install "kb-memory[ingest]" -i https://pypi.org/simple/`
81
100
  > - 模型下载走 HuggingFace 镜像:`$env:HF_ENDPOINT='https://hf-mirror.com'`(Windows)/ `export HF_ENDPOINT=https://hf-mirror.com`(Linux、macOS)。
82
101
  >
83
102
  > 重要:用源码 venv 安装时 `kb` 命令只装在虚拟环境内。**每个新终端都要先激活虚拟环境**
@@ -98,9 +117,9 @@ curl http://127.0.0.1:8000/api/v1/healthz
98
117
  > `/healthz` 的 `embedder` 字段会显示 `unloaded/loading/ok/error`,模型加载失败时
99
118
  > 写/查请求返回 **503 `EMBED_MODEL_UNAVAILABLE`**(含模型名与修复建议),不再裸 500。
100
119
  >
101
- > **CPU 即可运行,不占显卡**:全新安装默认拿到 CPU 版 torch(`device=cpu`),
102
- > 不占用 GPU 显存,无独立显卡的机器也能完整使用。有显卡想加速(大库、批量入库)
103
- > 可另装 CUDA 版 torch 后显式 `KB_DEVICE=cuda` 启用 GPU 加速。
120
+ > **CPU 即可运行,不占显卡**:Windows / macOS 安装默认使用 CPU 版 torch;
121
+ > Linux 按上面的命令先装 CPU 轮子即可,无独立显卡的机器也能完整使用。
122
+ > 有显卡想加速(大库、批量入库)可另装 CUDA 版 torch 后显式 `KB_DEVICE=cuda`。
104
123
  >
105
124
  > **LLM 默认关闭(`KB_LLM_MODE=off`)**:服务启动不会探测/加载/调用任何大模型,
106
125
  > 零显存、零成本、纯离线;记忆写入、文档入库、混合检索完整可用。
@@ -250,8 +269,9 @@ claude mcp add --transport http kb http://127.0.0.1:8000/mcp/
250
269
  }
251
270
  ```
252
271
 
253
- 挂载后可用的 MCP 工具:`write_memory` / `search_memory` / `read_memory` /
254
- `update_memory` / `delete_memory` / `add_document` / `add_webpage` / `ask_kb`。
272
+ 挂载后可用的 MCP 工具(11 个):`write_memory` / `search_memory` / `read_memory` /
273
+ `update_memory` / `delete_memory` / `link_memory` / `unlink_memory` / `list_links` /
274
+ `add_document` / `add_webpage` / `ask_kb`。
255
275
 
256
276
  > 启用 `KB_API_KEY` 鉴权后,MCP 客户端需在连接配置加 `headers`(`Authorization: Bearer <key>`);
257
277
  > 仓库内 `.mcp.json` 模板不含真实 key(JSON 不支持注释),配法见 [USER_GUIDE §5.2](docs/USER_GUIDE.md#52-客户端适配n20启用鉴权后各客户端如何带-key)。
@@ -265,6 +285,9 @@ claude mcp add --transport http kb http://127.0.0.1:8000/mcp/
265
285
  | GET | `/api/v1/memories/{id}` | 读取单条记忆 |
266
286
  | PATCH | `/api/v1/memories/{id}` | 更新内容或标签 |
267
287
  | DELETE | `/api/v1/memories/{id}` | 删除单条记忆 |
288
+ | POST | `/api/v1/memories/{id}/links` | 建立记忆关联 `{to_id, relation?}`;relation: `related/supersedes/depends_on/part_of/contradicts` |
289
+ | GET | `/api/v1/memories/{id}/links` | 列一跳关联(含内容/方向;`?project=&relation=`) |
290
+ | DELETE | `/api/v1/memories/{id}/links/{to_id}` | 取消关联(任一方向;删除后同步清理) |
268
291
  | POST | `/api/v1/search` | 混合检索 `{query, top_k?, mode?, type?, tag?}`,mode: `hybrid/vector/keyword` |
269
292
  | POST | `/api/v1/documents` | 文档入库:multipart `file` 字段或 JSON `{"path": "本地路径"}` |
270
293
  | GET | `/api/v1/documents` | 已入库文档列表(按 source 聚合) |
@@ -277,6 +300,11 @@ claude mcp add --transport http kb http://127.0.0.1:8000/mcp/
277
300
  | GET | `/api/v1/governance/config` | 治理配置:衰减+新鲜度开关与参数(只读) |
278
301
  | POST | `/api/v1/memories` → **409** | 启用语义去重(`KB_DEDUP_ENABLED=true`)后写入命中重复返回 409:`{"error":"DUPLICATE","duplicate_of":"<已有记录id>","similarity":<相似度>}`(不写入) |
279
302
 
303
+ > 记忆关联默认启用一跳检索扩展:命中的 memory 会带出直接关联的邻居(结果附
304
+ > `via: {id, relation}`),无关联数据时与基础检索完全一致;开关与上限见
305
+ > `KB_LINK_EXPAND_ENABLED` / `KB_LINK_EXPAND_LIMIT`。设计见
306
+ > [memory-links spec](docs/superpowers/specs/2026-10-22-kb-memory-links-design.md)。
307
+ >
280
308
  > 记忆治理(去重/衰减/新鲜度)均默认关闭、零行为变化;用法见 [USER_GUIDE §3.5](docs/USER_GUIDE.md#35-记忆治理a3语义去重--衰减--新鲜度)。
281
309
 
282
310
  示例(Windows PowerShell 可直接复制;中文必须按 UTF-8 字节发送):
@@ -314,11 +342,13 @@ curl -X POST http://127.0.0.1:8000/api/v1/memories \
314
342
  | 配置项 | 默认值 | 说明 |
315
343
  |---|---|---|
316
344
  | `KB_LLM_MODE` | `off` | LLM 模式:`off`(默认,不加载/不调用 LLM,零显存零成本)/ `local`(仅本地 Ollama)/ `auto`(本地优先,云端降级)/ `cloud` |
317
- | `KB_DEVICE` | 空 | 嵌入模型设备:空=自动检测,可显式设 `cpu` / `cuda` |
345
+ | `KB_DEVICE` | 空 | 嵌入模型设备:空或 `auto`=自动检测(非交互启动回退 `cpu`),可显式设 `cpu` / `cuda` |
318
346
  | `KB_WATCH_DIR` | `data` | serve 模式监听目录,文件变动自动入库;空串或 `.` = 不启动 |
319
347
  | `KB_DATA_DIR` | `kb_data` | 运行数据根目录(ChromaDB、运行时状态等) |
320
348
  | `KB_API_HOST` / `KB_API_PORT` | `127.0.0.1` / `8000` | REST 与 MCP 监听地址 |
321
349
  | `KB_EMBED_MODEL` | `BAAI/bge-small-zh-v1.5` | 嵌入模型(轻量默认,约 92 MB);可换 `BAAI/bge-m3`(约 2.2 GB,更强),见「嵌入模型怎么选」 |
350
+ | `KB_TORCH_THREADS` / `KB_EMBED_BATCH_SIZE` | `0` / `32` | 低配 CPU 调优:线程数 `0`=自动(按 cgroup/affinity,最多 4);2G 内存可把批大小降到 8–16 |
351
+ | `KB_IDLE_UNLOAD_SECONDS` | `600` | 嵌入模型空闲 N 秒后卸载;`0`=不卸载 |
322
352
  | `KB_LLM_MODEL` | 空 | 本地 Ollama 模型名(默认空=不配;配 `KB_LLM_MODE=local/auto` 时须按自己电脑选模型,以 `ollama list` 为准) |
323
353
  | `KB_OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama 端点 |
324
354
  | `KB_LLM_API_KEY` / `KB_LLM_BASE_URL` / `KB_LLM_CLOUD_MODEL` | 空 | 云端 LLM(可选):**任意 OpenAI 兼容服务商**(DeepSeek / OpenAI / 通义 / 硅基流动等),仅填在本机 `.env` |
@@ -327,6 +357,8 @@ curl -X POST http://127.0.0.1:8000/api/v1/memories \
327
357
  | `KB_API_KEY` | 空 | 空=不鉴权(本地回环零摩擦);非空=启用 Bearer/X-API-Key 鉴权 |
328
358
  | `KB_RERANK_ENABLED` / `KB_RERANK_MODEL` / `KB_RERANK_TOP_N` | `false` / `BAAI/bge-reranker-v2-m3` / `20` | 检索精排(A3.5):融合候选送 CrossEncoder 重排,默认关 |
329
359
  | `KB_SPARSE_ENABLED` | `false` | 稀疏第三路(A3.5):BGE-M3 稀疏向量 + 倒排索引参与归一化加权融合,默认关 |
360
+ | `KB_LINK_EXPAND_ENABLED` / `KB_LINK_EXPAND_LIMIT` / `KB_LINK_EXPAND_SCORE_RATIO` | `true` / `3` / `0.5` | 记忆关联一跳扩展:每次最多追加邻居数、邻居分数相对种子的比例 |
361
+ | `KB_LINK_MAX_PER_RECORD` | `50` | 单条记忆的关联边上限(防止单点成为巨型枢纽) |
330
362
 
331
363
  ## CLI 速查(无需启动服务)
332
364
 
@@ -337,7 +369,7 @@ python -m kb stats # 统计:类型分布 / 访问
337
369
  python -m kb ask "问题" --client TraeWork # 终端 RAG 问答(LLM 不可用时输出检索命中)
338
370
  python -m kb audit --client TraeWork --days 7 # 查某客户端/项目存过/读过什么
339
371
  python -m kb eval --file tests/eval_zh_50.jsonl # 检索质量评测(Recall@1/@5 + MRR)
340
- python -m kb forget --stale --days 90 # 清理超期未命中记忆
372
+ python -m kb forget --stale --days 90 --dry-run # 全作用域预览(默认 dry-run,删除前自动备份)
341
373
  python -m kb dedup --threshold 0.92 # 语义去重
342
374
  ```
343
375
 
@@ -348,7 +380,7 @@ python -m kb dedup --threshold 0.92 # 语义去重
348
380
  ```
349
381
  kb/ kb 服务源码(config / models / embedder / storage / bm25 / retriever /
350
382
  service / llm / ingest / watcher / api / mcp / cli + reranker / sparse / eval)
351
- tests/ kb 验收测试(425 项通过 / 1 跳过,含 eval_zh_50.jsonl 检索评测数据集)
383
+ tests/ kb 验收测试(462 项通过 / 1 跳过,含 eval_zh_50.jsonl 检索评测数据集)
352
384
  docs/ 用户指南、开发者手册与设计文档
353
385
  kb_data/ kb 运行数据(gitignore)
354
386
  ```
@@ -0,0 +1,2 @@
1
+ """kb:本地 Agent 记忆与知识服务。"""
2
+ __version__ = "2.2.0" # 对应 git tag v2.2.0(生命周期日志 version= 字段用)
@@ -18,6 +18,7 @@ from pydantic import BaseModel, Field, field_validator
18
18
  from kb import __version__
19
19
  from kb.config import Settings, get_settings
20
20
  from kb.idle import start_idle_watchdog
21
+ from kb.links import LinkError
21
22
  from kb.logging_setup import setup_logging
22
23
  from kb.monitor import MonitorAgent, run_once_summary
23
24
  from kb.mcp import create_mcp_server
@@ -203,6 +204,23 @@ class MemoryUpdate(BaseModel):
203
204
  tags: list[str] | None = None
204
205
 
205
206
 
207
+ class LinkCreate(BaseModel):
208
+ """建立记忆关联请求;record_id 为起点,to_id 为目标,relation 默认 related。
209
+ English: Create-link request; record_id is the source, to_id the target, and
210
+ relation defaults to related."""
211
+ to_id: str
212
+ relation: str = "related"
213
+
214
+
215
+ def _link_http_error(exc: LinkError) -> HTTPException:
216
+ """把 LinkError 映射为统一错误 JSON:NOT_FOUND→404,其余约束错误→422。
217
+ English: Map LinkError to the unified error JSON: NOT_FOUND→404, other
218
+ constraint violations→422."""
219
+ status = 404 if exc.code == "NOT_FOUND" else 422
220
+ return HTTPException(status_code=status,
221
+ detail={"error": exc.code, "message": exc.message})
222
+
223
+
206
224
  class SearchRequest(BaseModel):
207
225
  """检索请求;query 必填,top_k/mode 带默认值,type/tag 可选过滤。
208
226
  project:选择项目子库(空=仅全局库;非空=项目子库+全局库);agent_id/client 仅审计。
@@ -684,6 +702,59 @@ def create_app(settings: Settings | None = None,
684
702
  detail={"error": "NOT_FOUND", "message": "记录不存在"})
685
703
  return {"ok": True}
686
704
 
705
+ @app.post("/api/v1/memories/{record_id}/links")
706
+ def create_memory_link(record_id: str, body: LinkCreate,
707
+ agent_id: str = "default",
708
+ client: str = "HTTP",
709
+ project: str | None = None) -> dict:
710
+ """建立两条记忆的关联;重复建边幂等(created=false)。
711
+ English: Link two memories; duplicate edges are idempotent (created=false)."""
712
+ err = validate_project(project)
713
+ if err:
714
+ raise HTTPException(status_code=422, detail={
715
+ "error": "INVALID_ARGUMENT", "message": err})
716
+ try:
717
+ return kb.link_memories(record_id, body.to_id, relation=body.relation,
718
+ agent_id=agent_id, client=client,
719
+ project=project)
720
+ except LinkError as exc:
721
+ raise _link_http_error(exc)
722
+
723
+ @app.get("/api/v1/memories/{record_id}/links")
724
+ def list_memory_links(record_id: str,
725
+ relation: str | None = None,
726
+ project: str | None = None) -> dict:
727
+ """列记忆的一跳邻居(含内容/类型/标签/方向)。
728
+ English: List one-hop neighbors of a memory (content/type/tags/direction)."""
729
+ err = validate_project(project)
730
+ if err:
731
+ raise HTTPException(status_code=422, detail={
732
+ "error": "INVALID_ARGUMENT", "message": err})
733
+ try:
734
+ return kb.list_links(record_id, relation=relation, project=project)
735
+ except LinkError as exc:
736
+ raise _link_http_error(exc)
737
+
738
+ @app.delete("/api/v1/memories/{record_id}/links/{to_id}")
739
+ def delete_memory_link(record_id: str, to_id: str,
740
+ relation: str | None = None,
741
+ agent_id: str = "default",
742
+ client: str = "HTTP",
743
+ project: str | None = None) -> dict:
744
+ """取消两条记忆的关联(任一方向),返回 {"ok": true, "removed": n}。
745
+ English: Remove links between two memories in either direction."""
746
+ err = validate_project(project)
747
+ if err:
748
+ raise HTTPException(status_code=422, detail={
749
+ "error": "INVALID_ARGUMENT", "message": err})
750
+ try:
751
+ removed = kb.unlink_memories(record_id, to_id, relation=relation,
752
+ agent_id=agent_id, client=client,
753
+ project=project)
754
+ except LinkError as exc:
755
+ raise _link_http_error(exc)
756
+ return {"ok": True, "removed": removed}
757
+
687
758
  @app.post("/api/v1/search")
688
759
  def search(body: SearchRequest) -> dict:
689
760
  """混合检索;带 project 时检索该项目子库 + 全局库(项目命中加分)。