Dify实战:构建财务报销审核助手,自动检测违规与补全字段
[Dify实战] 财务报销审核助手检测违规、补全字段、生成汇总先说结论这个项目做的就是一套基于 Dify 工作流的财务报销审核助手输入是一段杂乱报销信息比如微信聊天里转过来的行程说明、发票附言、出差申请输出是三类东西——是否违规的判定结果、缺什么字段的补全提示、以及按部门/费用类型汇总的报销清单。整套流程靠 Dify 的编排能力串起来没有写一行业务后端代码全部在画布上完成。我大概用了两个下午搭完这套流程前后改了四版提示词经历了字段识别错乱、日期格式打架、金额单位混用等一堆坑。这篇就把从需求拆解到工作流配置的完整过程写透包括每个节点的意图、参数怎么调、提示词怎么收敛、以及遇到问题怎么排查。如果你是第一次用 Dify 做这类文本抽取加审核判断的场景这篇可以直接当参考手册用。1. 项目整体设计与思路拆解1.1 报销审核场景里真正难的是什么财务报销审核看起来是收票、核对、打款实际上大量时间耗在信息核对和缺失沟通上。报销人发来一句话“这周去客户那边谈续约打车花了八十多住宿七百麻烦给报了”里面缺抬头、缺发票类型、缺时间、缺城市审核人员得反复问就算信息齐全还得判断是否符合差旅标准、是否超预算、是否缺附件。这些问题非常分散且高度重复。我调研过的几个财务系统通常只有表单录入和审批流不具备“理解一句自然语言描述并自动提取报销要素”的能力。如果把这件事交给纯大模型对话框又面临输出格式不可控、结果不稳定、无法跟业务规则挂钩的问题。Dify 的价值在于把大模型当作“理解引擎”把节点当作“规则骨架”让自由文本在受控的流程里被加工成结构化数据。这正是我做这套助手的核心出发点。1.2 为什么选 Dify 而不是写代码或纯提示词工程对比过三条路线纯 Python 写抽取逻辑需要维护一堆正则和规则字段一变就崩而且没法让业务人员参与调整。纯提示词工程单次调用大模型最快但每次输出格式都有偏差金额单位不统一时尤其明显没有校验环节等于无底洞。Dify 工作流用节点的编排方式把“抽取—校验—判断—补全—汇总”拆成步骤每步都有独立提示词和输出 schema出错时可以单独调试某个节点而不是推翻重来。我最终选 Dify 还有一个实际原因它自带调试运行面板可以逐节点看输入输出对定位问题是降维打击。写代码调试要打日志看变量Dify 里点一下节点就能看到实时的 prompt 和返回内容。另一个原因是团队里财务同学可以看懂节点流程后续调规则不用每次都找我。1.3 核心目标拆解成可落地的能力项需求拆解是这套项目里最花时间的部分。我把它拆成了四个能力字段抽取从非结构化文本中提取报销人、部门、费用类型、金额、日期、城市、事由、是否含发票附件等基础要素违规检测将抽取结果对照差旅标准、费用上限、预算额度等规则输出违规项和风险等级字段补全识别缺失字段生成面向报销人的补全问题或默认值建议汇总生成把多条报销记录按部门或费用类型聚合成汇总表方便财务做日报/周报这四个能力合在一起才构成一个“能直接放进工作流”的助手。单独做任何一个都比较简单合并之后的价值是指数级上升的。2. 工作流核心配置与节点详解2.1 工作流整体拓扑这套助手用的是 Dify 的 Chatflow 类型入口是一个会话框用户直接输入报销描述后端开始走节点也可以用 Workflow 类型做批处理一次喂入多行文本逐条处理。Chatflow 更适合“财务和报销人对话式使用”Workflow 更适合批量场景建议先做 Chatflow稳定后再加批量版本。节点链路由以下部分组成开始 → 问题理解与预处理 → 字段抽取(LLM) → 规则判定(Code/条件分支) → 违规检测(LLM) → 字段补全(LLM) → 汇总生成(LLM/模板) → 结束这个链路并非一成不变。实际项目中字段抽取和违规检测也可以用同一个 LLM 节点完成但为了便于单点调试和规则升级拆开不仅更清晰还能利用不同模型或不同 temperature 参数来处理不同任务。2.2 入口参数设计入口参数就是用户最初输入的那段自然语言文本我这里命名为raw_text。这个参数会同时被问题理解节点和字段抽取节点使用。入口变量设计表变量名类型说明示例raw_textString原始报销描述本周去上海拜访客户高铁票553元住宿两晚共760元打车46元default_departmentString默认部门可选销售部current_userString当前操作人张三入口参数不一定要多但default_department和current_user这种兜底信息很有用因为一段报销描述里通常不会写“我是哪个部门的”这两个参数从登录态或会话上下文取就可以避免每条都让用户重输。2.3 节点1问题理解与预处理这个节点用的是 LLM 节点作用不是抽取字段而是做三件事判断用户输入的是不是报销相关信息可能有人发“你好”“帮我查一下天气”如果输入太短或信息量不足直接输出“无法处理请补充信息”标准化文本格式去除空格、统一中文标点、将括号内容整体保留判断是否报销相关是最容易忽略的一步。测试时发现如果不做这个判断随便输入“帮我写个周报”系统也会硬解析出一堆字段非常荒谬。加上这个节点之后非报销输入被拦截在流程外。提示词模板参考节选你是报销审核助理。用户会输入一段与报销相关的自然语言。你的任务是 1. 判断这段内容是否与报销相关若完全无关输出INVALID 2. 若相关输出VALID 3. 若内容过少比如只有“打车”两个字输出NEED_MORE_INFO 只输出以上三个词之一不要输出其他解释。这里模型建议用便宜快速的版本比如gpt-4o-mini或者国产轻量模型因为这个任务的输出非常有限不需要重模型。实测中这个节点通常 2 秒内返回。2.4 节点2字段抽取 LLM这是整套流程最关键的节点。目标是把raw_text解析成结构化 JSON输出到后续节点使用。输出结构我定义为{ employee_name: 报销人姓名, department: 部门, expense_type: 费用类型交通/住宿/餐饮/办公/其他, amount: 123.45, currency: CNY, expense_date: YYYY-MM-DD, city: 城市, purpose: 事由, has_invoice: true, has_attachment: false, missing_fields: [字段名] }这个节点的提示词是最难调的踩过的坑后面专门讲。这里先给一个核心设计原则所有字段都要给出明确的判定标准并且要求模型“没有信息就写 null不要猜”。我之前犯过一个错误让模型“推断”报销人所属部门结果模型给每个城市都编了个部门导致后面统计全错。后来改成“仅从原文提取禁止推断”才好。另一个细节是金额拆分。一条报销描述里经常混合多个费用类型比如“高铁553元住宿760元打车46元”。我的做法是字段抽取阶段先提取总金额和主要费用类型在汇总节点再按费用类型明细拆分。如果一开始就要求模型一次性输出明细列表输出格式容易崩错误率明显上升。2.5 节点3规则判定Code 节点字段抽取完要先做一次“机器规则校验”再做“大模型违规判断”。机器规则的作用是保证百分百准确的硬性约束不被模型幻觉影响。我用的是 Code 节点Python输入是上一节点的 JSON 输出定义了这样几条硬规则如果amount为 null 或小于等于 0标记AMOUNT_INVALID如果expense_date格式不是 YYYY-MM-DD标记DATE_FORMAT_ERROR如果has_invoice为 false 且金额大于 200标记INVOICE_MISSING如果expense_type为“住宿”且金额大于 800标记HOTEL_OVER_LIMIT如果expense_type为“餐饮”且金额大于 300标记MEAL_OVER_LIMIT这些规则本身就是业务方给的财务会告诉你“普通员工住宿上限 800 元餐饮一天上限 300 元”不是模型学出来的是规则表写死的。用 Code 节点而不是让 LLM 做这步判断是为了保证“超限”这种硬伤必须 100% 被抓住不允许模型心情不好就放过去。Code 节点核心逻辑示例def main(raw_data: dict) - dict: errors [] warning [] amount raw_data.get(amount) expense_type raw_data.get(expense_type) has_invoice raw_data.get(has_invoice) if amount is None or amount 0: errors.append(AMOUNT_INVALID) if expense_type 住宿 and amount and amount 800: errors.append(HOTEL_OVER_LIMIT) if expense_type 餐饮 and amount and amount 300: errors.append(MEAL_OVER_LIMIT) if has_invoice is False and amount and amount 200: errors.append(INVOICE_MISSING) return { errors: errors, warning: warning, passed: len(errors) 0 }需要说清楚的是这里amount我定义为每条报销单的总金额而不是明细金额。如果一条输入里“高铁553元住宿760元”按场景拆分更好但初版为了控制复杂度先按单条记录处理如果要支持多条混合描述需要在字段抽取节点追加一个items数组。2.6 节点4违规检测 LLMCode 节点抓硬伤LLM 节点用来判断“语义层面”的违规。比如事由和费用类型不匹配“购买办公用品”但费用类型选了“餐饮”报销描述中提到的出差城市与申请单上的城市不一致描述中带有“加急费”“找朋友代开”这类可疑措辞这个节点不用输出太多字段只输出{ violations: [ {type: RULE_VIOLATION, level: high, description: xxxx} ], risk_level: high/medium/low }节点输入是字段抽取结果和硬规则校验结果提示词里要求模型只关注“逻辑不一致”不要重复 Code 节点已经判断过的硬规则。这样分工明确模型输出也更稳定。实测证明给模型减轻负担后违规检测的准确率有明显提升——它不需要再计算金额是否超限只盯着语义矛盾就行。2.7 节点5字段补全 LLM字段补全节点的目标很明确针对missing_fields里的字段生成一句自然语言问题让报销人补充回答。比如“您提交的报销记录缺少发票信息请确认是否已取得发票并补充发票号码。”这个节点还可以做一步“默认值建议”。比如缺少expense_date但描述里有“上周五”LLM 可以计算出具体日期并提示“系统将自动使用 2025-04-11 作为报销日期如不正确请指出”。这种方式能减少来回沟通的次数直接给报销人确认即可。补全节点输出{ questions: [缺少发票信息请补充发票号, 缺少出差城市请补充是哪个城市], suggested_values: {expense_date: 2025-04-11} }这一步做得好不好直接影响用户体验。很多财务助手“一问一答”用户回答完一个字段又追问另一个字段体验很差。我的做法是一次性把所有缺失项列出来统一问提高效率。2.8 节点6汇总生成汇总节点负责把多条报销记录聚合成一个简洁的汇总结果。这里我用的是 LLM 节点加模板变量模板变量里填的是前面节点的输出。汇总输出文本示例【报销汇总单】 报销人张三 部门销售部 费用类型交通 553元、住宿 760元、打车 46元 合计1359元 风险等级medium 补全提醒缺少发票信息合计超200元需补充这里的模板不是简单字符串拼接而是把 JSON 转成易读文本再做一次格式化。实测中直接拼 JSON 会又长又乱转成自然语言模板后其他人也更容易阅读和转发。3. 提示词工程与数据处理的精修3.1 提示词收敛的三个阶段我前后迭代了四版提示词。第一阶段是“自由发挥”式让模型随便输出结果字段名乱飞一会儿employee_name一会儿name第二阶段开始增加“字段定义表”效果明显改善第三阶段增加“禁止推断”第四阶段增加“输出格式说明必须合法 JSON”。一个比较实用的技巧是给字段定义表的时候不仅写“字段名”还写“这个字段在原文中通常长什么样”。例如expense_date报销发生日期原文可能体现为“4月11日”、“上周三”、“2025-04-11”、“今天”等。这样模型对日期格式的解析准确率提升明显因为“上周三”这种表达在普通 JSON Schema 里没有对应关系必须靠自然语言解释。3.2 JSON 格式稳定性保障LLM 节点输出 JSON 经常出问题常见的问题是多输出了解释性文字、JSON 里带了注释、字段值带了中文顿号而不是英文逗号。Dify 的 LLM 节点可以在输出变量处定义 JSON 结构启用“结构化输出”功能就能强制模型按 JSON Schema 输出。如果没用 Dify 的结构化输出功能那就在提示词末尾加一条强约束只输出 JSON不要输出 markdown 代码块标记不要输出任何解释性文字。JSON 必须是合法的便于机器解析。实测下来加上这句能减少 80% 的格式错误。另外建议在提示词里加一个示例few-shot一个正面示例加上一个负面示例效果比十行规则描述都好。3.3 日期格式与金额单位处理这是所有字段里最容易翻车的两个。日期方面我最终的方案是在字段抽取节点的提示词里给定“标准格式转换规则”中文数字四月十一日转成数字相对时间上周三基于系统当前日期推算时间范围4月10日到4月12日取第一天作为 expense_date金额方面必须处理“元/块/毛”的混用描述里可能出现“八十多”“553块钱”“760元左右”的说法。策略是要求模型输出纯数字只保留两位小数null的情况单独判断。这里需要特别强调的是“左右”的处理我要求模型直接忽略“左右”这类模糊词按最接近的整数处理并在missing_fields里不标记为缺失——因为实际报销场景里“左右”并不影响报销单录入但必须在汇总里体现“以实际发票为准”的提示。金额单位统一在 Code 节点里再做一次兜底校验如果amount是字符串且包含“元”“块”会报AMOUNT_FORMAT_ERROR再把字符串交给 LLM 节点处理格式转换避免模型输出“553元”这种带单位的值直接参与后续计算。3.4 变量作用域与传递设计Chatflow 里创建自定义变量中间结果都存进去。我用了三个中间变量extracted_data存储字段抽取节点输出的 JSONcheck_result存储规则判定和违规检测的结果final_reply存储最终回复文本好处是后续加节点时不需要把前面的节点链接全改一遍直接引用变量即可。改流程的时候特别方便不用一行行查节点 ID。4. 实操过程与调试实录4.1 第一个完整 Demo从搭建到跑通第一步在 Dify 里创建空白 Chatflow 应用。入口变量配置成功后依次添加 LLM 节点做问题理解、字段抽取、规则判定、违规检测、字段补全最后模板节点生成汇总。每个节点的输入输出都在流程面板上直接连线非常直观。第一次跑通大概用了 40 分钟。期间最大的问题是变量类型不匹配——字段抽取节点输出的是一个 Object但规则判定节点输入时手误选了 String导致程序一直报“类型错误”。排查时逐个节点点开看输入输出才发现是连线连错了节点。Dify 的连线非常灵活但这也意味着容易不小心连错我的建议是连接完一条线就立刻跑一次测试别等全部连完再跑。4.2 测试样例与结果复盘选了三条典型样例做测试从简到难样例一完整信息张三销售部4月11日去上海拜访客户高铁553元住宿760元打车46元发票已开样例二缺字段这周四出差去杭州住宿两晚花了七百多高铁票三百多还没拿到发票样例三违规信息项目聚餐四个人吃了880元在一家高档餐厅有发票初次测试结果样例一全对样例二日期解析成了“undefined”样例三没有触发餐饮超限规则因为 Code 节点写的是“餐饮金额大于 300 报错”但实际逻辑应该判断“人均是否超限”四个人880人均220确实没超。这两个问题很有代表性。日期解析问题出在提示词没有明确说明“上个月”“这周四”应该怎么算。修正提示词后加了一句“相对日期请基于当前系统时间推算当前日期是 {today}”并让模型输出“未提及时返回 null”。餐饮超限问题是规则定义太死。后来跟财务确认餐饮报销通常按人均而不是一餐总额。于是 Code 节点新增了person_count字段如果存在“N个人”的表达就用总额除以人数再对比标准。修正后的规则if expense_type 餐饮 and amount and person_count and (amount / person_count) 100: errors.append(MEAL_PER_PERSON_OVER_LIMIT)顺便再分享一个调试思路Code 节点的输出不要只返回结果最好把计算中间量也带出来比如“人均金额220限额100”。这样后续问题排查时能直接看到为什么判定违规不用再反向推导。我现在所有 Code 节点都会返回debug_info字段。4.3 调优后的最终效果修正之后重新跑三个样例结果全部符合预期。样例二被成功判断为缺字段并给出补全问题样例三被判断为人均餐饮超标风险等级为高。接下来测试了一批批量数据100条把 ChatGPT 生成的模拟报销文本逐一走接口调用。最终字段抽取的准确率大概在 90% 以上剩下的 10% 基本集中在“金额单位混乱”和“一句话里包含多个费用明细”的场景。后者目前建议在输入规范层面引导用户分条填写而不是靠模型硬拆。5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因解决方式模型输出 JSON 带注释或解释文字提示词约束不够强启用 Dify 结构化输出提示词写明“只输出 JSON”金额字段是字符串带单位字段抽取提示词没有明确“输出数字类型”在提示词加“amount 为 float 类型不要带货币符号”日期显示 undefined相对日期“上周三”“这周四”无换算规则在字段抽取提示词中注入当前日期硬规则不触发规则定义粒度过粗比如餐饮未考虑人数细化 Code 节点逻辑增加人数/天数等参与计算汇总模板乱入 JSON 原始内容模板变量类型直接引用了 Object在汇总节点前加一个模板/文本处理节点将 Object 转成易读文本一条描述里多个费用类型识别不全字段抽取阶段只输出总额增加 items 数组结构按费用类型拆分非报销内容被解析出字段缺少意图拦截加上问题理解节点输入无关内容直接输出 INVALID变量类型不匹配报错连线连错或手动选了错误类型逐个查看节点输入输出建议连接完一条线就测试一次报销人名字和部门名相似导致抽取混淆模型无法区分谁是报销人谁是部门在字段定义中加入“报销人通常是人名部门通常是公司内设部门名”5.2 调试工具的使用技巧Dify 的调试功能比较完善。对话流界面右侧可以实时查看每个节点输入输出我调 Prompt 时都是开两个页签同时修改。另一个好用的功能是“运行历史”可以回看之前某一轮跑的完整链路不用重新输入一遍。遇到抽风的时候直接翻历史比对上一轮和这一轮的差异比一步步重跑快很多。5.3 踩过的三个最坑的点第一不要在同一个节点里既让模型抽取字段又判违规。这两个任务的优化方向完全不同——抽取追求“忠实原文”违规判断追求“业务逻辑合理性”。合在一起模型容易自我矛盾而且抽取出错时你无法判断是抽取问题还是判断问题。拆开后哪个节点错了就只调哪个节点的提示词效率高得多。第二不要盲目追求复杂的 prompt 模板。草稿版本我把所有规则都塞进一个提示词变量引用一塌糊涂调试时根本不知道是哪个环节出了问题。后来把规则拆到 Code 节点提示词只留任务描述和输出格式清爽得多。复杂逻辑交给代码自然语言只做表达。第三“补全字段”不要只靠模型也要有规则兜底。比如has_invoice字段模型可能不确定但规则可以判断“金额大于 200 却没有发票标记”就强制触发补全。模型给出“无法判断”时规则应当接管而不是让模型硬猜一个结果。6. 配套资源与扩展方向6.1 资源包内容说明这套项目的配套资源包含三类工作流 DSL 文件可在 Dify 中直接导入包含全部节点配置和变量定义导入后按实际业务改一下部门名单和审批人配置即可用提示词模板集合每个节点的稳定版提示词带参数说明和多版本对比方便按自己的场景改写字段映射配置表把业务字段报销人、费用类型、金额、日期等与模型输出字段的映射关系整理成表格方便对接内部财务系统团队内部试用时财务同事最关心的是“违规规则能不能改”我将所有规则统一收敛到 Code 节点和少数几个变量里强调过“只改配置不改流程”的原则。6.2 已探明的后续扩展方向这套流程目前是 Chatflow 单条处理模式再往下扩展有三个方向一是批量导入模式。把入口从会话框改成文件上传解析 Excel 后逐行走工作流并在最后追加一个总汇总节点。这个对财务月末集中审核非常有用可以把一整天的工作量缩短到几分钟。二是知识库辅助审核。把公司差旅制度、审批流程 SOP 上传到 Dify 知识库作为审核节点的参考资料让模型引用具体条款来自动判断“超限”“不合规”。这里对知识库分块策略要求较高每个块最好对应一个完整条款避免截断导致上下文混乱。三是与审批系统联动。通过 Webhook 将最终审核结果推进到内部审批流Dify 直接把结果 POST 出去。做这个之前建议先确认企业审批系统的接口文档避免在接口规范上踩坑。6.3 小结之外的个人体会用 Dify 搭这套财务报销审核助手之后最大的感触是这类“自然语言理解规则校验”的场景真正的难点不在模型能力而在流程设计和字段定义。你花在拆流程、定字段、写 Code 规则上的时间远多于调提示词的时间。把业务规则说清楚、把输入输出边界定死比追求一个“万能提示词”重要得多。如果你也想做类似的东西我的建议是先找财务要一份真实的报销单模板和差旅标准哪怕只有两页纸也比自己拍脑袋设计十个字段强。流程搭完先用三个月的历史数据做回放测试跑通后再开放给业务使用起步不要急慢慢迭代就是了。