memorag
by yjb136698605
README.md
# memorag
把一堆 Markdown 笔记变成一个**可检索的知识库**:一条命令建索引,查询返回最相关的几段,零 LLM token、零外部服务、纯本地 SQLite。
面向的场景是「给 Agent 用的长期记忆」:在几千个笔记块里,用一句中文自然问法找到那一段,再把原文喂给模型。也可以直接当个人笔记搜索用。
## 它解决什么问题
纯 `grep` 只能字面匹配,换个说法就找不到;纯向量库又经常把泛泛而谈的段落排在真正答案前面,而且中文短查询效果不稳。memorag 把两者**分层**用:
- **关键词永远是主召回路径**(FTS5 + trigram)
- **向量只做重排补充**(可选,未装 `fastembed` 自动退化为纯 FTS)
- 四路候选按 **RRF(倒数排名融合)** 合并,再叠加时间衰减与访问反馈
最关键的细节是中文查询的分词:SQLite 没有中文分词器,所以正文按**字**切分(`unicode61`),查询侧同时构造 4 字滑窗短语和 2 字短语。只做 4 字滑窗会有一个隐蔽的坑——「周报」「待办」这类双字实体常被切在窗口边界上(`份 周 报`、`些 待 办 和`),而 FTS 短语要求连续相邻,错位一个字就完全匹配不到。
## 快速开始
```bash
pip install memorag # 只要关键词检索,无第三方依赖
pip install 'memorag[embed]' # 加语义重排(fastembed + ONNX,CPU 可跑)
pip install 'memorag[mcp]' # 作为 MCP 服务供 Agent 调用
```
```bash
memorag init # 生成 ~/.memorag/memorag.toml
$EDITOR ~/.memorag/memorag.toml
memorag build
memorag search "周报怎么生成"
```
从源码:
```bash
git clone <repo> && cd memorag
pip install -e '.[all]'
python -m unittest discover -s tests
```
## 配置
```toml
[index]
db = "~/.memorag/index.db"
model = "BAAI/bge-small-zh-v1.5" # 留空则只做 FTS5 + trigram
model_dir = "~/.memorag/models"
# weights = { fts = 1.0, trigram = 0.6, spell = 0.85, vector_knowledge = 0.7, vector_other = 0.35 }
# kind_bonus = { memory = 4.0, archive = 2.4, doc = 2.2, task = 2.6, activity = 0.0, snapshot = -20.0 }
[[sources]]
name = "notes"
path = "~/notes"
kind = "memory" # 自定义类别,检索时可用 --kind 过滤
pattern = "*.md"
recursive = true
exclude = ["archive/**", "**/.trash/**"]
[[sources]]
path = "~/notes/archive"
kind = "archive"
recursive = true
strip_frontmatter = true # 剥掉 YAML 头,避免元数据污染召回
[[sources]]
path = "~/logs/2026-11-06.md"
kind = "activity"
recursive = false
chunker = "activity" # 按「- HH:MM 内容」逐行切块
```
`kind_bonus` 是排序时的类别加权:知识类给正分、日志类给 0、快照类给负分。这样「某规则怎么定」这类问题不会把流水日志顶上来。
## 命令
```bash
memorag build # 全量重建(并发时后来者自动跳过)
memorag search "查询" -n 8 # 检索,返回编号 + 路径 + 摘要
memorag search "查询" --kind doc --json
memorag get 12,34 # 按编号取全文
memorag stats # 块数 / 词表 / 向量 / 库大小
memorag serve # MCP stdio 服务
```
MCP 模式暴露四个工具:`search`、`get_chunks`、`reindex`、`stats`。典型用法是两步:先 `search` 拿摘要和编号,需要原文时再 `get_chunks`,避免把整库塞进上下文。
## 索引结构
| 表 | 作用 |
|---|---|
| `idx` | FTS5 主索引,正文按字切分,带 `kind/path/label/date` 元数据 |
| `idx_tri` | FTS5 trigram,做子串召回(关键词被写在不同位置时兜底) |
| `vec` | 块向量(float32 blob),按文本 hash 对账,重建后自动重挂 |
| `vocab` | ASCII 词频表,用于查询拼写纠错 |
| `use_feedback` | 访问反馈:被搜索命中记 `hits`,被取全文记 `uses`,用于下次排序提权 |
反馈按**文本 hash** 记账而不是 rowid——每次重建 rowid 都会重排,按 id 记会错挂到别的块上。
## 评测
仓库带一个 57 条中文用例的评测脚本思路,同一套用例上:
| 版本 | top3 命中 |
|---|---|
| 仅 4 字滑窗 | 82.5% |
| 加 2 字短语 | 89.5% |
| 再加英文专名加权 | 91.2% |
主要瓶颈是**排序和分词**,不是语义理解——所以先修这两块,而不是急着微调 embedding 模型。评测时记得设 `MEMORAG_NO_FEEDBACK=1`,否则跑评测本身会通过 `use_feedback` 改变排序,两次结果对不上。
权重扫描的结论(向量权重 0.7 / 0.85 / 1.0 / 1.2 / 1.6 → top3 91.2 / 91.2 / 91.2 / 87.7 / 89.5):默认 0.7 已经够,调高只是让个别条目挪位置。
## 已知限制
- 中文按字切分,等同于「无分词」方案:对同义词、缩写无能为力,靠 4 字/2 字短语和向量兜底
- 全量重建而非增量更新:几千块约 15 秒,十万块需要改成增量
- 向量模型体积与首次下载依赖 `fastembed`;不装则纯 FTS,效果下降但可用
- `fcntl` 文件锁仅 Unix;Windows 下并发重建需自行加锁
## 许可证
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues