跳转到内容

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/