链载Ai

标题: 你的RAG知识库,真的“喂”对数据了吗?拆解dify分段策略,告别无效召回 [打印本页]

作者: 链载Ai    时间: 4 天前
标题: 你的RAG知识库,真的“喂”对数据了吗?拆解dify分段策略,告别无效召回

dify源码的解析

上一篇推文中,我们亲手实现了 RAG 系统中 5 种实用的文本分段策略,感受到了“手搓”代码的扎实感。今天,我们将更进一步,直接“潜入”广受好评的低代码平台 dify 的源码内部,一探究竟

—— 它究竟是如何优雅且高效地完成知识库文件的分段、向量化与存储的?

通过这次对 dify 源码的拆解,我们希望能为大家揭开一个成熟 RAG 系统在数据预处理层面的设计思路与工程实现,为构建或优化自己的知识库系统提供宝贵的参考。

dify源码结构

因为主要实现的功能和逻辑都集中在api/文件夹下,所以我们主要分析该文件夹下的内容:

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 版本。需要特别说明的是,在相近的版本迭代中,其知识库处理的核心逻辑与代码架构保持稳定,因此,您完全可以将对1.5.0 版本的理解,应用于邻近版本的分析中。

dify知识库的分段包含:通用分段、父子分段和QA分段三个类型

我们在本节中将主要介绍三个模块中关于知识库实现的代码: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:确保用户已登录。

@account_initialization_required:确保账户已初始化。

@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对象,这样分段之后的内容包含metedata信息,可以关联原始文本保留上下文信息。

剩余的类都是继承自TextSplitter的多类型文本分段器:

以上就是text_splitter.py这个脚本实现的算法内容。

fixed_text_splitter.py

EnhanceRecursiveCharacterTextSplitter是继承自RecursiveCharacterTextSplitter的子类,在父类的基础上增加了对非tokenizer的计数,该类重写了一个类方法from_encoder,用于动态选择可以使用模型自带的encoder或者使用字符计数。

FixedRecursiveCharacterTextSplitter类继承EnhanceRecursiveCharacterTextSplitter类,先按照固定分隔符粗切一遍,然后对超过chunk_size的部分递归切分。

以上就是fixed_text_splitter.py脚本中两个类的实现内容。

splitter中定义的分段方法在索引构建inedx_processor中调用,索引构建中包含了QA、通用、父子分段方法的索引构建方法






欢迎光临 链载Ai (http://www.lianzai.com/) Powered by Discuz! X3.5