Dify 中级实验(19):综合实战——如何把 19 个实验串成一条生产级流水线?
Dify 中级实验(19):综合实战——如何把 19 个实验串成一条生产级流水线?
Dify 实验系列 · 中级 19/20 | 实验编号:DIFY-102-20
基于 Dify 1.16.1 实测(2026-08)
1. 业务场景
先讲一个我们实际遇到的场景。
一家电商公司的客服团队每天收到几百条用户问题,从「API 报 502 了」到「我要退货退款」什么都有。原来全靠人工分拣:读一遍、判断类型、分给对应客服、记录工单、跟踪处理——一个工单的流转要经过五六双手。他们想用 Dify 把这条链路自动化:用户用自然语言提问题,系统自动完成质量检查、意图分类、路由分发、分派 Agent 处理、工单聚合、报告生成,出错还能兜底降级。
我们当时评估这个需求的第一反应是「这不就是把前面的节点串起来嘛」。真正动手才发现——串起来和设计好,是两回事。节点连起来只解决「正常路径」,而生产系统 90% 的复杂度都在异常路径上:输入太短怎么办?分类器认不出来怎么办?处理 Agent 挂了怎么办?
这不是个例。任何「用户用一句话描述问题、系统要全自动处理到底」的场景都是这个模式:工单系统、故障报修、投诉处理、审批流——把前面学过的参数提取、问题分类、条件分支、多 Agent 协作、错误处理、降级回退串进一个完整系统,就是「从会搭节点到会设计系统」的分水岭。
2. 场景痛点
这个流程的痛点,在电商客服团队身上体现得最直接:
- 人工分拣慢、成本高:每条工单都要人工读一遍再分类分派,高峰期排队,用户等回复等到投诉——分拣本身不产生价值,却是每天绕不开的环节。
- 分类不准导致派错人:问题类型判断错了,工单分给错误的处理组,用户被来回转接,体验极差——「人工判断」在高峰期最脆弱,而高峰期恰恰是最需要准确的时刻。
- 模糊输入没有拦截:用户只发一句「有问题」,系统应该提示补充信息,而不是硬着头皮走完整个处理流程,最后产出一份没头没尾的工单。
- 出错就全崩:处理链路里任何一环异常(Agent 不可用、解析失败),整条流水线就断了,工单无声消失——生产系统必须要有错误捕获和降级出口。
本质上,真正的系统设计能力 = 把每个环节的正确路径和异常路径都设计出来,而不是只会把节点连起来。
3. 方案:为什么是「多 Agent 智能工单系统」
本实验是系列 1 的总检验:把参数提取、问题分类、条件分支、多 Agent 协作、错误处理、降级回退串进一个完整系统——多 Agent 智能工单系统。19 个节点、25 条边的全链路,跑通它,你就从「会搭节点」进化到了「会设计系统」。
选它的理由:
- 覆盖前面全部核心能力:PE 参数提取、QC 问题分类、IF-ELSE 路由、多 Agent 分支、or-chain 聚合、错误捕获与降级——一个实验全部串起来;
- 职责分离的标准写法:分类器负责粗分、路由代码负责精算,「分类-路由」职责分离,改规则不动架构;
- 有完整的错误兜底:错误捕获 + 降级记录 + 友好提示,生产级系统该有的「体面失败」这里都有。
这篇文章我们就用它搭「多 Agent 智能工单系统」:用户用自然语言提问题,系统自动完成质量检查 → 意图分类 → 路由分发 → 分派 Agent 处理 → 工单聚合 → 报告生成 → 错误兜底。
4. 整体架构
链路很清晰:入口收问题 → 参数提取 → 质量检查拦截模糊输入 → 意图分类 → 路由分发 → 四路 Agent 分支处理 → or-chain 聚合 → 生成报告 → 错误捕获兜底。19 个节点、25 条边,全链路的关键是「每个检查类节点都接一个消费它的 IF-ELSE」——否则校验、分类、错误处理全是死代码。
5. 模块设计
5.1 输入质量检查(Code)→ 必须接 IF-ELSE
这是本实验最容易踩的坑:检查类节点输出 valid 后,下游必须接一个消费它的 IF-ELSE,否则 reask 逻辑就是死代码:
def main(user_input: str, issue_type: str) -> dict:
issues = []
# 1. 空输入 / 描述过短
if not user_input or len(user_input.strip()) < 5:
return {"valid": "false", "has_warning": "false", "warnings": [],
"sanitized": user_input, "action": "reask",
"reask_message": "描述过于简短,请提供更多信息"}
# 2. 意图不明确
if not issue_type or issue_type == "other":
issues.append("无法识别问题类型")
# 3. 敏感内容检查
for pattern in ["密码", "银行卡", "身份证", "验证码"]:
if pattern in user_input:
issues.append("包含敏感信息关键词: {}".format(pattern))
has_warning = len(issues) > 0
return {"valid": "true", "has_warning": "true" if has_warning else "false",
"warnings": issues, "sanitized": user_input,
"action": "continue" if not has_warning else "warn_and_continue",
"reask_message": ""}
对应的 IF-ELSE 只有两个 case:case_valid → 意图分类、case_invalid → end_reask。输入「有问题」这种模糊描述时,正确行为是提示补充,而不是一路走到「需人工审核」。
5.2 路由分发(Code)——只信 QC 的真实输出字段
问题分类器(QC)的输出只有 4 个字段:class_id / class_name / class_label / usage——没有 class_names、classes、confidence。路由代码只消费 class_id/class_name,置信度 0.9 是硬编码的演示值:
def main(class_id: str, class_name: str, urgency_level: str, user_id: str, channel: str) -> dict:
route_id = class_id or "other"
route_name = class_name or "未分类"
confidence = 0.9 # QC 无 confidence 输出,硬编码演示回退逻辑
is_vip = str(user_id or "").strip().lower().startswith("vip")
if route_id not in ["tech", "after_sale", "business"]:
route_id, route_name = "other", "未分类"
priority = "high" if urgency_level == "high" else "normal"
force_manual = "false"
note = ""
if is_vip and route_id in ["tech", "after_sale"]:
priority, note = "high", "VIP 客户,优先处理"
if channel == "phone" and route_id == "after_sale":
force_manual, note = "true", "电话渠道售后,转人工处理"
if confidence < 0.5:
route_id, route_name = "other", "未分类"
note = "置信度过低({}),由人工分类".format(confidence)
return {"route": route_id, "route_name": route_name, "priority": priority,
"force_manual": force_manual, "note": note}
QC 的 5 条类边(tech/after_sale/business/account/other)全部指向同一个路由代码节点是合法拓扑——分类器负责粗分,路由代码负责精算,这是「分类-路由」职责分离的标准写法。
5.3 多 Agent 分支与 or-chain 聚合
四个处理分支(技术/售后/商务 Agent、未分类转人工)各自输出 agent_response/agent_ok,汇聚到「工单聚合」代码节点——用 or-chain 语义取第一个有效结果(演示环境里 Agent 用代码节点模拟,接真实 Agent 时换成 HTTP 调用其 API):
agent_response = tech_resp or after_resp or business_resp or manual_resp
needs_human_review = "true" if route == "other" else "false"
聚合后再接「生成工单报告」(LLM)→「错误捕获」(Code 检查各节点是否有 error)→「错误判定」(IF-ELSE:case_error → 降级记录 + 友好提示;case_ok → 正常结束)。错误被处理了 ≠ 没事了——降级路径也要生成记录。
6. 运行验证
| 测试场景 | 输入 | 预期 | 实测结果 |
|---|---|---|---|
| 技术问题 | 「我们的 API 连续返回 502 错误,非常紧急」 | 分类 tech → 高优先级 → 技术 Agent → 生成工单 | 全链路走通,工单含分类/优先级/处理结果 ✓ |
| 售后问题 | 「上周买的商品有质量问题,我要退货退款」 | 分类 after_sale → 售后 Agent 处理 | 售后分支触发 ✓ |
| 模糊输入 | 「有问题」 | 质量检查 valid=false → 提示「描述过于简短」 | 走 reask 分支,未误入处理链路 ✓ |
| VIP 客户 | 同售后问题 + user_id=vip1001 | 自动标记高优先级 | 路由代码识别 VIP,priority=high ✓ |
| Agent 不可用 | 让 Agent 分支返回 error | 错误捕获 → 降级提示「需人工审核」 | 降级链路生效,输出友好提示 ✓ |
7. 实战坑
| 坑 | 现象 | 修复 |
|---|---|---|
| 检查类节点后没接 IF-ELSE | cd_quality 直接连分类器,reask 逻辑成死代码——输入「有问题」走了「需人工审核」而不是提示补充 | 检查类 code(valid/reask 输出)必须接 if-else 消费其状态:valid→继续 / invalid→reask 出口(本实验 cond_quality 两个 case) |
| 路由代码引用 QC 不存在的字段 | class_names[0]/confidence 取不到值,路由全错 |
QC 只有 class_id/class_name/class_label/usage;路由只消费 class_id/class_name,置信度逻辑自行硬编码 |
| PE 与 QC 查询字段名混用 | 参数提取节点配 query_variable_selector(那是 QC/KB 的字段) |
PE 用 query;QC/KB 用 query_variable_selector——两类节点字段名相反,极易混 |
| boolean 直接输出 | valid/has_warning/force_manual/agent_ok 判断不到 | 全部展平为 string "true"/"false" |
| 多 End 节点 variable 重名 | end / end_fallback / end_reask 输出变量重复,校验报错 | 每个 End 用不同 variable 名 |
| 兜底分支塞默认数据 | 未分类工单被「假装处理成功」 | 未分类走 cd_manual 转人工 + needs_human_review=true,诚实标记需人工 |
采坑点来自本实验 DSL 生成与运行验证的真实记录(死代码缺陷、QC 字段、PE/QC 字段名、多 End 变量)。
8. 实验文档及源码获取
- 实验文档(完整操作步骤):DIFY-20:综合实战(上)——多Agent智能工单系统.md
- 源码(可直接导入):dify102_20_智能工单系统.yml
- DSL 目录:dify-102/dsl/
文章聚焦核心配置与采坑点;实验文档还包含工单聚合完整代码、工单报告提示词模板、评估标准(分类准确率/工单完整性/错误恢复率)与后续扩展方向(工单数据库、自动回复、升级机制)。
下一篇:Dify 中级实验(20):综合实战——自动化报告生成流水线如何从数据到周报一步到位?
💬 你在这个实验的场景里踩过什么坑?欢迎评论区分享你的实战经验。
更多推荐




所有评论(0)