跳到主要内容

dsRAG 总览:这是什么 + 全景图 + 阅读地图

30 秒导读: dsRAG 是一个面向密集长文(财报、法律、学术论文)的检索引擎。 它不满足于朴素 RAG「切块 → 向量搜 → 塞给 LLM」,而是加了三招——语义分节、 上下文头、相关段落抽取——把检索质量从「捞到几个孤立片段」提升到「取回一整段 连贯、带背景的答案」。在 FinanceBench 上,它的准确率是 96.6%,而朴素基线只有 32%。

本章是这套多文件文档的入口(Layer 0/1)。读完你应能讲清:dsRAG 是什么、端到端怎么转、 由哪些可插拔部件搭成、最小怎么用,以及接下来该按什么顺序读 01–05 各章。具体算法细节 留给各章,这里只走高层。


1. 这是什么(零基础也能懂)

一句话定义

dsRAG 是一个非结构化数据的检索引擎(retrieval engine)——你把一堆文档喂给它,之后 用自然语言提问,它返回文档里最相关的那几段文本

它专攻朴素 RAG 最容易翻车的场景:密集、专业、答案往往横跨好几页的长文档。

先说清楚:朴素 RAG 为什么不够用

"RAG"(Retrieval-Augmented Generation,检索增强生成)的套路是:把文档切成小块(chunk)、 每块算一个向量存起来;提问时把问题也变成向量,找最近的几块,拼进 prompt 让 LLM 作答。

这套朴素做法有三个老毛病,dsRAG 正好一招对一坑:

朴素 RAG 的毛病dsRAG 的对策直觉
机械按固定长度切块,常把一个完整意思拦腰切断Semantic Sectioning(语义分节)先让 LLM 按「语义连贯」把文档分成节,再在节内切块
每个 chunk 脱离上下文——"它"指谁、这段属于哪一章,向量模型无从得知AutoContext(上下文头)给每个 chunk 前面拼一段文档级/章节级背景再去 embed
只返回孤立的固定长度小块,复杂问题的答案其实横跨一整段RSE(相关段落抽取)查询后把相邻的相关 chunk 智能拼回连续的长「段」

三招各解决什么(README §What is dsRAG)

  • Semantic Sectioning — 给文档标行号,让 LLM 指出每个「语义连贯节」的起止行,并生成节标题; 节再按需切成小 chunk。节标题会喂给下一步的 AutoContext。
  • AutoContext — 造出「上下文头」(contextual chunk header),含文档级 + 章节级背景, 拼在 chunk 前面再做 embedding,让向量/reranker 看到的是「有出处的」文本。
  • RSE(Relevant Segment Extraction) — 查询时的后处理:把一簇相关 chunk 智能合并成更长的 「段」(segment)。简单事实题答案常在单个 chunk;复杂题答案横跨一长段——RSE 不被固定块长绑死。

README 举的例子很直观:问"苹果最近财年的关键财务结果",RSE 会把整个 "Consolidated Statement of Operations"节(5–10 个 chunk)作为一段返回; 而问"苹果 CEO 是谁",最相关段就缩成提到"Tim Cook, CEO"的单个 chunk

它有多好(README §Eval results)

  • FinanceBench(几百份 10-K/10-Q 的开卷问答):朴素基线 32%,dsRAG 用大多默认参数 + Claude 3.5 Sonnet 作答,96.6%
  • KITE(作者自建,4 个数据集 50 题,0–10 打分)。下表看出 CCH(上下文头)和 RSE 各自都大幅超基线,合用最强:
Top-k(基线)RSECCH+Top-kCCH+RSE
AI Papers4.57.94.77.9
BVP Cloud2.64.46.37.8
Sourcegraph5.76.65.89.4
Supreme Court6.18.07.48.5
平均4.726.736.048.42

一句话直觉

把 dsRAG 想成一个会做读书笔记的图书管理员:入库时它先把每本书分好章节、给每页 贴上"这段出自哪本书哪一章"的便利贴;你来查资料时,它不是撕给你零散几页,而是把 相关的连续几页整段抽出来递给你。

本节不涉及任何底层代码。目标只有一个:让完全没听过 dsRAG 的人知道"它是干嘛的"。


2. 顶层全景(它大概怎么转)

dsRAG 的世界围绕一个核心对象转:KnowledgeBase(知识库,定义于 dsrag/knowledge_base.py:35)。它有两条主干道——**ingest(摄取)**把文档变成可检索的 chunk+向量;**query(查询)**把问题变成一批连续的答案段。

端到端全景图

下图从左到右是摄取(建库),从右到左往回是查询(用库)。中间共享两个存储: VectorDB(存向量)和 ChunkDB(存 chunk 原文)。

┌───────────────────────────────────────┐
│ KnowledgeBase(中枢对象) │
│ knowledge_base.py:35 │
└───────────────────────────────────────┘
│ ▲
═══ INGEST 摄取侧(add_document) ═══ ═══ QUERY 查询侧(query) ═══
│ │
┌──────────┐ ▼ │
│ 原始文件 │ ①parse 解析 ┌──────────┐ │
│ pdf/docx │ ─────────────────────▶ │ 纯文本 │ │ ⑦问题(可多条)
│ md/txt │ (VLM 或普通抽取) │ /元素 │ │ │
└──────────┘ └────┬─────┘ │ ▼
│ │ ⑧embed query
②section 语义分节 ▼ │ │
(LLM 找节的起止行) ┌──────────┐ │ ▼
│ 语义节 │ │ ⑨VectorDB 检索 top-k
③chunk 切块 └────┬─────┘ │ │
(节内切成小块) ▼ │ ▼
┌──────────┐ │ ⑩rerank 重排
④AutoContext 补头 │ chunks │ │ │
(拼文档/章节背景) └────┬─────┘ │ ▼
│ │ ⑪RSE 相关段落抽取
⑤embed 嵌入 ▼ │ (把相邻相关 chunk 拼成段)
(chunk+头 → 向量) ┌──────────┐ │ │
│ 向量 │ │ ▼
⑥upsert 落库 └────┬─────┘ │ ⑫取回连续 segment
│ │ (从 ChunkDB 拼原文+段头)
┌─────────────────┴────────┐ │ │
▼ ▼ │ ▼
┌───────────┐ ┌───────────┐ │ ┌──────────┐
│ VectorDB │◀─────检索────┤ (共享) ├─┘ │ 结果:一批 │
│ 存向量 │ │ │ │ 带分数、页 │
└───────────┘ │ ChunkDB │───▶│ 码的段 │
│ 存 chunk │ └──────────┘
│ 原文 │
└───────────┘

怎么读这张图: 摄取侧 ①→⑥ 顺流而下把文件变成「向量 + 原文」两份存储;查询侧 ⑦→⑫ 逆流而上,先靠 VectorDB 找候选、rerank 精排、RSE 拼段,最后回 ChunkDB 取连续原文。 ① 语义分节、④ AutoContext、⑪ RSE 就是第 1 节说的三招落地的地方。

主线走一遍(高层,不进代码)

摄取一份文档(入口 KnowledgeBase.add_document,knowledge_base.py:280):

  1. 解析 + 分节 + 切块 三步打包在 parse_and_chunk 里(dsrag/dsparse/main.py:22), 产出 sectionschunks——详见 01 摄取管线
  2. AutoContext(add_document.py:46auto_context)给每个 chunk 生成上下文头, 得到"待嵌入文本"chunks_to_embed——详见 02 AutoContext
  3. 嵌入 + 落库:get_embeddings 算向量,add_chunks_to_db / add_vectors_to_db 分别写进 ChunkDB 和 VectorDB(均在 add_document.py)。

查询一次(入口 KnowledgeBase.query,knowledge_base.py:858):

  1. 检索 + 重排:_get_all_ranked_results(knowledge_base.py:799)对每条 query 并发地向 VectorDB 搜 top-200,再过 reranker 精排。
  2. RSE:get_meta_documentget_relevance_valuesget_best_segments (都在 dsrag/rse.py)算出最优的连续 chunk 区间——详见 03 RSE
  3. 取回内容:_get_segment_content_from_database(knowledge_base.py:821)按区间 从 ChunkDB 拼回原文、加上段头,返回带 doc_id / chunk_start / chunk_end / content / score / 页码 的 dict 列表。

3. 六大可插拔组件(一句话职责表)

KnowledgeBase 不是铁板一块。它由六个可替换组件拼成,每个都有默认实现、也能换成 你自己的子类(README §Components)。构造时若不传,_initialize_components (knowledge_base.py:133)会填入下面这些默认值:

组件一句话职责默认实现装配位置
VectorDB存 embedding 向量 + 少量元数据,负责相似度检索BasicVectorDBknowledge_base.py:152
ChunkDBdoc_id+chunk_index 存 chunk 原文;RSE 靠它取回全文BasicChunkDBknowledge_base.py:157
Embedding定义嵌入模型(文本 → 向量)OpenAIEmbeddingknowledge_base.py:147
Reranker检索后、RSE 前对 chunk 做更精准重排(可选但强烈推荐)CohereRerankerknowledge_base.py:148
LLMAutoContext 里生成文档标题 / 文档摘要 / 章节摘要OpenAIChatAPIknowledge_base.py:149
FileSystem存 VLM 解析出的页面图片(本地 / S3)LocalFileSystemknowledge_base.py:160

还有一个较新的 VLM client(vlm_client,knowledge_base.py:162)用于视觉解析 PDF, 严格说是第七个可插拔件。六大组件的持久化(整套配置存成 JSON、可重建)与每种实现的取舍, 详见 04 可插拔组件

为什么这么设计: 每个组件都实现 to_dict() / from_dict(),所以整个 KB 的配置能 序列化成一份 JSON 存盘(_save,knowledge_base.py:166),下次只凭 kb_id 就能 原样重建——这是 dsRAG"建完即持久、无需显式 save"的基础。


4. 最小使用示例

下面两段来自 README §Tutorial,是感受 dsRAG 最快的方式。**默认走 OpenAI(嵌入 + AutoContext)

  • Cohere(重排)**,需设 OPENAI_API_KEYCO_API_KEY 两个环境变量。

建库(一步到位): create_kb_from_file 是个便捷函数(dsrag/create_kb.py:47), 内部就是 new 一个 KnowledgeBase 再调 add_document:

# 示意,非源码(改编自 README Quickstart)
from dsrag.create_kb import create_kb_from_file

file_path = "dsRAG/tests/data/levels_of_agi.pdf"
kb_id = "levels_of_agi"
kb = create_kb_from_file(kb_id, file_path) # 解析→分节→切块→AutoContext→嵌入→落库
# KB 会自动持久化到磁盘,无需手动 save

查询: 传一批查询字符串,拿回一批 segment。注意 query 收的是 list[str] (支持多条 query 一起检索):

# 示意,非源码(改编自 README Quickstart)
from dsrag.knowledge_base import KnowledgeBase

kb = KnowledgeBase("levels_of_agi") # 仅凭 kb_id 就能从磁盘重建整个 KB
search_queries = ["What are the levels of AGI?", "What is the highest level of AGI?"]
results = kb.query(search_queries) # 返回 list[dict]:doc_id/chunk_start/chunk_end/content/score...
for segment in results:
print(segment)

换模型(不用 Cohere): 传入 LLM 子类和 Reranker 子类即可,例如全用 OpenAI + NoReranker(README §Basic customization)——这正是第 3 节说的"可插拔"在用户侧的样子。


5. 阅读地图(01–05 建议顺序)

本库文档按由浅入深、跟着数据流走排。推荐顺序:

  1. 01 摄取管线 — 从文件到 chunk。讲 dsparse 子系统: VLM/普通解析、语义分节如何用 LLM 找节的起止行、节内怎么切块。对应全景图 ①②③。
  2. 02 AutoContext — 给每个 chunk 补上下文头。讲文档标题/摘要、 章节摘要如何生成并拼进 chunks_to_embed。对应全景图 ④,入口 add_document.py:46
  3. 03 RSE 相关段落抽取查询侧的主算法。讲把 chunk 相关性 转成「meta-document 上求最优连续区间」的思路:get_meta_documentget_relevance_valuesget_best_segments。对应全景图 ⑨⑩⑪。
  4. 04 可插拔组件与持久化 — 六大组件各自的实现选项、 to_dict/from_dict 序列化、KB 如何存盘与重建。深挖第 3 节那张表。
  5. 05 对话层 — 站在 KB 之上:AutoQuery(把用户话变检索 query)、跨多个 KB 检索、以及带引用回答(dsrag/chat/citations.py)。

怎么挑着读: 只想会用 → 读本章 + 挑 01/03;想懂"为什么准" → 重点 02(上下文头) 和 03(RSE);想接自己的向量库/模型 → 直奔 04;做问答产品 → 加读 05。


6. 顶层「巧妙之处」提炼

不深入算法,先记住这几个贯穿全库的设计决策——它们是 dsRAG 区别于朴素 RAG 的"精华":

  • "嵌入的文本"和"存的文本"分家。 AutoContext 造的上下文头只拼进用来 embed 的 文本(chunks_to_embed),而 ChunkDB 里存的是干净原文。于是检索受益于上下文, 返回给用户的却不被背景噪音污染(见 add_document.py:46auto_context 返回两份)。
  • 查询的产物不是 chunk,而是"段"。 RSE 把检索问题重构成「在拼接后的 meta-document 上 找一段总相关性最高的连续区间」,所以答案长度随问题自适应,而非被固定块长绑死 (knowledge_base.py:1023 起的 RSE 步骤)。
  • 整个 KB 可序列化 = 配置即数据。 六大组件都能 to_dict/from_dict,KB 配置存成一份 JSON(_save,knowledge_base.py:166),凭 kb_id 就能重建——建库即持久,无需显式保存。
  • 多 query 并发 + 自动放大预算。 querylist[str],内部线程池并发检索 (knowledge_base.py:804),且每多一条 query 就把 RSE 的总长度上限调大 (knowledge_base.py:996),让多角度提问自然取回更多内容。
  • 组件默认值内置、也全可换。 不传参就用 OpenAI+Cohere+Basic*(_initialize_components, knowledge_base.py:133);要私有化/换供应商,传子类实例即可,核心流程一行不改。

7. 代码地图(导航索引)

顶层入口与主干文件。行号 as-of sourceCommit;优先用符号名 grep 定位(更抗行号漂移)。

主题文件路径符号名
中枢对象 / 构造dsrag/knowledge_base.py:35KnowledgeBase.__init__
组件默认装配dsrag/knowledge_base.py:133_initialize_components
摄取入口(add_document)dsrag/knowledge_base.py:280KnowledgeBase.add_document
查询入口(query)dsrag/knowledge_base.py:858KnowledgeBase.query
检索+重排dsrag/knowledge_base.py:787_search / _get_all_ranked_results
取回连续段落dsrag/knowledge_base.py:821_get_segment_content_from_database
KB 序列化/重建dsrag/knowledge_base.py:166_save / _load
便捷建库函数dsrag/create_kb.py:47create_kb_from_file / create_kb_from_directory
解析+分节+切块(01)dsrag/dsparse/main.py:22parse_and_chunk
AutoContext(02)dsrag/add_document.py:46auto_context
嵌入 / 落库dsrag/add_document.py:150get_embeddings / add_chunks_to_db / add_vectors_to_db
RSE 主算法(03)dsrag/rse.pyget_meta_document / get_relevance_values / get_best_segments
RSE 预设参数dsrag/rse.py:151RSE_PARAMS_PRESETS(balanced / precision / find_all)
六大组件(04)dsrag/database/dsrag/embedding.pydsrag/reranker.pydsrag/llm.pyVectorDB / ChunkDB / Embedding / Reranker / LLM / FileSystem
对话层(05)dsrag/chat/chat.py / auto_query.py / citations.py

一处提醒(诚实优先): README §query 里把 RSE 预设写成 "balanced" / "precise" / "comprehensive",但源码 rse.py:151 实际的键是 balanced / precision / find_all; query 传入未知预设名会抛 ValueError(knowledge_base.py:968)。以源码为准。