Dify 高级实验(10):综合实战——如何从零搭建一个企业级智能客服平台?
1. 业务场景
先讲一个我们实际遇到的场景。
一家做企业服务 SaaS 的公司,客服体系越来越撑不住了:用户量涨了三倍,客服团队还是那几个人。用户的问题五花八门——技术问题要查文档(「你们的 API 支持哪些鉴权方式?」)、订单问题要查系统(「我的订单到哪了?」)、投诉要转人工(「我要投诉,转人工!」)、管理层还要定期看客服质量数据(「上周客服满意度怎么样?」)。之前的实验里,我们分别搭过智能客服、RAG 问答、工单通知的单个应用,但都是「各管一段」——现在要把它们整合成一个真正能上线的平台,还要解决定时周报、异常告警这些后台活。
我们第一次做这种整合时,第一反应也是「把之前搭好的应用拼在一起不就完了」。真正动手才发现——单点 demo 和可上线平台之间,隔着一整层工程:多应用怎么拆分、后台定时任务谁来触发、应用之间怎么协作、整条链路怎么验收,每一件都是没趟过的新问题。这也是我们把这一课放到系列最后的原因。
这不是个例。任何「从单点实验走向生产系统」的团队都是这个模式:实验阶段每个能力都是一个独立 demo,一旦要交付,就得面对「多应用怎么拆分、怎么协作、怎么验收」这三件绕不开的事——这也是本系列最后一课要解决的问题。
2. 场景痛点
这个流程的痛点,在从实验走向交付的团队身上体现得最直接:
- 单点 demo 整合不到一起:智能客服、RAG 问答、工单通知各自能跑,但用户要的是一个统一入口——四类需求四套系统,用户根本不知道该找谁。
- 后台任务没人管:周报、告警这类定时任务,实验时都是手动触发跑一次,上线后每周一 9 点谁来自动跑?定时、异常检查、推送全都没有着落。
- 应用间协作没有现成通道:Dify 不支持工作流直接调工作流,多应用协同要么靠 HTTP 伪调用、要么靠代码模拟——没人趟过这条路,集成时处处是坑。
- 多应用系统没法验收:单应用能跑冒烟,多应用整条链路(投诉 → 工单 → 后台告警)谁来验证?缺一套系统的验收方法,交付时心里没底。
本质上,从实验到交付的瓶颈不是「单个功能不会做」,而是「多应用编排 + 定时任务 + 验收体系」这三块工程能力——功能是点,系统是网,把点连成网才叫交付。
3. 方案:为什么是这套综合架构
最后一课不教新功能,而是把高级 01-09 的能力整合成一个可落地的系统架构,同时补齐两块工程能力:
- 多应用拆分编排:一个业务拆成多个独立应用(Chatflow 门户 + Workflow 后台定时),用「问题分类器路由 + 代码节点模拟外部系统 + 定时触发器」协作——Dify 不支持工作流直接调工作流,就用「HTTP 伪调用 / 代码模拟」组合;
- 验收体系(TR4 报告):多应用系统不能只跑冒烟——按 TR4 验证验收报告模板出八节报告(§2 功能验证 / §4 错误路径 / §5 性能 / §6 缺陷清单 / §7 验收结论),每个应用单独建 Key 验收 + 全链路联调,验收方只报告不修改。
这篇文章我们就用它搭一个「企业级智能客服平台」——系统由两个应用构成:
| 应用 | 形态 | 职责 |
|---|---|---|
| 门户入口(dify103_10_01) | advanced-chat | 用户统一入口,四路分类路由 |
| 后台定时任务(dify103_10_02) | workflow | 每周一 9:00 生成周报 + 检查异常推送 |
4. 整体架构
链路很清晰:门户入口四路分类路由承接用户请求,后台定时任务每周自动产出周报与告警。两个应用一个面向用户、一个面向后台,通过「问题分类器路由 + 代码节点模拟外部系统 + 定时触发器」协作,各自独立部署、独立验收。
5. 模块设计
5.1 问题分类器(qc_main)
四类 + 兜底规则——含义模糊一律归「技术问题」走知识库问答,保证没有无响应分支:
query_variable_selector:
- start
- sys.query
instruction: |-
将用户问题归入最合适的一个类别(单标签,每问题只归一类):
技术问题:产品功能咨询、API接入、报错排查、平台使用。示例:"你们的 API 支持哪些鉴权方式?"
订单查询:订单状态、物流进度、发货时间。示例:"我的订单到哪了?""订单 OD20240701001 什么时候发货?"
投诉转人工:投诉、退款纠纷、人工客服、转人工。示例:"我要投诉,转人工!"
数据分析:数据看板、客服质量评估、周报、趋势分析。示例:"上周客服满意度怎么样?"
如果问题不属于以上任何一类,或含义模糊、置信度低,一律归入"技术问题"(兜底,走知识库问答)。5.2 订单查询(cd_order)
mock 订单库 + 正则提取订单号 + 「未找到」语义(found=false + 空信息,让 LLM 引导用户核对,而不是塞默认订单):
def main(query: str) -> dict:
import re, time
time.sleep(0.3)
mock_orders = {
"OD20240701001": {"status": "配送中", "eta": "预计明天到达", "items": "智能音箱 x1"},
"OD20240702002": {"status": "已发货", "eta": "预计3天后到达", "items": "无线耳机 x1"},
"OD20240703003": {"status": "已签收", "eta": "已送达", "items": "智能手表 x1"}
}
m = re.search(r"OD\d{6,}", query or "")
order_id = m.group(0) if m else ""
order = mock_orders.get(order_id)
if not order:
return {"found": "false", "order_id": order_id or "", "status": "",
"eta": "", "items": "", "reply": ""}
return {"found": "true", "order_id": order_id, "status": order["status"],
"eta": order["eta"], "items": order["items"], "reply": ""}5.3 转人工(cd_ticket)
上下文传递——对话变量
conversation_history 取最近 3
轮随工单带走,人工接手不用重新问一遍:
def main(query: str, conversation_history: list) -> dict:
import time, random
time.sleep(0.3)
ticket_id = "TK-" + str(random.randint(100000, 999999))
priority = "high" if "投诉" in (query or "") else "normal"
history = conversation_history if isinstance(conversation_history, list) else []
context = ";".join(str(h) for h in history[-3:]) if history else "(无历史对话)"
return {"ticket_id": ticket_id, "priority": priority,
"message": "已为您转人工客服,工单号 " + ticket_id + "(优先级:" + priority
+ ")。客服将携带对话上下文处理,请保持在线。"}5.4 定时触发器(trig)
后台任务没有 start 节点,入口是 trigger-schedule;常量输入(report_week / push_channel)由 cd_const 代码节点提供(trigger 无业务输出字段),且 cd_const 必须有入边(trigger → cd_const),否则成孤立节点不执行:
data:
desc: ''
frequency: weekly
mode: visual
timezone: Asia/Shanghai
type: trigger-schedule
visual_config:
monthly_days: []
on_minute: 0
time: 09:00 AM
weekdays:
- mon5.5 异常分支(cond_alert)
cd_monitor 输出 has_anomaly(boolean 展平为
"true"/"false"
字符串,变量选择器才可见),IF/ELSE 字符串比较用 is:
- case_id: case_alert
conditions:
- comparison_operator: is
value: "true"
variable_selector:
- cd_monitor
- has_anomaly
logical_operator: and
- case_id: case_normal
conditions:
- comparison_operator: is
value: "false"
variable_selector:
- cd_monitor
- has_anomaly
logical_operator: and6. 运行验证
| 场景 | 输入 | 预期 | 实测 |
|---|---|---|---|
| 技术问题 | 你们的 API 支持哪些鉴权方式? | 分类 tech,知识库检索后回答 | 与预期一致 |
| 订单查询 | 我的订单 OD20240701001 到哪了? | 分类 order,查 mock 库回复「配送中,预计明天到达」 | 与预期一致 |
| 订单未找到 | 订单 OD999999999 什么时候发货? | found=false,LLM 引导核对订单号 | 与预期一致 |
| 投诉转人工 | 我要投诉,转人工! | 生成工单 TK-xxxxxx(优先级 high),带对话上下文 | 与预期一致 |
| 数据分析 | 上周客服满意度怎么样? | 固定回复「分析报告每周一自动发送」 | 与预期一致(固定文案分支,不调 LLM) |
| 后台定时 | 手动触发 workflow | 生成周报文本 + 检测到 2 条异常,推送告警 | 与预期一致(has_anomaly=true 走告警分支) |
7. 实战坑
| 坑 | 现象 | 修复 |
|---|---|---|
| 多应用拆分后验收缺全链路视角 | 门户各分支单测全过,但「投诉 → 工单 → 后台告警」整条链路没人验证 | 验收分两层:每个应用独立建 Key 单点验证 + 全链路联调用例(TR4 报告 §2 功能验证两批数据都要);Service API 触发的 run 拿不到 run_id,节点级取证受限,全链路以输出断言为主 |
| TR4 测试输入与内置 mock 不匹配 | 用 OD202607 系列测试号查订单全部「未找到」,误报 FAIL | 测试输入必须用应用内置 mock 值(门户 mock 是 OD202407 系列);判定 FAIL 先核对是不是输入错误,再定性应用缺陷(2026-08-03 dify-103 二验 3 个 FAIL 全是脚本/输入问题) |
LLM prompt 写双花括号
{{query}} |
Dify 只替换 {{#节点id.字段#}}
三花括号,双花括号字面保留传给
LLM——回答「提供的文档内容为空」,且宽松判定(长度/关键词)会掩盖成 PASS
假阳性 |
prompt 一律 {{#sys.query#}} /
{{#kb_docs.result#}} 三花括号引用;自检清单加「LLM prompt
无 {{变量名}} 双花括号」(2026-08-03 dify102_12
实测根因) |
| 定时触发器配置报错 | time 写 "08:00" 发布报
Invalid time format: '08:00'. Expected 'HH:MM AM/PM' |
time: 09:00 AM(AM/PM 格式)+
weekdays: [mon](3
字母小写);发布才报错,静态校验查不出,必须触发发布验证 |
| 查不到数据返回默认数据 + found 恒 true | 问不存在的订单号返回默认订单信息,下游「未找到请核对」提示永不触发 | 兜底分支返回「未找到」语义:found=false + 空字段,让 LLM 按 prompt 引导用户核对(cd_order 实测后修复) |
8. 实验文档及源码获取
- 实验文档(完整操作步骤):DIFY-103-10:综合实战——企业级智能客服平台.md
- 源码(两个应用,均可直接导入):dify103_10_01_企业级智能客服平台-门户入口.yml(Chatflow 门户)、dify103_10_02_企业级智能客服平台-后台定时任务.yml(后台定时)
- 目录:dify-103/dsl(全部源码)
文章聚焦核心配置与采坑点;系统架构图、各组件与前置实验的对应关系、完整分步操作见实验文档原文。
系列 2 完——中级与高级两批次至此完结(企业级及后续系列见目录)。
- Dify 高级实验(01):智能文档处理——如何把非结构化文档变成结构化数据?
- Dify 高级实验(02):对话式 BI 分析——如何让业务人员用自然语言直接查数?
- Dify 高级实验(03):智能客服——如何让 AI 自动应答并把疑难问题转人工?
- Dify 高级实验(04):RAG 增强问答——如何让知识库问答更准还能多轮追问?
- Dify 高级实验(05):多步推理——如何把复杂问题拆解成子问题逐个击破?
- Dify 高级实验(06):自动化报告——如何让系统定时自动生成周报?
- Dify 高级实验(07):智能审批——如何让机器自动完成流程审批?
- Dify 高级实验(08):智能告警——如何用 AI 替代固定阈值监控?
- Dify 高级实验(09):测试用例生成——如何让 AI 自动产出测试用例?
- Dify 高级实验(10):综合实战——如何从零搭建一个企业级智能客服平台?