RAG 管线可插拔架构
学习目标
- 理解 Dify RAG 管线的三层解耦架构
- 掌握三种索引处理器、四种检索方法和 25+ 向量数据库后端的可扩展设计
- 学会分析 RAG 管线中各层的扩展点和集成方式
前置知识
本章涉及 RAG 和记忆系统的通用原理,建议先阅读:
下文假设你已理解 RAG 的通用概念,直接聚焦 Dify 的具体实现。
项目实践
三层解耦架构
Dify 的 RAG 管线分为三个独立的层次:
索引处理器层
IndexProcessorFactory 根据索引类型返回对应的处理器实例:
# 伪代码class IndexProcessorFactory: def init_index_processor(self) -> BaseIndexProcessor: match self._index_type: case IndexStructureType.PARAGRAPH_INDEX: return ParagraphIndexProcessor() case IndexStructureType.QA_INDEX: return QAIndexProcessor() case IndexStructureType.PARENT_CHILD_INDEX: return ParentChildIndexProcessor()三种索引类型的适用场景:
| 索引类型 | 适用场景 | 特点 |
|---|---|---|
| 段落索引 | 通用文档检索 | 按段落分割,最简模式 |
| QA 索引 | 问答对知识库 | 将文档转化为问题-答案对 |
| 父子索引 | 精细化检索 | 父段定位 + 子段返回,平衡上下文与精准度 |
向量存储后端层
BaseVector 抽象接口定义了所有向量数据库必须实现的方法:
# 伪代码,展示 BaseVector 接口class BaseVector(ABC): @abstractmethod def get_type(self) -> str: ... @abstractmethod def create(self, texts, embeddings) -> list[str]: ... @abstractmethod def add_texts(self, documents, embeddings) -> list[str]: ... @abstractmethod def text_exists(self, id: str) -> bool: ... @abstractmethod def delete_by_ids(self, ids: list[str]) -> None: ... @abstractmethod def delete_by_metadata_field(self, key, value) -> None: ... @abstractmethod def search_by_vector(self, query_vector, **kwargs) -> list[Document]: ... @abstractmethod def search_by_full_text(self, query, **kwargs) -> list[Document]: ... @abstractmethod def delete(self) -> None: ...每个 VDB 后端作为独立的 Python 包实现此接口:
api/providers/vdb/├── vdb-pgvector/ # PostgreSQL + pgvector├── vdb-milvus/ # Milvus├── vdb-qdrant/ # Qdrant├── vdb-weaviate/ # Weaviate├── vdb-elasticsearch/ # Elasticsearch├── vdb-chroma/ # Chroma└── ... (25+ providers)每个 VDB 包包含:
- 独立的
pyproject.toml(可作为单独包发布) src/目录中的实现代码tests/目录中的集成测试
检索策略层
四种检索方法:
class RetrievalMethod(StrEnum): SEMANTIC_SEARCH = "semantic_search" # 纯向量相似度 FULL_TEXT_SEARCH = "full_text_search" # 全文检索 HYBRID_SEARCH = "hybrid_search" # 混合(语义 + 全文) KEYWORD_SEARCH = "keyword_search" # 纯关键词匹配Rerank 模块:
# 伪代码class RerankModel(RerankBase): def invoke(self, query, docs, score_threshold, top_n) -> RerankResult: # 调用 Rerank LLM 对候选文档重排
class WeightRerank(RerankBase): def invoke(self, query, docs, weights) -> RerankResult: # 基于权重规则的简单重排Rerank 通过 rerank_factory.py 选择具体实现,支持模型驱动和规则驱动两种策略。
文档处理流水线
文档从导入到可检索的完整流程:
关键组件:
- Splitter:多种分片策略(固定长度、段落、语义边界)
- Embedding:通过
ModelInstance.invoke_text_embedding()调用 - 去重:
BaseVector._filter_duplicate_texts()基于doc_id自动去重
问题与规避
陷阱 1:向量数据库选择对成本和性能的影响
不同 VDB 后端在查询延迟、内存占用和功能支持上差异巨大。
Dify 的规避策略:
- 统一
BaseVector接口,用户可随时切换 VDB 后端 - 每个 VDB 包独立测试,确保功能正确性
- 支持 25+ 后端,用户可根据部署环境选择
陷阱 2:检索结果相关性不足
纯语义搜索可能返回语义相似但不精确的结果。
Dify 的规避策略:
- 支持混合检索(语义 + 全文),平衡召回率和精确度
- Rerank 模块对候选结果二次排序
- 可配置
score_threshold过滤低分结果
陷阱 3:文档格式兼容性
PDF、PPT 等格式的文本提取可能丢失结构信息。
Dify 的规避策略:
- 使用
readabilipy提取 HTML 内容 - 不同格式使用不同的
datasource处理器 - 索引处理器层抽象了格式差异,上层无需关心
设计取舍
独立 VDB 包 vs 统一依赖
Dify 选择将每个 VDB 后端作为独立 Python 包而非统一的单体依赖:
优势:
- 按需安装,减少部署体积
- 各 VDB 的依赖冲突隔离
- 单独更新某个 VDB 不影响其他
代价:
- 包管理复杂度增加(uv workspace 管理 25+ 子包)
- 公共接口变更需要同步更新所有实现
- 测试矩阵增大(25 后端 × 4 检索方法 × 3 索引类型)
替代方案:将所有 VDB 驱动放入单一包。但这会导致依赖膨胀和版本冲突。
参考来源
- Dify RAG 模块源码:
api/core/rag/ - Dify VDB Providers:
api/providers/vdb/