← 返回文章列表

Dify 高级实验(07):智能审批——如何让机器自动完成流程审批?

1. 业务场景

先讲一个我们实际遇到的场景。

一家中型公司的财务部,每天收到几十条报销申请。员工在 OA 里写一段话:「报销 5000 元差旅费,市场部王五 E1001」——然后这笔钱就要经过「领导审批 → 财务审核」两道人工关卡。金额大的要领导签字,金额小的领导也得逐条看;审批标准全凭领导个人判断,同样的金额有的领导批有的领导卡,员工私底下抱怨「报销像开盲盒」。财务总监算过一笔账:一笔小额报销从提交到打款平均要走 3 天,其中 90% 的时间花在等人审批上。

我们第一次听到这个需求时,第一反应也是「审批流嘛,OA 里配个条件分支就有了」。后来把规则一条条列出来才发现——金额四档、招待费特殊规则、按部门路由,这些判断维度全是能写死的确定性逻辑;真正卡住自动化的不是「判定」,而是「读懂一句话里的金额和事由」。这一步,正好是参数提取器的活。

这不是个例。任何「规则可描述的流程审批」都是这个模式:报销审批、请假审批、采购审批、工单分级、风控拦截——金额、类型、职级这些判断维度都是确定的,完全可以用规则描述,却偏偏靠人一层层手工流转,慢且标准不统一。

2. 场景痛点

这个流程的痛点,在财务和员工身上体现得最直接:

本质上,审批的瓶颈不是「该不该批」,而是「规则明确的判断不该让人逐条做」——金额档位、费用类型、特殊规则都是确定性逻辑,机械化执行正是代码的强项。

3. 方案:为什么是这套规则路由范式

Dify 工作流里的「参数提取 → 代码规则引擎判定 → IF/ELSE 三路分支」链路,把审批从「人等判断」变成「机器秒级判定」。

选它的理由,我们实际对比过:

这篇文章我们就用它搭一个「报销审批机器人」:员工提交报销申请文本,机器按规则自动判定走哪条审批路径。

4. 整体架构

graph TD start["开始:expense_request"] --> pe["参数提取器 pe_extract:amount / expense_type / department / reason / employee_id"] pe --> rules["Code 审批规则引擎 cd_rules:auto_approve / need_approval / human_review / reject"] rules --> cond{"IF/ELSE 三路分支 cond_route"} cond -- "自动通过" --> la["LLM 生成通过通知 lm_approve"] la --> ea["结束 result_auto"] cond -- "需审批" --> cn["Code 模拟通知审批人"] cn --> en["结束 result_need"] cond -- "转人工/驳回" --> ct["Code 生成审批单"] ct --> ch["Code HTTP 通知(模拟)"] ch --> eh["结束 result_human"]

链路很清晰:入口收报销文本 → 提取结构化字段 → 规则引擎判定 → 三路分支收尾。规则引擎是这个架构的决策核心——所有审批标准集中在一个代码节点里,改规则只改一处,全流程生效。

5. 模块设计

5.1 参数提取器(pe_extract)

注意 PE 的查询字段名是 query(与知识库/分类器的 query_variable_selector 相反),reasoning_mode: function_call,五个参数声明:

- data:

    instruction: |

      从以下报销申请内容中提取字段信息。注意:

      - amount 是报销金额(数字,单位元)

      - expense_type 是费用类型(差旅/办公/招待/设备,如无法归类填"其他")

      - department 是所属部门

      - reason 是报销事由(简要概括)

      - employee_id 是员工编号(如 E1001)

      - 如果某个字段没有明确信息,填 null 而不是编造

    parameters:

    - description: 报销金额(元)

      name: amount

      required: true

      type: number

    - description: 费用类型(差旅/办公/招待/设备)

      name: expense_type

      required: true

      type: string

    # department / reason / employee_id 同理……

    query: [start, expense_request]

    reasoning_mode: function_call

    title: 参数提取

    type: parameter-extractor

5.2 审批规则引擎(cd_rules)

入参先 float() 防御转换(PE 提取的 number 到代码节点可能是字符串),输出 action 供分支路由:

def main(amount, expense_type, department):

    amount = float(amount) if amount else 0

    result = {"amount": amount, "action": "", "approver": "", "reason": ""}

    if amount <= 0:

        result["action"] = "reject"; result["reason"] = "金额无效"

    elif amount < 1000:

        result["action"] = "auto_approve"; result["reason"] = "小额自动通过"

    elif amount < 10000:

        result["action"] = "need_approval"; result["approver"] = "部门经理"; result["reason"] = "需部门经理审批"

    else:

        result["action"] = "human_review"; result["approver"] = "财务总监"; result["reason"] = "大额需财务总监审批"

    # 特殊规则:招待费即使小额也需审批

    if expense_type == "招待" and amount > 500:

        result["action"] = "need_approval"; result["approver"] = "部门经理"

    return result

5.3 三路分支(cond_route)

字符串比较用 is(不是 =/==);case_human 是 OR 条件,同时覆盖 human_reviewreject所有被边引用的 case_id 必须在 cases 中定义

- data:

    cases:

    - case_id: case_auto

      conditions:

      - comparison_operator: is

        value: auto_approve

        variable_selector: [cd_rules, action]

      logical_operator: and

    - case_id: case_need

      conditions:

      - comparison_operator: is

        value: need_approval

        variable_selector: [cd_rules, action]

      logical_operator: and

    - case_id: case_human

      conditions:

      - comparison_operator: is

        value: human_review

        variable_selector: [cd_rules, action]

      - comparison_operator: is

        value: reject

        variable_selector: [cd_rules, action]

      logical_operator: or

    title: 审批路由分支

    type: if-else

5.4 三个独立结束节点

多 End 的 variable 必须唯一result_auto / result_need / result_human,各取本分支输出:

- data:

    outputs:

    - type: string

      value_selector: [lm_approve, text]

      variable: result_auto

    title: 结束(自动通过)

    type: end

6. 运行验证

输入(expense_request) 期望行为 实测
报销 500 元交通费,市场部张三 E1001 action=auto_approve,LLM 生成「已自动通过」通知 与预期一致
报销 5000 元差旅费,销售部李四 E1002 action=need_approval,通知部门经理审批 与预期一致
报销 50000 元设备采购,技术部王五 E1003 action=human_review,生成审批单并模拟推送,审批人财务总监 与预期一致
报销 800 元招待客户,市场部赵六 E1004 特殊规则生效:action=need_approval(而非自动通过) 与预期一致

7. 实战坑

现象 修复
多个 End 节点 variable 重名 三个结束节点全叫 result 时校验脚本查不出(双脚本全绿),运行结果互相覆盖 每个 End 用唯一 variable(result_auto/result_need/result_human),validate_dsl.py 已加跨节点检查(dify103_07 实测教训)
IF/ELSE 字符串比较用 =/== Pydantic 校验报错(字符串只支持 is/contains 等),旧 DSL 迁移后运行才炸 字符串一律 is;cases 必须定义所有被边引用的 case_id,否则该分支 UI 无连线(dify02_02 迁移实测)
下游引用 PE 输出用错字段 引用 pe_extract.text 取不到值——校验器对 PE 只认 parameters 里声明的 name;PE 的 text 输出也不能作为 end/selector 来源 只引用 parameters 里声明的 name(amount/expense_type/employee_id);要全量 JSON 用代码节点 json.dumps 打包(dify08_01 实证)
代码节点入参类型不符 PE 提取的 number 传进代码是字符串,"12000" > 10000TypeError: '>' not supported between instances of 'str' and 'int' 代码内 float(amount) if amount else 0 防御转换;同时 main() 参数名必须与 variables 的 variable 名一致(按名传参)

8. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过