用 Dify 搭建企业级 AI 工作流:从需求拆解到自动化部署全流程实战
引言:企业 AI 应用落地,为什么总是"卡在最后一公里"
很多团队都经历过这样的场景:技术选型时把大模型能力夸上了天,POC 阶段效果惊艳,可一旦要真正上线到生产环境,问题接踵而至——
- 单次对话能力 ≠ 业务流程能力:大模型擅长回答"一个问题",但真实业务往往是"多步骤、有条件分支、需要调用外部系统"的链路。比如客服场景,需要先识别意图、再检索知识库、判断是否需要人工介入,最后组织回复。
- 提示词工程难以维护:随着业务规则增加,Prompt 越写越长,逻辑全塞在一段文本里,无法测试、无法版本管理、无法复用。
- 集成成本高:把 LLM 嵌入现有系统,要处理鉴权、流式输出、超时重试、变量传递、结果格式校验……每一个都是坑。
- 非技术人员难以参与:业务方想调整流程,却只能提需求给开发,迭代周期长。
Dify 正是为解决这些问题而生。它把"用大模型做应用"这件事从"写一堆胶水代码"变成了"可视化编排 + 标准化 API"。本文将从部署开始,带你完整走通一条企业级 AI 工作流。
一、认识 Dify:开源 LLM 应用开发平台
Dify 是一个开源的 LLM 应用开发平台(Apache 2.0 协议),核心能力包括:
- 工作流编排:通过画布拖拽,把 LLM、知识检索、代码执行、HTTP 请求等节点串成一条可执行的流程。
- 知识库(RAG):支持文档导入、切片、向量化、检索,内置多种召回策略。
- 多模型接入:兼容 OpenAI、Anthropic、通义千问、智谱、Ollama 等数十种模型供应商。
- 一键 API 化:每个应用自动暴露 RESTful API,前端/后端可直接调用。
- 可观测性:提供调用日志、Token 消耗、Trace 追踪。
Dify 区分两种应用类型:Chatflow(对话型,支持多轮)和 Workflow(工作流型,单次输入单次输出,适合自动化任务)。本文聚焦 Workflow 类型,因为它更适合"工单处理""文档审核""数据抽取"这类一次性、可编排的自动化场景。
二、Dify 工作流核心概念
2.1 节点类型
工作流由若干节点串联而成,常用节点包括:
| 节点 | 作用 |
|---|---|
| Start | 入口节点,定义工作流的输入变量 |
| LLM | 调用大模型,支持系统提示词、上下文、变量插值 |
| Knowledge Retrieval | 从知识库检索相关片段,结果注入下游 LLM |
| Question Classifier | 意图分类,把输入路由到不同分支 |
| IF/ELSE | 条件分支,根据变量值判断走向 |
| Code | 执行 Python/JS 代码,做数据转换 |
| Template | Jinja2 模板渲染,拼接文本 |
| HTTP Request | 调用外部 API,打通企业内部系统 |
| Variable Aggregator | 多分支结果汇聚 |
| End | 输出节点,定义返回给调用方的结构 |
2.2 变量传递
Dify 用"变量"在节点间传递数据。每个节点的输出会自动注册为变量,下游节点通过 {{node_id.output_var}} 引用。例如 LLM 节点输出文本存在 {{llm_1.text}},可在 Template 节点中拼接、在 IF/ELSE 中判断、在 HTTP Request 中作为请求体。
输入变量类型支持:String、Number、Boolean、Object、Array[String]、Array[Object] 等,类型在 Start 节点声明,保证流程类型安全。
2.3 条件分支
条件分支是工作流"智能化"的关键。两种典型用法:
- Question Classifier:用 LLM 做语义分类,适合"这个工单属于退款/物流/售后哪一类"这种模糊判断。
- IF/ELSE:基于明确变量值判断,适合"订单金额 > 1000 走人工""知识库置信度 < 0.6 触发兜底"这种规则判断。
二者常配合使用:先用 Classifier 做粗分类,再用 IF/ELSE 做精细路由。
三、Docker 部署 Dify:5 分钟拉起本地环境
Dify 提供完整的 Docker Compose 部署方案。
# 1. 克隆仓库
git clone https://github.com/langgenius/dify.git
cd dify/docker
# 2. 复制环境变量模板
cp .env.example .env
# 3.(可选)修改 .env,如关闭匿名遥测、调整端口
# EXPOSE_NGINX_PORT=8080
# CONSOLE_API_URL=http://localhost:8080
# 4. 启动全部服务(包括 api、worker、web、db、redis、weaviate 等)
docker compose up -d
# 5. 查看服务状态,确认均为 healthy / running
docker compose ps
启动完成后,浏览器访问 http://localhost:8080 即可进入 Dify 控制台,首次访问需注册管理员账号。
提示:生产环境建议配置独立的 PostgreSQL 与 Redis、对象存储(S3)、以及外置向量数据库(如 Weaviate / Milvus / Qdrant),并通过
.env调整相应参数。模型供应商密钥可在控制台"设置 → 模型供应商"中统一配置。
四、创建你的第一个工作流
- 登录控制台,点击"创建空白应用" → 选择"工作流应用" → 命名为"工单分类"。
- 在画布上从左到右依次拖入节点:Start → LLM → End。
- 在 Start 节点 添加输入变量
ticket_content(String / 段落输入类型)。 - 在 LLM 节点 配置:
- 模型:选择已接入的模型(如
gpt-4o-mini) - 系统提示词:
你是一个客服工单分类助手,请把用户问题归类为:退款、物流、售后、其他。只输出类别名称。 - 上下文变量:插入
{{#start.ticket_content#}} - 在 End 节点 把输出变量指向
{{#llm.text#}}。 - 右上角点击"运行"测试,输入"我的包裹三天了还没到",应返回"物流"。
- 点击"发布",工作流即生成可调用的 API。
发布后,在"访问 API"页面可拿到 API Server 地址和 API Key(形如 app-xxxxxxxx)。
五、实战案例:智能客服工单分类与自动回复工作流
下面这个案例综合了知识库检索、条件分支、HTTP 调用,是一个真实可用的企业级流程。
5.1 流程设计
Start(ticket_content)
└─> Knowledge Retrieval(检索 FAQ 知识库)
└─> LLM(生成回复 + 置信度评估, 输出结构化 JSON)
└─> IF/ELSE(置信度是否 >= 0.7)
├─ YES ─> End(自动回复)
└─ NO ─> HTTP Request(创建人工工单) ─> End(转人工提示)
5.2 工作流 DSL 配置(JSON)
Dify 支持导出/导入 DSL,下面是该流程的精简版配置(实际导入时字段更全,这里展示核心结构,可直接作为理解与二次编辑的蓝本):
{
"app": {
"name": "智能客服工单处理",
"mode": "workflow",
"description": "自动分类工单,命中知识库则自动回复,否则转人工"
},
"workflow": {
"graph": {
"nodes": [
{
"id": "start",
"type": "custom",
"data": {
"type": "start",
"variables": [
{ "variable": "ticket_content", "type": "text-input", "label": "工单内容" }
]
}
},
{
"id": "knowledge_1",
"type": "custom",
"data": {
"type": "knowledge-retrieval",
"dataset_ids": ["<your-dataset-id>"],
"query_variable_selector": ["start", "ticket_content"],
"retrieval_mode": "multiple",
"multiple_retrieval_config": {
"top_k": 3,
"score_threshold": 0.5,
"reranking_enable": false
}
}
},
{
"id": "llm_1",
"type": "custom",
"data": {
"type": "llm",
"model": { "provider": "openai", "name": "gpt-4o-mini", "mode": "chat", "completion_params": { "temperature": 0.2 } },
"prompt_template": [
{
"role": "system",
"text": "你是客服助手。根据检索到的 FAQ 回答用户问题,并严格输出 JSON。\n格式:{\"reply\": \"回复内容\", \"confidence\": 0.0~1.0}\n若 FAQ 足以回答,confidence >= 0.7;若 FAQ 不足,confidence 设为 0。\n\nFAQ:{{#knowledge_1.result#}}\n用户问题:{{#start.ticket_content#}}"
}
],
"structured_output_enabled": true,
"structured_output": {
"type": "object",
"properties": {
"reply": { "type": "string" },
"confidence": { "type": "number" }
},
"required": ["reply", "confidence"]
}
}
},
{
"id": "if_else_1",
"type": "custom",
"data": {
"type": "if-else",
"cases": [
{
"logical_operator": "and",
"conditions": [
{
"variable_selector": ["llm_1", "structured_output", "confidence"],
"comparison_operator": ">=",
"value": "0.7"
}
]
}
]
}
},
{
"id": "http_1",
"type": "custom",
"data": {
"type": "http-request",
"method": "post",
"url": "https://your-internal-api.example.com/tickets",
"headers": "{\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer {{internal_token}}\"\n}",
"body": "{\n \"content\": \"{{#start.ticket_content#}}\",\n \"reason\": \"low_confidence_auto_routed\"\n}"
}
},
{
"id": "end_auto",
"type": "custom",
"data": {
"type": "end",
"outputs": [
{ "variable": "reply", "value_selector": ["llm_1", "structured_output", "reply"] },
{ "variable": "routed_to", "value": "auto" }
]
}
},
{
"id": "end_manual",
"type": "custom",
"data": {
"type": "end",
"outputs": [
{ "variable": "reply", "value": "您的问题较为复杂,已转交人工客服,工单号 {{#http_1.body#}}" },
{ "variable": "routed_to", "value": "manual" }
]
}
}
],
"edges": [
{ "source": "start", "target": "knowledge_1" },
{ "source": "knowledge_1", "target": "llm_1" },
{ "source": "llm_1", "target": "if_else_1" },
{ "source": "if_else_1", "sourceHandle": "true", "target": "end_auto" },
{ "source": "if_else_1", "sourceHandle": "false", "target": "http_1" },
{ "source": "http_1", "target": "end_manual" }
]
}
}
}
5.3 知识库准备
在"知识库"页面新建一个数据集,上传 FAQ 文档(支持 txt/md/pdf/docx/csv),Dify 会自动完成切片与向量化。完成后把数据集 ID 填入上面 DSL 的 dataset_ids。建议在数据集设置里开启"问答模式",让切片更贴合客服问答场景。
5.4 通过 API 调用工作流(curl)
发布后,用 curl 调用 /v1/workflows/run:
curl -X POST 'http://localhost/v1/workflows/run' \
-H 'Authorization: Bearer app-xxxxxxxxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"inputs": {
"ticket_content": "我昨天买的耳机有杂音,能换吗?"
},
"response_mode": "blocking",
"user": "customer-1024"
}'
阻塞模式下返回示例:
{
"task_id": "9d1dxxxx-xxxx",
"workflow_run_id": "dj2kxxxx-xxxx",
"data": {
"id": "dj2kxxxx",
"workflow_id": "fldxxxx",
"status": "succeeded",
"outputs": {
"reply": "您好,耳机在 7 天内出现非人为质量问题可申请换新,请提供订单号...",
"routed_to": "auto"
},
"elapsed_time": 3.21,
"total_tokens": 612,
"total_steps": 6
}
}
5.5 Python 集成示例(requests)
用 requests 封装一个可复用的客户端,包含阻塞与流式两种调用方式:
import json
import requests
class DifyWorkflowClient:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url.rstrip("/")
self.api_key = api_key
def run(self, inputs: dict, user: str, response_mode: str = "blocking"):
"""阻塞模式:等待整个工作流跑完一次性返回结果。"""
url = f"{self.base_url}/v1/workflows/run"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
payload = {
"inputs": inputs,
"response_mode": response_mode,
"user": user,
}
resp = requests.post(url, headers=headers, json=payload, timeout=60)
resp.raise_for_status()
return resp.json()
def run_stream(self, inputs: dict, user: str):
"""流式模式:通过 SSE 逐步接收节点事件,适合长流程或前端实时展示。"""
url = f"{self.base_url}/v1/workflows/run"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
payload = {"inputs": inputs, "response_mode": "streaming", "user": user}
with requests.post(url, headers=headers, json=payload, stream=True, timeout=60) as resp:
for line in resp.iter_lines():
if not line:
continue
chunk = line.decode("utf-8")
if chunk.startswith("data: "):
event = json.loads(chunk[6:])
yield event
if __name__ == "__main__":
client = DifyWorkflowClient(
base_url="http://localhost",
api_key="app-xxxxxxxxxxxxxxxx",
)
# 阻塞调用
result = client.run(
inputs={"ticket_content": "我昨天买的耳机有杂音,能换吗?"},
user="customer-1024",
)
print("回复内容:", result["data"]["outputs"]["reply"])
print("路由去向:", result["data"]["outputs"]["routed_to"])
print("耗时:", result["data"]["elapsed_time"], "秒")
print("Token 消耗:", result["data"]["total_tokens"])
# 流式调用示例
for event in client.run_stream(
inputs={"ticket_content": "订单 8899 还没发货"},
user="customer-1025",
):
# event["event"] 可能是 workflow_started / node_started / node_finished / text_chunk / workflow_finished
if event.get("event") == "workflow_finished":
print("\n最终输出:", event["data"]["outputs"])
5.6 用官方 Python SDK 调用
pip install dify-client
from dify_client import WorkflowClient
client = WorkflowClient("app-xxxxxxxxxxxxxxxx")
client.base_url = "http://localhost/v1"
response = client.run(
inputs={"ticket_content": "耳机有杂音想换货"},
user="customer-1024",
response_mode="blocking",
)
print(response.text)
六、常见问题 FAQ
Q1:工作流支持循环和数组遍历吗? A:Workflow 不支持显式 for 循环。处理数组有两种方案:① 用 Code 节点写 Python 逻辑做批量转换;② 用"迭代(Iteration)"节点对数组逐项执行子流程,再在出口聚合结果,适合对一批文档/订单做同构处理。
Q2:知识库召回效果不好,如何优化?
A:从三方面入手:① 提升文档切片质量,调整分块大小与重叠(chunk overlap),客服场景建议开启问答模式;② 在检索节点适当提高 top_k、调低 score_threshold,必要时开启 Rerank 重排序;③ 在 LLM 节点提示词里加"严格依据检索内容回答,否则说明无法回答"以降低幻觉。
Q3:API 调用偶发超时怎么办?
A:长流程建议用 streaming 模式,避免网关一次性等待超时;客户端设置合理 timeout 并加指数退避重试;Dify 侧可在 .env 调整 WORKFLOW_MAX_EXECUTION_TIME 控制单次执行上限,必要时把重子流程拆成独立应用异步触发。
Q4:如何做权限控制和计费?
A:Dify 按 API Key 区分应用,企业可在前面自建一层 API 网关做 Key 管理、配额限流、调用日志聚合,再按响应里的 total_tokens 字段计费。多租户建议每个业务方独立应用、独立 Key,知识库按权限隔离。
Q5:能否把工作流导出做版本管理? A:可以。每个应用支持导出 DSL(YAML 格式),纳入 Git 仓库即可版本化管理,也支持在不同环境(开发/测试/生产)间导入迁移,配合 CI/CD 可实现工作流即代码(Workflow-as-Code)的发布流水线。
总结
从"会调一次大模型"到"跑通一条企业级 AI 工作流",中间隔着需求拆解、流程编排、知识集成、API 对接、可观测运维等一堆工程问题。Dify 的价值在于把这些环节标准化:用画布表达业务逻辑、用节点封装能力、用变量贯通数据、用 API 暴露服务。
本文从 Docker 部署、核心概念、到智能客服工单分类实战,完整演示了一条"知识检索 → LLM 生成 → 条件路由 → 自动/人工分流"的工作流。把这套模式套用到文档审核、数据抽取、营销文案生成等场景,只需替换节点和提示词,即可快速复制。
AI 落地没有银弹,但好的工具能让你把精力花在业务设计而非胶水代码上——这正是 Dify 这类平台存在的意义。