知识库与 RAG
这章讲什么: 你把资料丢进知识库后,DeepTutor 怎么检索它。重点是「一个知识库绑一种检索引擎」的可插拔设计,而不是某一种 RAG 算法的细节。
1. 它要解决的小问题
不同资料适合不同检索法:平铺文档适合向量检索;有大量交叉引用的知识适合图检索(GraphRAG);长 PDF 适合按页/层级索引(PageIndex)。一个辅导系统不该只押一种 RAG——得让每个知识库挑自己合适的引擎,但对上层(聊天循环里的 rag 工具)暴露同一个接口。
2. 思路 / 直觉:引擎按知识库绑定,工厂路由
RAGService(deeptutor/services/rag/service.py,RAGService)是统一入口。它的核心一句话(docstring):provider 按知识库解析——建库时显式传的 provider 优先,否则从 DeepTutor 权威 KB 配置里读,metadata 作为旧版兜底。
四套引擎在工厂里登记(deeptutor/services/rag/factory.py):
| provider | 适合 | 常量 |
|---|---|---|
llamaindex | 默认,向量 + BM25;大库可用 FAISS ANN | DEFAULT_PROVIDER(factory.py:27) |
pageindex | 长文档按页/层级索引 | PAGEINDEX_PROVIDER(factory.py:28) |
graphrag | 图结构知识 | GRAPHRAG_PROVIDER(factory.py:29) |
lightrag | 轻量图 RAG(还有 lightrag_server 服务端引擎) | LIGHTRAG_PROVIDER(factory.py:30) |
get_pipeline 按名字拿到 pipeline,list_pipelines(factory.py:169)列出可用引擎给 UI 选。pipeline 实现分目录摆在 deeptutor/services/rag/pipelines/{llamaindex,graphrag,pageindex,lightrag,lightrag_server}/,共用 pipelines/base.py 的基类。
3. 图示:一次检索的路由
怎么读: 从上到下,聊天里的 rag 工具最终落到「这个 KB 绑的那套引擎」。
聊天循环里模型调 rag 工具(query, kb_name)
│
▼
RAGService:看 kb_name 绑了哪个 provider
│ (建库时显式 > KB 配置 > metadata 兜底)
▼
factory.get_pipeline(provider)
│
┌────┼─────────┬──────────┬────────────┐
▼ ▼ ▼ ▼ ▼
llamaindex graphrag pageindex lightrag lightrag_server
(向量+BM25) (图) (页/层级) (轻量图) (服务端)
│
▼
命中段落 + sources → 回到工具结果 → 塞回对话
4. 几个值得看的细节
4.1 按文件类型路由解析
FileTypeRouter(deeptutor/services/rag/file_routing.py,FileTypeRouter/DocumentType/FileClassification)按文件类型把文档分流到合适的解析路径(PDF、office 文档等各走各的提取器)。解析引擎本身在 deeptutor/services/parsing/engines/(含 MinerU、PyMuPDF4LLM 等)。
4.2 多查询聚合:SmartRetriever
SmartRetriever(deeptutor/services/rag/smart_retriever.py,SmartRetriever)是建在 RAGService 之上的高层 helper:它先生成若干查询变体(或用传入的 hints),并行检索(asyncio.gather),把命中的段落聚合后再让 LLM 合成一个答案(smart_retriever.py:18-46,retrieve)。失败的查询用 return_exceptions=True 跳过,不拖垮整体。
# 示意,非源码:一个问题 → 多个查询变体 → 并行检索 → 聚合
queries = hints or await generate_queries(context, n=3) # 没给 hints 就让 LLM 造
results = await gather(*(search(query=q, kb_name=kb) for q in queries),
return_exceptions=True) # 失败的跳过
passages = [r["content"] for r in results if ok(r)]
answer = await aggregate(context, passages) # LLM 合成
4.3 索引版本化与预检
RAG 服务带了一组工程化设施:index_versioning.py(带版本的 KB 索引 + 重建流程)、index_probe.py、preflight.py(检索前的健康检查)、embedding_signature.py(embedding 配置签名,变了就提示重建)、linked_kb.py(链接外部 KB,如 Obsidian)。这些是「让大库检索稳」的配套,README 的 v1.3.x/v1.4.x 多个版本在打磨它们(如 FAISS 向量后端、re-index 韧性)。
5. 边界与局限
- 具体每套引擎的检索算法细节没在本章展开(它们各自封在
pipelines/<name>/里,接口统一在base.py)——本章只讲「可插拔路由」这个架构决定。 - 默认
llamaindex的 FAISS 是懒导入,缺了就回退到SimpleVectorStore的暴力扫描(pyproject.toml:83-87注释,issue #552),所以小库不装 faiss 也能跑,只是大库慢。
6. 代码地图
| 主题 | 文件 | 符号 |
|---|---|---|
| 统一检索入口 | deeptutor/services/rag/service.py | RAGService |
| 引擎工厂 / 路由 | deeptutor/services/rag/factory.py | get_pipeline、list_pipelines、DEFAULT_PROVIDER |
| pipeline 基类 | deeptutor/services/rag/pipelines/base.py | (基类) |
| 文件类型路由 | deeptutor/services/rag/file_routing.py | FileTypeRouter、DocumentType |
| 多查询聚合 | deeptutor/services/rag/smart_retriever.py | SmartRetriever.retrieve |
| 索引版本化 | deeptutor/services/rag/index_versioning.py | (版本化逻辑) |
| rag 工具(LLM 入口) | deeptutor/tools/rag_tool.py | (rag 工具) |