Dify 企业级实验(02):跨应用状态传递——多轮对话的状态如何跨应用不丢?
1. 业务场景
先讲一个我们实际遇到的场景。
企业客服最常见的对话长这样:用户第一轮说「我要退货」,第二轮补上订单号,第三轮才说清楚期望怎么处理。信息是分轮次到齐的——客服对话应用每轮只拿到一小块,但后台工单应用要的是一整份。
我们第一次接这类需求时,第一反应也是「状态嘛,存数据库不就行了」。真正动手才发现——Dify 的 chatflow 原生就有对话变量(conversation_variables),跨轮次保持,配合工具调用和变量写回就能组成状态闭环,根本不用自己搭存储。难的不是存,是「每一轮都在正确的时间把状态拿出来用」。
如果对话应用收集到的状态传不到后台,工单就建不起来;就算建起来了,用户第二天回来问「我的工单处理到哪了」,系统也得记得住、答得上。
这不是个例。任何「用户分步提供信息、后台分步处理」的场景都是这个模式:理赔资料收集、预约信息确认、多步表单填写——对话跨轮次,状态就必须跨轮次。
2. 场景痛点
这个流程的痛点,在客服团队身上体现得最直接:
- 状态随轮次丢失:用户第二轮补了订单号,第一轮的问题描述没存住,信息永远凑不齐,工单建不起来——用户重复描述,体验直线下降。
- 重复建单:用户再问一次,系统当成新单又建一张,后台被重复工单淹没,真实问题淹没在噪音里。
- 信息不全硬建单:缺订单号也创建工单,工单到后台无法处理,变成死单。
- 跨应用两张皮:对话应用记得、后台不记得,状态不同步,两边各说各话。
本质上,多轮对话的价值恰恰在「跨轮次记住」——状态没地方放、没人管,用户说过的话就等于白说。
3. 方案:为什么是对话变量 + 状态写回
Dify 的 chatflow 有一个原生状态容器——对话变量(conversation_variables),配合工具调用与变量写回,正好组成跨应用状态闭环。选它的理由:
- 平台原生:对话变量跨轮次保持(102-15/103 实测),天然就是「状态容器」,不用自己造;
- 写回闭环:工单应用返回工单号后,用 assigner 节点写回对话变量,用户下一轮能查到、系统也知道已建单,不重复创建;
- 可持久化可审计:工单记录落到外部 KV,工作流重启状态不丢。
这篇文章我们就用它搭一个「客服工单状态闭环」:客服对话应用收集状态、工单应用处理、工单号写回对话变量。
4. 整体架构
链路很清晰:每轮提取新信息 → 与对话变量合并 → 校验完整性 → 建单或追问 → 工单号写回。状态闭环的关键是「写回」——工单号回到对话变量,用户下一轮就能查到。
5. 模块设计
5.1 对话变量(状态容器)
chatflow 在 conversation_variables 里声明 4
个状态变量,跨轮次保持(102-15/103 实测)。id
必须是合法 UUID(非 UUID 会报 SQL 500):
conversation_variables:
- description: 已收集的订单号
id: 53ad6c85-cc60-4d1f-b8bf-06d7e6fb90a3
name: order_id
selector: [conversation, order_id]
value: ''
value_type: string
# issue_desc / expectation / ticket_no 同结构5.2 合并校验 code(cd_update)
PE 每轮只提取新信息,要和对话变量已有值合并,并输出两个字符串状态标志:
def main(pe_order_id, pe_issue_desc, pe_expectation,
cv_order_id, cv_issue_desc, cv_expectation, cv_ticket_no) -> dict:
order_id = pe_order_id or cv_order_id or ""
issue_desc = pe_issue_desc or cv_issue_desc or ""
expectation = pe_expectation or cv_expectation or ""
ticket_exist = "true" if cv_ticket_no else "false"
missing = []
if not order_id: missing.append("订单号")
if not issue_desc: missing.append("问题描述")
if not expectation: missing.append("期望结果")
complete = "true" if not missing else "false"
if ticket_exist == "true":
message = "您的工单 " + str(cv_ticket_no) + " 正在处理中,请耐心等待…"
elif complete == "true":
message = "已收集完整信息,正在为您创建工单…"
else:
message = "请补充以下信息:" + "、".join(missing) + "。"
return {"order_id": order_id, "issue_desc": issue_desc, "expectation": expectation,
"complete": complete, "ticket_exist": ticket_exist, "message": message}5.3 触发条件(if5)
创建工单需要 complete=true 且
ticket_exist=false——校验结果必须接 if-else
消费,否则状态不全也会触发下游(102-20 实测教训):
- id: if5
data:
type: if-else
title: 是否创建工单
cases:
- case_id: create
logical_operator: and
conditions:
- comparison_operator: is
value: 'true'
variable_selector: [cd_update, complete]
- comparison_operator: is
value: 'false'
variable_selector: [cd_update, ticket_exist]5.4 工单工具的参数模板
跨应用传递 = 工具调用参数。坑:chatflow 的 tool 节点参数模板引用 code 节点输出偶发为空(工具报参数 required);本实验状态本就存在对话变量,改用对话变量模板语义最贴合:
- id: tool5c
data:
type: tool
title: 创建工单(工单应用)
provider_type: workflow
tool_name: dify104_02_gongdan
tool_parameters:
order_id:
type: mixed
value: '{{#conversation.order_id#}}'
issue_desc:
type: mixed
value: '{{#conversation.issue_desc#}}'
expectation:
type: mixed
value: '{{#conversation.expectation#}}'
tool_configurations: *tool_parameters # 双写同值5.5 写回工单号(状态闭环)
工具返回 ticket_no 后,用 assigner
节点写回对话变量(write_mode: over-write),用户下一轮就能查到工单号,且
ticket_exist=true 保证不重复建单。
6. 运行验证
| 输入 | 预期 | 结果 |
|---|---|---|
| ①「我要退货」 | PE 未提取到订单号 → 追问「请补充订单号」 | 通过(4 轮多轮对话实测) |
| ② 补订单号+问题描述 | 字段齐全 → 调用工单应用(Workflow as Tool)创建工单 → 返回工单号 | 通过 |
| ③④ 后续轮次问「我的工单处理到哪了」 | 对话变量已写回工单号 → 回复处理中,不重复建单 | 通过 |
| 检查 KV 工单记录 | 只有 1 条(不重复创建) | 通过 |
7. 实战坑
| 坑 | 现象 | 修复 |
|---|---|---|
| chatflow 工具参数模板引用 code 节点输出 | 偶发为空,工具报参数 required | 改用对话变量模板
{{#conversation.x#}}(实测,本实验) |
| 工具重建时 parameters 传空数组 | 调用方传参校验失败 | parameters 必须 = 子工作流 start 变量(实测,本实验) |
code 字符串状态判断用
elif complete: |
字符串 "false" 恒真,误触发「已收集完整」分支 | 必须 == "true"
显式比较(实测,本实验) |
| 对话变量 id 非 UUID | 报 SQL 500 错误 | 使用合法 UUID(实测,102-15) |
| 校验结果不接 if-else | 状态不全也触发下游(死代码) | if-else 消费 complete/ticket_exist(实测,102-20) |
| code 节点沙箱禁写文件 | 报 PermissionError: /tmp |
工单记录改用本机 KV 模拟服务持久化(实测,本批) |
8. 实验文档及源码获取
- 实验文档(完整操作步骤):DIFY-104-02:跨应用状态传递——多轮对话状态跨应用不丢.md
- 源码(可直接导入,一个应用一个 DSL):
- 源码一(客服对话 chatflow):dify104_02_01_客服对话.yml
- 源码二(工单创建 workflow):dify104_02_02_工单创建.yml
- 全部源码目录:dify-104/dsl
文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。
- Dify 企业级实验(01):多应用编排——如何让多个 Dify 应用协同完成一条业务链?
- Dify 企业级实验(02):跨应用状态传递——多轮对话的状态如何跨应用不丢?
- Dify 企业级实验(03):事件驱动流水线——Webhook 与定时触发如何组成异步处理链?
- Dify 企业级实验(04):性能优化实战——长流程从 60 秒到秒回有哪些手段?
- Dify 企业级实验(05):Token 成本控制——AI 应用省钱改造怎么做?
- Dify 企业级实验(06):可观测性体系——日志埋点与监控告警如何落地?
- Dify 企业级实验(07):安全与合规——全链路脱敏与权限分级怎么做?
- Dify 企业级实验(08):人机协同审批流——机器预审与人工确认如何配合?
- Dify 企业级实验(09):复杂业务状态机——订单状态流转与非法跳转防护?
- Dify 企业级实验(10):知识库持续更新闭环——数据飞轮怎么转起来?
- Dify 企业级实验(11):企业 API 工具化——如何把客户系统封装成 Dify 工具?
- Dify 企业级实验(12):外部系统集成——第三方系统如何通过 Dify API 双向编排?