今年我在折腾 Dify 的时候,突然被一个英文单词戳中了:hindsight。英文里有句老话叫 "hindsight is 20/20",翻译过来就是"事后看什么都清楚",说难听点叫马后炮,说好听点叫后见之明。过去我一直觉得这是个贬义词,直到我亲手把它做成了一款基于 Dify 的复盘 AI 应用,名字就叫 hindsight。它的活很简单:把项目记录、聊天记录、一段失败的经历丢进去,它会像一个经历过一切的老师傅,帮你把已经发生的事拆成事实、偏差、教训和下一次的行动清单。这篇文章就讲讲我为什么想做这么个东西、怎么从零用 Dify 搭起来、中间踩了哪些坑,以及你完全可以照抄的配置思路。
1. 为什么叫 hindsight:把"事后聪明"从贬义变成生产力
1.1 人人都是事后诸葛亮,但没人把"事后"用好
先说点虚的。你有没有过这种经历:项目上线后出问题,复盘会上所有人突然都变成了诸葛亮,这个说"当时我就觉得方案有问题",那个说"早该想到用户会这么操作"。可下次做新项目,同样的坑照样踩。问题出在哪?出在"事后思考"这件事完全是散装、靠脑子的。洞察产生了,但没被记录、没被结构化、更没被沉淀成下次可执行的检查项。
我做 hindsight 的初衷,就是把这种零散的"事后聪明"变成一条固定流水线。它不是让你对着失败自我检讨,而是把一段原始材料扔进一个标准流程里,产出一份结构化的复盘报告。说得直白一点:别人复盘靠开会,你复盘靠 AI 帮你拆解。
1.2 这个应用到底解决什么问题
我实际用下来,hindsight 能覆盖三类典型场景,这也是我推荐你先从这三个方向试的原因:
- 项目复盘:把项目群里的关键对话、周报、上线记录扔进去,让它找出目标与实际结果的偏差。
- 对话分析:客服聊天记录、销售沟通录音转的文字,看看到底是哪句话让客户态度变了的。
- 个人记录回看:每天写几句日记式记录,让 AI 帮你提炼"今天有什么下次可以做得不一样"。
它适合谁?适合已经在用或准备用 Dify 的开发者、做 AI 应用的产品经理,以及所有被"复盘走过场"折磨过的职场人。如果你只是想要一个现成的一键复盘工具,hindsight 的思路也值得你参考,因为背后的提示词设计逻辑是通用的。
2. 整体方案:基于 Dify 的 hindsight 应用架构
2.1 为什么选 Dify 而不是自己硬编码
说实话,第一版 hindsight 是我用 Python 脚本调大模型 API 写的,当时觉得也就几百行代码的事。但做到后面就发现一个尴尬问题:提示词改一版就要动代码,对话历史要自己管,想加一个"抽取事实"的中间步骤还得自己拼字符串。后来我把整个流程迁到了 Dify 上,用 Workflow/Chatflow 而不是写代码,原因有三:
- 可调试性好:每个节点单独跑,哪一步输出不对劲,直接在面板上看得清清楚楚。
- 改提示词不用发版:Dify 里改完提示词保存就能生效,产品同事也能上手调,不用每次找我改代码。
- 后续扩展方便:接飞书机器人、接知识库、接外部 API,都是画布上拉节点的事。
hindsight 我选的是Chatflow(聊天流)而不是 Workflow。因为复盘这件事,用户大概率会追问,比如"第二步你说的偏差具体指什么"、"能不能针对这个教训再给一个例子"。Chatflow 天然支持多轮对话,Workflow 更偏向一次性跑完拿结果。如果你确定只做单次分析,用 Workflow 会更简单。
2.2 核心流程:五个节点把"复盘"拆成流水线
整个 hindsight 的核心,是一条五段式分析流水线。这个结构不是拍脑袋定的,而是我从"复盘方法论"里搬出来的:先搞清楚发生了什么,再看和预期差多少,然后想为什么会差,最后给出下一次怎么办。
| 节点 | 作用 | 对应变量 |
|---|---|---|
| 开始(Start) | 接收用户输入的原始材料 | query |
| LLM 节点 1 | 事实提取,把原文变成结构化 JSON | facts |
| LLM 节点 2 | 偏差识别,对比目标与结果 | gap_analysis |
| LLM 节点 3 | 教训提炼,梳理原因与关键转折 | lessons |
| LLM 节点 4 | 行动建议,输出下次可执行清单 | actions |
| 结束(End) | 汇总四步结果,拼装成报告 | 最终回复 |
为什么要把"事实提取"单独拆成一个节点,而不是让后面的分析节点直接读原文?这是我踩过坑之后才想明白的。直接让 AI 读原文做分析,它经常会脑补出不存在的细节,比如原文说"用户流失严重",它能自己编出"可能是因为价格太高"。但如果先强制它抽取事实字段,再基于事实做分析,虚构的概率会明显降低。流程拆开,本质上是给 AI 设了一道"不许乱编"的栅栏。
2.3 一个关键设计:反事实推理
hindsight 这个名字的真正含义,落在第三个节点里:反事实推理(counterfactual)。所谓反事实,就是"如果当时换一种做法,结果会不会不一样"。这是复盘里最有价值、也最容易扯淡的部分。
我让 AI 在偏差分析之后,专门生成 2 到 3 条"如果当时……"的假设,但有一条硬性要求:每条假设后面必须标注它是基于事实的推断,还是纯粹猜测。这一步在提示词里写死,后面我会把提示词模板贴出来。因为复盘可以大胆假设,但不能把假设写成结论,否则报告就变成 AI 编故事了。
3. 关键实现:提示词编排与节点配置
3.1 事实提取节点的提示词模板
先看第一个 LLM 节点。这个节点的输入是用户刚提交的原始记录,输出是一段 JSON。我试过好几个版本,最后稳定在这个提示词上:
你是一个严谨的信息抽取器。请阅读用户提供的复盘材料,提取以下字段,并只输出一个 JSON 对象,不要输出任何解释文字。 字段要求: - background: 事件发生的背景,一句话概括 - goal: 用户/当事人原本想达成的目标 - actions: 实际采取的关键行动,最多列5项 - result: 实际发生的结果,尽量引用原文关键词 - turning_points: 过程中影响事态走向的2-3个关键节点 规则: 1. 所有字段必须基于原文,不能推测。 2. 原文没有提到的信息字段置为 null。 3. actions 和 turning_points 保留原文短语,不要改写。 材料内容: {{user_input}}几个细节为什么要这么写:强调"只能基于原文",是为了防止后面分析跑偏;"保留原文短语"是因为用户提供的复盘材料往往有口语细节,AI 一旦改写就容易丢失情绪和关键措辞;强制 JSON 输出,是为了让 Dify 的下游节点能安全引用变量。
3.2 分析与建议节点的提示词设计
偏差识别节点接收的是facts变量,提示词核心是让 AI 对比"目标"和"结果"之间的差距,并按严重程度排列。这里我用了一个比较有效的技巧:让 AI 先复述一遍目标和结果,再做对比。先复述能让它真正读进去,而不是随便生成一段漂亮话。
经验提炼节点是 hindsight 的灵魂,提示词关键词是"归因"和"反事实":
基于事实清单和偏差分析结果,请完成: 1. 找出2-3个最可能导致偏差的原因,每个原因必须引用事实清单中的具体内容作为证据。 2. 针对每个原因,生成一条"如果当时……"的反事实假设。 3. 明确标注每条反事实假设是"基于事实的推断"还是"推测"。 4. 输出的教训要写成可复用的规则,不要写成对个人的指责。最后行动建议节点更简单,要求输出带时间范围、带责任人的动作列表。因为复盘报告如果只写"下次要注意"等于没写,AI 得给出可执行、可检查的动作。
3.3 参数配置:模型、温度与上下文
模型选择上,hindsight 默认接的是推理能力较强的模型,因为复盘任务本质上是逻辑分析,不是简单问答。如果你的 Dify 里同时接了多种模型,建议事实提取用便宜一点的轻量模型,后面三个分析节点用强模型,性价比最高。温度我统一调到 0.4 左右。太低(接近 0)输出太死板,太高(超过 0.7)容易在推断环节放飞自我。让我比较直白地说:0.4 是我试了二十几次之后的最稳值。
上下文管理是一个差点坑死我的点。Chatflow 默认会把多轮对话历史全部传进去,导致第一轮抽取的facts变量还好好的,到了第三轮追问时,模型开始盯着历史看,把用户新的追问当成复盘材料的一部分。解决办法是:在节点参数里显式设置history变量,并且把事实提取节点固定只读取sys.query里最新输入的原始材料,前面的历史不参与这一轮提取。
4. 实操过程:从零搭一个能跑的 hindsight
4.1 环境准备:Dify 部署与 Chatflow 创建
假设你已经有一台能跑 Docker 的服务器,这是最快的方式。第一次部署 Dify 大概十来分钟,可以边喝咖啡边等:
git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env docker compose up -d启动以后,打开http://你的服务器IP,第一次进来会让你设置管理员账号。然后进入应用列表,新建应用,选Chatflow,名字就叫hindsight。
进了编排界面,左侧是节点面板,右侧是画布。默认画布上已经有两个节点:开始(Start)和结束(End)。我需要先到"开始"节点里,把输入字段从默认的sys.query改成自定义的user_input,原因前面说过:我不想让多轮历史污染原始材料。改完以后,开始节点下方会暴露一个名为user_input的变量,后面所有节点都要从它身上拿数据。
4.2 节点配置顺序与变量传递
按顺序拖出四个 LLM 节点,我把它们改名成fact_extract、gap_analysis、lesson_extract、action_plan,方便排查问题。
第一个节点配置如下:
- 模型:选轻量模型
- 上下文:
{{#start.user_input#}} - 提示词:用上面 3.1 节里的模板
- 输出变量:命名
facts - 输出格式:JSON
第二个节点gap_analysis:
- 上下文:
{{#fact_extract.facts#}} - 提示词:3.2 节里的偏差识别提示词
- 输出变量:
gap
第三个节点lesson_extract:
- 上下文:把
{{#fact_extract.facts#}}和{{#gap_analysis.gap#}}拼接起来 - 输出变量:
lessons
第四个节点action_plan:
- 上下文:接收
facts、gap、lessons三份数据 - 输出变量:
actions
最后在结束节点里写一句话,把四个变量打包返回:
【事实还原】 {{#fact_extract.facts#}} 【偏差分析】 {{#gap_analysis.gap#}} 【核心教训】 {{#lesson_extract.lessons#}} 【行动建议】 {{#action_plan.actions#}}4.3 实际案例跑一遍,看看输出长什么样
我拿一段一个朋友给我吐槽的"本周项目翻车记录"当测试材料,原文大概是这样的:
"周一开始开发一个优惠券功能,周五要上线。周一产品说需求没问题,周二我发现库存接口和优惠券系统对不上,问了后端,后端说周三才能改。周三又说联调环境挂了,顺延到周四。周四终于联调完,测试跑出一个严重 bug。周五硬着头皮上线,当天晚上用户反馈领不到券,转化率掉了 3%。"
跑完 hindsight,输出大概长这个结构:
事实还原: - 背景:五天期限的优惠券功能开发任务 - 目标:周五正常上线并提升转化 - 关键转折:接口对不上(周二)、联调环境故障(周三)、严重 bug(周四)、线上故障(周五) - 结局:上线当晚故障,转化率下跌约 3% 偏差分析: - 目标与结果间严重偏差 - 最严重偏差:技术风险没有被前置识别,直到周二才发现接口问题 核心教训(节选): - 接口依赖未在开发前确认,属于"基于事实的推断"类教训 - 联调环境故障没有预案,属于"推测"级假设 - 可复用规则:任何涉及外部系统的功能,开工前必须先核对接口契约 行动建议(节选): - 下次项目开工第一天,由后端列出所有依赖接口清单,输出接口契约文档(建议周五前完成) - 为联调环境故障预设备用环境切换方案(建议下次项目启动前完成演练)可以看到,hindsight 没有把责任推给某个人,而是把问题落到了"流程节点"和"下次动作"上。这就是我当初想达到的效果:复盘不指责,只转化。
4.4 兜底技巧:JSON 解析失败怎么办
Dify 的 LLM 节点虽然能设定输出格式为 JSON,但模型偶尔还是会抽风,返回一段带注释的 JSON,或者干脆把提示词里的规则也复述一遍。我的兜底方案是加一个代码节点(Code),放在fact_extract后面,专门负责清洗返回内容。
代码节点里我放了一段 Python,做两件事:从模型输出里找到{开头}结尾的子串,然后用json.loads尝试解析,解析失败了就丢出一个默认的facts空结构。代码如下:
import json, re def main(context: dict) -> dict: raw = str(context.get("raw_output", "")) # 提取 JSON 子串 match = re.search(r'\{.*\}', raw, re.S) if match: try: data = json.loads(match.group(0)) return {"facts": data} except Exception: pass return {"facts": { "background": None, "goal": None, "actions": [], "result": None, "turning_points": [] }}这个节点看着简单,但能在关键时刻保住整条流程不中断。复盘这种事最怕的就是"用户把材料都交出来了,流程却报错了"。
5. 常见问题与排查技巧实录
5.1 AI 乱归因,把臆测写成结论
这是我用第一版 hindsight 时最常遇到的问题。模型为了输出"深度分析",会编造一条看似合理的因果链,比如"客户流失严重,推测是因为竞品降价"——原文里根本没提竞品。后来我在提示词里做了三重限制:
- 事实提取节点强制"所有字段必须基于原文,不能推测"。
- 偏差分析节点要求"每个原因必须引用事实清单中的内容作为证据"。
- 反事实假设明确标注"推断"还是"推测"。
三重限制加上去之后,输出质量肉眼可见地提升。这里想提醒你一点:不要指望一条提示词解决所有问题,流程上的限制比提示词更可靠。
5.2 中文材料的语气与情绪被 JSON 抽干
把事实转成结构化 JSON 有一个损失:口语里的情绪、语气、犹豫都没了。比如"我其实很早就觉得有问题,但当时没敢说",转成turning_points之后变成了"察觉问题但未提出"。信息没丢,但决策时的心理负担丢了。
我的处理办法是:在事实提取节点里加了一个字段contextual_hints,专门用来保留原文中情绪化、模糊化的表述,比如"没敢说""有点慌""拖了很久"。分析节点拿到这个字段后,能感知到很多事实之外的信号。这个字段我是后来加的,但它反而成了整个复盘报告里最有人情味的部分。
5.3 上下文太长了,后面的节点"失忆"
当用户贴了一大段项目日志,然后再追问两句,Dify 会把整个对话历史传给后续节点,导致三个问题:响应变慢、Token 变贵、分析内容被历史稀释。我的解法是严格执行"单轮分析"模式:
- 开始节点只暴露
user_input,不引入history。 - 四个分析节点全部只引用前序节点的输出变量,不引用会话历史。
- 如果想做多轮问答,也是围绕已有的
facts和gap变量继续问,而不是重新分析原文。
实际用下来,这种"一次性材料 + 结构化结果持久化"的模式,比让模型全程记住上下文要稳定得多。Dify 的变量机制刚好支持这个玩法,你只需要在节点设置里手动引用变量,别偷懒直接塞#sys.history#就行。
5.4 复盘报告太长太啰嗦,没人看
第一版输出结果是一个大长串,用户反馈"看完第一段就不想看后面的了"。后来我在结束节点里把报告拆成了两块:上面是精炼版,十五秒能看完;下面按需展开查看,详细版只在这个人想深究时才需要给。Dify 的 Chatflow 里可以直接用条件分支(IF/ELSE)节点做这个事,根据用户是否点了"详细复盘"再拼接第二个长报告。这个改动不大,但用户留存率高了很多。
6. 扩展思路:hindsight 还能怎么玩
6.1 接机器人,每周五自动做项目周复盘
hindsight 现阶段最大的用法,是我把它接到了一个自建的群机器人上。每周五下午,机器人会把群里这一周的所有项目相关消息拉出来,扔给 hindsight,然后生成一份"本周项目复盘简报",自动发到群里。简报里不会点名道姓骂谁,只会列出流程上的问题和下周行动项。
如果你也想这么做,可以在 Dify 上把应用发布为 API,然后用一段简单的 webhook 代码把它接到飞书或者钉钉机器人上。核心代码不复杂,大概就是收消息、调 Dify API、把返回内容发回群里:
import requests def handle_message(raw_text): resp = requests.post( "http://your-dify-api/chat-messages", json={ "inputs": {"user_input": raw_text}, "query": "请对以上内容进行一次事后复盘", "response_mode": "blocking", "user": "weekly-bot" } ) return resp.json().get("answer")6.2 把复盘结果回填知识库,形成个人经验库
这是一个我打算下一步做的方向。当前 hindsight 复盘完,报告就飘散在对话流里了。更好的做法是加一个节点,把每次产生的lessons写到 Dify 知识库里作为文档存储。这样一来,等你要做下一个项目的时候,可以把真正的"经验"翻出来看,而不是靠记忆去回想。AI 帮我们把每一次后悔都变成了可以检索的资产,这是"后见之明"这个题目里最值钱的部分。
6.3 与语音转文字结合:开完会直接出复盘
如果你所在团队经常有复盘会议,可以把语音转文字服务输出的会议纪要直接丢进 hindsight,让它提炼"会议中提到的偏差与教训"。我试过几种转录服务,效果都可以,关键是喂进去的材料别太乱,转录后的文本最好先做一次基础清洗,比如去掉"嗯""那个"之类的语气词,hindsight 的分析质量会高一个档次。
另外还有一个思路值得提一下:不要把 hindsight 局限在"失败复盘"上。成功的事情也可以丢进去跑一遍。分析为什么成功、哪个动作起了决定性作用,和复盘失败一样有价值。我目前也在收集"成功场景"的案例,看看模型在这类输入下会不会给出更有建设性的输出。
做这个应用的过程中,我最大的体会是:hindsight 这个名字起得有点"反讽"的意思,因为传统的 hindsight 总是事后才明白,而我想要的其实是把"事后明白"变成"事前检查清单"。Dify 只是工具,真正难的是把复盘的逻辑想明白并落进提示词和流程里。如果你也在折腾类似的东西,我建议别急着堆节点,先把你要复盘的场景拆清楚:事实是什么、目标是什么、偏差是什么、教训是什么、下次怎么做。拆清楚了,剩下的就是用 Dify 把这些环节一个个串起来。
最后再分享一个小技巧:调好第一版以后,找几个真实案例跑一遍,然后盯着输出改提示词。别用自己编的完美案例调试,因为真实材料里的模糊、噪声、情绪,才是这个应用真正要处理的难题。