Loop 节点实战:用滚动摘要处理超长文本

Loop 节点实战:用滚动摘要处理超长文本(含踩坑记录和方案对比)

Dify 1.13 本地部署 | DSL 0.6.0 | 2026-03


场景

我有一个 AI 助手(基于 OpenClaw),每天和我产生大量对话(多个 session,最大单日 235k 字符)。为了让它能够保持与我的对话记忆,我设置了一个每晚把对话提炼归档的定时任务,之前的归档方案是创建cron,派个子agent让它一次性读完全天对话然后总结,但经常遇到两个问题:

  1. LLM 上下文溢出:单日对话量动辄 5-10 万字符,超过大部分模型的有效处理能力
  2. 执行不稳定:单次 LLM 调用处理太多内容,容易超时或漏掉信息。动作链过长:每次要筛选今日有新内容的对话、要过滤无效命令数据、要索引提炼、要写入memory文件,要发送归档成功/失败的通知,还要把对话里的todo抽出来写到另一个todo记录。

差不多每周总有那么一两天对话特别多很重要但是啥也没记住,所以想在 Dify 里构建一个能分块消化大文本的工作流。该工作流应该可以同样用于:对超长文本的会议纪要、项目流水日志、知识条线内容(以及类似这种按时间顺序依次产生信息的业务场景),进行相对高质量的消化提炼,形成对人友好的汇总纪要。(不适用于像分布式代码库这样需要频繁定义、上文索引、跳转调用的文本)


写在方案之前:为什么要用Dify

其实AI助手优先给的建议是直接写脚本,简单粗暴即可解决问题。不过我不想AI助手底下左一堆脚本右一堆脚本,管理起来很麻烦,而且灵活性太差,哪个地方一升级或环境变化就可能失灵了,偷偷失灵甚至无法察觉。而之前的长动作链agent,不稳定又很费token,必须得gpt5.2水平的基座模型才能勉勉强强稳定跑完,对话一长性价比高模型全都会挂。

最终我选择了dify作为实施改造的工具:1、可读性好,维护性好。dify给了我一目了然知道哪个工作是什么运行机制的界面,而且出了错可以快速排查局部解决。2、稳定而且省token。在用dify替代大部分固定动作之后,整个归档动作变得快速而且稳定。而基座模型专注于纯粹的信息提炼环节,kimi2到minmax2.5水平的模型完全可以胜任,并且提炼质量还很好(还可以单独改这部分的prompt进行优化,避免之前的整体prompt优化瓶颈)。

方案分析:为什么是 Loop 而不是 Iteration

在我准备用dify开始实现我的想法之后,我安排我的AI助手对dify怎么实现我的目的进行研究。

大概有两个路线:

  1. 把长文本预处理拆分成固定大小片段——输入进dify进行分段提炼——最终在整合并重新提炼;
  2. 用一种类似流式处理的办法,把长文本连续地输入进dify,dify进行迭代性质地提炼压缩。(我原本打算考虑用agent模式的反复调用,不过AI助手给了我Loop建议。)

AI助手在结合我的使用场景以及对dify进行分析调研后,给出了如下方案,并在互动中得以实践验证。

以下内容为AI助手的整理输出:

(下面的“我”指我的AI助手,本篇主要内容为AI视角的输出整理)

Dify 有两种循环类容器:迭代(Iteration)循环(Loop)

先看两者本质区别:

迭代(Iteration) 循环(Loop)
语义 ForEach — 遍历数组,每个元素独立处理 While — 条件循环,有共享可变状态
元素间关系 互不知道对方的存在 每轮能看到上一轮的产出
并行 支持并行执行 严格串行

关键决策点:如果你需要"读一段,总结一段,下一段时带着之前的总结一起理解"——这就是状态积累,只有 Loop 能做。

Iteration 只能做 Map-Reduce(每段独立总结,最后合并),但合并阶段会丢失跨段上下文。

我的选择:Loop 做滚动摘要(rolling summary),每轮输入 = 上轮摘要 + 当前新段落,上下文窗口大小恒定,不随迭代数增长。


踩坑记录(重点)

我(指AI助手,下文同)做了多组对照实验来理解 Loop 节点的行为,发现了几个文档里没写清楚的关键点。

坑 1:loop-end 节点 = break 语句

这是最大的坑。我最初的循环体结构是:

loop-start → Code → LLM → Assigner → loop-end

结果:无论设什么条件,每次只循环 1 轮就退出。

我做了 3 组对照实验排除变量(纯计数、加 break 条件、加 LLM 节点),全部只跑 1 轮。最后通过一个在 Dify UI 里手动搭建的测试工作流(只有 loop-start → Code,没有 loop-end)发现了规律。

结论loop-end 不是"本轮结束,回到循环头"的标记。它是 break 语句——执行到 loop-end 就直接退出循环。

正确做法:循环体最后一个节点(比如 Assigner)就是死端(dead end),不接 loop-end,也不接任何出边。循环引擎会自动回到 loop-start 开始下一轮。

loop-start → Code → LLM → Assigner  ← 到这里结束,无出边
                                      循环引擎自动回到 loop-start

坑 2:break_conditions 的语义是"满足时退出"

不是"满足时继续"。

比如 is_done ≥ 1 的意思是:当 is_done >= 1退出循环,不是"当 is_done >= 1 时继续循环"。

坑 3:比较运算符必须用 Unicode

Dify DSL 的 comparison_operator 不接受 ASCII 写法:

  • >= 会报错,必须写
  • <= 会报错,必须写
  • != 会报错,必须写

坑 4:break_conditions 必须引用 Code 节点的输出

break_conditionsvariable_selector 不能直接引用 loop variable,必须引用循环体内 Code 节点的输出字段

(注意:该段的分析结论并不准确,更正确的方式是去查源代码与文档里对loop的介绍。但此处我保持AI原本探索总结并输出的样式,未做修改。按AI指引的方法,至少是可以“用”的,但这些类似于经验总结而非官方标准。)


工作流架构

最终跑通的工作流长这样:

Start(conversation_text, date)
  │
  ↓
Code [分段切割]
  按段落边界切割,每段 ≤12k 字符
  输出: chunks[], chunk_count
  │
  ↓
Loop (loop_count=25, break: is_done ≥ 1)
  loop_variables: index=0, running_summary="", done=0
  │
  ├─ Code [取当前段]
  │    输入: chunks, index
  │    输出: current_chunk, new_index, is_done
  │
  ├─ LLM [滚动提炼]
  │    system: 提炼规则(过滤噪音、三层结构、融合主题)
  │    user: [规则重注入] + date + running_summary + current_chunk
  │    输出: text(更新后的完整摘要)
  │
  └─ Assigner [更新循环变量]
       index ← new_index
       running_summary ← LLM.text
       done ← is_done
       (dead end,无出边)
  │
  ↓
End → summary = running_summary

几个关键设计点:

  • 分块在 Dify 内部:Code 节点按段落边界切割,不在段落中间截断
  • loop_count = 25:覆盖约 300k 字符(25 × 12k),足够处理历史最大日量
  • break 条件:Code 节点计算 is_done = (index+1 >= chunk_count),退出时机由数据量决定

测试结果

基础功能验证

测试 输入规模 迭代轮数 耗时 结果
单个大 session 83k 字符 7 轮 49.5s succeeded
全天 bundle(6 sessions) 92k 字符 8 轮 158.5s succeeded
历史最大日(推算) 235k 字符 ~20 轮 - loop_count=25 可覆盖

方案对比:逐 session 处理 vs 全天 bundle 一次性处理

用同一天的数据(6 sessions,92k 字符)跑了两种方案,然后用 LLM 做结构化质量评估:

方案 A:每个 session 单独过工作流,产出 6 份独立摘要(10,097 字符)
方案 B:全天 bundle 一次性进 Loop(首版 3,541 字符,优化后 8,748 字符)

评估维度 方案 A(逐 session) 方案 B(全天 bundle)
还原度 7/10 — 事实准确但有失真 8.5/10 — 忠实还原
深度 6/10 — 过度结构化,编造不存在的框架 8/10 — 保留原始推理过程
意义识别 5/10 — 系统性归因错误 9/10 — 准确识别核心时刻

意外发现:方案 A 字数更多,但质量更差。原因是单 session 完整上下文给了 LLM 太多"创作空间"——它会自己编造不存在的分析框架、把 AI 的贡献归因给用户。方案 B 的滚动摘要反而因为每轮只看到一小段,被迫忠实还原而非重新组织,质量更好。


关键优化:每轮规则重注入

方案 B 首版有一个问题:迭代曲线出现塌缩

首版: 1502 → 2573 → 4585 → 6803 → [810] → 1468 → 2099 → 3541
                                      ↑
                              这里从 6803 骤降到 810

第 5 轮处理的是几段很短的自动化 session(纯工具调用,基本没有对话内容),LLM 把之前积累的 6803 字符摘要重写压缩成了 810 字符。system prompt 里写了"噪音时原样保留已有摘要",但没用——长上下文场景下 LLM 对系统指令的遵从性会衰减。

(——此处AI陷入无尽打补丁的思维惯性循环,作者真人介入,给了AI重复提示词方案的提示。)

解决方案:在每轮 LLM 的 user message 开头重新注入关键规则:

【本轮强制执行规则】
R1 噪音判定:如果本轮新增内容全是系统日志/工具调用/启动问候,
   必须将「已有摘要」原文逐字输出,禁止缩减、重组或改写。
R2 忠实还原:只记录原文中真实出现的事实。禁止构造原文不存在的框架。
R3 融合不分段:同一主题再次出现时融合进已有段落。
R4 三层结构:每个主题包含「做了什么」「判断与决策」「可复用模式」。
【规则结束,以下为本轮数据】

归档日期:{date}
---
已有摘要:{running_summary}
---
本轮新增对话内容:{current_chunk}

效果:

优化后: 1502 → 2573 → 4585 → 6580 → [8059] → 8150 → 8559 → 8748
                                       ↑
                               不再塌缩,正常增长

原理:system prompt 在长上下文里会被"遗忘",但 user message 开头的内容处于模型注意力的最近位置,遵从性显著更高。每轮重复注入相当于在每次 LLM 调用的最近上下文中刷新指令权重。


总结

Loop 节点完全可以处理大文本的窗口化提炼场景。关键经验:

  1. Loop-end 是 break,不是 end-of-iteration — 这是最容易踩的坑,循环体应该是 dead end 结构
  2. Loop 适合状态积累,Iteration 适合独立批处理 — 滚动摘要用Loop比Iteration更适合
  3. 每轮重注入规则 — 解决 LLM 在多轮迭代中的指令遵从性衰减问题
  4. 逐段处理不一定比一次性处理质量差 — 滚动摘要反而比完整上下文更忠实,因为 LLM 没有空间"借题发挥"

实测数据:92k 字符(6 个 session 合并),8 轮迭代,约 330 秒完成,最终产出 8,748 字符的结构化归档摘要。已接入每日自动化 cron 任务稳定运行。(对比原本的子agent方案需要600秒+,并且经常超时失败。)

——————————

以上具体实践内容基本为AI自发探索执行,我只在关键处给予提示和方向确认。最终生成质量符合要去,短期我选择并行两种方案(原方案子agent全权处理;新方案子agent负责调用dify工作流和消息通知),每日存档成 日期归档 和 日期归档_dify,同时运行一段时间,长期再看稳定行与质量选择其中一种或重新进行分工上的划分。