轻量级 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>
347 lines
8.8 KiB
Markdown
347 lines
8.8 KiB
Markdown
# 萌芽 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-vec(rag.sqlite3)
|
||
```
|
||
|
||
当前索引规模参考:
|
||
|
||
```text
|
||
笔记文件数: 495
|
||
分块数: ~1800
|
||
rag.sqlite3: ~10MB(chunk 表 + 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),主要方向:
|
||
|
||
- 中文分词换 jieba(BM25 召回率最大瓶颈)
|
||
- 放宽文件去重(当前每文件只取 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
|
||
```
|