Skip to main content
Glama
alexiuhubhiu

my-kb

by alexiuhubhiu
README.md
# my-kb 个人知识库

本地离线、可溯源、中文友好的 MCP 文档检索服务(个人 agent 生态知识底座)。

> **使用者请从这里开始 → [docs/USER_GUIDE.md](docs/USER_GUIDE.md)**
> 白话手册:这东西是什么、资料放哪儿、怎么入库、搜不到怎么办、怎么让 AI 带出处作答。

- **格式**:Markdown / txt / PDF(文本层)/ docx;扫描件 PDF 可选 RapidOCR 摄取通道
- **检索**:SQLite FTS5 trigram + bm25 + 学科术语扩展(弱语义);短查询/引擎不可用自动 LIKE 回退
- **溯源**:每块固化 `domain / doc_id / 文档标题 / 章节链(section_path) / 页码或偏移(page_or_offset)`
- **离线硬约束**:零网络调用、零 API key、零 embedding/向量依赖
- **接口**:MCP(FastMCP stdio,4 个 `kb_*` 工具)与 CLI(6 子命令)复用同一批 handler

## 快速开始

```bash
# 1) 安装(全新 venv;OCR 通道额外安装 [ocr] extra:rapidocr_onnxruntime,pip 装包自带模型、之后纯离线)
python -m venv .venv
.venv/Scripts/pip install -e ".[dev,ocr]"

# 2) 摄取(增量;可用 --glossary 指定术语表;默认读 <kb_root>/_glossary/<domain>.md)
my-kb ingest --domain ds --source D:\kb_root\ds --glossary D:\kb_root\_glossary\ds.md

# 2b) 扫描件(无文本层 PDF):显式 --ocr 触发 RapidOCR 摄取(溯源标注 第N页(OCR))
my-kb ingest --domain math --source D:\kb_root\math --ocr

# 3) 检索
my-kb search --domain ds "什么是平衡二叉树" -k 3
```

## CLI 命令

| 命令 | 用途 |
| --- | --- |
| `my-kb ingest --domain <d> --source <dir> [--glossary <f>] [--ocr] [--prune] [--force-ocr]` | 增量摄取:新增/变更;`--ocr` 对扫描 PDF 走 OCR;`--prune` 才允许废弃 source 内缺失文档;`--force-ocr` 强制重建已入库废块 PDF |
| `my-kb rebuild --domain <d> --source <dir> [--glossary <f>] [--ocr]` | 整 domain 权威重建 |
| `my-kb sync [--root <kb_root>] [--domain <d>] [--ocr] [--prune] [--dry-run]` | **批量增量摄取**:遍历 `<root>/<domain>` 逐域入库(跳过 `_`/`.` 前缀目录),术语表取 `<root>/_glossary/<domain>.md`;目录名与 domain 不同名时读 `<root>/_domains.md` 映射;`--dry-run` 只比对不写库 |
| `my-kb status [--domain <d>]` | 库/域统计 |
| `my-kb search --domain <d> "<query>" [-k 5] [--no-expand]` | 自测检索(与 kb_search 同引擎) |
| `my-kb terms --domain <d> [--file <f>]` | 打印/校验术语表解析 |
| `my-kb backup [--keep 10]` | 手动备份 db → `data/backup/` |
| `my-kb web [--host 127.0.0.1] [--port 8787] [--no-open]` | 启动本地 Web 控制台(见下一节) |

## Web 控制台(本地 GUI,v0.3)

不想敲命令时用这个:一个只监听 `127.0.0.1` 的本地页面,能看域健康状况、搜资料、跑同步/入库/废弃/备份,长任务日志实时滚动。

```powershell
# 方式一:双击(Windows 启动器;优先用仓库内 .venv,其次按 PATH 解析)
scripts\start_gui.bat

# 方式二:CLI 子命令(与 serve.py 共用同一份启动逻辑)
my-kb web --port 8787

# 方式三:直接起 ASGI
python serve.py --port 8787
```

- 默认地址 `http://127.0.0.1:8787/`,启动后自动开浏览器(加 `--no-open` 可关掉)。
- **只本机可访问**:Host/Origin 白名单仅 `127.0.0.1`/`localhost`;非 GET 请求要求 `Content-Type: application/json`(挡跨站表单)。用 curl 手测记得带 `-H "Content-Type: application/json"`。
- 启动器怎么挑解释器:`MY_KB_PYTHON` → 仓库内 `.venv\Scripts\python.exe` → `py -3` → `python`,每个候选都由 `scripts\check_env.py` 自检(能 `import fastapi, uvicorn` **并且**能真的构造 `FastAPI()`)。全都不合格时会把每个候选的失败原因打在屏幕上并暂停,不会一闪而过。
- 根目录清单存在 `data/gui_config.json`(首次启动自动生成,页面「设置」里改)。默认两个根:资料域 `D:\kb_root`、产出域 `D:\knowledge\subjects`;目录不存在的根会留空路径并提示去设置里配。
- 写操作全部走**单线程任务队列**(串行,避免抢 SQLite 写锁);任务日志用 SSE 推送——断线重连带 `Last-Event-ID` 续传,缓冲超过 2000 条会先提示「已截断」。
- `prune` 仍然只是**废弃**(`status=deprecated`,不删源文件);`rebuild`(整域重建)会物理清空该域索引,页面会二次确认。
- 前端是零构建原生 HTML/JS(无 CDN、无 node_modules),只有 `web/static/` 下三个文件。
- 术语表面板会诊断「只做了扩展词、没有独立词条」的词——直接查这些词是 0 结果(踩过两次的那个坑)。

## MCP 工具

| 工具 | 说明 |
| --- | --- |
| `kb_search(domain, query, top_k=5, expand=True)` | 词面检索 + 溯源片段;0 结果给 hint |
| `kb_domains()` | domain 统计(选域前探测) |
| `kb_ingest(domain, source, mode="incremental", glossary="", ocr=False)` | 摄取;mode=rebuild 重建该域;ocr=True 对扫描 PDF 走 RapidOCR |
| `kb_forget(domain, rel_path="", doc_id=0)` | 按 rel_path/doc_id 废弃文档 |

启动 MCP server(stdio):`python server.py` 或由 MCP 客户端运行器拉起。

## 知识库根目录约定(`D:\kb_root`)

资料根与代码库、学习产出目录三者分离:

```
D:\kb_root\                资料根(教材/讲义/习题等第三方资料,不进代码库)
  _glossary\<domain>.md    术语表(权威;每 domain 一个)
  _raw\<domain>\...        暂不入库的原始件(如 OCR 效果差的板书),`_` 前缀 → sync 自动跳过
  <domain>\...             一级子目录名即 domain(如 gaoshu / hcie)
D:\knowledge\subjects\     产出根(笔记/思维导图/实验),与资料分离
  _domains.md              目录名 → domain id 映射(中文/连字符目录名必需)
  _glossary\<domain>.md    产出域术语表(与资料域同构,各自镜像)
  <科目目录>\...            一级子目录名经 _domains.md 映到 domain(如 hcie-datacom → hcie_notes)
```

- **一条命令更新整个知识库**:`my-kb sync --root D:\kb_root --ocr`(逐域增量、幂等、默认不废弃)。
- 新增一门课 = 建 `D:\kb_root\<拼音小写 domain>\` + `_glossary\<domain>.md`,无需改代码。
- 摄取是**运维动作**(CLI),不暴露给 LLM;客户端只拿到只读的 `kb_ask` / `kb_coverage`。

### 资料域 / 产出域(双域分工)

同一科目的**第三方资料**与**自产产出**分成两个 domain,`kb_ask` 返回的溯源因此能区分
「教材原话」与「我自己的总结」。命名约定:**裸名 = 资料域,`_notes` 后缀 = 产出域**。

| 域 | 根目录 | 内容 | domain 示例 |
| --- | --- | --- | --- |
| 资料域 | `D:\kb_root` | 教材 / 讲义 / 习题 / 实验手册(只读) | `hcie` |
| 产出域 | `D:\knowledge\subjects` | 笔记 / 思维导图 / 实验记录(LLM 写入) | `hcie_notes` |

产出根的一级目录是人读的中文/连字符名,而 domain id 只允许 `[a-z0-9_-]`,
故用 `_domains.md` 解耦(一行 `目录名 = domain_id`;`#` 注释与空行忽略;
未映射的目录若名字本身非法则跳过该域并打印提示,不中断整批)。

```bash
my-kb sync --root D:\knowledge\subjects --domain hcie_notes
```

### Obsidian 兼容

产出域的笔记常由 Obsidian 编辑,摄取时按 md 做三项归一,避免编辑器语法污染索引:

- **YAML frontmatter 整块置空**(保留行号,溯源「行N」不错位);
- **围栏代码块内的 `#` 不当标题**(VRP / Python 配置注释极常见),围栏行本身不入正文;
- **`[[双链]]` / `![[嵌入]]` 归一化为可见文本**(`[[目标|别名]]`→别名、`[[目标#锚点]]`→目标)。

扫描时跳过路径中任一层以 `.` 或 `_` 开头的条目:

- `.` 开头是工具目录(`.git` / `.obsidian` / `.trash` / `.workbuddy`),尤其 `.trash` 装的是被删笔记;
- `_` 开头是「**存档但不索引**」约定(与根目录 `_glossary` / `_raw` 同源)。典型用法是产出域里的
  `_索引/` 主题索引页——它们是 Obsidian 关系图谱的骨架,但入库只会跟正文抢检索排名。

索引页由 `scripts/gen_vault_index.py` 生成(`--dry-run` 预览、缺省刷新全部登记科目 +
`vault/_索引/知识库总览.md` 总览页)。原因:Obsidian 图谱只认 `[[双链]]`,不认目录结构,
没有枢纽页就是一地孤岛。脚本是纯增量产物,不改动正文;科目配置见其 `SUBJECTS` 登记表。

## 客户端挂载(任意 MCP 客户端)

my-kb-bridge 是标准 stdio MCP 服务,任何 MCP 客户端(Kun / Claude Code / Codex / …)都能挂,
差别只在各自的配置文件位置。最小配置(`command` 换成自己的解释器路径):

```json
{
  "mcpServers": {
    "my-kb-bridge": {
      "type": "stdio",
      "command": "<python>",
      "args": ["D:/my_kb/server_bridge.py"],
      "env": {
        "PYTHONUTF8": "1",
        "PYTHONIOENCODING": "utf-8",
        "MY_KB_PERSONA_DIR": "D:\\my_agents_v7\\personas",
        "MY_KB_BRIDGE_DEFAULT_PERSONA": "tutor",
        "MY_KB_ROOT": "D:\\kb_root"
      }
    }
  }
}
```

- `MY_KB_ROOT` 必须指向资料根:术语表默认路径与检索期术语扩展都靠它(不设则回退 DB 镜像,且术语表编辑不即时生效)。
- `MY_KB_PERSONA_DIR` 指向 my_agents 的人设目录,`kb_ask` 据此读 `## visible_domains` 做限域检索。
- 本机解释器为 `C:\Users\1249840596\.workbuddy\binaries\python\envs\default\Scripts\python.exe`
  (editable 安装 my-kb,含 OCR 依赖);客户端挂载后**需重启客户端**才生效。

## 二期:my-kb-bridge(人设可见域编排,只读)

把检索能力按 **agent 人设可见域** 编排给上层 LLM(与 my-agents 记忆底座联动,`server_bridge.py`):

- **角色边界**:my-agents 管“记忆 + 人设即文件”;本服务只读消费 my_kb 库,按人设 `profile.md` 的
  `## visible_domains` 声明限域检索;不写任何数据、不认识 agent 的记忆,纯本地离线。
- **人设声明**(在 my_agents 的 `personas/<name>/profile.md` 里加可选节):

  ```markdown
  ## visible_domains

  - gaoshu
  - ds
  ```

- **启动**(stdio,单独挂载,与 my-kb / my-agents 并列):

  ```bash
  set MY_KB_PERSONA_DIR=D:\my_agents_v7\personas        # 缺省即此值
  set MY_KB_BRIDGE_DEFAULT_PERSONA=tutor          # 缺省人设(可选)
  python D:\my_kb\server_bridge.py
  ```

| 工具 | 说明 |
|---|---|
| `kb_ask(question, persona="", top_k=5, expand=True)` | 按人设可见域检索:命中返回带溯源 hits,无命中返回 `coverage=false` 提示 |
| `kb_coverage(persona="")` | 该人设可见域及各域在库统计(文档/块/词条),对话前探测用 |

- **行为约定**:domain 只能来自人设声明;未声明 = 知识库不可用(返回明确提示);绝不跨域检索。

## OCR 摄取通道(扫描件 PDF)

- **引擎**:`rapidocr_onnxruntime`(PaddleOCR 的 ONNX 移植,CPU 本地推理)。
  安装即随包获得模型(“装包=首次获取”),识别全程纯离线、零 API key。
- **触发**:OCR 是显式能力。无文本层 PDF 不带 `--ocr` 仍报“OCR 不支持”;带 `--ocr` 逐页识别,
  产物与文本层 PDF 同构进入同一 chunks 表,溯源标注 `第N页(OCR)`(如 `第3页(OCR)`、跨页 `第3-4页(OCR)`)。
- **质量取舍**:印刷体中文可读性高;数学公式/符号允许识别错误——溯源会指回原 PDF 页码供人工复核。
  单页识别失败记日志跳过;整份全败进 ingest 的 failed 清单,不中断整批。
- **“有没有文本层”怎么判**(`core/parsers.text_layer_pages_valid`,四重门槛):
  文档总字符 ≥ 40、有效正文页 ≥ 1、有效页正文合计 ≥ 30、**有效页占比 ≥ 10%**。
  只管页码戳的 PDF、以及**「扫描件 + 文字封面/目录」**(323 页书只有封面目录带字,
  总字符量大但占比 1.5%)都会判为无文本层 → 走 OCR;否则入库的只有目录、正文全丢。
  实测真教材占比 34%~99%,门槛留了足够余量。代价:一份已按文本层入库的 PDF
  不会自动改判,需 `rebuild --ocr` 重跑该域。

## 删除与安全(废弃策略)

- **默认只增改、不废弃**:incremental ingest 永不把缺失文件标为 deprecated。
- `--prune`:仅当显式开启时才废弃 **本次 source 目录内** 扫描缺失的 active 文档
  (按 `source_path` 前缀判定);目录外文档绝不触碰——只 ingest 讲义子目录不会误废弃
  板书/习题等其他子目录的文档。废弃前报告给出 `预检:…废弃 N 篇` 提示。
- `rebuild` 语义不变:整 domain 权威重建。
- `--force-ocr`:对已入库但内容为废块(PDF 只有页码/页眉戳被误判为文本层)的文档,
  即使文件 hash 未变也强制以 OCR 通道重建(溯源标注 `第N页(OCR)`)。

## 目录约定

```
D:\kb_root\           资料根(用户原文,不进代码库)
  _glossary\ds.md     术语表(每 domain 一个)
  ds\...              一级子目录即 domain
D:\my_kb\             本仓库
  data\my_kb.sqlite3  索引库(单库+domain 列过滤)
  data\backup\        迁移/手动备份
  data\logs\          日志
  data\gui_config.json Web 控制台的根目录清单(首次启动生成,gitignore)
  core\               解析/切块/摄取/检索核心
  web\                Web 控制台(FastAPI 应用 + 零构建前端)
  serve.py            Web 控制台唯一启动入口(my-kb web 转发到这里)
  tests\              pytest 与回归集
```

## 术语表格式

```markdown
# ds.md
# 以 # 开头为注释;词条 = 扩展词1, 扩展词2, ...
二叉树 = 二叉树, binary tree, 二叉排序树, BST
平衡二叉树 = 平衡二叉树, AVL树, AVL tree, 高度平衡树
```

检索 query 子串命中“原词”时,自动扩展为“原词 + 扩展词”合并进 FTS MATCH(可用 `--no-expand` 关闭)。

## 数据一致性

- 文档级 SHA-256 整文件哈希判变更;增量更新单文档事务,删除 = `deprecated` 留档(保留行、不检索)
- `rebuild` 整 domain 单事务权威重建;chunks 的一切写路径经 `core/fts.py` 代码同步 FTS
- 迁移版本化(`PRAGMA user_version`),迁移前自动 backup(保留最近 10 份)

## 回归与测试

```bash
python -m pytest tests/ -q
```

回归集 `tests/regression_queries.py` 含 22 条概念题,G3 Top-1 命中率 ≥ 80%、P95 < 3s(CI 宽松上限)。