feat: 初始提交 mengya-rag 知识库项目

轻量级 Obsidian Markdown RAG 系统,包含:
- Markdown 结构感知分块(标题层级 + 代码块/表格整体保留)
- FastEmbed + BAAI/bge-small-zh-v1.5 本地向量化
- SQLite + sqlite-vec 向量库(无需外部服务)
- BM25 + 向量混合检索,RRF 融合
- 盘点模式(有哪些/列表/目录类问题)
- DeepSeek API 生成回答
- mengya-rag CLI 工具(ask/search/context/read/sync/index/status)
- docs/RAG优化方案.md 待实施优化计划

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
shumengya
2026-06-19 14:12:06 +08:00
commit c410d69970
21 changed files with 4902 additions and 0 deletions

346
README.md Normal file
View File

@@ -0,0 +1,346 @@
# 萌芽 RAG 知识库
轻量版 Obsidian 笔记 RAG 项目,基于 LlamaIndex 和 DeepSeek API。
检索使用 Markdown 结构感知分块 + 本地小 embedding + BM25 的混合检索,适合个人 Obsidian 笔记这种规模。
## 项目结构
```text
mengya-rag/
├── data/
│ ├── notes/ # 从 bigmengya 同步来的 Obsidian Markdown 笔记(不入库)
│ └── index/bm25/ # 本地索引目录(不入库)
│ └── rag.sqlite3 # SQLite + sqlite-vec 本地向量库
├── docs/
│ └── RAG优化方案.md # 下一步优化计划
├── scripts/
│ ├── sync_notes.sh # 同步 bigmengya 笔记
│ ├── sync_notes.py
│ ├── build_index.sh # 构建索引
│ ├── build_index.py
│ ├── ask.sh # 命令行问答
│ └── ask.py
├── src/mengya_rag/
│ ├── config.py # 配置读取
│ ├── markdown_chunker.py # Markdown 结构感知分块
│ ├── embeddings.py # Embedding 生成FastEmbed / SentenceTransformers
│ ├── vector_store.py # SQLite + sqlite-vec 向量存储
│ ├── indexing.py # 建索引(分块 + 向量写入)
│ ├── retrieval.py # 混合检索、RRF 融合、盘点模式
│ ├── qa.py # DeepSeek 生成回答
│ └── cli.py # CLI 命令入口
├── .env.example
├── pyproject.toml
└── README.md
```
## 技术栈
- RAG 基础框架LlamaIndex
- 本地 embedding`BAAI/bge-small-zh-v1.5`
- embedding 运行FastEmbed + ONNX
- 大模型回答DeepSeek API
- 当前推荐模型:`deepseek-v4-flash`
- 检索方式BM25 + 向量检索
- 融合方式RRF 类融合 + 原始相似度过滤 + 同文件去重
- 数据库:无
- 向量库SQLite + sqlite-vec
- 服务端:无,当前是命令行工具
## 初始化
```bash
cd /shumengya/project/agent/mengya-rag
cp .env.example .env
```
编辑 `.env`,填入:
```env
DEEPSEEK_API_KEY=你的 DeepSeek API Key
```
安装依赖:
```bash
uv sync
```
## 同步笔记
笔记源路径:
```text
bigmengya:/shumengya/docker/mengyanote-backend/data/mengyanote/
```
同步到本地:
```bash
./scripts/sync_notes.sh
# 或
uv run python scripts/sync_notes.py
```
## 构建索引
```bash
./scripts/build_index.sh
# 或
uv run python scripts/build_index.py
```
## 提问
```bash
./scripts/ask.sh "萌芽笔记是什么"
# 或
uv run python scripts/ask.py "萌芽笔记是什么"
```
也可以直接使用安装后的命令:
```bash
uv run mengya-ask "Docker 常用命令有哪些"
```
## Agent 调用推荐
统一入口是 `mengya-rag`。给大模型 Agent 或脚本调用时,推荐固定使用 `--json`,输出是一行 JSON便于解析。
```bash
# 查看配置、索引和笔记数量
uv run mengya-rag status --json
# 只检索,不调用大模型,返回命中文档和内容
uv run mengya-rag search "Docker 常用命令" -k 5 --json
# 返回可直接注入上游大模型的 context 字段
uv run mengya-rag context "WireGuard 怎么配置" -k 4 --json
# 按 search/context 返回的 source 读取完整笔记
uv run mengya-rag read "Docker/Docker常用命令总结.md" --json
# 本项目自己调用 DeepSeek 生成回答
uv run mengya-rag ask "Docker 常用命令有哪些" -k 4 --json
# 同步笔记并重建索引
uv run mengya-rag reindex --json
```
Agent 优先使用这几个命令:
- `status --json`:检查索引是否存在、笔记数、节点数、模型配置。
- `search --json`:拿结构化检索结果,包含 `results[].source``title``heading_path``score``content`
- `context --json`:拿拼好的 `context` 字符串,适合直接放进其他大模型提示词。
- `read --json`:按 `source` 读取完整 Markdown 原文,适合检索命中后补充上下文。
- `ask --json`:让本项目完成 RAG + DeepSeek 回答,返回 `answer``sources`
- `reindex --json`:一条命令完成同步笔记和重建索引。
常用参数:
```bash
--mode auto|hybrid|inventory # auto 默认hybrid 普通语义检索inventory 盘点/目录类检索
-k, --top-k 数字 # 控制返回条数
--env-file /path/.env # 指定配置文件
--notes-dir /path/notes # 覆盖笔记目录
--index-dir /path/index # 覆盖索引目录
```
## 查询模式
项目会自动区分两类问题:
- 普通问答例如“WireGuard 命令怎么用”,走 Markdown 分块 + 向量/BM25 混合检索。
- 盘点列表:例如“查一下我目前博客文章有哪些”“查一下安卓 Gradle 相关笔记”,走文件级目录检索,优先列出相关 Markdown 文件和内容预览。
## 技术原理
### Markdown 分块
项目不使用固定字符数暴力切割,而是使用结构感知分块:
```text
Markdown 文件
解析 frontmatter
识别 H1-H6 标题
按标题层级组织分块
代码块和表格整体保留
长块使用滑动窗口兜底
```
每个 chunk 前会附加上下文前缀:
```text
文件: AI/控制台Agent工具安装教程.md
标题: 控制台Agent工具安装教程
标题路径: H1 > H2 > H3
```
每个 chunk 会保存这些元数据:
```text
source_file
rel_path
file_name
folder_path
title
heading_path
h1
h2
h3
h4
h5
h6
chunk_index
tags
created_at
```
### 索引构建
执行:
```bash
./scripts/build_index.sh
```
构建流程:
```text
Markdown 文件
结构感知分块 → TextNode
bge-small-zh-v1.5 生成 embedding → 归一化
写入 SQLite + sqlite-vecrag.sqlite3
```
当前索引规模参考:
```text
笔记文件数: 495
分块数: ~1800
rag.sqlite3: ~10MBchunk 表 + sqlite-vec 向量表)
```
### 普通问答检索
普通问答适合“怎么做”“是什么”“原理是什么”这类问题。
```text
用户问题
query 清洗
BM25 关键词检索
本地向量语义检索
RRF 融合
同文件去重
相关性过滤
top-k chunk 注入 DeepSeek
生成回答和来源
```
### 盘点列表检索
盘点列表适合“有哪些”“查一下”“相关笔记”“目录”“清单”这类问题。
```text
用户问题
识别为盘点类问题
构建文件级摘要节点
文件级 BM25 检索
关键词覆盖过滤
输出 Markdown 文件列表、路径和内容预览
```
## 重新更新知识库
笔记变更后按顺序执行:
```bash
./scripts/sync_notes.sh
./scripts/build_index.sh
```
## 设计取舍
- Markdown 笔记按结构感知分块:识别 frontmatter、H1-H6、代码块、表格。
- H1-H6 标题会写入 `h1``h6` 元数据,同时保存 `heading_path` 面包屑。
- 代码块和表格整体保留,不跨块切割;长块再用滑动窗口兜底。
- 每个 chunk 都带 `source_file``heading_path``chunk_index``tags``created_at`
- 本地 embedding 使用 `BAAI/bge-small-zh-v1.5`,通过 FastEmbed/ONNX 运行,不依赖 PyTorch 和 GPU。
- 检索采用 BM25 + 向量召回,再用 RRF 融合和同文件去重,避免只靠关键词或只靠语义。
- 使用 DeepSeek 只负责生成回答:减少 API 调用,只在提问时调用大模型。
- 本地向量库使用 SQLite + sqlite-vec无需 Qdrant、Milvus、Postgres 等额外服务。
## 当前效果
目前比较适合处理:
- 某类笔记有哪些
- 某个教程在哪里
- 根据笔记总结某个主题
- 列出博客文章
- 列出 Agent 安装教程
- 查 Gradle 构建相关内容
示例:
```bash
./scripts/ask.sh '查一下我目前博客文章有哪些'
./scripts/ask.sh '查一下我的安卓gradle构建相关笔记'
./scripts/ask.sh '查一下我的Agent安装教程'
```
## 当前限制
- 当前是 SQLite + sqlite-vec 单机向量库,数据量很大后可以换 Qdrant 或 Chroma。
- 没有 reranker复杂问题可能还会有弱相关内容混入。
- embedding 模型是轻量中文模型,效果够用但不是最强。
- `build_index` 每次是全量重建,还没有增量索引。
- frontmatter 解析是轻量手写版,不是完整 YAML 解析。
- 当前是 CLI 工具,没有 Web 页面和 API 服务。
## 后续优化方向
详见 [docs/RAG优化方案.md](docs/RAG优化方案.md),主要方向:
- 中文分词换 jiebaBM25 召回率最大瓶颈)
- 放宽文件去重(当前每文件只取 1 chunk长文章不友好
- QA Prompt 加强引用约束,降低幻觉
- 注入上下文加 token 预算(节省费用 + 提升精度)
- 可选:换 `BAAI/bge-m3` 嵌入模型(中英混合效果更好)
- 增量索引,只重建变更文件
- 加 reranker例如 `bge-reranker-v2-m3`
- 数据量变大后接 Qdrant
## 调参
`.env` 中可调整:
```env
TOP_K=6
EMBED_MODEL=BAAI/bge-small-zh-v1.5
EMBED_BATCH_SIZE=32
VECTOR_WEIGHT=0.65
BM25_WEIGHT=0.35
```