Dify
langgenius/dify项目目录
项目简介
Dify 是搭建和发布 AI 应用的平台,可在可视化流程中组合模型、知识检索、条件分支和工具,支持人工输入、API 调用以及运行记录查询。
如何写进简历
售后通 · 家电电商售后协办平台 | 场景化示例
项目描述:面向家电电商客服,结合订单、故障描述与售后凭证检索适用政策,生成维修、退换或补料建议。客服修改确认后继续办理,并跟踪工单回写结果。
技术栈:Python、Flask、Dify、PostgreSQL、Redis、Celery
主要工作:
- 人工审批与断点续办:基于
Dify运行快照保存变量与未完成节点,将客服修订持久化为人工输入,通过Celery恢复原运行,支持售后审核跨请求暂停、修改与继续执行。 - 工单幂等与回执一致性:以售后单号、处置版本和动作类型构造稳定业务键,通过唯一约束和办理状态拦截并发重试;保存工单回执,超时结果未知时先查询原动作,避免重复创建维修或退换单。
- 流式续接与任务状态管理:以
workflow_run_id关联事件和运行结果,保留task_id用于停止请求;断线后续接原运行或查询最终状态,区分等待审核、执行失败与流程完成,避免通过重跑流程恢复进度。
面试题与回答
如何在 Agent 系统中设计 Human-in-the-loop 人工介入能力?
展开收起
核心回答
在
Dify中,可以用人工输入节点把工作流分成“准备内容、等待人处理、根据决定继续执行”三段。节点展示上游草稿和待填字段,后端保存表单与运行现场;用户提交有效输入后,服务端记录选择和修改内容,再投递恢复任务,让后续节点读取这些结果并走对应分支。这里需要区分表单已提交、工作流已恢复、业务动作已完成。表单状态检查能阻止重复提交,暂停记录能限制重复恢复,但都不直接保证下游接口只执行一次。
介入点怎样设置,输入输出是什么
适合介入的位置包括缺少必要字段、需要审阅模型草稿,以及执行外部写入之前。以一个工作流配置示例说明:模型生成草稿后进入人工输入节点,展示草稿并允许修改,提供“确认”和“退回”两个动作;确认后将修改内容交给下一节点,退回则进入另一条处理分支。
Dify的人工输入回调会用变量池渲染表单内容、解析默认值,并把表单关联到节点及本次运行。尚未提交时返回PauseRequested;恢复后读取保存的提交值,构造节点输出,并以所选动作的标识作为selected_handle。因此后续分支消费的是明确的字段和动作,而非从一句聊天中的“同意”猜测意图。表单创建与恢复输出。等待期间保存什么
当前
Python后端通过Flask提供接口,由Graphon执行图。收到暂停事件时,WorkflowRunAgg将运行参数、序列化的RuntimeState、累计耗时和响应流过滤状态组织成WorkflowResumptionContext,保存未完成节点及运行状态;暂停上下文写入配置的存储,数据库中的WorkflowPause记录其存储键及暂停原因。暂停现场保存、暂停记录持久化。这样人工处理可以与原来那次请求分开,恢复时按运行记录找回现场,无需让用户重新提交原始任务。它也不是在服务进程中一直阻塞等待一个输入框。
提交以后怎样继续
服务端先检查表单是否仍可提交、有无过期,再校验动作和输入字段。
mark_submitted()在事务内锁住表单行,只允许从WAITING变为SUBMITTED,同时保存动作、输入、提交者及时间;并发或重复提交会收到已处理错误。提交校验与调度、表单状态原子更新。对于运行中的人工输入表单,服务随后按
workflow_run_id投递Celery恢复任务。消费者读取暂停上下文,用RuntimeState.from_snapshot()重建运行现场;在带行锁的恢复事务中检查运行仍为PAUSED、未请求停止、暂停记录匹配且尚未恢复,再标记为运行中,调用对应的工作流或对话流恢复入口。恢复任务、恢复状态转换。取舍与重复执行边界
人工输入节点让等待、表单和分支成为明确流程,代价是要处理过期、恢复失败和业务结果跟踪。当前提交表单与投递恢复任务之间存在失败窗口:
enqueue_resume()捕获入队异常后只记录日志,表单可能已经提交而工作流还没继续,不能仅凭提交接口成功就显示业务办理完成。若接入发邮件、创建订单等写入工具,可在业务侧增加动作标识、幂等处理和回执核对,并为已提交但未继续的运行设计补偿入口。这些需要结合具体业务实现;已有的表单防重与恢复状态转换,不能代替业务动作的防重复执行。
参考资料
- 国内:JavaGuide:AI 可观测性:参考长时间等待可以分成多段执行、用运行标识关联暂停与恢复的解释。
- 国内:zero2Agent:HITL 交互链路:参考暂停后展示草稿、提交修改、继续处理的交互组织;具体恢复机制按本文的
Dify实现展开。 - 国内:WeThinkIn:工具权限、沙箱和人工审批如何协同:参考人工确认绑定具体动作、最终结果仍需业务回执验证的区别。
- 国内:Spring AI Alibaba:人类反馈:参考预设介入点、回写人工输入、再按反馈分支的设计;其框架接口不用于
Dify。 - 国外:LangGraph:Interrupts:参考持久化运行现场与恢复输入的职责划分;
Dify使用自己的表单、暂停记录与恢复入口。 - 国外:LangChain:Human-in-the-loop:参考批准、编辑、拒绝和直接回答的语义差异,帮助区分人工输入与真正执行工具。
工具调用如何保证幂等,避免模型重试导致重复副作用?
展开收起
核心回答
以“售后通”创建维修、退换工单的场景化接入设计为例:由业务服务保存稳定的办理标识,用数据库唯一约束和办理状态处理并发重试,成功时返回原工单回执,结果未知时先查询原动作。
Dify负责流程编排与人工审批,工单系统负责实际写入;两边需要共享业务标识,不能把工作流恢复当成工单已经幂等。例如客服确认一次维修申请后,模型重试、接口超时重试和工作流续办都应指向同一次办理。客服明确改成换货则是新的处置动作,在业务允许变更的状态下应形成新的处置版本。能否改处置仍需校验工单状态,幂等键不代替业务状态约束。
哪个标识代表同一次办理
接入服务在客服确认处置时固定租户、售后单号、处置版本和动作类型,作为业务键;同时保存规范化参数摘要,包含处理方式、目标商品等影响办理的字段。同键且参数一致才视为重试,同键却改了参数应拒绝并要求建立新处置版本。
workflow_run_id关联流程运行,task_id用于停止执行,模型的工具调用 ID 关联一次调用与返回。它们都不能替代跨重试的业务键;即使同一次办理被重新受理为另一个流程运行,也应查回原来的办理记录。业务键与处置版本由可信服务维护,不能每次让模型重新生成。并发请求怎样只推进同一次动作
在业务数据库中,为业务键建立唯一约束,保存参数摘要、办理状态及工单回执。不要采用“先查不存在,再发起办理”的非原子流程:两个请求可能同时通过检查。
第一次请求通过插入或条件更新领取办理权。重复请求读取已有记录:成功则返回同一张工单;处理中则返回进度;只有确认尚未产生副作用、符合重试约定的失败才重新执行。同一数据库内的工单写入和结果登记可以放在同一个事务里提交,避免工单已创建却没有留下成功记录。
外部工单成功,本地却超时怎么办
调用外部工单系统前,先持久化办理记录,并向下游透传同一业务键。若下游已经创建工单,但响应丢失或本地保存回执前进程退出,应将结果标为待确认,按原业务键查询或对账,确认后补写回执。
下游支持幂等时,按它的键范围、有效期和参数比较规则使用原键重试;不支持时,需依靠它的业务唯一号或查询能力核对。没有可核对的结果,就保留待确认状态并交由人工处理,不能换键重发。“没收到成功”与“确定没有办理”是两种状态。
工作流恢复怎样与业务状态衔接
Dify的人工表单提交会锁定表单记录,恢复事务也会检查暂停状态;这些保护的是表单和流程状态。当前实现仍存在表单已提交、恢复任务尚未成功入队的窗口,见提交与恢复调度。接入层应按原运行补查、续办,并让恢复后的写工具继续使用原业务键。验证时重点覆盖:同键并发只得到同一张工单、同键不同参数被拒绝、下游成功后响应丢失仍能查回原工单,以及重复恢复不会新建第二张工单。流程结束和工单办理结果分别核对,以实际业务回执确认完成。
参考资料
- 国内:JavaGuide:结构化输出与 Function Calling:采用可信执行层生成业务键、唯一约束兜底和下游透传的分工,组织售后写工具的调用链。
- 国内:JavaGuide:大模型应用安全:采用动作标识与参数摘要分离、结果未知单独建模的办法,说明改处置与重试的区别。
- 国内:zero2Agent:工具管理与调用:采用并发工具调用的事务边界与异步状态回传,解释流程运行和业务办理分别保存什么。
- 国内:WeThinkIn:Agent 设计模式与工作流编排:采用业务已提交但检查点未保存的故障窗口,说明恢复时为什么先核对外部结果。
- 国外:AWS:Making retries safe with idempotent APIs:采用显式请求意图、原子记录与等价回执,说明相同参数不一定代表同一次办理。
- 国外:Stripe:Idempotent requests:借鉴真实接口对同键参数、结果保存和保留期的约定,说明下游幂等仍需遵守具体接口契约。
用 Python 调用 Dify 或 Coze 工作流时,应关注哪些工程细节?
展开收起
核心回答
接入
Dify时,由Python业务服务持有应用密钥,调用已发布工作流,把开始节点输入映射到inputs,再分别处理阻塞结果或SSE事件。Dify的Flask接口把执行请求交给应用生成服务,工作流节点由graphon引擎运行;这里不是把LangGraph当成Dify的执行器。接入层最关键的是分清两类标识:
workflow_run_id用于查询这次运行,task_id用于请求停止任务。它们都不是业务单号或幂等键。流式接入应尽早保存这些标识,以最终状态和输出完成业务记录;断线后查询或显式续接原运行,不重复提交同一次业务。从请求到结果
向
POST /v1/workflows/run发送应用级Bearer凭据,请求包含inputs、response_mode和稳定的user。例如工作流的开始节点定义了target,才在inputs中传入这个名字;换成另一个工作流时,应重新核对输入定义。user表示终端用户,同一用户的不同执行可以使用同一个值,它不负责请求去重。response_mode=blocking等待返回结果;response_mode=streaming返回text/event-stream。接收workflow_started时保存运行标识与任务标识,节点事件可用于展示执行进度;接收workflow_finished后检查data.status,成功时再按约定读取data.outputs,失败时保存error。不能只凭HTTP 200或出现过节点输出就认定整个流程成功;遇到人工输入暂停,也应保留等待状态,而不是把连接结束记为失败。固定版本的执行入口明确区分两种响应模式;流事件数据结构分别提供运行开始和运行完成的信息。
断线、续接与停止
已知
workflow_run_id时,使用GET /v1/workflows/run/{workflow_run_id}查询运行状态、输出和错误。这个查询用于确认结果,不是重新执行流程;若请求在收到运行标识前就断开,不能声称已经能可靠查询原运行,需要结合平台日志或业务结果进一步核对。这个版本还提供
GET /v1/workflow/{workflow_run_id}/events,由客户端使用原user主动续接事件流。include_state_snapshot=true可先返回已执行节点的状态摘要;运行已结束时会发送完成事件。这不等于自动重连,也不是按事件游标逐条补齐所有历史事件。 接入层仍应以运行状态和最终输出确认结果,不能通过再次调用执行接口来“恢复连接”。具体约定见事件续接入口。需要停止时,调用
POST /v1/workflows/tasks/{task_id}/stop,按接口要求传入user,不能把workflow_run_id填进停止地址。停止接口调用任务停止服务;运行中的任务通过停止标记与引擎中止命令处理。停止请求返回成功,不应解释成已经撤销此前的业务写入,外部接口的办理结果仍需单独确认。接入层怎样验证
先用一个输入输出明确的小流程确认字段映射,再覆盖成功、节点失败、人工暂停、收到运行标识后断线和停止请求等情况。若流程包含业务写入,还应模拟“下游已办理,但调用方没收到结果”后再次提交,核对是否重复办理;这部分保证依赖业务接口和接入层设计,不能由
Dify的运行 ID 推导出来。