在之前的文章中,已经实现了 RAG 系统中 5 种实用的文本分段策略,深入理解了代码实现。本文将进一步“潜入”广受好评的低代码平台 Dify 的源码内部,探究其如何优雅且高效地完成知识库文件的分段、向量化与存储。通过对 Dify 源码的拆解,期望能揭示一个成熟 RAG 系统在数据预处理层面的设计思路与工程实现,为构建或优化知识库系统提供宝贵的参考。
Dify源码解析
由于主要功能和逻辑集中在 api/ 文件夹下,本文将重点分析该文件夹的内容:
Dify 源码结构
configs/—— 配置管理 职责:集中管理运行时配置(app 配置、各环境配置、部署特定配置)。 使用点:app_factory 在初始化时会读取并注入这些配置。
constants/—— 常量定义 职责:集中定义全局常量(字符串键、默认值、枚举等),避免散落硬编码。 使用点:各层(controllers/services/models)引用以保证统一字段名/默认值。
contexts/—— 请求/操作上下文 职责:定义请求生命周期内的上下文对象(例如当前用户、租户、trace id、db session 管理等)。 使用点:中间件、控制器与服务通过上下文共享信息。
controllers/—— 控制器 / 路由层(API 接入点)职责:定义 HTTP/REST/GraphQL 路由与请求处理逻辑(参数校验、权限检查、调用服务层)。 使用点:对外暴露 API,是外部调用的第一接触层。
core/—— 核心业务模块职责:包含应用的核心业务实现、算法模块或关键子系统。 使用点:服务层与 controllers 调用此处实现具体业务逻辑。
services/—— 业务逻辑层(Service)职责:封装具体业务流程(协调 models、core、外部服务),实现核心用例。 使用点:controllers 调用 services 来执行业务,services 又调用 core等实现具体算法。
models/—— 数据模型层(ORM / DTO) 职责:数据库 ORM 模型、domain model、以及与持久层交互的实体定义。 使用点:services 通过 models 执行持久化读写。
docker/—— 与容器/部署相关脚本 职责:docker-compose、部署辅助脚本或容器环境配置。 使用点:本地开发与部署流程。
events/—— 事件与消息 职责:事件定义、事件发布/订阅、消息队列集成(如 RabbitMQ/Kafka 事件处理器)。 使用点:异步任务触发、系统解耦的跨模块通知。
extensions/—— 第三方扩展与插件封装 职责:封装数据库、缓存、监控、认证等第三方库的初始化与使用适配(例如 SQLAlchemy / Redis / Sentry)。 使用点:app_factory.py 在启动时加载并注入这些扩展。
factories/—— 对象/服务工厂 职责:构造复杂对象或服务实例(数据库连接池、客户端适配器、processor 工厂等)。 使用点:配合 dependency injection 或按需创建可复用组件。
fields/—— 自定义字段/序列化定义 职责:定义序列化/反序列化字段、表单字段、模型字段扩展(常见于 Marshmallow/Pydantic 等)。 使用点:在 controllers 或 models 中用于输入校验与输出格式化。
libs/—— 通用工具库 职责:放置项目通用函数、工具类、helper(logs、日期处理、网络工具等)。 使用点:被各模块频繁复用,保持轻量与无业务耦合。
migrations/—— 数据库迁移 职责:数据库 schema 迁移脚本(Alembic / Django migration 等)。 使用点:数据库变更管理、部署时执行迁移。
schedule/—— 定时任务 / 调度 职责:周期性任务定义(如 cleanup、索引重建、数据同步),可能基于 APScheduler、Celery beat 等。 使用点:后台维护任务与定期作业。
tasks/—— 异步任务 职责:定义可异步执行的任务(Celery、RQ 等 worker 任务),例如长时间运行的索引、导入任务、通知发送。 使用点:事件/服务触发异步任务并把任务推送到 worker。
templates/—— 模板文件 职责:HTML、邮件模板或其他文本模板(Jinja2 等)。 使用点:用于返回 HTML 页面或渲染邮件/通知内容。
tests/—— 测试用例 职责:单元测试、集成测试、端到端测试代码及测试用的 fixture。 使用点:CI 执行、保证代码质量与回归检测。
Dify 知识库部分的实现
本次源码解析基于 Dify 1.5.0 版本。需要特别说明的是,在相近的版本迭代中,Dify 知识库处理的核心逻辑与代码架构保持稳定,因此,对 1.5.0 版本的理解也适用于邻近版本的分析。
Dify 知识库的分段策略主要包括三种类型:通用分段、父子分段和 QA 分段。
- 通用分段:按照分隔符切分文本,然后对于超过最大字符限制的 chunk 进行二次切分;
- 父子分段:子段用于检索,命中子段的父段作为上下文内容统一返回,分段方法同样是基于分隔符和最大字符限制;
- QA分段:使用 LLM 针对用户文件生成问答对,每个问答对作为一个 chunk;
本节将重点介绍 Dify 知识库实现中的三个核心模块:controllers、core 和 services。这些模块共同构成了 Dify 知识库的业务核心。
请求入口(Controllers)
/api/controllers/console/datasets/datsets_segments.py 脚本定义了前端 API 调用的首步处理操作,负责路由请求到相应的类,并进行参数及权限校验,其中包括:
- 获取文档的分段列表。
- 添加新的分段或子分段。
- 更新分段或子分段的内容。
- 删除分段或子分段。
- 批量导入分段。
需要说明的是,console 文件夹用于处理 Web 前端调用,而 service_api 目录则处理外部 HTTP API。
class DatasetDocumentSegmentListApi()get():根据 document_id 查询分段列表 delete():根据 ID 删除指定文档中的多个分段
class DatasetDocumentSegmentApi()patch():更新指定文档中的分段状态(启用/禁用)
class DatasetDocumentSegmentAddApi()post():对指定文档中增加新的分段
class DatasetDocumentSegmentUpdateApi()patch():更新指定分段中的内容 delete():删除指定分段
class DatasetDocumentSegmentBatchImportApi()post():接收 CSV 格式文件,解析文件内容并批量在指定文档中批量创建分段 get():获取批量导入的任务
class ChildChunkAddApi()post():添加子分段 get();获取子分段列表 patch():更新子分段内容
class ChildChunkUpdateApi()delete():删除指定的子分段 patch():更新指定子分段的内容
路由层的每个类都使用了装饰器进行权限控制,具体如下:
@login_required:确保用户已登录。
@accountinitializationrequired:确保账户已初始化。
@setup_required:确保系统已完成设置。
服务编排(Services)
Controllers 层接收到 API 调用后,会处理参数并调用服务层进行实际的业务逻辑处理。在 Dify 中,知识库文件的处理主要位于 api/services/dataset_service.py 脚本文件。该脚本包含三个与知识库服务相关的核心类:DatasetService(知识库管理)、DocumentService(文件管理)和 SegmentService(分段管理)。DatasetService 和 DocumentService 分别负责知识库和文档级别的增删改查操作。本文将重点讲解文档分段部分的操作。
SegmentService
multi_create_segment:批量创建分段 create_segment:创建分段 update_segment:更新分段 delete_segment/delete_segments:(批量)删除分段 update_segments_status:更新分段状态,可用/禁用

create_child_chunk:创建子段 update_child_chunks/update_child_chunk:(批量)更新子段内容 delete_child_chunk:删除子段 get_child_chunks:获取分段下的全部子段内容; get_child_chunk_by_id:根据 ID 获取子段内容; get_segments:分页返回指定文档下的分段; update_segment_by_id:更新指定分段内容; get_segment_by_id:获取指定分段;
除了上述三个主要层级的操作,dataset_services.py 还负责知识库的权限验证,以及管理数据模型(models)和数据集之间的绑定关系。整体而言,该脚本的架构清晰,解耦程度高,这对于后续的代码修改和测试非常有益。
核心操作(Core)
从服务层的脚本中可以发现,其主要处理数据库操作。那么,具体的算法逻辑,例如文本分段和索引构建,是在哪里实现的呢?这正是本节将要讲解的核心操作层。知识库相关的操作集中在 api/core/rag 文件夹下。首先,介绍该目录下所有模块的作用:
cleaner — 文本/文档清洗与规范化 data_post_processor — 检索结果的后处理 datasource — 数据源抽象与检索服务 docstore — 文档存储与元数据管理 embedding — 向量化与缓存 entities — 数据结构与元信息定义 extractor — 各种格式文档的提取器(ETL 前端) index_processor — 索引构建与维护(推测) models — 模型适配与调用封装 rerank — 结果重排序/二次评分 retrieval — 检索核心算法与流程封装 splitter — 文档拆分器(chunking)
本节仅对 splitter 文件夹下的内容进行详细讲解。如果对其他文件夹内容有疑问,欢迎在评论区留言探讨。
在 splitter/ 路径下,关于文档分段处理的具体操作包含两个主要部分,下文将分别进行讲解,并阐述它们之间的关系。
text_splitter.py
该脚本提供了多种不同的分段方法。首先,它定义了一个抽象基类 TextSplitter,用于规范所有文本分段方法的通用接口和逻辑。此基类中定义了实现不同分段逻辑的抽象方法 @abstractmethod。整体分段逻辑是将文本列表转换为 Document 对象列表,随后对 Document 列表进行迭代分段,最终将分段后的小块合并为 chunk_size 限制范围内的分段。
之所以需要 create_documents() 方法将文本列表转换为 Document 类型列表,是因为直接对 List[str] 类型的文本进行分段可能导致分段后的小块与原始文本失去关联。通过将每个小块包装成 Document 对象,分段后的内容将包含元数据信息,从而能够关联原始文本并保留上下文信息。
剩余的类都是继承自 TextSplitter 的多类型文本分段器:
CharacterTextSplitter:按固定字符或指定分隔符,最基础是按照chunk_size + chunk_overlap拆分;MarkdownHeaderTextSplitter:按 Markdown header 拆分,保留文档结构;TokenTextSplitter:按照 token 切分,需要加载 tokenizer;RecursiveCharacterTextSplitter:通用分段方法,核心思想是从小到大递归拆分。按照脚本中提供的默认分隔符,[",首先使用段落分隔符。如果分段结果还是过大,则逐步使用换行、空格、字符级别分段,迭代处理直到文本段大小符合标准。", "
", " ", ""]
上述内容即为 text_splitter.py 脚本所实现的算法核心。
fixed_text_splitter.py
EnhanceRecursiveCharacterTextSplitter 是 RecursiveCharacterTextSplitter 的子类,它在父类的基础上增加了对非 tokenizer 的计数功能。该类重写了一个类方法 from_encoder,用于动态选择使用模型自带的 encoder 或字符计数。
FixedRecursiveCharacterTextSplitter 类继承自 EnhanceRecursiveCharacterTextSplitter 类,其分段逻辑是先按照固定分隔符进行粗粒度切分,然后对超出 chunk_size 限制的部分进行递归切分。
上述内容即为 fixed_text_splitter.py 脚本中两个类的实现概览。
在 splitter 模块中定义的分段方法会在索引构建 index_processor 中被调用。index_processor 包含了针对 QA、通用以及父子分段方法的索引构建逻辑。

以上是对 Dify 源码中知识库部分的讲解,主要聚焦于文本分段策略。如有任何想法或建议,欢迎在评论区留言探讨。
