
先说个扎心的事实很多团队的大模型应用死在了胶水代码上。模型是好的、RAG 思路是对的但前端对接、后端编排、知识库管理、日志追踪……这些活儿堆起来一个需求两周起步。后来我们把这套东西搬到了 Dify 上同样的功能两天搞定。这篇就聊聊我们用 Dify 落地的完整过程包括那些官方文档不会告诉你的坑。一、Dify 是个啥为什么值得看一句话概括Dify 是一个开源的 LLM 应用开发平台把 Prompt 编排、RAG 知识库、Agent 工具调用、应用发布这些脏活累活全部可视化 平台化了。你以前自己搭一个知识库问答要写的东西大概是这样一条链路文档解析 → 分块 → Embedding → 向量库 → 检索 → 拼 Prompt → 调模型 → 后处理 → API 封装 → 前端。每一个环节都是代码每一个环节都可能出 Bug。Dify 把这条链路变成了画布上拖节点而且它是开源的基于 Apache 2.0 改的附加条款商用注意看一眼 LICENSE可以私有化部署模型随便接——OpenAI、通义、DeepSeek、本地 vLLM 起的服务都行。对企业来说数据不出内网 模型可替换这两点就足够有吸引力了。二、部署十分钟起一套但有几个隐藏选项部署本身很简单docker-compose 一把梭# docker-compose.yaml精简版完整版去官方仓库拿 services: api: image: langgenius/dify-api:0.15.3 restart: always environment: # 模型供应商的密钥在界面里配这里配的是基础组件 - DB_PASSWORDdifyai123456 - REDIS_PASSWORDdifyai123456 # 向量库可以选weaviate(默认) / qdrant / milvus - VECTOR_STOREqdrant # 文件存储本地 or S3 - STORAGE_TYPEopendal - OPENDAL_SCHEMEfs - OPENDAL_FS_ROOT/storage depends_on: - db - redis - qdrant worker: image: langgenius/dify-api:0.15.3 restart: always # 异步任务都在 worker 里跑文档嵌入、批量导入 # 知识库大了之后worker 数量要加 deploy: replicas: 2 web: image: langgenius/dify-web:0.15.3 restart: always ports: - 80:3000 qdrant: image: langgenius/qdrant:v1.7.3 restart: always volumes: - ./volumes/qdrant:/qdrant/storage起来之后访问 80 端口注册管理员账号去设置 → 模型供应商里配模型。我们接的是内网 vLLM 起的 Qwen2.5-14BOpenAI-API-compatible 方式配上一个 bge-large-zh-v1.5 做 Embedding。第一个坑就在 Embedding 模型上知识库建好之后再换 Embedding 模型所有文档要重新嵌入几万页文档重嵌一遍要几个小时。所以动手建知识库之前先把 Embedding 模型定死。如果后面要换正确姿势是新建一个知识库用新模型重新嵌入验证效果后再切流量而不是直接在老库上换。三、实战搭一个HR 制度问答 IT 自助双能力机器人光讲概念没意思上完整案例。需求是这样的公司员工经常问年假怎么算试用期多久VPN 连不上怎么办这类问题HR 和 IT 同事每天被重复问题轰炸。目标做一个问答机器人制度问题查 HR 知识库技术问题查 IT 知识库都答不了就创建工单转人工。整体编排在 Dify 里我们用的是 Chatflow对话型工作流画布长这样注意几个设计点先分类再检索而不是把 HR 和 IT 知识库混在一起检索。混着检索的话年假和休假日系统报错这种问题容易互相干扰召回质量。检索之后还有一道置信度检查答不了就老实转工单。宁可说我不确定帮你转人工也别一本正经地编个假制度出来——员工照着假制度去请假那乐子就大了。引用来源必开回答里带上出自《考勤管理制度》第 3.2 节可信度蹭蹭往上涨也方便 HR 发现答错时快速定位。工作流 DSLDify 的应用是可以代码化的Dify 有个特别实用的功能整个应用可以导出成 DSLYAML 格式这意味着应用的配置可以进 Git、可以 Code Review、可以在不同环境之间同步。这对工程团队太重要了——以前 Prompt 改了没人知道现在每次改动都有 diff 记录。导出来的 DSL 长这样节选核心部分# HR-IT助手.ymlDify DSL有删减 app: name: HR-IT智能助手 mode: advanced-chat # chatflow 类型 version: 0.1.5 model: provider: openai_api_compatible model: qwen2.5-14b-instruct parameters: temperature: 0.2 # 制度问答要稳定温度调低 workflow: graph: nodes: - id: classify type: llm data: prompt_template: - role: system content: | 你是企业内部问题分类器。判断用户问题属于哪一类 - hr考勤、请假、薪酬、福利、入职离职等制度问题 - it电脑、网络、账号、软件等技术人员问题 - other闲聊或无法判断 只输出一个词hr / it / other不要解释。 - id: if-branch type: if-else data: conditions: - variable_selector: [classify, text] comparison_operator: contains value: hr - id: hr-retrieval type: knowledge-retrieval data: dataset_ids: - hr-policy-dataset-id retrieval_mode: multiple # 多路召回 multiple_retrieval_config: top_k: 4 score_threshold: 0.5 # 相似度阈值低于0.5的不要 reranking_enable: true # 开启重排 reranking_model: model: bge-reranker-large - id: hr-answer type: llm data: prompt_template: - role: system content: | 你是企业HR制度问答助手。严格依据下方检索到的制度内容回答 并在回答末尾注明来源文档和章节。 如果检索内容无法回答问题只输出【NOT_CONFIDENT】 不要编造制度内容。 检索到的制度内容 {{#hr-retrieval.result#}} - id: confidence-check type: if-else data: conditions: - variable_selector: [hr-answer, text] comparison_operator: not contains value: NOT_CONFIDENT - id: create-ticket type: http-request data: method: post url: https://itsm.internal.example.com/api/v1/tickets headers: Authorization: Bearer {{#env.ITS_TOKEN#}} body: type: json data: | { title: {{#sys.query#}}, source: ai_assistant, category: auto, description: AI助手无法回答用户原问题{{#sys.query#}} }看到 【NOT_CONFIDENT】 这个标记了吗这是个很好用的小技巧让模型在没把握的时候输出一个约定的标记然后用条件分支检查这个标记比让模型直接输出置信度数字靠谱多了——模型自己打的分经常虚高但答不出来就输出特定标记这个行为微调过 Prompt 之后很稳定。代码节点处理 Dify 原生能力覆盖不了的业务逻辑Dify 内置了一个 Code 节点可以直接跑 Python/JavaScript用来做格式转换、数据加工这类活。比如我们的场景里要把工单系统返回的 JSON 加工成用户友好的回复# Dify Code 节点加工工单创建结果 # 输入变量ticket_response (object) - HTTP节点返回的工单系统响应 # 输出变量reply (string), ticket_id (string) import json def main(ticket_response: dict) - dict: 把工单系统的原始响应加工成用户能看懂的话 业务规则 1. 创建成功 → 告知单号和预计响应时间 2. 创建失败 → 降级提示给出人工渠道 try: code ticket_response.get(code, -1) data ticket_response.get(data, {}) if code 0 and data.get(ticket_id): ticket_id data[ticket_id] # 工单系统返回的 SLAP4问题 8 小时内响应 sla_hours data.get(sla_hours, 8) reply ( f这个问题我暂时没有把握已经帮你转给专业同事了。\n\n f 工单号{ticket_id}\n f⏱️ 预计 {sla_hours} 小时内会有同事联系你。\n f着急的话也可以直接打 IT 服务台内线 8888。 ) return {reply: reply, ticket_id: ticket_id} else: # 工单系统异常降级到人工渠道 reply ( 不好意思工单系统好像开小差了没能自动帮你建单。\n 你可以直接打内线 8888 找 IT 服务台或者发邮件到 it-helpexample.com说明下问题就行。 ) return {reply: reply, ticket_id: } except Exception as e: return { reply: 系统出了点小状况请联系 IT 服务台内线 8888。, ticket_id: }业务系统怎么调 Dify 的 API编排完之后Dify 会自动生成 API。企业微信机器人、Web 页面、飞书应用谁想接就直接调import requests import json class DifyChatClient: 业务系统对接 Dify 应用的客户端 def __init__(self, base_url, api_key): # api_key 在 Dify 应用的访问 API页面生成 # 注意每个应用一个 key别搞混 self.url f{base_url}/v1/chat-messages self.headers { Authorization: fBearer {api_key}, Content-Type: application/json } # 会话管理同一个用户的连续对话要用同一个 conversation_id self._user_conversations {} def chat(self, user_id: str, message: str, stream: bool False): 发送用户消息 - user_id: 你业务系统里的用户标识Dify 用它做限流和日志归组 - message: 用户输入 payload { inputs: {}, # 如果工作流定义了开始变量在这里传 query: message, response_mode: streaming if stream else blocking, user: user_id, } # 关键带上 conversation_id 才能延续多轮对话 conv_id self._user_conversations.get(user_id) if conv_id: payload[conversation_id] conv_id if not stream: resp requests.post(self.url, headersself.headers, jsonpayload, timeout60) resp.raise_for_status() data resp.json() # 记下 conversation_id下次带上 self._user_conversations[user_id] data[conversation_id] return { answer: data[answer], conversation_id: data[conversation_id], # 元数据里有引用的知识库片段前端可以展示来源 metadata: data.get(metadata, {}), } else: return self._stream_chat(payload) def _stream_chat(self, payload): 流式响应打字机效果必备 resp requests.post(self.url, headersself.headers, jsonpayload, streamTrue, timeout120) for line in resp.iter_lines(): if not line: continue line line.decode(utf-8) if line.startswith(data: ): chunk json.loads(line[6:]) event chunk.get(event) if event message: yield {type: text, content: chunk[answer]} elif event message_end: # 对话结束时拿到 conversation_id 和引用 yield { type: end, conversation_id: chunk.get(conversation_id), metadata: chunk.get(metadata, {}), } elif event error: yield {type: error, content: chunk.get(message)} # 企业微信回调里的用法示例 def on_wecom_message(msg): client DifyChatClient( base_urlhttp://dify.internal.example.com, api_keyapp-xxxxxxxx # 应用级 API Key ) result client.chat( user_idmsg[from_user], # 用企微 userid 做标识 messagemsg[content] ) return result[answer]这里的第二个坑user 字段别乱传。Dify 的会话隔离、日志检索、按用户限流全靠这个字段。我们一开始图省事统一传了 test结果日志里所有人的对话搅在一起排查问题的时候根本分不清谁是谁而且有个用户连续触发限流把所有人的请求都带崩了。四、数据流一次问答在 Dify 里发生了什么这个每个节点留痕的特性是我们排查问题时的救命稻草。有一次业务方反馈机器人说年假是 5 天但新制度明明改成了 10 天打开 Dify 的日志一看检索出来的片段还是老文档——是 HR 同事更新了文档但没在 Dify 里重新同步。知识库文档更新后必须重新嵌入Dify 不会自动感知外部文件变化这算是第三个坑。五、踩坑清单官方文档不会写但这些事你必须知道坑 1分块策略别用默认的。默认 500 token 一块对制度类文档效果一般——一条年假规定经常被拦腰切断。我们的做法按 Markdown 标题分块Dify 支持父级分块 Parent-child chunking保证一个完整条款在一个块里。就这一改动回答准确率从 71% 提到 89%。坑 2Rerank 必开。向量检索的 top-k 召回噪声不小加一个 bge-reranker 做精排检索质量立竿见影。显存够的话强烈建议把 Rerank 模型也部署在本地。坑 3别在 Chatflow 里塞太多分支。我们第一版野心很大塞了 HR、IT、行政、财务四个方向十几个节点结果画布乱成一锅粥改一处牵全身。后来拆成了四个独立应用前面加一层轻量路由。每个应用职责单一DSL 才好维护。坑 4worker 资源要给够。知识库批量导入文档时嵌入任务全压在 worker 上。我们有次导入 2000 份文档默认 1 个 worker 跑了 6 个小时还把 API 进程的内存挤爆了。worker 扩到 3 个副本之后同样的事 40 分钟干完。坑 5测试集要在上线前建好。Dify 自己没有评估体系我们从工单历史里抽了 120 条真实问题做成测试集每次改 Prompt、换模型、调分块都跑一遍防止改好一处、改坏三处。这个习惯救过我们至少两次。坑 6版本升级先看 Breaking Changes。Dify 迭代很快隔三差五发版。有次我们直接拉最新镜像升级DSL 格式变了旧的 Chatflow 导入直接报错。现在的规矩是生产环境锁定版本号升级先在测试环境导入 DSL 验证。说了这么多泼点冷水收个尾。Dify 不是银弹适合的场景企业内部知识库问答、客服机器人、工单预分类、报表解读这类编排为主、深度定制为辅的应用。团队有 1-2 个懂 Prompt 的人但不想养一个平台研发团队——Dify 就是为你准备的。不太适合的场景需要极低延迟毫秒级、超深度定制 Agent 编排逻辑、或者已有成熟 LLM 中间件的团队。这些场景自己写代码自由度更高硬套 Dify 反而束手束脚。我们现在的姿势是80% 的标准需求用 Dify 快速搭20% 的深度定制需求自研微服务两边通过 API 互相调用。Dify 里甚至可以把你自研的服务注册成自定义工具Custom Tool编排能力不减反增。一句话总结如果你正在手搓第三个 LLM 应用的胶水代码停一下先把 Dify 拉起来跑一遍。有些轮子真没必要自己造。