Dify 里搭一条多阶段 Workflow ——把"复杂任务流水线"平移成多阶段 Workflow
一、蓝本:课程第八章的流水线在干什么
先把课程那套组装回忆一遍(细节见第八章 08 篇导读):
一次"开始制作" → 备料入库(brief 制作要求 / skills 方法手册 / contracts 交付契约)
→ Graph 定阶段(世界观 → 剧情大纲 → 场景……)
→ 每个阶段:应用生成任务单 → 总导演 story-director 用 task 委派一次 → 角色 Agent 干活
→ 产物写 /staging/<任务ID>/(候选)→ 审核通过后转正 → 后续角色按 readFiles 读正式版
世界观设计师那份任务单(课程示例,四字段是主角):
{
"assignee": "world-designer",
"goal": "设计世界规则与人物档案",
"readFiles": ["/revisions/1/brief.md", "/contracts/world.schema.json", "…"],
"writeFiles": ["/staging/<任务ID>/world.json", "…"],
"skillPath": "/skills/world-building/SKILL.md",
"acceptanceCriteria": ["符合世界规则", "角色数量与制作要求一致"]
}
配套护栏:总导演全局 write deny(只委派不动手)、委派边界三查(不是 task 不行、委派对象不对不行、已委派过再委派也不行)、角色只能写任务单的 writeFiles。
本篇的任务:把这张图逐块搬到 Dify。
二、总装:主 Workflow 当总导演,三个子流程当角色
课程的三层分工,在 Dify 上各归各位:
| 课程的层 | Dify 里变成 |
|---|---|
| LangGraph Graph 定阶段 | 主 Workflow 的画布连线 |
| Agent 应用准备任务单 | 开始节点输入变量 + 各子流程的输入定义 |
| 总导演 task 委派 | 工具节点调用子流程("工作流"当工具) |
总装图(本篇要搭的东西):
主 Workflow(总导演)
开始(brief 文本、角色数量、场景数量)
→ [工具] 世界观设定子流程
输入:brief、方法说明、角色数量 → 输出:world_json(世界规则 + 人物档案)
→ 代码节点:契约校验(= acceptanceCriteria 的结构版)
→ 条件分支:不达标 → 失败支路;达标 ↓
→ [工具] 大纲生成子流程
输入:world_json、方法说明、场景数量 → 输出:outline_json(含 scenes 数组)
→ 代码节点:契约校验 → 条件分支
→ 迭代节点:遍历 scenes
每次迭代体 → [工具] 场景生成子流程 → 单个场景 JSON
→ 迭代输出:场景数组(迭代节点的输出本身就是列表)
→ 代码节点:终检(拼装完整产物包)
→ 结束
子流程怎么建(配置意图): 每个角色单独建一个 Workflow 应用——开始节点声明输入变量、结束节点声明返回什么;再回主流程,用"工具-工作流"把它当工具节点调用。填进去的输入 = 任务单,返回的输出 = 交付物。
⭐ 子流程天然带着课程 mode: isolated 的消息隔离:总导演只拿得到输出变量,看不到子流程内部的提示词与中间步骤。
先做一个选择:总导演用普通流程连线,还是 Agent 节点? 课程的总导演是"拿着 task 工具的 Agent";本篇选前者——连线顺序确定,Agent 节点的自主性这一版用不上,还要搭上"护栏变软"的代价(第七节展开)。
三、任务单平移:四字段各换什么形状
| 任务单字段 | 课程里 | Dify 里 | 档位 |
|---|---|---|---|
| readFiles | 路径列表,角色自己 read_file | 子流程输入变量:资料正文以文本参数或知识库检索结果传入 | 换形状 |
| skillPath | SKILL.md 按需加载 | 方法说明文本参数传入,或方法文档进知识库检索 | 换形状(丢渐进披露) |
| writeFiles | staging 候选路径 | 子流程输出变量:约定返回结构化 JSON | 换形状(路径没了) |
| acceptanceCriteria | contracts Schema + Validator | 主流程代码节点校验输出 JSON | 换形状 |
readFiles:从"自己去读"变成"填表给你"。 课程里路径只是位置,读不读由角色决定,所以有"备料 ≠ 读到"的失效点。Dify 里资料以变量值进入,进不进上下文由主流程决定——失效点跟着变形:传了参数 ≠ 模型用了,验收要看输出,别信输入。
skillPath:方法传入的两条近似路。 课程 Skill 的精髓是渐进式披露(先给简介,要用再读全文)。Dify 没有原生对应:固定步骤干脆封进子流程——方法从"模型读的说明"变成"画布上的固定序列",软建议变硬步骤;可变的方法写成文档放知识库按需检索——但检索不命中就不进上下文,披露层级只能靠分段设计近似(02 篇差距清单②)。
writeFiles:从"写到哪"变成"返回什么"。 在子流程里约定输出变量的 JSON 形状。本篇自定一份"世界产物契约",代替课程的 world.schema.json:
{
"worldRules": "……世界规则……",
"characters": [
{ "name": "……", "identity": "……", "goal": "……" }
]
}
acceptanceCriteria:从契约文件变成校验代码。 见下一节。
四、验收:代码节点当 Validator
主流程在每个子流程调用之后接一个代码节点做结构校验——这就是课程的 JSON Schema / Zod 校验换的家。示例(Python,检查上节那份契约,含"角色数量与制作要求一致"这条验收标准):
import json
def main(world_text: str, required_characters: int) -> dict:
# ① 先保证是合法 JSON:LLM 偶尔会在外面包一层说明文字
try:
data = json.loads(world_text)
except json.JSONDecodeError as e:
return {"valid": False, "errors": f"不是合法 JSON:{e}"}
if not isinstance(data, dict):
return {"valid": False, "errors": "world 产物必须是 JSON 对象"}
errors = []
# ② 结构检查:worldRules 必须是非空文本
if not isinstance(data.get("worldRules"), str) or not data["worldRules"].strip():
errors.append("缺 worldRules(世界规则)")
# ③ 结构检查:characters 是非空数组,每个角色三字段齐全
characters = data.get("characters")
if not isinstance(characters, list) or not characters:
errors.append("characters 必须是非空数组")
else:
for i, c in enumerate(characters):
for field in ("name", "identity", "goal"):
if not str(c.get(field, "")).strip():
errors.append(f"第 {i + 1} 个角色缺 {field}")
# ④ 数量线:acceptanceCriteria 里的"角色数量与制作要求一致"
if isinstance(characters, list) and len(characters) != required_characters:
errors.append(f"角色数量 {len(characters)},要求 {required_characters}")
return {"valid": len(errors) == 0, "errors": ";".join(errors)}
两个课程味的分工提醒:
-
代码节点只查结构。 "worldRules 非空"查得了;"符合世界规则"这种语义项查不了——先用数量线这类可硬化的检查顶住,语义审查(Judge)是下一篇的正题。能直接比较的交给代码,需要理解含义的交给模型——课程第十章 03 篇的分工原样成立。
-
校验失败走条件分支的失败支路。 本篇先做"记录并终止";带问题报告返工的完整闭环,下一篇补上。
五、迭代节点:scenes 数组变成一串场景
大纲子流程输出的 outline_json 里约定一个 scenes 数组(本篇自定:每个元素含 title、summary 两个文本字段)。主流程用迭代节点遍历:
-
迭代输入:outline 的 scenes 数组;
-
迭代体:一次"场景生成子流程"调用——输入是当前元素的 summary、上游的 world_json、方法说明;输出是单个场景的 JSON;
-
迭代结束,节点输出的数组本身就是场景列表,交给终检(变量聚合器不是干这活的:它汇合的是多路分支的变量,05 篇审核分支的汇合才是它的用法)。
课程对应:场景编剧按大纲逐场景干活;Dify 把这个 for 循环画在画布上。注意迭代体之间的隔离与课程子 Agent 一致——第 2 个场景默认看不到第 1 个场景生成了什么。课程里角色靠 Workspace 共享上下文;这里若要场景衔接,必须把"已生成内容"显式传给下一轮——这正是下一节差距的现场。
六、最大的差距:没有 Workspace,产物在变量里流动
课程流水线的三样东西——staging(候选暂存)、promote(审核转正)、readFiles(后续角色读正式版)——全都建立在"有文件系统"上。Dify 没有:
-
中间产物以变量传递:world_json 这个变量本身就是"候选品"。
-
"候选 → 转正"的近似:过没过代码节点校验,就是候选与正式的分界线。画布上只从"校验通过"支路往下连线,下游引用到的 world_json 必然是过了校验的版本——用结构保证"没转正不被消费"。
-
要跨次运行留产物:变量出了这次运行就没了。需要持久化,就用 HTTP 节点把校验通过的产物写到外部存储——候选与正式版用不同接口或路径区分,把 staging / releases 的语义搬到存储服务那一侧。
⚠️ 课程警告"模型输出 ≠ 文件已落盘",在 Dify 变形为"变量有值 ≠ 产物已持久化":LLM 说写好了、代码节点验过了,都还只在内存里。落盘永远是一次显式的 HTTP 写入;关键产物写完记得读回核对——课程"写后读回"的习惯原样平移。
七、护栏平移:从"运行时拦截"退到"结构约束"
课程三道护栏,在 Dify 里各剩多少:
| 课程的护栏 | 课程实现 | Dify 近似 | 剩多少 |
|---|---|---|---|
| 总导演只委派不动手(全局 write deny) | permissions + 提示词 | 总导演用普通流程节点——根本没有工具可乱调 | 结构上更硬 |
| 只委派一次 | 委派边界三查(运行时拦截) | 画布一条调用边只执行一次;迭代体内按数组长度执行 | 结构保证;次数=数组长度要靠输入校验兜住 |
| 角色只写 writeFiles 白名单 | 文件路径级权限 | 子流程只声明约定的输出变量;节点不挂多余工具;敏感操作收进固定参数的代码 / HTTP 节点 | 粒度粗:路径白名单退成接口白名单 |
如果总导演改用 Agent 节点(第二节的选择题):自主性回来一点,护栏就软一截——挂着的子流程工具都可能被它选、选几次由策略决定,"只委派一次"只剩提示词约束,要靠后续代码节点校验"产物只有一份世界观"来兜底。
差距明说:课程那种"参数级的运行时拦截",Dify 没有——拦不住"这一次调用的参数想写哪里",只能在两层设防:结构层(连了什么节点、子流程声明了什么变量)与授权层(工具允不允许用,部分工具需授权)。所以越敏感的操作,越该从"模型决定参数"改成"代码节点固定参数"——02 篇第 11 条的结论,在这里落地成做法。
平移检查清单
| 课程机制 | 这一篇搭出来的 Dify 形态 |
|---|---|
| 总导演 story-director | 主 Workflow 的普通流程连线 |
| task 委派 + isolated 消息隔离 | 工具节点调用子流程(只见输出变量) |
| 任务单四字段 | 输入变量(readFiles / skillPath)+ 输出变量(writeFiles)+ 校验代码(acceptanceCriteria) |
| contracts Schema + Validator | 代码节点结构校验 + 条件分支 |
| staging / promote | 候选变量 / 校验通过后才被下游引用;持久化走 HTTP 节点写外部存储 |
| 只委派一次护栏 | 一条边一次执行的结构保证 + 代码节点兜底 |
| 按大纲逐场景生成 | 迭代节点(输出的数组即场景列表) |
如果只记 3 句话
-
任务单从"给模型看的说明书"变成"给子流程填的表单":readFiles / skillPath 是输入变量,writeFiles 是输出变量,acceptanceCriteria 是代码节点——接口变硬,交接更稳。
-
没有 Workspace,产物在变量里流动:"过没过校验"就是候选与转正的分界;要跨次运行留产物,就显式用 HTTP 节点写外部存储。
-
护栏从"运行时拦截"退到"结构约束 + 工具授权":粒度变粗是明账,补救是输入白名单化、不挂多余工具、敏感操作固定参数。
一个自检题
世界观设计师任务单的四个字段,在这条 Dify 流水线里各落成什么形状?哪个字段"形状变了但更硬了",哪个丢了最核心的机制?
参考答案:
-
readFiles → 子流程输入变量(资料正文/知识库检索结果直接传入);skillPath → 方法说明文本参数,或方法文档进知识库检索;writeFiles → 子流程输出变量(约定返回的 JSON);acceptanceCriteria → 主流程代码节点校验。
-
更硬的是 writeFiles 与 acceptanceCriteria:任务单从"给模型看的说明书"变成"给子流程填的表单 + 代码验收",交接是硬接口。
-
丢得最狠的是 skillPath:渐进式披露没了——检索不命中就不进上下文(02 篇差距清单②),只能靠分段设计或子流程封装近似。
承上启下
本篇的失败支路只做了"记录并终止"——课程 runReviewLoop 被砍掉了一半。完整的闭环:带着失败原因回到生成重做、再审、最多 N 次、超限转人工。下一篇用代码节点当 Validator、LLM 节点当 Judge,加上条件分支与循环节点,补上审核返工这半圈。
- 点赞
- 收藏
- 关注作者
评论(0)