Dify 文章工作流:换主题不用改 Prompt

为什么你的提示词一直在打工,却从来不涨工资

我见过太多人写提示词的状态:每一次开新选题,打开一个空白的聊天框,把上一次的提示词复制过来,然后开始动手改——把「AI 绘画」换成「智能家居」,把「面向设计师」换成「面向宝妈」,把「3000 字」改成「5000 字」,改着改着发现上下文对不上了,索性从头再写一遍。一个小时后,你得到了一份全新的提示词,和上一份有 80% 的相似度。

这不是你提示词写得不好。这是你把「会变的东西」和「不会变的东西」搅在了一起。而 Dify 这类工作流平台的全部价值,恰恰在于把这两类东西切开:提示词是资产,主题是参数。

把主题硬编码进提示词的三个隐性成本

先说清楚「硬编码」到底贵在哪。很多人的直觉是:改一改嘛,又不难。真正的成本不在改写那十分钟,而在下面三件事上。

成本一:你无法比较两个版本哪个更好

如果提示词每周都变,你就永远得不到一个稳定的基线。这周出的文章结构好,是因为你优化了提示词,还是因为这次的主题本身就好写?下周效果差了,是提示词退步了,还是选题太难?你无法回答,因为你把两个变量同时动了。Dify 的工作流一旦锁定提示词,主题就变成唯一的自变量,你才第一次拥有可以对照实验的条件。

成本二:好的经验无法沉淀

某天你灵光一现,在提示词里加了一句「每节结尾必须留一个悬念,让读者想看下一节」,效果出奇地好。但当这句话随着主题一起被改掉,这个经验就蒸发了。三个月后你想不起来自己当初为什么那么写。而在 Dify 里,这一句应该被放在「分节扩写」节点的系统提示词里,它是一次性的投资,会跟着每一条主题跑。

成本三:你没法把活交出去

硬编码提示词的最终形态是「只有你会用」。同事想帮你出一篇稿,得先问你「你上次那套提示词在哪」,然后你花二十分钟解释哪里要改。而在 Dify 里,别人只需要在开始节点填六个框:主题、读者、关键词、字数、语气、禁用词。他甚至不需要知道后面的提示词长什么样。

一个反直觉的判断:写得越细的提示词,越难复用

这是我最想强调的一点,也是大多数人做错的地方。很多人以为提示词越具体越好——具体到「写一篇讲 AI 绘画在小红书变现的文章,面向 25-35 岁女性自由职业者,3000 字,语气亲切,多用 emoji」。这句话作为一次性指令确实很好,但它作为资产是零价值的,因为它把这个主题的所有特征都焊死了。

真正可复用的提示词,反而要写得「空」——它只描述结构和规则,不描述内容。「每节开头用一句反常识的判断」「每节必须有至少一个具体数字或案例」「禁止使用『综上所述』『众所周知』」。这些规则跟主题无关,跟内容无关,只跟质量有关。它们才是资产。Dify 的工作流让你第一次能系统地把这些规则拎出来,单独存放、反复调用。

先想清楚:什么该变成变量,什么必须留在提示词里

动手搭工作流之前,先做一次「切分手术」。拿一张纸,把你过去一年写过的提示词摊开,逐句问自己:这句话换主题后还成立吗?成立,留在提示词;不成立,抽成变量。

大部分情况下,切分结果出奇地一致。下面是我在几十个项目里反复验证过的变量清单。

六个必选变量与三个进阶变量

必选变量:没有它就跑不起来

  • topic(主题):一句话说清要写什么。不要写成标题,写成陈述句,比如「用 Dify 搭建一个能自动出稿的文章工作流」比「Dify 文章工作流教程」给模型的信号强得多。
  • audience(目标读者):写清楚读者的认知起点和真实痛点。「不懂代码的运营」和「会写 Python 的独立开发者」,拿到的同一篇稿子会长得完全不一样。
  • keywords(核心关键词):三到六个必须自然出现的词。注意是「自然出现」,不是「强行堆砌」——这一点要在提示词里作为约束写死。
  • word_count(字数):总字数。分节扩写时要能自动拆到每节,所以这个值最好是整数。
  • tone(语气):专业、轻松、犀利、温和。语气决定了模型用不用第一人称、能不能用反问、敢不敢下判断。
  • forbidden_words(禁用词):这个变量很多人漏掉,但它是最省事的一个。把所有你不想要的词——行业黑话、过度承诺、违禁表述——一次性列进去,后面每一条主题都自动生效。

进阶变量:让同一套工作流跑出不同形态

  • reference(参考素材):粘贴的原文段落、数据表、竞品文章摘要。它决定了这篇稿子是「凭空生成」还是「有据可依」。
  • section_count(章节数):让大纲节点知道该出几节。技术教程通常 6-8 节,观点长文 3-5 节。
  • language_style(语言风格枚举):把语气拆成可枚举的值,比如「口语化」「书面化」「工具文档风」,方便后面做条件分支。

变量到底该定义在哪里:开始节点还是提示词节点

这是 Dify 新手最容易搞混的地方。Dify 里有两个地方可以「写东西」:开始节点的输入字段,和提示词节点里的文本。很多人的错误是把主题写在了提示词节点里,然后在开始节点里空着,结果就是——开始节点形同虚设,这条工作流还是没法复用。

记住一条铁律:所有会变的东西,一律定义在开始节点。开始节点是这条工作流的「接口」,它定义了外部世界能传什么进来。提示词节点是「实现」,它只负责消费这些变量,拼接成给模型的指令。换句话说:开始节点是函数签名,提示词节点是函数体。你不会把参数值写死在函数体里。

在 Dify 的开始节点里,每个字段要设置三样东西:变量名、字段类型(文本/段落/数字/下拉选项)、是否必填。主题、读者、关键词建议用「段落」类型,方便填长内容;字数和章节数用数字;语气用下拉选项,把预设选项固定下来,避免每次输入格式不一致。

为什么「参考素材」适合做可选字段

并不是每篇文章都有素材。如果把它设成必填,用户每次都要硬凑一段,反而制造噪音。更好的做法是设成可选,然后在提示词里加一句条件判断:「如果 reference 不为空,优先使用其中出现的数据和案例;如果为空,则依靠模型自身的知识,并避免编造具体数字」。这一句写进 Dify 的提示词节点,就永久生效了。

变量命名规范:为什么必须是英文蛇形

这一条看起来是小事,但踩坑的人极多。Dify 的提示词节点里引用变量用的是双花括号语法,比如 {{topic}}。同时,Dify 的很多节点支持在字段里写表达式,表达式里变量是作为标识符被解析的。

如果你把变量命名为中文,比如叫「主题」,那么在 {{主题}} 这种引用里,解析器可能把它当成一个未定义的标识符,或者在不同版本、不同节点里表现不一致。更麻烦的是 API 对接:Dify 的 API 调用要求你在 inputs 里传出字段名,如果这个字段名是中文,很多客户端库、服务端框架、网关会做编码处理,轻则报错,重则静默丢失,排查起来极其痛苦。

所以变量名一律用英文蛇形命名:topic、target_audience、core_keywords、word_count、tone_style、forbidden_words、reference_material、section_count。名字要能自解释,不要用 a、b1 这种。

可复制的变量定义表

| 变量名             | 显示名       | 类型     | 必填 | 示例值                                             | 用途                     |
|--------------------|--------------|----------|------|----------------------------------------------------|--------------------------|
| topic              | 主题         | 段落     | 是   | 用 Dify 搭建自动出稿的文章工作流                   | 全文的唯一自变量         |
| target_audience    | 目标读者     | 段落     | 是   | 不懂代码但有内容需求的运营、市场人员               | 决定语言深浅与举例方式   |
| core_keywords      | 核心关键词   | 段落     | 是   | Dify,工作流,提示词模板,变量                        | 必须自然出现的关键词     |
| word_count         | 总字数       | 数字     | 是   | 7000                                               | 用于拆解到每节的字数     |
| tone_style         | 语气         | 下拉     | 是   | 专业克制 / 口语轻松 / 犀利直白 / 温和科普          | 控制人称、修辞与判断强度 |
| forbidden_words    | 禁用词       | 段落     | 否   | 赋能,闭环,抓手,生态位                              | 全文不得出现的词         |
| reference_material | 参考素材     | 段落     | 否   | (粘贴原文、数据表或竞品摘要)                     | 提供事实依据,减少编造   |
| section_count      | 章节数       | 数字     | 是   | 8                                                  | 大纲节点输出几节         |
| language_style     | 语言风格     | 下拉     | 否   | 口语化 / 书面化 / 工具文档风                       | 供后续条件分支使用       |

三段式流程设计:大纲、扩写、合并

变量切好之后,就要设计流程。Dify 的工作流画布是可视化的,节点之间连线,数据顺着线流动。一条完整的文章工作流,最稳的结构就是三段:先出大纲,再逐节扩写,最后合并输出。

很多人会问,为什么不直接一个节点出全文?因为一个节点出的全文,你无法干预中间过程,也无法只重跑一部分。三段式的本质是把一次不可控的长生成,拆成三次可控的短生成。下面逐段讲。

段一:大纲生成——把主题变成可执行的结构

大纲节点的任务是:拿到六个变量,产出一份章节清单,每节带标题和要点。这个节点是整条链路的地基,它决定了后面所有节点的输入格式。

为什么大纲必须强制返回 JSON

这是新手最容易忽略的设计决策。如果你让模型返回一段普通文本的大纲,比如「第一节:为什么……,要点:A、B、C;第二节……」——人是能看懂的,但下一个节点看不懂。Dify 的迭代节点需要遍历一个数组,如果拿到的是一个字符串,它就没法逐项迭代。

所以大纲节点必须配上 Dify 的结构化输出能力:在提示词节点里开启输出格式,定义一个 schema,规定返回 { "outline": [ { "index": 1, "title": "...", "points": [...], "target_words": 900 } ] }。这样后面直接用变量引用就能取到数组,迭代节点直接吃。

这是 Dify 相对纯聊天界面的核心优势之一:节点之间靠结构化数据传参,而不是靠自然语言拼接。自然语言拼接在节点多了之后会失控——你不知道上一段的哪个词会干扰下一段,也不知道模型是不是漏写了某个字段。

大纲 JSON 提示词模板
你是一位有二十年经验的内容策划。请根据以下信息,为文章设计一份章节大纲。

【主题】{{topic}}
【目标读者】{{target_audience}}
【核心关键词】{{core_keywords}}
【总字数】{{word_count}}
【章节数】{{section_count}}
【语气】{{tone_style}}
【参考素材】{{reference_material}}

要求:
1. 章节数必须正好是 {{section_count}} 节,不多不少。
2. 每节标题不超过 20 个字,必须是一个具体的判断或动作,不能是「概述」「简介」这类空词。
3. 每节给 3 到 5 个要点,要点必须是可执行的动作或可验证的事实,不写套话。
4. 第一节要直接切入痛点,不要在开头做冗长的背景铺垫。
5. 每节的 target_words 按照总字数平均分配,最后两节可以略多,用于收尾和落地。
6. 核心关键词要分散到不同章节,不要全部挤在一节里。

只输出 JSON,不要输出任何解释、前后缀或 Markdown 代码块标记。
格式如下:
{
  "outline": [
    {
      "index": 1,
      "title": "章节标题",
      "points": ["要点一", "要点二", "要点三"],
      "target_words": 900
    }
  ],
  "overall_logic": "用一句话说明这篇稿子的主线逻辑"
}

大纲节点之后要加一个代码节点做兜底

模型偶尔会不听话。它可能在 JSON 前面加一句「好的,以下是大纲」,也可能用 Markdown 代码块包起来。所以在大纲节点和迭代节点之间,最好插一个代码节点(Dify 支持 Python/JavaScript),做三件事:去掉前后缀和代码块标记、检查 JSON 是否合法、检查数组长度是否等于 section_count。如果校验失败,走失败分支重试一次。这一段后面还会细讲。

段二:分节扩写——迭代节点是整条链路的发动机

拿到大纲数组之后,就该逐节写了。Dify 的迭代节点可以遍历一个数组,对每个元素跑一遍子流程,把所有结果收集起来。这正是分节扩写最自然的实现方式。

迭代节点里要传三样东西

每一节扩写时,模型需要三种上下文,缺一不可:

  1. 全局上下文:主题、读者、语气、禁用词、整份大纲。让模型知道自己在写整篇文章的第几节,前后是什么结构。
  2. 当前节要点:这一节的标题和 points 数组。这是本节的内容骨架。
  3. 上一节结尾:上一节最后两三句原文。这是解决「节间衔接生硬」的关键。

前两样比较好理解,第三样最容易漏。很多人第一次做分节扩写,跑出来的稿子每一节都像是单独的一篇,读起来有明显的断裂感——因为每一节都是从零开始的,模型不知道上一节已经讲到了哪。加上「上一节结尾」,模型就能自然地接上。

具体的做法是:迭代节点里维护一个「上一节结尾」的变量,初始为空;每一轮结束后,用代码节点截取本轮输出的最后 100-150 字,写回这个变量,供下一轮使用。Dify 的迭代节点支持在子流程内引用外层变量,这个回写稍微绕一点,最常见的做法是用变量赋值节点在迭代结束前更新。

分节扩写提示词模板
你是资深内容作者,正在为一位内容编辑撰写一篇长文的其中的一节。

【整篇文章主题】{{topic}}
【目标读者】{{target_audience}}
【全文语气】{{tone_style}}
【全文禁用词(出现即失败)】{{forbidden_words}}
【完整大纲(供你理解前后文,不要复述其他节的内容)】
{{outline_text}}

【你要写的这一节】
第 {{current_index}} 节,标题:{{current_title}}
本节要点:
{{current_points}}

【本节目标字数】{{current_words}} 字(上下浮动不超过 15%)
【上一节的结尾原文】
{{previous_ending}}

写作要求:
1. 开头不要重复本节标题,直接写内容。如果上一节结尾不为空,本节第一句要能自然衔接上一节,但不要写「正如上文所述」这类衔接词。
2. 只写本节,不要提前写后面章节的内容,也不要总结全文。
3. 每个要点至少用一个具体例子、数字或步骤来支撑,禁止只给观点不给证据。
4. 核心关键词出现在本节时保持自然,不要为了出现而出现。
5. 禁止使用以下词汇:{{forbidden_words}}
6. 结尾留一个自然的过渡,为下一节铺垫,但不要写「下一节我们将」这种直白的提示。

只输出本节的正文内容,不要输出标题、不要输出节序号、不要输出任何说明。

为什么每节都要把「完整大纲」喂进去

有人会觉得这样很浪费 token——八节就要把大纲传八遍。但这是必要成本。如果你只传当前节,模型就不知道前后是什么,很容易在某节里把后面该讲的东西提前讲了,导致后文无话可说、开始注水。把大纲传进去,相当于给模型一张地图,它知道自己在哪、要去哪、不该越界。

真正的成本优化在别处:把大纲压缩成「节序号 + 标题」的清单再传,而不是传完整的 points。这样 token 能省一半,效果几乎不损失。

段三:合并输出——拼装只是开始,格式清理才是关键

迭代节点跑完,你会得到一个结果数组。合并节点要做四件事:给全文加一个主标题、拼装各节正文、在最前面加一段摘要、做格式清理。

主标题单独生成,不要用大纲的第一节标题

很多人偷懒,直接把第一节标题当文章标题。这是错的。节标题和文章标题的功能完全不同:节标题服务于结构,文章标题服务于点击。所以合并节点之外,最好再挂一个小的提示词节点,专门根据主题和全文摘要生成主标题,要求给三个候选,再由一个代码节点或人选定一个。

摘要放在最后生成,不要放在最前

这是我在实践中反复验证过的顺序问题。如果你先让模型写摘要,它写的一定是「空对空」的预告。正确的做法是:全文拼装完成之后,把全文正文喂给一个摘要节点,让它压缩成 80-100 字。这样摘要里就会有真实的信息密度,而不是「本文将介绍……」这种废话。

合并节点的最小配置

Dify 的合并靠模板节点(或者代码节点里的字符串拼接)。最简版本就是一个模板,把主标题、摘要、各节正文按顺序拼起来。更稳的版本是在模板里加条件:如果某节正文为空或过短,插入一行「本节内容生成失败,请重试」,并在最终日志里标红。这样你一眼就能看出哪一节出了问题,而不是交出去之后才发现少了一段。

为什么分节扩写比一次性生成好:四个真实差异

总有人问:一次生成不香吗,省得搞这么复杂。下面这四个差异,是我在几十篇长文里对比出来的,不是理论推演。

差异一:一次性生成会「后段塌陷」

模型在生成长文时,注意力是有限的。写到三千字以后,开头定下的语气、结构、用词习惯会逐渐稀释,这就是「风格漂移」。更严重的是「后段塌陷」:文章的后三分之一开始重复前文的观点,句式变得模板化,判断变得含糊,「综上所述」「总而言之」开始高频出现。

分节扩写天然规避了这个问题,因为每一节都是一次独立的短生成,每一节都从「满血状态」开始。你不需要一篇 7000 字的稿子中途不崩,你只需要每一节 900 字不崩。后者对模型来说容易得多。

差异二:可控性——坏了只修一节

这是最实际的收益。一次性生成的稿子,如果你对第五节不满意,你只有两个选择:要么整篇重跑,赌这次第五节能写好;要么手动改。而分节扩写,你可以只重跑第五节——在 Dify 里就是把迭代节点单独跑一次,或复制一个测试运行只带第五个输入。

重跑的粒度决定了迭代速度

内容创作的本质是迭代。一篇稿子从初稿到能用,通常要改三到五轮。如果每轮都是整篇重跑,你的迭代成本是 O(n);如果每轮只是局部重跑,成本是 O(1)。这个数量级的差异,决定了你一天能出几篇稿。

差异三:但分节也有代价——衔接会生硬

这里必须诚实。分节扩写不是没有代价的。最明显的代价就是节间衔接。每一节单独看都很好,连起来读却像八个人各写一段。读者会在每一节开头感到一次「重新启动」,这对于追求流畅阅读体验的文章是硬伤。

解决方案有三层,逐层递进:一是提示词里带「上一节结尾原文」,让模型自然续接;二是要求每节开头「不重复标题、写一句承接上文的具体句子」,而不是抽象过渡;三是写完之后的润色环节,专门有一个节点只做「衔接修补」——把每两节交界处各取前后 100 字,让模型重写这 200 字,让它读起来像同一支笔写的。第三层最费事,但对成稿质量提升最明显。

差异四:字数分布更均匀

一次性生成 7000 字,模型很容易把一大半篇幅花在开头,到后面草草收尾。因为「开头好写,结尾难写」是模型的固有倾向。分节扩写则给了每节一个硬指标,模型在写每一节时都知道自己要凑够 900 字,分布的均匀性会好很多。这也是为什么大纲节点里要给每一节单独的 target_words,而不是只给总字数。

硬编码提示词与变量化提示词对比表

回到最初的论点,用一张表把两种做法的差异说清楚。

对比维度 硬编码提示词 Dify 变量化提示词
换主题的操作 重写整段提示词,改 5-8 处 在开始节点改一个字段 topic
单次换主题耗时 10 到 30 分钟,含调试 1 分钟内,不含生成时间
出错风险 高,容易漏改某处上下文 低,只有参数变,逻辑不变
质量基线 每次都在漂移,无法对比 稳定,可做 A/B 对照
经验沉淀 随主题一起被覆盖 沉淀在提示词节点里,持续生效
协作 只有作者本人会改 填写输入字段即可,零学习成本
批量出稿 基本不可行 通过 API 批量传参数即可
适用场景 一次性、极特殊的需求 持续性、系列化、模板化的选题

一次性生成与分节扩写对比表

对比维度 一次性生成全文 分节扩写
风格一致性 后段易漂移 整体稳定,交界处需修补
单节点可控性 低,只能整篇重跑 高,可单节重跑
修一次的成本 整篇 token 成本 单节 token 成本
字数控制 前多后少,分布不均 按 target_words 均匀分布
结构可控性 依赖模型自觉 由大纲节点锁定,逐节落实
段落衔接 自然 需靠上下文与润色修补
实现复杂度 一个节点搞定 需大纲、迭代、合并、代码兜底
适合篇幅 1500 字以内较稳 3000 字以上几乎必选

大多数人做错的地方:五个常见误区

这一节是我最想写的。前面讲的是怎么做对,这一节讲的是为什么你做了工作流却还是没变快。这些坑我自己全踩过。

误区一:把工作流做成「一键出稿」的黑盒

新手搭完工作流最喜欢干的事,是把所有节点连成一条直线,点一下运行,等三分钟,得到一篇稿子。看起来很美,但很快你就发现:遇到不合适的稿子,你根本不知道是哪一步出的问题。是主题理解偏了,还是大纲太散,还是某一节扩写注水了?

正确做法是在每个关键节点之后都留一个「可观察点」——大纲节点之后看一下 JSON,合并之前看一下每节字数,而不是等到最后才看结果。Dify 的运行日志里能看每个节点的输入输出,你要养成点进去看的习惯。工作流的价值不在于自动化,而在于让每一步都可检查。

误区二:把示例文章整篇塞进参考素材

很多人以为素材越多越好,把一篇写得好的范文整篇贴进 reference_material。结果模型写出来的稿子全是那篇范文的影子——句式、结构、甚至论点都照搬。参考素材的作用是提供事实,不是提供风格。风格应该由提示词规则来控制,而不是由一篇范文来暗示。

正确的做法是:素材只放数据和案例,不放成篇的论述。如果是竞品文章,只摘它的核心结论和数据出处,不摘它的表达。

误区三:在每一个节点里都重写一遍规则

我见过有人在大纲节点里写了一段「语气要专业克制」,然后在分节扩写节点又写一遍,合并节点再一次。三处规则不一致时,模型会遵循哪一条?你无法预测。更严重的是一旦你要改语气,得改三处,漏一处就打架。

正确的做法是:全局规则只定义一次。要么全部用变量传入,要么放在一个共享的系统提示词里。Dify 没有特别强的「全局提示词」机制,所以最稳的做法是把「语气、禁用词、读者」这些跨节点规则都做成开始节点的变量,然后在每个节点里用同一套引用。变量只在一处被改,全局同步。

误区四:只优化提示词,不优化数据结构

这是最有价值的一条。很多人调工作流调了一周,不停改提示词措辞,效果时好时坏。但他们从来没想过:也许问题不在措辞,而在数据结构。比如大纲的 points 如果是一整段话而不是数组,模型在扩写时就得自己拆解,每次拆得都不一样,稳定性自然差。如果改成三到五条独立字符串,扩写节点的输入就变得确定,输出也变得稳定。

在 Dify 里,数据结构的设计比提示词的字词选择更重要。同样一句话「本节要点」,用数组传和用字符串传,效果能差出两个档次。每次遇到不稳定,先问自己:是不是某个字段该是数组的,我传了字符串?

误区五:一次性把参数调到「完美」再上线

有人把一条工作流调试到十全十美才敢用,结果花了三周。更聪明的做法是:先用两个差异极大的主题跑通骨架,确认「换主题不改提示词」这条底线成立,然后再慢慢打磨。骨架对了,细节可以边跑边改;骨架不对,细节调得再好也是白搭。下面第四节就是讲这个验证方法。

换主题不改 Prompt:用一个对照实验证明给你看

光说不练没有说服力。这一节给你一个可以立刻自己做的验证:用同一条工作流,跑两个完全不同的主题,看提示词有没有改动一个字。

实验设计:两个「八竿子打不着」的主题

选主题有个原则:差异越大越能暴露问题。如果两个主题都属于「AI 工具教程」,它们天然相似,模型靠惯性也能写得差不多,验证不出什么。所以要故意选跨领域的。

  • 主题 A:用 Dify 搭建自动出稿的文章工作流(面向运营,技术类)
  • 主题 B:城郊周末露营的装备清单怎么配(面向新手家庭,生活类)

这两个主题的读者、语气、关键词、结构逻辑都完全不同。如果提示词里有任何硬编码的痕迹——比如写死了「本文聚焦 AI 工具」,或者默认读者是技术人——主题 B 一定会露馅:要么写出来还是一股技术味,要么直接跑偏。

只改开始节点的输入字段

实验的关键约束是:除了开始节点的输入,其它节点一律不动。下面是把主题 A 换成主题 B 时,你需要改的内容——只有这一张表的右列。

变量 主题 A 取值 主题 B 取值
topic 用 Dify 搭建自动出稿的文章工作流 新手家庭城郊周末露营的装备清单
target_audience 有内容需求的运营与市场人员 完全没有露营经验的城市家庭
core_keywords Dify,工作流,提示词模板 露营,装备清单,新手,预算
tone_style 专业克制 温和科普
forbidden_words 赋能,闭环,抓手 硬核,天花板,闭眼入
section_count 8 6
word_count 7000 5000

怎么证明「提示词零改动」

用提示词节点的导出功能对比

Dify 的工作流支持导出为 DSL 文件(YAML 格式)。做实验之前导出一份 v1.yml,两个主题都跑完之后再导出一份 v2.yml,用 diff 工具对比。如果提示词节点里的文本完全一致,diff 结果里只会出现运行记录相关的内容,不会有任何 prompt 字段的变化。这是最硬的证据。

观察两次输出的差异在哪

diff 证明提示词没变之后,再看输出。如果两次输出在结构上一致(都是同样的节数逻辑、同样的 JSON 结构、同样的每节要点密度),但在内容上完全不同(一个是技术流程,一个是露营清单),就说明变量化是真的生效了。

反过来,如果主题 B 的输出里出现了「我们会用到模型」「提示词设置」之类的词,说明某个提示词的角落还藏着硬编码的痕迹,回去找那句话,把它改成变量或者删掉。

一个容易忽略的坑:默认值里藏着的硬编码

有时候提示词明明没改,但主题 B 的效果还是不对。检查一下开始节点的默认值。很多人在搭工作流时,为了让测试跑得通,把 topic 的默认值填成了第一个主题,把 forbidden_words 填成了第一组词。结果换主题时忘了改默认值,模型收到的还是旧参数。

更隐蔽的是,有些人把「专业克制」这种语气直接写进了下拉选项的默认值里,而主题 B 本来就是温和科普。这类问题在 diff 里看不出来,因为提示词确实没变,但参数传错了。所以做对照实验时,一定要逐个核对开始节点的每一个字段,确认都换成了新值。

失败处理:三种最常见的报错与应对

工作流一旦跑起来,失败是常态。Dify 提供了失败分支、重试、超时设置,但关键是你要知道哪些失败需要兜底、哪些失败值得重试。下面是最常见的三种。

失败一:模型返回的不是合法 JSON

这是大纲节点最常见的失败。即便你开启了结构化输出,模型偶尔还是会返回带 Markdown 代码块标记的内容,或者在 JSON 前多说一句话。Dify 的结构化输出能拦住一部分,但拦不住全部。

三层兜底策略

  1. 第一层:开启结构化输出。在提示词节点里配置输出 schema,让 Dify 在模型返回时做格式约束。这一步能解决 80% 的问题。
  2. 第二层:代码节点清洗。写一个代码节点,先去掉首尾的 ```json 和 ```,再找到第一个 { 和最后一个 } 之间的内容,然后 json.loads。如果解析失败,返回一个带 ok: false 标记的对象。
  3. 第三层:条件分支重试。判断 ok 是否为真,为假时走一个重试节点,把原始返回和「你上次的返回不是合法 JSON,请只输出 JSON」一起传回去,最多重试两次。
import json

def main(raw: str) -> dict:
    text = raw.strip()
    if text.startswith("```"):
        text = text.split("```")[1]
        if text.startswith("json"):
            text = text[4:]
    start = text.find("{")
    end = text.rfind("}")
    if start == -1 or end == -1:
        return {"ok": False, "data": [], "reason": "no json object found"}
    try:
        obj = json.loads(text[start:end + 1])
    except Exception as e:
        return {"ok": False, "data": [], "reason": str(e)}
    outline = obj.get("outline", [])
    if not isinstance(outline, list) or len(outline) == 0:
        return {"ok": False, "data": [], "reason": "outline empty"}
    return {"ok": True, "data": outline, "reason": ""}

注意这个代码节点输出的 data 是数组,可以直接接到迭代节点的输入上。这就是前面说的「数据结构优先」——代码节点不仅清洗格式,还顺便把结构对齐了。

失败二:某节字数不达标

字数不达标有两种情况:模型写少了,或者写多了。写少了更常见,也更麻烦。常见的表现是某节只写了两三百字,就草草收尾,然后开始总结。

判断标准要放宽,不要卡死

如果你把标准设成「必须正好 900 字」,那几乎每一节都会触发重试,成本爆炸。合理的标准是 ±20%。即 900 字的目标,720 到 1080 字都算合格。超出这个区间的,才走重试。

重试时要把「为什么重试」告诉模型

很多人重试时就是原样再跑一遍,结果大概率还是短的。正确的做法是把失败原因带回去:「你上次只写了 380 字,目标是 900 字。请把第三个要点展开成三个具体步骤,每个步骤给一个例子,重新写这一节」。带上诊断信息的重试,成功率会高很多。

另外,字数不达标的根因往往不在扩写节点,而在大纲节点。如果某一节的 points 只有两条空泛的要点,模型拿什么也扩不出 900 字。所以字数不达标时,先回头看看大纲那一节是不是写得太薄。

失败三:迭代节点中途失败

迭代节点是串行或并行地跑子流程,跑到第三节时模型超时了,整条迭代就中断了。Dify 的迭代节点支持设置最大并行数和错误处理策略,但默认的行为是中断整个迭代。

把「失败」变成「占位」

最稳的做法不是让迭代失败,而是让失败的节输出一个明确的占位符,比如 [本节生成失败:超时]。这样迭代能完整跑完,你在合并节点里一眼就能看出哪一节缺了,然后单独重跑那一节。如果让迭代直接中断,你前面跑完的五节就浪费了。

具体实现:在迭代子流程里,给扩写节点的失败分支接一个代码节点,返回占位字符串。同时在日志里记录失败节的 index。跑完看一遍日志,就知道该补哪一节。

并行度不要开满

Dify 的迭代节点可以设置并行数,并行跑能省时间,但并行数开太大会触发模型接口的限流,反而更容易失败。经验值是开 2 到 3。如果你用的是第三方模型的 API,限流更严格,建议开 1 到 2。

把工作流变成生产力:输出、接口与批量

工作流跑通了只是第一步。真正让它变成生产力的,是把它接出去,让结果能自动流到你需要的地方。

输出:先导出 Markdown,再考虑其他格式

合并节点的输出建议直接是 Markdown。原因很简单:Markdown 是纯文本,任何下游系统都能处理,转 HTML 一行代码,转 Word 用现成库,转富文本粘进编辑器也不变形。不要在工作流里直接拼接 HTML,因为 HTML 一旦带了标签,后面想改格式就要重新解析。

Markdown 输出的最小约定

  • 主标题用一级标题,节标题用二级标题。
  • 摘要放在标题下方,用引用块包起来。
  • 每节之间用空行分隔,不要用多余的分隔线。
  • 代码块统一用三反引号加语言标记。

这些约定看起来琐碎,但一旦固定下来,你的导出脚本就能稳定地按规则切分、重排、加样式。

接口:接 Webhook 还是走 API

Dify 的应用可以发布成 API,你在外部系统里用 HTTP 请求调用,把变量放在 inputs 里传进去。这是最通用的方式,适合自己做前端或者接到自己的后台。

但如果你的需求是「在 Notion 建一条记录就自动出稿」,那更适合查一下 Dify 是否支持工作流的 Webhook 触发,或者用外部工具做中转:Notion 触发 → 中转服务读取记录 → 调用 Dify API → 把结果写回 Notion。这条链路里 Dify 只负责「生成」,其它的编排交给外部工具。

API 调用的几个注意事项

  1. 参数名要和开始节点的变量名完全一致,这也是前面强调英文蛇形命名的原因。
  2. 可以传 response_mode: blocking 等待结果,也可以传 streaming 边生成边取。长文建议用 streaming,避免请求超时。
  3. 记录每次调用的 task_id 和 workflow_run_id,出问题时能回溯到具体是哪一次运行。
  4. 在开始节点设好默认值,这样即使某个可选字段没传,工作流也不会直接报错。

批量:一次跑十篇的正确姿势

批量跑长文有个反直觉的结论:不要试图用 Dify 内部去批量遍历主题列表。虽然可以用迭代节点套迭代节点,但两层迭代会让日志难以追踪,失败定位也变成噩梦。

更好的做法是:Dify 只负责「一次出一篇」,批量交给外部的脚本。你写一个简单的 Python 脚本,读一个 CSV(每行一个主题及其参数),循环调用 API,把结果写进各自的文件。脚本挂了可以重跑某一行,日志清晰,成本也好算。

批量脚本的关键:幂等

写批量脚本时,最容易犯的错是不做幂等。脚本跑到第七篇时崩了,你重新跑一遍,前六篇又被覆盖,白白烧钱。正确做法是每篇输出完成后,在目标位置写一个完成标记(比如文件存在就跳过),重跑时自动续上。

节点配置清单:一次搭好,长期复用

最后给你一张完整的节点清单,照着配就行。这张表本身也是一份可以复制的资产。

【Dify 文章工作流 · 节点配置清单】

1. 开始节点
   - 输入字段:topic / target_audience / core_keywords / word_count
              / tone_style / forbidden_words / reference_material
              / section_count / language_style
   - 命名:全部英文蛇形,全部设默认值

2. 提示词节点 · 大纲生成
   - 引用:start.topic, start.target_audience 等
   - 开启结构化输出,schema 为 outline 数组
   - temperature: 0.3(结构要稳)

3. 代码节点 · JSON 清洗与校验
   - 输入:大纲节点的原始返回
   - 输出:ok(布尔) / data(数组) / reason(字符串)

4. 条件分支 · 校验结果
   - ok == true  -> 进入迭代
   - ok == false -> 重试节点(最多 2 次)

5. 迭代节点 · 分节扩写
   - 输入:代码节点输出的 data 数组
   - 并行数:2
   - 子流程:
     a) 变量赋值:previous_ending = 空字符串(初始化)
     b) 提示词节点:分节扩写提示词
     c) 代码节点:截取本轮结尾 150 字,回写 previous_ending
     d) 代码节点:统计字数,标记是否达标
   - 失败策略:输出占位符,不中断

6. 模板节点 · 全文合并
   - 拼装:主标题 + 摘要 + 各节正文
   - 空节检测:为空或过短时插入占位提示

7. 提示词节点 · 摘要生成
   - 输入:合并后的全文
   - 输出:80-100 字摘要,不放开头套话

8. 提示词节点 · 主标题生成
   - 输入:主题 + 摘要
   - 输出:三个候选标题

9. 结束节点 · 输出
   - 输出:markdown(字符串)/ failed_sections(数组)

把这张清单变成团队规范

如果你们是多人协作,这张清单本身就应该是团队规范的一部分。新同事搭工作流时照抄这份配置,变量名怎么起、哪些节点开结构化输出、并行数开多少,都不用再讨论。这是「提示词是资产」这个论点的另一层含义——不只是提示词,整条工作流的配置都是资产。

常见问题

Q1:Dify 的工作流和直接在聊天窗口里写提示词,到底差在哪?

差的不是模型能力,而是有没有把变量和逻辑分开。在聊天窗口里,主题和规则混在一段话里,改一次主题就要重写整段。在 Dify 的工作流里,主题是开始节点的一个输入字段,规则是提示词节点里的固定文本,两者物理隔离。这正是「换主题不改 Prompt」能够成立的前提。而且 Dify 的节点能用结构化数据传参,这比聊天窗口里自然语言拼接要稳定得多。

Q2:我的主题差别很大,一条 Dify 工作流真的能通用吗?

能,但有边界。工作流通用的前提是「文章的结构逻辑」相似,比如都是「观点 + 论证 + 落地」。如果一篇是教程、另一篇是小说,那就不该用同一个大纲提示词。实际做法是:按文体拆几条 Dify 工作流,每条工作流内部主题可变、结构固定。大多数人需要的其实是两三条,而不是几十条。

Q3:分节扩写时,迭代节点跑得特别慢怎么办?

先看瓶颈在哪。如果是模型响应慢,可以适当提高并行数(但要留意接口限流);如果是每节的输入太长,可以把大纲压缩成「节序号 + 标题」再传,能省掉一半输入。另外,如果某一节本身就不长,可以考虑把它和下一节合并成一节,减少迭代次数。最后,把迭代节点放在 Dify 里跑本身就比在本地脚本里慢,这是平台调度的成本,接受它。

Q4:提示词里要不要写「请仔细思考」「你是一位专家」这类话?

「你是一位专家」在 Dify 的节点里价值不大,因为模型早就知道了,这句话更多是心理安慰。「请仔细思考」对新一代模型也没多大用。真正有用的是具体的结构约束:「每节给出三个要点」「不要复述标题」「禁用以下词」。在 Dify 工作流里,把情绪化的话换成可检验的规则,质量提升远比加一句「请认真」明显。

Q5:为什么大纲节点必须输出 JSON,普通文本不行吗?

因为下游的迭代节点需要一个数组来遍历。普通文本大纲是一整段字符串,迭代节点没法拆,只能再用一次模型调用去拆,既慢又不可控。强制 JSON 之后,大纲的每个字段都变成可寻址的变量,迭代节点直接取 title 和 points,中间没有任何自然语言解析。这是 Dify 工作流稳定性的地基。

Q6:这套 Dify 工作流适合写多长的文章?

1500 字以内,直接用一次性生成更简单,不值得拆成三段。3000 到 8000 字是分节扩写的最佳区间,结构清晰、每节可控。超过一万字,单靠这条链路会吃力,需要在上层再加一层「分部分」的结构,也就是先出大章节,再出小节,再逐小节扩写。但说实话,一万字以上的稿子,人工介入的价值已经开始超过自动化的价值了。

结语:把提示词当资产,把主题当参数

回到最开始那句话。大多数人觉得自己提示词写得不好,于是不停改措辞、加细节、找技巧。但真正的问题往往不在这里,而在于你把该变成参数的东西,硬编码进了规则里。Dify 这样的工作流平台,最大的价值不是「自动化」,而是逼你做一次清晰的分工:哪些是不变的资产,哪些是要变的参数。

一旦这个分工做对了,你会发现事情突然简单了——你的提示词不再是一个每周重写的消耗品,而是一个越用越顺手的工具。你换主题时只需要动开始节点的几个框,剩下的交给工作流。这就是「提示词是资产,主题是参数」的全部意思。

如果你的工作流里还有一句话是「跟主题有关」的,那它就是一根还没拔掉的硬编码。找出来,改成变量。这一步做完,你的 Dify 工作流才算真正属于你,而不是属于某一个主题。

THE END
喜欢就支持一下吧
点赞48 分享
评论 抢沙发
头像-知了AI
欢迎您留下宝贵的见解!
提交
头像-知了AI

昵称

取消
昵称表情代码快捷回复

    暂无评论内容