Metadata-Version: 2.4
Name: kb-memory
Version: 2.5.0
Summary: Local-first RAG knowledge base & agent memory service: hybrid retrieval + memory governance, REST + MCP, fully usable offline without an LLM
Author: fish827-08
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/fish827-08/rag-kb
Project-URL: Repository, https://github.com/fish827-08/rag-kb
Keywords: mcp,memory,agent,rag,knowledge-base,local-first
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi<1.0,>=0.110
Requires-Dist: uvicorn[standard]<1.0,>=0.29
Requires-Dist: mcp<3.0,>=2.0
Requires-Dist: chromadb<2.0,>=1.5
Requires-Dist: rank_bm25<0.3,>=0.2.2
Requires-Dist: jieba<0.43,>=0.42
Requires-Dist: sentence-transformers<7.0,>=3.0
Requires-Dist: transformers<6,>=4.40
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: langchain-text-splitters<2.0,>=0.3
Requires-Dist: pydantic-settings<3.0,>=2.0
Requires-Dist: typer<1.0,>=0.12
Requires-Dist: rich<16.0,>=13.0
Provides-Extra: ingest
Requires-Dist: pypdf<7.0,>=4.0; extra == "ingest"
Requires-Dist: python-docx<2.0,>=1.1; extra == "ingest"
Requires-Dist: markitdown<0.2,>=0.1; extra == "ingest"
Requires-Dist: openpyxl<4.0,>=3.1; extra == "ingest"
Requires-Dist: xlrd<3.0,>=2.0; extra == "ingest"
Requires-Dist: trafilatura<3.0,>=1.8; extra == "ingest"
Requires-Dist: lxml_html_clean<1.0,>=0.4; extra == "ingest"
Requires-Dist: watchdog<7.0,>=4.0; extra == "ingest"
Provides-Extra: llm
Requires-Dist: openai<4.0,>=1.0; extra == "llm"
Provides-Extra: full
Requires-Dist: pypdf<7.0,>=4.0; extra == "full"
Requires-Dist: python-docx<2.0,>=1.1; extra == "full"
Requires-Dist: markitdown<0.2,>=0.1; extra == "full"
Requires-Dist: openpyxl<4.0,>=3.1; extra == "full"
Requires-Dist: xlrd<3.0,>=2.0; extra == "full"
Requires-Dist: trafilatura<3.0,>=1.8; extra == "full"
Requires-Dist: lxml_html_clean<1.0,>=0.4; extra == "full"
Requires-Dist: watchdog<7.0,>=4.0; extra == "full"
Requires-Dist: openai<4.0,>=1.0; extra == "full"
Dynamic: license-file

# rag-kb — 本地 Agent 记忆与知识服务

<!-- mcp-name: io.github.fish827-08/kb-memory -->

[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
[![Glama MCP score](https://glama.ai/mcp/servers/fish827-08/rag-kb/badges/score.svg)](https://glama.ai/mcp/servers/fish827-08/rag-kb)
[English](README_EN.md) | 中文

**本地优先、完全离线可用的 RAG 知识库与 Agent 记忆服务**——把文档、网页、CSV/Excel 变成可检索、可提问的本地知识库，同时为 Claude Code / Cursor / TraeWork 提供跨会话记忆。REST + MCP 双协议，混合检索（向量 + BM25 归一化加权融合）；不配置 LLM 也能入库和检索。

<!-- GitHub Topics: mcp, memory-service, rag, ai-agent, knowledge-base, local-first, llm, claude-code, hybrid-search, embedding -->

**kb** 是本地优先、完全免费的 RAG 知识库与 Agent 记忆服务（v2.5.0）。

> 开源协议：[Apache-2.0](LICENSE)（含专利授权，可商用）。

## kb — 本地优先的 RAG 知识库与 Agent 记忆服务

Windows 单进程常驻（`python -m kb serve`），把本地文档、网页、CSV/Excel 统一入库，
提供混合检索与 RAG 问答；同一份库同时也是 Claude Code / Cursor / TraeWork
的跨会话记忆。REST + MCP 双协议。

**无 LLM 时存取与检索完整可用**——记忆写入、文档入库、混合检索不依赖任何大模型；
配置本地 Ollama 或云端 API 后，`/ask` 问答能力自动启用。

### 能力边界：LLM 是可选的

| 能力 | 没有 LLM | 有 LLM | 作为 Agent 的 skill |
|---|---|---|---|
| 记忆写入 / 更新 / 删除 | ✅ | ✅ | ✅ |
| 文档 / 表格入库 | ✅ | ✅ | ✅ |
| 混合检索、来源追溯 | ✅ | ✅ | ✅ |
| 表格单元格查询、限定条件 | ✅ 确定性匹配 | ✅ 仍走确定性 | ✅ |
| 文档问答（RAG） | ❌ | ✅ | ✅ Agent 自己生成 |
| 聚合（有多少 / 平均） | ❌ | ✅ | ✅ |
| 概念性 / 模糊问题 | ❌ | ✅ | ✅ |

> 注意：不需要的是 **LLM**；检索仍需要一个轻量嵌入模型（默认 92MB，CPU 可跑），
> 表格问答完全不经过嵌入模型。把 kb 挂进 Agent 时，kb 只负责精确检索，生成交给 Agent。

## 三种用法

### 1. 本地 RAG 知识库：文档、网页、表格进同一个库

这是 kb 的主定位：**一个跑在本机的 RAG 知识库**。PDF、Word、Markdown、网页、
CSV/Excel 都进同一个本地库，检索走「向量 + BM25」混合召回；接上 LLM 后
可以在检索结果上生成带来源的答案。

```powershell
pip install "kb-memory[ingest]"
python -m kb serve
# 打开 http://127.0.0.1:8000/ui，拖入文档或整个文件夹

python -m kb import .\docs --project kb          # 命令行批量入库
python -m kb search "部署步骤" --project kb       # 纯检索，不需要 LLM
python -m kb ask "部署步骤有哪些" --project kb     # 有 LLM 时生成带来源的答案
```

数据不出本机。检索结果带 `source`；`/search` 支持 `group_by_file`，
可以按文件聚合，先看「哪几个文件相关」，再看具体片段。

### 2. 结构化数据：CSV / Excel 精确到单元格

测试和交付现场经常是几十个用例表、导出报表、配置清单，散在不同目录。Excel 手动筛选慢，
跨文件查一个值要在十几个表里翻；数据又不能传到在线文档。

kb 的做法是：**整文件夹导入，逐行入库，离线提问，不需要 LLM。**

```powershell
pip install "kb-memory[ingest]"
python -m kb serve
# 打开 http://127.0.0.1:8000/ui，把整个文件夹拖进去（自动过滤不支持的格式）

python -m kb import "D:\用例数据" --project smoke   # 也可以命令行批量导入
python -m kb ask "用户550的分数是多少" --project smoke
# → 用户550：分数=30     （表格确定性匹配，未配置 LLM 也能答）

python -m kb table query "D:\用例数据\成绩.xlsx" "五班张芳的数学成绩"
# → 直接查单个文件：不建库、不加载嵌入模型，适合先验证表格能否被正确解析
```

> 表格行**不参与向量嵌入**（只进关键词索引和确定性匹配），写入按 2000 条分批
> （Chroma 单次写入上限约 5461）。CPU 实测：
>
> | 数据 | 解析 | 入库 | 吞吐 | 首次查询 | 稳态查询 |
> |---|---|---|---|---|---|
> | 800 行 / 4 sheet（5 列） | 0.02s | 1.17s | ~680 行/秒 | — | — |
> | 20,000 行 / 40 sheet（5 列） | 3.26s | 15.53s | 1288 行/秒 | 1.21s（建缓存） | 7.5ms（问题带表名） |
> | 20,000 行 / 18 sheet（真实业务表 14 列） | 7.97s | 31.80s | 629 行/秒 | 1.57s（建缓存） | 326ms（问题不带表名） |
>
> 稳态差距来自“是否先按 sheet 收窄”：问题带表名/班级时只扫目标 sheet（毫秒级）；
> 不带时扫描全部 2 万行（约 0.3 秒）。首次查询需要从 Chroma 读回并解析表格行建缓存。
> 嵌入模型全程保持 `unloaded`。
>
> 可复现：`python scripts/bench_table_query.py --ingest --sheets 40 --rows 500`。

CSV 用标准库解析，XLSX 用 `openpyxl`、旧版 `.xls` 用 `xlrd` 逐 sheet 读取；每行变成 `列名=值 | ...` 文本，
既能精确命中单元格，也能走向量 + BM25 混合检索。多 sheet 会按表名消歧（问「五班张芳」
只答五班）。它不替代 SQL/BI，适合“快速在一堆表里找一个值/一行记录”的场景。

### 3. Agent / 脚本：REST 与 MCP 都能读写记忆和知识库

同一份服务同时暴露 REST 和 MCP，13 个 MCP 工具覆盖记忆与 RAG 的增删查：

| 能力 | REST | MCP |
|---|---|---|
| 写/查/删记忆 | `POST/GET/DELETE /api/v1/memories` | `write_memory` / `search_memory` / `delete_memory` |
| 文档入库 | `POST /api/v1/documents` | `add_document` / `add_webpage` |
| 展示/删除知识库 | `GET /api/v1/documents`、`DELETE /api/v1/documents/{source}` | `list_documents` / `delete_document` |

> `search_memory(query, table=true)` 会先尝试确定性表格问答：命中返回结构化答案
> `{"table": true, "answer", "sources"}`，未命中回退普通检索。Agent 因此也能精确查表。

```powershell
# 写入一条记忆
Invoke-RestMethod -Uri http://127.0.0.1:8000/api/v1/memories -Method Post `
  -ContentType 'application/json' -Body '{"content":"冒烟用例必须先登录"}'

# 展示已入库资料
Invoke-RestMethod -Uri http://127.0.0.1:8000/api/v1/documents

# 删除某份资料的全部 chunk（source 含斜杠时用 ?source= 查询参数）
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/v1/documents/用例.csv" -Method Delete
```

## 核心特性

- **单进程常驻**：一个 `python -m kb serve` 同时提供 REST API 与 MCP 端点，无需额外组件
- **混合检索**：向量检索 + BM25 关键词检索，归一化加权融合排序（每路 min-max 归一化后按生效路数均权，N32/N33），中文分词友好
  - 嵌入模型：**默认 `BAAI/bge-small-zh-v1.5`（轻量，约 92 MB / 512 维）**——开箱成本低、CPU 上也快；需要更强语义理解可换 `BAAI/bge-m3`（约 2.2 GB / 1024 维），见「[嵌入模型怎么选](#嵌入模型怎么选)」
  - 可选精排：`KB_RERANK_ENABLED=true` 启用 bge-reranker-v2-m3 交叉重排（默认关）
  - 可选三路：`KB_SPARSE_ENABLED=true` 启用 BGE-M3 稀疏向量第三路（默认关，失败自动降级双路；需 bge-m3）
- **记忆管理**：写入 / 更新 / 删除 / 列表，支持 namespace、tags、type 过滤
- **记忆全共享**：所有记忆/知识与任何客户端、任务、AI 全共享——本地单用户定位，跨 agent、跨任务读到同一份用户记忆；client 仅用于审计归类与元数据；主键服务端生成
- **全局 + 分项目记忆（v2.1）**：不传 `project` 写入全局库；传 `project` 写入独立子库 `kb_data/projects/<project>/`（物理隔离，删目录即删项目记忆）；带项目检索 = 项目子库 + 全局库加权合并（`KB_PROJECT_BOOST`）
- **PyPI 安装与依赖瘦身（v2.1）**：`pip install "kb-memory[ingest]"`，摄取/LLM 拆为可选 extras；Linux 可先装 CPU 版 torch，总下载量显著下降
- **Skill 自动安装与发现（v2.1）**：skill 内置 `bootstrap.py`，**询问用户同意后**才建独立环境安装；`kb where` 告诉你装在哪，`kb autostart enable` 开机自启
- **空闲卸载（v2.1）**：`KB_IDLE_UNLOAD_SECONDS` 到期自动卸载嵌入模型并释放显存，后台占用趋零
- **记忆语言与自定义规则（v2.1）**：`KB_MEMORY_LANG=auto` 跟随操作系统语言存储记忆；`~/.kb/rules.md` 自定义"什么时候该存记忆"
- **存取审计**：每次写/读/改/删/检索/问答记 JSON 到 `logs/agent-audit/<客户端>__<项目>.log`（按 client+project 分文件）；用户可查：REST `GET /api/v1/audit?client=<客户端>[&project=<项目>]` 或 CLI `kb audit --client <客户端>`
- **知识入库**：本地文档（txt/md/pdf/docx 等）上传或路径导入，支持**整文件夹批量导入**并自动过滤不支持的文件；网页正文抓取入库
- **表格导入与精准问答（v2.4）**：CSV / XLS / XLSX 逐行入库（`列名=值` 文本），可直接问「小明的语文成绩」这类单元格问题，**无需 LLM 也能精确作答**；多 sheet（如 15 个班级表）按表名消歧（「五班张芳」只答五班），另支持总分/平均等简单聚合、同名多行、按值反查与薪酬/学历/地点限定
- **知识图谱可视化（v2.4）**：Web UI 用 SVG 展示记忆节点与关联边，点击节点查看内容；文档/网页/表格不进入图谱
- **RAG 与记忆分离（v2.4）**：文档/网页/表格属于 RAG 知识库，**不参与遗忘与衰减**，只在显式删除时移除；衰减/新鲜度仅作用于 memory
- **审计加固（v2.4.2）**：检索相关性下限 `KB_MIN_VECTOR_SCORE`（默认 0.30）、`type/tag` 过滤提前、网页入库 SSRF 防护与 5MB 响应上限、单文件上传上限 `KB_MAX_UPLOAD_BYTES`（默认 100MB）、统一错误 JSON、非 ASCII API Key 兼容、项目计数重启不丢
- **大库读取分页（v2.4.3）**：`iter_all` 改为每批 500 条分页读取，降低大库打开/重建 BM25 时的峰值内存；新增 680 条库 5 次进程重启的回归用例
- **MCP 文档管理（v2.4.4）**：新增 `list_documents` / `delete_document`，MCP 侧也能展示与删除 RAG 知识库（共 13 个工具）
- **Excel 兼容性与问答优化（v2.4.5）**：兼容不带 `<dimension>` 的导出文件（原先会报“表格为空”）、识别标题行与合并单元格、支持 `.xls`；多 sheet 同名实体只返回目标列有值的表，公式单元格提示“为公式”，来源按字段两列展示；`/search` 支持 `table=true`，仅检索也能返回结构化表格答案
- **单例与模型选择（v2.4.6）**：同一 `KB_DATA_DIR` 只允许一个长驻实例（数据目录锁，换端口不能绕过）；`kb serve --port 8001` 支持换端口；`kb table query` 可直接查单个表格文件；`/search` 支持 `group_by_file` 按文件聚合；MCP `search_memory` 支持 `table=true`；Web UI 可选择嵌入模型（库为空时），并保证至少保留一个模型；表格行跳过向量嵌入，批量导入显著提速；多实体查询（王五和欧阳娜娜）不再漏人
- **嵌入模型迁移（v2.5.0）**：每个模型独立 collection，换模型不再要求清空数据；迁移后台执行，**迁移期间只读（写入 409、读取与关键词检索继续）**，全量同步完成后才切换；失败保留旧库可重试；支持删除非活跃模型的数据
- **目录监听**：指定目录内新增/删除文件自动入库/清理（`KB_WATCH_DIR`）
- **CLI 工具**：`kb add/search/stats/ask/table query/eval/forget/dedup`——终端直接完成写入、检索、统计、单表查询与 RAG 问答
- **隐私护栏**：敏感 namespace 强制本地回答不出网；`/ask` 智能路由（本地优先，难题可选云端）
- **断网可用**：模型与数据全部落本地，无网络时存取与检索功能完整

## 快速开始（Linux / Windows / macOS）

**Linux（推荐，含 2 核 2G 小服务器）：先装 CPU 版 torch，再装 kb。**

```bash
pip install torch --index-url https://download.pytorch.org/whl/cpu   # CPU 轮子约 196 MB
pip install "kb-memory[ingest]"
python -m kb serve
```

> 不先装 CPU 版 torch 时，PyPI 默认会拉 CUDA 版本（torch + cudnn + nccl 等合计 **2–3 GB**），
> 这是 Linux 新用户最大的下载坑；只做记忆存取与检索不需要显卡。

> **首次启动会下载嵌入模型**（默认约 92 MB）。国内网络建议先设置
> `HF_ENDPOINT=https://hf-mirror.com`；下载完成并缓存后可完全断网运行。
>
> **同一 `KB_DATA_DIR` 只允许一个实例**。端口被占时用 `python -m kb serve --port 8001`
> 换端口；换端口**不能**绕过单例限制——两个进程写同一份数据会损坏索引。
> 需要并行跑两个实例时，分别设置不同的 `KB_DATA_DIR`。

**2 核 2G 服务器建议配置**（写入 `.env` 或环境变量）：

```ini
KB_DEVICE=cpu
KB_LLM_MODE=off
KB_TORCH_THREADS=2          # 0=自动（按 cgroup/affinity 配额，最多 4）
KB_EMBED_BATCH_SIZE=16      # 默认 32；内存紧张降到 8–16
KB_IDLE_UNLOAD_SECONDS=300  # 空闲 5 分钟卸载嵌入模型
```

启动阶段不加载嵌入模型（百 MB 级）；首次检索/写入才加载默认 `bge-small`（约 92 MB），
加载后常驻通常 < 1 GB，检索为毫秒级，2 核 2G 可运行。LLM 问答（`/ask`）在 2G 上
建议保持关闭或使用云端。

**Windows / macOS：**

```bash
pip install "kb-memory[ingest]"   # 核心 + 文档/网页摄取；[llm]=云端问答，[full]=全部
python -m kb serve
```

**源码开发安装：**

```powershell
python -m venv venv
.\venv\Scripts\Activate.ps1          # Linux/macOS: source venv/bin/activate
pip install -r requirements.txt
pip install -e ".[full]"             # 开发/测试需要摄取、监听与 LLM 可选依赖
python -m kb serve
```

> **首次安装要下载什么、大概多久**（先看一眼，避免以为卡死了）：
> - Linux 先装 CPU 版 `torch`（约 **196 MB**）后，整条核心依赖约 **0.5–0.7 GB**；
>   Windows / macOS 核心依赖约 **400–600 MB**（Windows CPU 版 `torch` 约 124 MB 为最大单体）；
> - 可选 `[ingest]` 额外约几十 MB（PDF/DOCX/Office 解析、网页提取、目录监听）；
> - **首次写入/检索时**才加载并下载嵌入模型（懒加载，启动本身很轻），**默认 `bge-small-zh-v1.5` 约 92 MB**（若显式配了 `KB_EMBED_MODEL=BAAI/bge-m3`，则约 2.2 GB）；
> - 国内网络可加 pip 镜像加速（实测阿里云可用、清华可能 403），但镜像同步新包有延迟
>   （实测阿里云对 `kb-memory` 返回过 404）；建议同时挂官方源兜底：
>   `pip install "kb-memory[ingest]" -i https://mirrors.aliyun.com/pypi/simple/ --extra-index-url https://pypi.org/simple/`
>   若仍报 `No matching distribution`，改用官方源：`pip install "kb-memory[ingest]" -i https://pypi.org/simple/`
> - 模型下载走 HuggingFace 镜像：`$env:HF_ENDPOINT='https://hf-mirror.com'`（Windows）/ `export HF_ENDPOINT=https://hf-mirror.com`（Linux、macOS）。
>
> 重要：用源码 venv 安装时 `kb` 命令只装在虚拟环境内。**每个新终端都要先激活虚拟环境**
> （Windows `.\venv\Scripts\Activate.ps1`，Linux/macOS `source venv/bin/activate`），
> 否则会提示 `kb：未找到命令`。改用 `python -m kb <子命令>` 可绕过激活。
> 不知道自己的 kb 装在哪里、怎么启动：`kb where`，或运行 skill 包里的
> `python bootstrap.py --status` / `--start-only`。

启动后健康检查：

```powershell
curl http://127.0.0.1:8000/api/v1/healthz
```

**浏览器界面（2026-11-03 新增）**：打开 `http://127.0.0.1:8000/ui`，可直接上传文档
（含整个文件夹）、抓取网页、查看已入库文档并做 RAG 问答（含来源片段与分数）；
页面底部的「记忆图谱」用 SVG 展示记忆节点与关联边，点击节点可查看内容。
未配置 LLM 时勾选「仅检索」即可先验证召回；CSV / Excel 表格可直接提问。
页面为纯本地静态文件，离线可用。

**LLM 也可以在页面里配**：右上角「设置」支持 off / local(Ollama) / auto / cloud 四种模式，
填 Ollama 地址与模型（可一键读取 `ollama list`）或云端 Base URL / 模型 / Key，保存即生效
（成功/失败都有页面提示），配置落在 `kb_data/llm_config.json`（不入库），重启后沿用。
云端不预填任何服务商，可填任意 OpenAI 兼容端点。

**界面中英双语**：右上角 `EN / 中文` 一键切换（与 README 双语文档一致）；问答有计时与超时提示，
首次查询若在加载嵌入模型会明确提示，不会看起来像卡死。

> 嵌入模型采用懒加载：启动后空闲时几乎不占内存，**首次写入或检索**才加载
> `BAAI/bge-small-zh-v1.5`（约 92 MB，自动下载并缓存，之后断网可用）；
> 未配置 LLM 时服务照常启动，`/ask` 返回 503 与配置指引。
> `/healthz` 的 `embedder` 字段会显示 `unloaded/loading/ok/error`，模型加载失败时
> 写/查请求返回 **503 `EMBED_MODEL_UNAVAILABLE`**（含模型名与修复建议），不再裸 500。
>
> **CPU 即可运行，不占显卡**：Windows / macOS 安装默认使用 CPU 版 torch；
> Linux 按上面的命令先装 CPU 轮子即可，无独立显卡的机器也能完整使用。
> 有显卡想加速（大库、批量入库）可另装 CUDA 版 torch 后显式 `KB_DEVICE=cuda`。
>
> **LLM 默认关闭（`KB_LLM_MODE=off`）**：服务启动不会探测/加载/调用任何大模型，
> 零显存、零成本、纯离线；记忆写入、文档入库、混合检索完整可用。
> 需要 RAG 问答（`/ask`）时再按下面方式配置本地或云端 LLM。

### 嵌入模型怎么选

默认用轻量模型，是为了让"先试试"的成本足够低；模型不强绑定，可随时按需切换：

| 模型 | 体积 | 维度 | 加载后常驻内存 | 适用 |
|---|---|---|---|---|
| **`BAAI/bge-small-zh-v1.5`（默认）** | **约 92 MB** | 512 | 约 100 MB 量级 | 日常偏好/决策/笔记类记忆；CPU 上也快 |
| `BAAI/bge-base-zh-v1.5` | 约 400 MB | 768 | 约 500 MB | 召回更好，内存与延迟中等 |
| `BAAI/bge-m3` | 约 2.2 GB | 1024 | 约 2 GB | 长文档、跨语言、语义更微妙的内容，需要更强语义召回 |

> 本项目在自带 50 题中文评测集上的实测（`kb eval`）：轻量模型 Recall@1 **0.760** / MRR **0.847** /
> 平均延迟 **28.6 ms**；bge-m3 为 0.740 / 0.841 / 105.6 ms。小样本下二者基本持平，轻量模型更快更省。
> 但 **bge-m3 在长文档与跨语言场景预期更强**，两个模型定位不同，按自己数据选。

**换模型前必读**：向量维度不同（512 vs 1024），ChromaDB 集合维度由首次写入的向量决定，
**不同模型的向量不能混在同一个 collection**。所以：

- **新库**（还没写过数据）：直接设 `KB_EMBED_MODEL=BAAI/bge-m3` 即可；
- **已有数据的库**：kb 会自动**沿用建库时使用的模型**（记录在 `kb_data/runtime.json`），
  不会因默认值变化而破坏你的数据，启动日志会记录实际沿用的模型；
  确要换模型，用下面的 Web UI 迁移或 `POST /api/v1/embed/migrate`。

**Web UI 选择模型（v2.4.6 / v2.5.0）**：右上角「设置 → 嵌入模型」可直接切换，选择会保存到
`kb_data/embed_config.json`，重启后仍然生效。两条硬规则：

- **至少要保留一个模型**：空模型名会被拒绝并提示「清空后服务将无法检索」。默认模型始终可用；
- **库中已有数据时走迁移**：v2.5.0 起切换不再要求先清空，而是把现有数据迁移到新模型。

**嵌入模型迁移（v2.5.0，只读窗口）**：

- 每个模型在各自作用域里有独立 collection，旧模型的数据不会被覆盖；
- 迁移在后台执行：**写入暂停（API 返回 409），读取继续**，语义检索临时降级为关键词（BM25）
  检索；迁移完成后自动切回混合检索；
- 全部数据同步完成才切换模型；**失败保留旧库与旧模型**，可直接重试（目标 collection 会增量修正）；
- 迁移完成后旧模型数据仍占磁盘，可在设置面板里逐个「删除数据」，或
  `POST /api/v1/embed/models/delete`（需 `confirm` 等于模型名）；
- 建议在空闲时段迁移：低配机器上两个模型不会同时驻留（迁移前会先卸载旧模型）。
- **可复现验收**：仓库内 `samples/`（记忆 / md / txt / html / json / docx / csv / 多 sheet xlsx）
  配合 `scripts/verify_model_migration.py`，迁移前后各跑一遍检索与表格问答；用法见
  [samples/README.md](samples/README.md)。

```powershell
# 迁移（库为空时等价于立即切换）；进度查询
curl -X POST http://127.0.0.1:8000/api/v1/embed/migrate -H "Content-Type: application/json" -d '{"model":"BAAI/bge-m3"}'
curl http://127.0.0.1:8000/api/v1/embed/migrate
```

### 配置 LLM（可选，`/ask` 问答需要）

`KB_LLM_MODE` 默认 `off`（不加载不调用）；四个档位：

| 档位 | 行为 |
|---|---|
| `off`（默认） | 完全不加载/不调用 LLM；记忆存取与检索完整可用 |
| `local` | 仅本地 Ollama（完全离线，隐私零出网） |
| `auto` | **本地优先，云端降级**：本地 Ollama 可用走本地，无本地但有云端 Key 走云端 |
| `cloud` | 全部走云端（本地仅做压缩与隐私隔离） |

**本地 LLM（Ollama）：**

```powershell
# 1. 安装并启动 Ollama（Windows 从开始菜单/托盘启动，不要从 AI 沙箱终端拉起）
# 2. 拉取一个适合你电脑的模型（按显存/内存选择，如 qwen3:4b 约 3.2GB、
#    qwen3:1.7b 约 1.8GB；国内可用魔搭加速，拉完 ollama cp 改成短名）
ollama pull <你的模型名>
# 3. 在 .env 配置后重启服务：
#    KB_LLM_MODE=local            # 仅本地
#    KB_LLM_MODEL=<你的模型名>    # 以 `ollama list` 输出的名称为准
#    KB_OLLAMA_BASE_URL=http://localhost:11434
```

**云端 LLM（任意 OpenAI 兼容服务商，不绑定 DeepSeek）：**
DeepSeek / OpenAI / 通义千问 / 硅基流动 / Moonshot 等均可，通用三键：

```ini
# .env
KB_LLM_MODE=auto                 # 本地优先、云端降级；或 cloud 全云端
KB_LLM_API_KEY=sk-xxx            # 服务商 API Key
KB_LLM_BASE_URL=https://api.deepseek.com   # 换成你所用服务商的 OpenAI 兼容端点
KB_LLM_CLOUD_MODEL=deepseek-v4-flash       # 云端模型名
```

验证：`GET /api/v1/healthz` 的 `llm` 字段——`local`/`cloud` 表示 LLM 已就绪，`disabled` 表示未启用。

### 常见问题：嵌入模型下载失败

- **中国大陆直连 `huggingface.co` 会超时**。设置 HF 镜像后重启即可：
  ```bash
  export HF_ENDPOINT=https://hf-mirror.com   # 或写入 ~/.bashrc 永久生效
  python -m kb serve
  ```
  模型会自动从镜像下载并缓存到 `~/.cache/huggingface/hub/`，之后断网也能离线加载。
- **模型已在本机缓存，但无外网**：`kb` 采用离线优先（先命中本地缓存，失败才联网），
  只要缓存目录完整即可完全离线运行。
- **报错里出现「torch 版本不满足 transformers 要求」**：transformers 检测到 torch 过旧会
  静默禁用 PyTorch 后端，底层只抛 `NameError: name 'nn' is not defined`。`kb` 在加载模型前
  预检这条依赖链，并直接给出两个版本号——按提示升级 torch（`pip install -U torch`）即可。

### 常见问题：重启后报 `Error loading hnsw index`

- **先排除外部环境**：不要用受限终端/沙箱与普通终端交替打开同一个 `kb_data`，也不要强杀进程。
  外部清理 `chroma.sqlite3-journal` 会让 Chroma 的 HNSW 段在下次启动时读不出来。
- **先停服务并备份**整个 `kb_data/`（至少 `chroma.sqlite3`）。
- 若记录仍在、只是索引不可读：删除对应 `chroma/<segment-id>/` 目录（保留 `chroma.sqlite3`）
  后重启，Chroma 会从库内向量重建索引。这是最后手段，务必先备份。
- 回归测试已覆盖「680 条 × 5 次进程重启」；仍复现请带日志与数据目录结构提 issue。

## 让 Agent 接入 kb（客户端无关）

把 kb 的接入规约交给 AI 客户端（TraeWork / Claude Code / Cursor / 自建 Agent），
让它们知道怎么读写记忆、按什么身份规约、怎么查审计。**两种方式，任选其一**：

1. **skill（推荐，能自动触发）——可选的独立步骤**：仓库内
   [`skills/kb-memory/SKILL.md`](skills/kb-memory/SKILL.md)
   是客户端无关的 Anthropic 开格式 skill。把它安装到你所用客户端的用户级 skills 目录后，
   该客户端的任何项目会话都会在读写记忆/RAG 问答/审计查询时**自动识别并触发**。
   安装 = 把 `skills/kb-memory` 目录复制过去即可（有脚本，也可手动复制，无需任何依赖）；
   更新（重新覆盖）、卸载、以及装好后的使用说明见
   [`scripts/README.md`](scripts/README.md)。

   > **不装也不影响 kb 服务**：skill 只是给 AI 客户端的「提示词包装」，与服务的安装、
   > 启动无关——跳过这一步，服务照常运行，你随时可用方法 2 的纯文本提示词接入；
   > skill 安装是**一次性、按需、独立执行**的，不会随 `kb serve` 自动触发，
   > 也不会写入你的任何客户端目录以外的文件。
2. **纯文本提示词（兜底，任何客户端通用）**：整段复制
   [`docs/AGENT_PROMPT.md`](docs/AGENT_PROMPT.md) 粘贴给 Agent 即可，不依赖 skill 机制。

> **`.trae-cn/skills` / `.claude/skills` / `.cursor/skills` 是任何客户端都认的「标准」吗？——不是。**
> 这些只是各家客户端各自的**用户级约定目录**：`SKILL.md` 本身是统一的 Anthropic 开格式，
> 但「装到哪个目录、能否自动触发」由各客户端自行决定，支持程度不一：

| 客户端 | 用户级 skills 目录 | 自动加载 |
|---|---|---|
| TraeWork | `~/.trae-cn/skills/` | 自动发现 |
| Claude Code | `~/.claude/skills/` | 高版本支持 |
| Cursor | `~/.cursor/skills/` | 逐步跟进 |
| 其他 / 自建 Agent | 无统一约定 | 需手动加载或不支持 |

> 不存在「所有客户端都遵循」的统一目录；你的客户端若不支持 skill，**永远有方法 2 兜底**
> （粘贴 `AGENT_PROMPT.md`，纯文本任何客户端可用）。安装方法、各客户端目录差异与加载机制、
> 相互引用关系：详细见 [`scripts/README.md`](scripts/README.md)（此处不重复）。

### 打包为独立 skill（Agensi / marketplace）

`skills/kb-memory/` 是 **kb 的独立交付单元**，只依赖 kb 服务本身：
包含标准 front matter 的 `SKILL.md`（包根）、MCP 配置模板 `mcp.json`、配置模板
`env.example`、独立 README 与 LICENSE，覆盖「安装 → 启动 → 配置 → 验证 → 排障」全流程。

```powershell
python scripts/build_skill_package.py        # 产物：dist/kb-memory-skill-<版本>.zip
```

zip 内 `SKILL.md` 位于**包根目录**，不含测试、运行数据或任何密钥；
构建脚本会校验 front matter、自包含说明与敏感信息。可直接提交到 Agensi 这类
skill marketplace，用户拿到包后按 `SKILL.md` 即可把本机 kb 接入自己的 Agent。

## MCP 挂载

MCP 端点（streamable HTTP）：`http://127.0.0.1:8000/mcp/`

**Claude Code**：本仓库已内置项目级 `.mcp.json`，在本目录启动 Claude Code 即自动挂载；
也可全局添加：

```powershell
claude mcp add --transport http kb http://127.0.0.1:8000/mcp/
```

**Cursor / TraeWork 及其他支持 MCP 的客户端**：在 MCP 配置中加入以下 JSON
（Cursor 放 `~/.cursor/mcp.json` 或项目 `.cursor/mcp.json`；TraeWork 在设置中添加 MCP 服务器）：

```json
{
  "mcpServers": {
    "kb": {
      "type": "http",
      "url": "http://127.0.0.1:8000/mcp/"
    }
  }
}
```

挂载后可用的 MCP 工具（13 个）：`write_memory` / `search_memory` / `read_memory` /
`update_memory` / `delete_memory` / `link_memory` / `unlink_memory` / `list_links` /
`add_document` / `add_webpage` / `list_documents` / `delete_document` / `ask_kb`。

> 启用 `KB_API_KEY` 鉴权后，MCP 客户端需在连接配置加 `headers`（`Authorization: Bearer <key>`）；
> 仓库内 `.mcp.json` 模板不含真实 key（JSON 不支持注释），配法见 [USER_GUIDE §5.2](docs/USER_GUIDE.md#52-客户端适配n20启用鉴权后各客户端如何带-key)。

## REST 端点速查

| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/v1/memories` | 写入记忆 `{content, tags?, source?, namespace?}` |
| GET | `/api/v1/memories` | 记忆列表，支持 `type/tag/source/q/limit/offset` 过滤 |
| GET | `/api/v1/memories/{id}` | 读取单条记忆 |
| PATCH | `/api/v1/memories/{id}` | 更新内容或标签 |
| DELETE | `/api/v1/memories/{id}` | 删除单条记忆 |
| POST | `/api/v1/memories/{id}/links` | 建立记忆关联 `{to_id, relation?}`；relation: `related/supersedes/depends_on/part_of/contradicts` |
| GET | `/api/v1/memories/{id}/links` | 列一跳关联（含内容/方向；`?project=&relation=`） |
| DELETE | `/api/v1/memories/{id}/links/{to_id}` | 取消关联（任一方向；删除后同步清理） |
| POST | `/api/v1/search` | 混合检索 `{query, top_k?, mode?, type?, tag?, table?, group_by_file?}`，mode: `hybrid/vector/keyword`；`table=true` 先尝试确定性表格问答；`group_by_file=true` 额外返回按文件聚合的 `files` 数组 |
| GET | `/api/v1/embed` | 当前嵌入模型、可选列表、迁移状态与各模型数据量 |
| POST | `/api/v1/embed/config` | 立即切换嵌入模型（仅空库；非空库请用 `/embed/migrate`） |
| POST | `/api/v1/embed/migrate` | 迁移现有数据到新模型（后台执行；库为空时立即切换；迁移期间写入返回 409） |
| GET | `/api/v1/embed/migrate` | 查询迁移状态与进度（`status/percent/done/total/readonly`） |
| POST | `/api/v1/embed/models/delete` | 删除非活跃模型的数据 `{model, confirm}`（`confirm` 必须等于模型名） |
| POST | `/api/v1/documents` | 文档/表格入库：multipart `file` 字段或 JSON `{"path": "本地路径"}`；CSV/XLS/XLSX 逐行入库 |
| GET | `/api/v1/documents` | 已入库文档列表（按 source 聚合，含 `kind=doc/web/table`） |
| DELETE | `/api/v1/documents/{source}` | 按 source 删除该文档全部记录（本地文件名等简单 source） |
| POST | `/api/v1/admin/reset` | 清空指定项目或全部数据；必须提供 `confirm`（项目名，或“清空全部”） |
| DELETE | `/api/v1/documents?source=<urlencoded>` | 按查询参数删除（**URL 等含斜杠的 source 必须用这个**） |
| POST | `/api/v1/ingest/web` | 网页入库 `{url}`，抓取正文切分入库；同 URL 重复入库自动替换旧记录 |
| POST | `/api/v1/ask` | RAG 问答 `{question, top_k?}`（`top_k` 默认 5，长文档可调至 8~10）；表格问句优先确定性作答（`llm="table"`），未配置 LLM 且非表格问题返回 503 |
| GET | `/api/v1/graph` | 记忆知识图谱 `?project=&limit=`（默认 200，上限 500）：memory 节点 + 关联边（v2.4） |
| GET | `/api/v1/llm` | 当前 LLM 配置与可用性（Key 只回是否已设置） |
| POST | `/api/v1/llm/config` | 运行时保存云端 / Ollama 配置并热生效 |
| GET | `/api/v1/llm/ollama/models` | 列出 Ollama 已安装模型（`?base_url=` 可覆盖） |
| GET | `/api/v1/healthz` | 健康检查与服务统计（含 `embedder` 状态） |
| GET | `/api/v1/governance/stats` | 记忆治理统计：`total_count`/`avg_access_count`/`stale_90d_count`（只读） |
| GET | `/api/v1/governance/config` | 治理配置：衰减+新鲜度开关与参数（只读） |
| POST | `/api/v1/memories` → **409** | 启用语义去重（`KB_DEDUP_ENABLED=true`）后写入命中重复返回 409：`{"error":"DUPLICATE","duplicate_of":"<已有记录id>","similarity":<相似度>}`（不写入） |

> 记忆关联默认启用一跳检索扩展：命中的 memory 会带出直接关联的邻居（结果附
> `via: {id, relation}`），无关联数据时与基础检索完全一致；开关与上限见
> `KB_LINK_EXPAND_ENABLED` / `KB_LINK_EXPAND_LIMIT`。设计见
> [memory-links spec](docs/superpowers/specs/2026-10-22-kb-memory-links-design.md)。
>
> 记忆治理（去重/衰减/新鲜度）均默认关闭、零行为变化；用法见 [USER_GUIDE §3.5](docs/USER_GUIDE.md#35-记忆治理a3语义去重--衰减--新鲜度)。

示例（Windows PowerShell 可直接复制；中文必须按 UTF-8 字节发送）：

```powershell
function Invoke-KbJson($Uri, $Body) {
  Invoke-RestMethod -Uri $Uri -Method Post -ContentType 'application/json; charset=utf-8' `
    -Body ([System.Text.Encoding]::UTF8.GetBytes(($Body | ConvertTo-Json -Depth 5)))
}

# 写入一条记忆
Invoke-KbJson http://127.0.0.1:8000/api/v1/memories @{ content = "用户偏好深色主题"; tags = @("偏好") }

# 混合检索
Invoke-KbJson http://127.0.0.1:8000/api/v1/search @{ query = "用户界面偏好"; top_k = 5 }

# 按 URL 删除网页文档（source 需 URL 编码；PowerShell 用 [uri]::EscapeDataString）
$src = [uri]::EscapeDataString("https://example.com/page")
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/v1/documents?source=$src" -Method Delete
```

Linux / macOS 用 `curl`（`curl` 在这里是原生命令，不是 PowerShell 别名）：

```bash
curl -X POST http://127.0.0.1:8000/api/v1/memories \
  -H "Content-Type: application/json" \
  -d '{"content": "用户偏好深色主题", "tags": ["偏好"]}'
```

## 配置项简表

全部配置以 `KB_` 前缀的环境变量或 `.env` 文件提供；完整键名见 [`.env.example`](.env.example)
（复制为 `.env` 后填写，`.env` 已被 gitignore，真实密钥只放本机，严禁入库）。

| 配置项 | 默认值 | 说明 |
|---|---|---|
| `KB_LLM_MODE` | `off` | LLM 模式：`off`（默认，不加载/不调用 LLM，零显存零成本）/ `local`（仅本地 Ollama）/ `auto`（本地优先，云端降级）/ `cloud` |
| `KB_DEVICE` | 空 | 嵌入模型设备：空或 `auto`=自动检测（非交互启动回退 `cpu`），可显式设 `cpu` / `cuda` |
| `KB_WATCH_DIR` | `data` | serve 模式监听目录，文件变动自动入库；空串或 `.` = 不启动 |
| `KB_DATA_DIR` | `kb_data` | 运行数据根目录（ChromaDB、运行时状态等） |
| `KB_API_HOST` / `KB_API_PORT` | `127.0.0.1` / `8000` | REST 与 MCP 监听地址 |
| `KB_EMBED_MODEL` | `BAAI/bge-small-zh-v1.5` | 嵌入模型（轻量默认，约 92 MB）；可换 `BAAI/bge-m3`（约 2.2 GB，更强），见「嵌入模型怎么选」 |
| `KB_TORCH_THREADS` / `KB_EMBED_BATCH_SIZE` | `0` / `32` | 低配 CPU 调优：线程数 `0`=自动（按 cgroup/affinity，最多 4）；2G 内存可把批大小降到 8–16 |
| `KB_IDLE_UNLOAD_SECONDS` | `600` | 嵌入模型空闲 N 秒后卸载；`0`=不卸载 |
| `KB_LLM_MODEL` | 空 | 本地 Ollama 模型名（默认空=不配；配 `KB_LLM_MODE=local/auto` 时须按自己电脑选模型，以 `ollama list` 为准） |
| `KB_OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama 端点 |
| `KB_LLM_API_KEY` / `KB_LLM_BASE_URL` / `KB_LLM_CLOUD_MODEL` | 空 | 云端 LLM（可选）：**任意 OpenAI 兼容服务商**（DeepSeek / OpenAI / 通义 / 硅基流动等），仅填在本机 `.env` |
| `KB_CHUNK_SIZE` / `KB_CHUNK_OVERLAP` | `500` / `100` | 文档切分参数 |
| `KB_SENSITIVE_NAMESPACES` | 空 | 逗号分隔的敏感 namespace，命中强制本地回答不出网 |
| `KB_API_KEY` | 空 | 空=不鉴权（本地回环零摩擦）；非空=启用 Bearer/X-API-Key 鉴权 |
| `KB_RERANK_ENABLED` / `KB_RERANK_MODEL` / `KB_RERANK_TOP_N` | `false` / `BAAI/bge-reranker-v2-m3` / `20` | 检索精排（A3.5）：融合候选送 CrossEncoder 重排，默认关 |
| `KB_SPARSE_ENABLED` | `false` | 稀疏第三路（A3.5）：BGE-M3 稀疏向量 + 倒排索引参与归一化加权融合，默认关 |
| `KB_LINK_EXPAND_ENABLED` / `KB_LINK_EXPAND_LIMIT` / `KB_LINK_EXPAND_SCORE_RATIO` | `true` / `3` / `0.5` | 记忆关联一跳扩展：每次最多追加邻居数、邻居分数相对种子的比例 |
| `KB_LINK_MAX_PER_RECORD` | `50` | 单条记忆的关联边上限（防止单点成为巨型枢纽） |

## CLI 速查（无需启动服务）

```powershell
python -m kb add "记忆内容" --tags 偏好 --client TraeWork                       # 写入（project 缺省自动取当前目录名）
python -m kb import .\docs --project kb                              # 批量导入文件/目录（一次编码、一次建 BM25）
python -m kb search "查询词" --client TraeWork                                 # 混合检索（v2.1：不传 project 查全局；传 project 合并子库）
python -m kb stats                            # 统计：类型分布 / 访问热度 / 陈旧分布
python -m kb ask "问题" --client TraeWork      # 终端 RAG 问答（LLM 不可用时输出检索命中）
python -m kb audit --client TraeWork --days 7   # 查某客户端/项目存过/读过什么
python -m kb eval --file tests/eval_zh_50.jsonl   # 检索质量评测（Recall@1/@5 + MRR）
python -m kb forget --stale --days 90 --dry-run  # 全作用域预览（默认 dry-run，删除前自动备份）
python -m kb dedup --threshold 0.92           # 语义去重
```

> `kb ask` 直连本地服务逻辑（不经 HTTP）；建议 `serve` 停止时使用，避免双进程写库竞争。

## 目录结构

```
kb/            kb 服务源码（config / models / embedder / storage / bm25 / retriever /
               service / llm / ingest / tables / watcher / api / mcp / cli +
               reranker / sparse / eval）
tests/         kb 验收测试（543 项通过 / 1 跳过，2026-11-04 实测；含 eval_zh_50.jsonl 检索评测数据集）
docs/          用户指南、开发者手册与设计文档
kb_data/       kb 运行数据（gitignore）
```

## 更多文档

- **用户使用手册**：[docs/USER_GUIDE.md](docs/USER_GUIDE.md)
- 设计文档（需求、架构、API、里程碑）：`docs/superpowers/specs/2026-08-23-kb-memory-service-design.md`
- **v2.1 设计（当前）**：`docs/superpowers/specs/2026-10-20-kb-v2.1-design.md`
- 开发者维护手册：`docs/DEV_HANDBOOK.md`


