谁说 RAG 一定要向量库?Markdown + SQLite 压榨出毫秒级文档检索
说到 RAG(检索增强生成),业界最惯常的思维往往是:上向量数据库、调 Embedding 模型、再写召回逻辑。 然而,在面对中小规模的内部知识库,这条路径往往伴随着较高的成本和复杂的运维。 今天,我们将反其道而行之:不上向量库、不调 Embedding,纯靠标准库的 SQLite FTS5,配合轻量的 jieba 中文分词,来实现一套毫秒级响应的文档检索方案。 这套方案的实现关键,是一个轻量级的文档索引器。 索引器要做到什么? 存储交给免管理后台、易协同的 Markdown,检索不借助外部 Embedding 模型,那么这个索引器唯一要攻克的难关,就是提供不亚于模糊语义的“检索精确度”。 具体而言,它必须满足以下三项核心指标: 响应快:用户提问后,毫秒级快速返回候选章节; 排序稳:利用 FTS5 内置的 BM25 算法,把最相关的章节推到顶部; 定位准:避开“粗暴按字数硬切片”的局限,精准定位并提取具体章节(Anchor)。 为了达成这些指标,我们需要文档切片、双层分词、虚拟表存储以及密度滑窗高亮等核心技术。在逐个拆解这些技术细节之前,我们先看一下索引器的工作流。 索引器的整体架构 整个设计围绕唯一的核心类 DocIndex 展开。它分为两部分:build() 负责离线建库,而 search() / list() / read() 负责在线查询。 graph TD %% Base Flow subgraph BuildPipeline [Build Pipeline 离线/初始化] A[Markdown 文件] -->|rglob 遍历| B[split_markdown 切块] B -->|生成 Anchor 锚点| C[Chunk 原文] C --> D[jieba 空格 Token 流] C -->|"作为 raw_content (不索引)"| E[(SQLite FTS5 虚拟表)] D -->|"作为 content (索引列)"| E end subgraph SearchPipeline [Search Pipeline 在线检索] F[用户 Query] -->|jieba / Tokens 提取| G[OR 拼接 Match 语句] G -->|MATCH 查询| E E -->|bm25 排序评分/负数越小越相关| H[命中 Chunk] H -->|密度滑窗 & 倒序高亮算法| I[SearchHit 片段] I -->|回显前端/大模型上下文| J[用户/大模型] end 数据模型:FTS5 虚拟表与“双列”设计 建索引的第一步,是定义 FTS5 虚拟表的 schema。 ...