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(基线) | RSE | CCH+Top-k | CCH+RSE | |
|---|---|---|---|---|
| AI Papers | 4.5 | 7.9 | 4.7 | 7.9 |
| BVP Cloud | 2.6 | 4.4 | 6.3 | 7.8 |
| Sourcegraph | 5.7 | 6.6 | 5.8 | 9.4 |
| Supreme Court | 6.1 | 8.0 | 7.4 | 8.5 |
| 平均 | 4.72 | 6.73 | 6.04 | 8.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):
- 解析 + 分节 + 切块 三步打包在
parse_and_chunk里(dsrag/dsparse/main.py:22), 产出sections和chunks——详见 01 摄取管线。 - AutoContext(
add_document.py:46的auto_context)给每个 chunk 生成上下文头, 得到"待嵌入文本"chunks_to_embed——详见 02 AutoContext。 - 嵌入 + 落库:
get_embeddings算向量,add_chunks_to_db/add_vectors_to_db分别写进 ChunkDB 和 VectorDB(均在add_document.py)。
查询一次(入口 KnowledgeBase.query,knowledge_base.py:858):
- 检索 + 重排:
_get_all_ranked_results(knowledge_base.py:799)对每条 query 并发地向 VectorDB 搜 top-200,再过 reranker 精排。 - RSE:
get_meta_document→get_relevance_values→get_best_segments(都在dsrag/rse.py)算出最优的连续 chunk 区间——详见 03 RSE。 - 取回内容:
_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 向量 + 少量元数据,负责相似度检索 | BasicVectorDB | knowledge_base.py:152 |
| ChunkDB | 按 doc_id+chunk_index 存 chunk 原文;RSE 靠它取回全文 | BasicChunkDB | knowledge_base.py:157 |
| Embedding | 定义嵌入模型(文本 → 向量) | OpenAIEmbedding | knowledge_base.py:147 |
| Reranker | 检索后、RSE 前对 chunk 做更精准重排(可选但强烈推荐) | CohereReranker | knowledge_base.py:148 |
| LLM | AutoContext 里生成文档标题 / 文档摘要 / 章节摘要 | OpenAIChatAPI | knowledge_base.py:149 |
| FileSystem | 存 VLM 解析出的页面图片(本地 / S3) | LocalFileSystem | knowledge_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_KEY和CO_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 建议顺序)
本库文档按由浅入深、跟着数据流走排。推荐顺序:
- 01 摄取管线 — 从文件到 chunk。讲
dsparse子系统: VLM/普通解析、语义分节如何用 LLM 找节的起止行、节内怎么切块。对应全景图 ①②③。 - 02 AutoContext — 给每个 chunk 补上下文头。讲文档标题/摘要、
章节摘要如何生成并拼进
chunks_to_embed。对应全景图 ④,入口add_document.py:46。 - 03 RSE 相关段落抽取 — 查询侧的主算法。讲把 chunk 相关性
转成「meta-document 上求最优连续区间」的思路:
get_meta_document→get_relevance_values→get_best_segments。对应全景图 ⑨⑩⑪。 - 04 可插拔组件与持久化 — 六大组件各自的实现选项、
to_dict/from_dict序列化、KB 如何存盘与重建。深挖第 3 节那张表。 - 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:46的auto_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 并发 + 自动放大预算。
query收list[str],内部线程池并发检索 (knowledge_base.py:804),且每多一条 query 就把 RSE 的总长度上限调大 (knowledge_base.py:996),让多角度提问自然取回更多内容。 - 组件默认值内置、也全可换。 不传参就用 OpenAI+Cohere+Basic*(
_initialize_components,knowledge_base.py:133);要私有化/换供应商,传子类实例即可,核心流程一行不改。