Dify 实现长文档自定义切片:高效处理大规模文档的智能解决方案
在本文之前我制作了合同审查和知识库智能入库助手两个智能体,后续使用以及和粉丝沟通中发现,之前设计切片功能无法支持长文档。而长文档本身又是实际业务中常见的内容,因此对于长文档的支持至关重要。
01 长文档处理中的痛点
在实际使用Dify处理长文档的过程中,我遇到了以下两个问题:
1. 处理大文档时的限制
Dify的HTTP节点的输出内容不能超过1MB,因此当文件内容超出这个量级,无法通过预处理缓存到本地,然后使用时HTTP节点读取的方式获取完整文本内容。
2. LLM 节点的输入限制
Dify中的LLM节点 存在输入和输出 token 数量的限制 。在长文档场景下,当文档超过模型的输入输出限制时,会出现参数丢失 或结果丢失内容的情况,导致输出不完整或者丧失关键信息。这在合同审查等需要处理大量文本的场景中,极大地影响了效率和准确性。
这些问题在日常应用中时常出现,尤其在处理法律类文档、合同条款时,解决文档的高效切分、处理和标注就显得尤为重要。
02 实现长文档的切片和标注
经过调试,最终的流程大致如下,先对文档按长度进行分段,缓存后使用Dify的LOOP节点循环读取并切片,最后增量缓存得到最终切片结果。

Dify中的节点结构如下:

接下来我们对该部分节点进行拆解:
1.预处理节点
该节点为一个HTTP节点,主要功能是将文档按照长度切分为多个分段并以jsonl格式缓存。主要参数为长文档内容缓存路径,每一段的长度以及冗余的首尾部分内容长度,参数配置如下:

核心代码:
python
def extract_chunks_to_file(self,text: str,context_len: int = 1000, head_len: int = 100, tail_len: int = 100) -> List[Dict]:
"""
切片长文本,按 JSONL 格式写入文件。返回文件路径和总切片数(行数)。
:param text: 原始长文本
:param config: 切片参数,包括 context_len、head_len、tail_len
:param output_path: 输出文件路径
:return: {"file_path": ..., "line_count": ...}
"""
output_path = self.get_cache_path("chunk_cache.json")
idx = 0
line_count = 0
with open(output_path, "w", encoding="utf-8") as f:
while idx < len(text):
chunk = text[idx:idx + context_len]
head = text[max(0, idx - head_len):idx]
tail = text[idx + context_len:idx + context_len + tail_len]
item = {
"text": chunk.strip(),
"head": head.strip(),
"tail": tail.strip()
}
f.write(json.dumps(item, ensure_ascii=False) + "\n")
idx += context_len
line_count += 1
return {
"file_path": output_path,
"line_count": line_count
}
处理结果缓存为jsonl格式,每行为一段数据,便于后续LOOP节点使用:

2.参数赋值节点
该节点主要对几个会话变量赋值,用于后续LOOP节点中各个节点使用,以下是各个参数的解释:
pre_process_filepath:预处理文件缓存地址
segment_loop_count:需要循环的次数
filename:上传的文件名,循环中无法直接使用初始参数赋值,因此使用会话变量控制
segment_loop_index:循环的索引字段,用于控制每次循环从预处理缓存文件中读取内容。

3.循环节点
该节点是文档切片的核心处理节点,循环终止条件通过会话变量控制,如下:

4.循环内部-读取预处理结果文件节点
该节点主要通过segment_loop_index读取jsonl中的指定行,然后将读取结果交由后续的切片节点以及参数提取节点使用。 读取脚本内容如下:
python
def read_chunk_by_line(self, file_path: str, line_num: int):
if not os.path.exists(file_path):
return {"error": f"File not found: {file_path}"}
try:
with open(file_path, "r", encoding="utf-8") as f:
for i, line in enumerate(f):
if i == line_num:
return json.loads(line)
return {} # 超出范围,返回空对象
except Exception as e:
return {"error": str(e)}
5.循环内部-最新的标题信息参数提取节点
该节点主要用于提取当前段文本所属的模块信息,用于元数据标注,由于预处理时直接按照长度截取,因此可能出现整段没有章节信息的情况,因此通过该节点+会话变量实现章节信息的缓存。
提示词如下(需根据实际需求微调):
python
你是一个文档结构分析助手,任务是从一段文本中识别其所属的章节结构,并输出该段落最接近的「大标题」和「小标题」。
请按以下规则进行结构提取:
1. 文档结构可能包含:
- 大标题(如"第一章 总则"、"第二部分 特殊条款")
- 小标题(如"第一节 范围"、"第二节 适用对象")
2. 请提取该段中出现的最新大标题与小标题:
- 若该段出现了新的章(如"第二章"),则刷新 `major_title`;
- 若同段未出现节,则 `minor_title` 置为空;
- 若该段只出现节(如"第一节"),则更新 `minor_title`,保留之前的 `major_title`;
- 若该段未出现任何结构标题,则返回空对象(不推测上下文)。
3. 不要识别"第x条"或内容标题,仅识别章 / 节 或同类结构(如"二、"、"2.")作为章节。
4. 如果段落存在多个结构标题(如同时有"第二章" 和 "第一节"),则都提取出来。
5. 如果段落中没有大小结构标题,则不修改其值。
6. 如果段落中只有小标题,没有大标题,则不修改大标题,只修改小标题的值。
请参考以下输入:
输入文本:
"""
"""
当前最新的大标题:
"""
"""
当前最新的小标题:
"""
"""
6.循环内部-文本切片节点
该节点为实际进行切片操作的节点,实现对每一段进行更细粒度的切分。
提示词如下(需根据实际需求微调):
python
你是一个擅长文档结构识别与内容提取的专家,任务是接收一段文本正文(包含上下文片段 head、text、tail),并依据文档是否具备结构化格式进行智能分段,并完成信息标注。
### 参考输入
需要处理的文本内容:
"""
"""
规则文件内容:
"""
"""
最新的大小标题:
"""
大标题:
小标题:
"""
### 你的目标如下:
1. 判断当前文本片段是否属于结构化文档(如合同、法律文件等)。
- 若包含明显结构符号(如"第X章"、"第X条"、"一、"、"1."、"1.1"、"(1)"等),视为结构化;
- 否则视为非结构化文本。
2. 对结构化文档:
- 按照最小结构单位进行切分(例如:"第12条"、"1.1"、"三、"等);
- 多个结构块请逐条输出,每条之间用分隔符 `---` 隔开。
3. 对非结构化文档:
- 按照语义+长度切分为 1~3 段;
- 同样以 `---` 进行分段输出。
4. 片段可能被截断(如只包含"第10条"的前半句或后半句),请结合上下文判断是否保留该段,或在 `text` 中做补全,避免文本丢失或断句。
5. 不要解释或输出无关文本,仅输出被切分后的段落结果。
6. 对每一个片段进行元数据标注,需要标注的字段从规则文件中获取,如果有多个层级,则每个层级都需要单独的字段标注,如果规则中没有设计则自行补充标注字段。
7. 不可丢失任何文本内容,每一个片段的text字段需要包含本段的所有内容。
8.如果段落中包含大小标题内容如章,节等,需要根据最新的大小标题判断当前段落属于哪个标题,并且将标题标注到元数据中。
9.如果输入文本为摘要或者目录,需要将该部分整体作为一个段落,对应标注即可。
### 输出格式要求
- 每段之间必须用 --- 分隔
### 输出示例
---
"chapter": "第一章 基本规定",
"section": "第一节 xxx",
"text": "第一条 为了保护民事主体的合法权益,调整民事关系,维护社会和经济秩序,适应中国特色社会主义发展要求,弘扬社会主义核心价值观,根据宪法,制定本法。"
---
"chapter": "第一章 基本规定",
"section": "第一节 xxx",
"text": "第二条 民法调整平等主体的自然人、法人和非法人组织之间的人身关系和财产关系。"
7.循环内部-
缓存最终切片结果节点
该节点为HTTP节点,主要实现了文本的追加写入,最终将文档分别写入到md和txt两个文档中,便于后续预览及上传知识库使用。
核心代码如下:
python
def append_result_to_file(self, content: str, filename: str):
"""
将 Markdown 格式文本内容同时追加写入 .txt 和 .md 文件。
:param content: Markdown 格式文本(str)
:param filename: 原始文件名(可带或不带后缀)
:return: 写入状态信息
"""
export_dir = os.path.join(self.base_dir, "exports")
os.makedirs(export_dir, exist_ok=True)
# 去掉后缀
name_without_ext = os.path.splitext(filename)[0]
txt_path = os.path.join(export_dir, f"{name_without_ext}.txt")
md_path = os.path.join(export_dir, f"{name_without_ext}.md")
# 清理 Markdown 块格式
clean_content = content.strip()
if clean_content.startswith("```"):
clean_content = clean_content.strip("`")
if clean_content.lower().startswith("json"):
clean_content = clean_content[4:].strip()
# 写入 .txt(纯文本格式)
with open(txt_path, "a", encoding="utf-8") as f_txt:
f_txt.write(clean_content + "\n\n")
# 写入 .md(保留 Markdown 格式)
with open(md_path, "a", encoding="utf-8") as f_md:
f_md.write(content.strip() + "\n\n")
return {
"status": "ok",
"txt_written_to": txt_path,
"md_written_to": md_path,
"chars_written": len(content)
}
8.循环内部-赋值节点
该节点实现了真正的循环控制,在该节点中对章节信息进行缓存并且递增循环索引字段,实现每次循环读取对应的内容

9.实际处理效果
从图中我们可以看到,生成了两个缓存文件,并且将每一条分为一个段,并且为每一段实现了元数据标注。

03长文档自定义切片的意义
有的读者可能会问:做这个功能的意义是什么?
我也经常会反思这个问题,最终的答案是:通过这个流程实现智能标注,以及灵活的切片规则,实现更适合自己业务的入库效果。
最终实现检索效率及质量的提升。
04 总结
在这篇文章中,我们深入探讨了如何通过Dify实现长文档自定义切片 ,并详细介绍了切片过程的设计与实现。长文档切片的功能,解决了实际业务中处理大规模文档的痛点,特别是在合同审查和知识库管理等场景下具有重要意义。
希望这篇文章能够在你的文档处理过程中提供帮助。如果你有任何问题或进一步的需求,欢迎随时联系我,我们一起探讨更多的智能化文档处理方法!
点个【在看】和【转发】支持我继续优化内容!你的鼓励是我继续打磨 AI 应用的最大动力💪
🎁 关注我的公众号【AI转型之路】 ,获取更多内容,有疑问也可以在公众号咨询。
加微信sgw_clj
发送66进群