在Dify中,知识库的文本分割(或称分块,Chunking)是其核心功能之一,它直接影响到RAG(Retrieval Augmented Generation)的召回效果和上下文理解能力。默认情况下,Dify的知识库通常采用基于字符长度的策略进行文本分割,例如“按固定长度分块并重叠”,这种方式简单高效,但缺乏语义理解。
要实现按语义分割文本,Dify本身在前端界面没有提供直接的"语义分割"选项。这是因为语义分割是一个更复杂的预处理过程,它需要在文本被Dify摄取之前完成。然而,由于Dify的知识库底层支持多种向量数据库(包括Weaviate),我们可以通过自定义数据摄取流程来绕过Dify默认的字符分块,将已经进行语义分割的文本块及其向量直接写入到Dify所使用的Weaviate实例中。
为什么需要按语义分割文本?
-
提高RAG的召回准确性:
- 问题: 传统的字符分块可能将一个完整的语义概念(如一句话、一个段落、一个观点)在中间截断。当用户提问时,如果查询相关的语义信息被分散到不同的块中,或者一个块包含了多个不相关的语义点,RAG模型在检索时就难以找到最相关的、完整的上下文。
- 优势: 语义分割确保每个文本块内部都包含一个相对完整的语义单元。这样,在检索时,更容易召回与用户查询高度匹配的、有意义的上下文块,减少噪音,提高相关性。
-
优化上下文窗口利用率:
- 问题: 大语言模型(LLM)的上下文窗口是有限的。如果文本块过长且包含不相关的冗余信息,或者过短导致语义不完整,都会浪费宝贵的上下文空间。
- 优势: 语义分割旨在创建大小适中、信息密度高且语义连贯的块。这使得传递给LLM的每个块都能提供最大的信息价值,让LLM更好地理解和推理。
-
减少幻觉(Hallucination):
- 问题: 当检索到的信息碎片化或不准确时,LLM可能会“脑补”缺失的部分,产生幻觉。
- 优势: 完整的语义块可以为LLM提供更坚实的事实基础,减少其自行推理或编造的可能性。
-
提升用户体验:
- 最终,更准确、更相关的RAG结果意味着用户能够获得更高质量的答案,提升了整体的AI助手体验。
实现流程方法
实现按语义分割文本并集成到Dify的Weaviate知识库中,主要分为以下几个步骤:
- 准备原始文本数据: 确保你有待处理的、完整的原始文本数据(例如:Markdown文件、PDF内容、网页内容等)。
- 选择语义分割策略:
- 基于规则/结构: 例如,根据Markdown的标题、段落、列表等结构进行分割。
- 基于句法/语义: 利用NLTK、SpaCy等库进行句子分割,或更高级的基于语义相似度进行段落划分。
- 基于模型: 使用预训练的分段模型,或者通过计算句子/段落间的嵌入向量相似度来识别语义边界。
- 执行语义分割: 使用选定的策略对原始文本进行处理,生成一系列具有语义完整性的文本块。
- 生成文本块的嵌入向量: 为每个分割好的文本块生成高质量的向量嵌入。
- 将文本块和向量写入Weaviate: 将处理好的文本块及其对应的向量,以及其他元数据(如来源、分块ID等)写入到Dify所连接的Weaviate实例中。
- Dify知识库配置: 在Dify中创建或修改知识库,确保它连接到你写入数据的Weaviate实例。Dify将能够直接检索这些预先分割和嵌入的文本块。
具体实现流程与代码说明(Python示例)
我们将使用Python来演示这个过程。为了实现“语义分割”,我们将采用两种策略:
langchain
的RecursiveCharacterTextSplitter
: 这是一个在实际应用中非常有效的“伪语义”分割器。它会尝试首先按双换行符(段落)、单换行符(行)、标点符号等进行分割,最后才退回到字符长度限制。这在一定程度上保留了语义完整性。- 基于句子嵌入相似度分割(高级): 这是一种更纯粹的语义分割方法。它通过计算相邻句子之间的嵌入相似度,并在相似度急剧下降的地方(表示主题切换)进行分割。
为了简化并提供可执行的代码,我们将主要展示RecursiveCharacterTextSplitter
的实现,并简要说明基于语义相似度的方法。
1. 环境准备
确保你已经安装了以下Python库:
pip install weaviate-client==3.* # Dify通常使用较新版本的Weaviate客户端
pip install langchain
pip install sentence-transformers
pip install tqdm # 用于显示进度条
pip install transformers torch # 对于embedding模型可能需要
2. Weaviate连接配置
确保你的Dify后端已经配置了Weaviate作为知识库,并记下Weaviate的URL和API Key(如果使用Weaviate Cloud或启用了API Key认证)。
3. 完整代码示例
import weaviate
from weaviate.embedded import EmbeddedOptions # 如果你使用Weaviate嵌入式模式 (不推荐用于生产)
from langchain.text_splitter import RecursiveCharacterTextSplitter
from sentence_transformers import SentenceTransformer
import os
from tqdm import tqdm
import json
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# --- Weaviate 配置 ---
# Dify 通常连接的是外部 Weaviate 服务。
# 请根据你的 Dify 后端配置,修改以下参数。
WEAVIATE_URL = os.getenv("WEAVIATE_URL", "http://localhost:8080")
# 如果你的 Weaviate 服务需要 API Key,请在这里填写。
# 例如,Weaviate Cloud 用户需要提供 AUTHENTICATION_KEY 和 WEAVIATE_API_KEY
# 如果是本地或无需认证的,可以设置为 None 或空字符串
WEAVIATE_API_KEY = os.getenv("WEAVIATE_API_KEY", None) # 例如 "YOUR_WEAVIATE_API_KEY"
WEAVIATE_AUTH_CONFIG = None
if WEAVIATE_API_KEY:
WEAVIATE_AUTH_CONFIG = weaviate.AuthApiKey(api_key=WEAVIATE_API_KEY)
# --- 嵌入模型配置 ---
# 选择一个适合中文的 Sentence Transformer 模型
# BAAI/bge-small-zh-v1.5 是一个性能不错的中文嵌入模型
# 也可以选择 "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" 等
EMBEDDING_MODEL_NAME = "BAAI/bge-small-zh-v1.5"
# --- Weaviate 知识库类名 ---
# 这个类名将是你在 Weaviate 中存储文本块的集合名称。
# Dify 知识库在导入时会指定一个类名,或者使用默认的 'Paragraph'。
# 确保你将数据导入到 Dify 知识库配置的那个类名中。
# 例如,如果 Dify 知识库配置使用的是 'MyCustomKnowledgeBase',这里就用这个名字。
WEAVIATE_CLASS_NAME = "DifySemanticKnowledge" # 建议自定义一个,避免与Dify默认导入冲突
# --- 文本分块配置 ---
CHUNK_SIZE = 500 # 每个文本块的最大字符数
CHUNK_OVERLAP = 50 # 文本块之间的重叠字符数,有助于保留上下文
# --- 示例文本 ---
# 请替换为你的实际文本数据,可以是读取文件、网页爬取等。
EXAMPLE_TEXT = """
Dify 的核心功能包括:
1. **数据管理**:支持多种数据源导入,如 PDF、Markdown、网页、API 等,并提供灵活的文本分块和嵌入服务。
2. **模型编排**:集成多种大语言模型(如 OpenAI GPT、Anthropic Claude、自定义模型等),允许通过拖拽界面进行复杂的提示词工程和链式调用。
3. **知识库**:基于 RAG 技术,实现私有知识库的问答能力,有效解决 LLM 的幻觉问题。
4. **应用部署**:一键部署为 Web 应用、API 或集成到其他平台。
5. **运营分析**:提供用户交互日志、成本分析等数据,帮助开发者优化应用。
为了在 Dify 中实现语义分割文本,我们通常需要绕过 Dify 内置的默认分块机制。
这意味着我们需要在数据进入 Weaviate 之前,手动对其进行语义处理。
Weaviate 是一个开源的向量数据库,它支持高效的向量搜索和数据管理。
Dify 的知识库模块正是利用 Weaviate(或 pg_vector, Milvus 等)来存储和检索嵌入向量。
自定义分块的好处在于,我们可以根据文本的实际内容和语义结构来划分,而不是简单地按字符长度。
例如,一个完整的概念或一段话,即使长度超过了默认分块限制,也可以被保留在一个块中。
这种做法显著提高了 RAG 的召回准确性和上下文质量,从而为用户提供更精准、更流畅的回答。
语义分块的实现方法有很多,如基于规则(段落、标题)、基于句法解析,或者更高级的基于嵌入相似度。
在实践中,LangChain 的 `RecursiveCharacterTextSplitter` 是一个很好的起点,它兼顾了结构和长度。
而基于嵌入相似度的方法则能实现更深层次的语义理解,但实现起来也更复杂一些。
"""
def init_weaviate_client