轻量级 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>
8.8 KiB
8.8 KiB
萌芽 RAG 知识库
轻量版 Obsidian 笔记 RAG 项目,基于 LlamaIndex 和 DeepSeek API。 检索使用 Markdown 结构感知分块 + 本地小 embedding + BM25 的混合检索,适合个人 Obsidian 笔记这种规模。
项目结构
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
- 服务端:无,当前是命令行工具
初始化
cd /shumengya/project/agent/mengya-rag
cp .env.example .env
编辑 .env,填入:
DEEPSEEK_API_KEY=你的 DeepSeek API Key
安装依赖:
uv sync
同步笔记
笔记源路径:
bigmengya:/shumengya/docker/mengyanote-backend/data/mengyanote/
同步到本地:
./scripts/sync_notes.sh
# 或
uv run python scripts/sync_notes.py
构建索引
./scripts/build_index.sh
# 或
uv run python scripts/build_index.py
提问
./scripts/ask.sh "萌芽笔记是什么"
# 或
uv run python scripts/ask.py "萌芽笔记是什么"
也可以直接使用安装后的命令:
uv run mengya-ask "Docker 常用命令有哪些"
Agent 调用推荐
统一入口是 mengya-rag。给大模型 Agent 或脚本调用时,推荐固定使用 --json,输出是一行 JSON,便于解析。
# 查看配置、索引和笔记数量
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:一条命令完成同步笔记和重建索引。
常用参数:
--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 分块
项目不使用固定字符数暴力切割,而是使用结构感知分块:
Markdown 文件
↓
解析 frontmatter
↓
识别 H1-H6 标题
↓
按标题层级组织分块
↓
代码块和表格整体保留
↓
长块使用滑动窗口兜底
每个 chunk 前会附加上下文前缀:
文件: AI/控制台Agent工具安装教程.md
标题: 控制台Agent工具安装教程
标题路径: H1 > H2 > H3
每个 chunk 会保存这些元数据:
source_file
rel_path
file_name
folder_path
title
heading_path
h1
h2
h3
h4
h5
h6
chunk_index
tags
created_at
索引构建
执行:
./scripts/build_index.sh
构建流程:
Markdown 文件
↓
结构感知分块 → TextNode
↓
bge-small-zh-v1.5 生成 embedding → 归一化
↓
写入 SQLite + sqlite-vec(rag.sqlite3)
当前索引规模参考:
笔记文件数: 495
分块数: ~1800
rag.sqlite3: ~10MB(chunk 表 + sqlite-vec 向量表)
普通问答检索
普通问答适合“怎么做”“是什么”“原理是什么”这类问题。
用户问题
↓
query 清洗
↓
BM25 关键词检索
↓
本地向量语义检索
↓
RRF 融合
↓
同文件去重
↓
相关性过滤
↓
top-k chunk 注入 DeepSeek
↓
生成回答和来源
盘点列表检索
盘点列表适合“有哪些”“查一下”“相关笔记”“目录”“清单”这类问题。
用户问题
↓
识别为盘点类问题
↓
构建文件级摘要节点
↓
文件级 BM25 检索
↓
关键词覆盖过滤
↓
输出 Markdown 文件列表、路径和内容预览
重新更新知识库
笔记变更后按顺序执行:
./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 构建相关内容
示例:
./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,主要方向:
- 中文分词换 jieba(BM25 召回率最大瓶颈)
- 放宽文件去重(当前每文件只取 1 chunk,长文章不友好)
- QA Prompt 加强引用约束,降低幻觉
- 注入上下文加 token 预算(节省费用 + 提升精度)
- 可选:换
BAAI/bge-m3嵌入模型(中英混合效果更好) - 增量索引,只重建变更文件
- 加 reranker,例如
bge-reranker-v2-m3 - 数据量变大后接 Qdrant
调参
.env 中可调整:
TOP_K=6
EMBED_MODEL=BAAI/bge-small-zh-v1.5
EMBED_BATCH_SIZE=32
VECTOR_WEIGHT=0.65
BM25_WEIGHT=0.35