上期文章我们提到使用dify基于多模态模型实现多种发票识别工作流的智能体。我们在实际工作中发现OCR识别的发票准确性要求比较高。基于多模态大模型OCR识别这块可能会遇到对票面信息识别不准的问题,这样会财务报销就会产生很大的问题。基于以上的问题我们来实现一个发票比对工作流。
下面我们首先介绍一下整体功能。
这里面主要功能:用户上传一张发票图片,发票会经过文档提取器。文档提取器提取用户上传的发票传递给2个llm多模态模型,两个多模态模型是实现发票票面信息的提取功能。然后将提取的发票票面信息发送给第三个基于llm文本的大模型,它充当模型裁判功能。主要的功能是将2个模型输出的JSON格式的数据比对,比对的结果输出给客户。从而实现发票识别比对判断功能。
实现的效果如下:
数据有差异的效果:
数据无差异效果:
下面我们重点介绍一下这个工作流是如何实现的。
创建工作流或者chatflow
接着来到Dify中按下图顺序依次点击并点击创建(注:chatflow和工作流配置基本差不多,下面我们就以chatflow讲解)
开始
开始节点点开后我们需要添加一个文件上传输入参数。点击开始节点输入字段,点击右边的“+”

我们选择单个文件,输入变量名称、支持的文件类型我们这里就选择图片。其他都可以默认,输入完成后,点击保存按钮
以上步骤完成开始节点设置。
文档提取器
接下来我们在工作流画布中,选择文档提取器和开始节点连接,去掉llm和开始节点连接
我们在文档提取器,输入变量中选中 sys.files
变量
llm(多模态发票识别)
接下来我们将文档提取器的连接线和llm大语言模型连接。然后按照以下几个步骤设置
1.模型选择,模型我们在模型下拉列表中选择自定义OpenAI-API-compatible Qwen/Qwen2-VL-72B-Instruct模型;模型最大标记4096
2.上下文,这里设置开始节点file 属性值
3.SYSTEM 提示词 我们输入如下内容
请提取这张照片的内容,其中内容格式‘发票号码’、'开票日期’、'‘出发时间’、‘始发站’、‘终点站’、‘车次’、‘票价’、‘身份证号’、‘姓名’、‘电子客票号’、‘购买方名称’、‘统一社会信用代码’字段返回信息,返回的结果信息以json格式返回
4.视觉 点击右边按钮开启多模态
5 视觉输入变量 选择节点filefiles
变量
以上完成llm模型的设置
以上我们需要再设置第二个多模态发票模型来对上传的图片进行发票识别,操作和上面一样,这里就不重复讲解,区别在于我们需要选择另外一个多模态模型。这里我们选择了智普的glm-4v-plus 来实现
主要需要注意的地方是 智普模型对用户输入的提示词需要有值作为输入参数,这里我们为了让工作流运行起来,我们填写“1”

配置好的2个多模态模型需要和上面文档提取器连接

基于文本发票比对模型
接下来我们需要将2个多模态模型的输出结果和一个llm文本大语言模型进行连接。这个模型的作用主要是接收2个模型输出json值,对2个json值进行判断逐行比对判断是否一致。
1.模型选择,模型我们在模型下拉列表中选择 deepseek-ai/DeepSeek-V2.5模型;
2.上下文,这里可以不填写
3.SYSTEM 提示词 我们输入如下内容
{
"Role": "JSON 数据比对专家",
"Profile": {
"专长": "精确比较和分析 JSON 数据",
"经验": "多年处理各种结构化数据的丰富经验",
"技能": ["准确识别差异", "使用颜色高亮标注", "详细的比对报告生成"]
},
"Goals": [
"逐行比较两个 JSON 数据的内容",
"识别并标记所有存在的差异",
"使用颜色(红色)高亮显示不同之处",
"生成清晰、易读的比对结果报告"
],
"Rules": [
"必须逐个键值对进行比较,不遗漏任何字段",
"只标注存在差异的部分,相同部分保持原样",
"使用红色作为差异标注的唯一颜色",
"对于数值型差异,需要考虑精度问题",
"对于字符串差异,需要考虑大小写和空白字符",
"保持 JSON 的结构完整性,不改变原有的格式和顺序"
],
"Workflows": [
"接收并解析两个待比对的 JSON 数据",
"确保两个 JSON 数据结构一致,如果不一致,报告结构差异",
"逐一比对每个键值对:",
" - 如果键不同,标记为新增或缺失",
" - 如果值不同,使用红色高亮标注",
"生成详细的比对报告,包括:",
" - 总体差异统计",
" - 每个差异项的具体描述",
" - 高亮显示的 JSON 数据"
],
"OutputFormat": {
"type": "json",
"structure": {
"summary": "总体比对结果摘要",
"differences": [
{
"key": "差异字段名",
"value1": "第一个 JSON 中的值",
"value2": "第二个 JSON 中的值",
"highlightColor": "red"
}
],
"highlightedJSON": "包含红色高亮的完整 JSON 数据"
}
},
"Examples": [
{
"input": {
"json1": {
"价税合计(小写)": "263.00",
"收款人": "段欣冉"
},
"json2": {
"价税合计(小写)": "213.00",
"收款人": "段牛冉"
}
},
"output": {
"summary": "发现 2 处差异",
"differences": [
{
"key": "价税合计(小写)",
"value1": "263.00",
"value2": "213.00",
"highlightColor": "red"
},
{
"key": "收款人",
"value1": "段欣冉",
"value2": "段牛冉",
"highlightColor": "red"
}
],
"highlightedJSON": {
"价税合计(小写)": "263.00",
"收款人": "段欣冉"
}
}
}
]
}
- user 提示词 我们需要输入上面2个模型的输出结果。
完整的模型配置如下图
直接回复
这个地方设置比较简单,在回复设置一下llm text文本输出,把比对的结果输出给用户即可。

完整的流程图如下:
dsl 文件
app:
description: ''
icon: 🤖
icon_background: '#FFEAD5'
mode: advanced-chat
name: 发票比对专家-火车票
use_icon_as_answer_icon: false
kind: app
version: 0.1.2
workflow:
conversation_variables: []
environment_variables: []
features:
file_upload:
allowed_file_extensions:
- .JPG
- .JPEG
- .PNG
- .GIF
- .WEBP
- .SVG
allowed_file_types:
- image
allowed_file_upload_methods:
- local_file
- remote_url
enabled: false
image:
enabled: false
number_limits: 3
transfer_methods:
- local_file
- remote_url
number_limits: 3
opening_statement: ''
retriever_resource:
enabled: true
sensitive_word_avoidance:
enabled: false
speech_to_text:
enabled: false
suggested_questions: []
suggested_questions_after_answer:
enabled: false
text_to_speech:
enabled: false
language: ''
voice: ''
graph:
edges:
- data:
isInIteration: false
sourceType: start
targetType: document-extractor
id: 1730994694827-source-1730994818842-target
source: '1730994694827'
sourceHandle: source
target: '1730994818842'
targetHandle: target
type: custom
zIndex: 0 - data:
isInIteration: false
sourceType: document-extractor
targetType: llm
id: 1730994818842-source-1730994952059-target
source: '1730994818842'
sourceHandle: source
target: '1730994952059'
targetHandle: target
type: custom
zIndex: 0 - data:
isInIteration: false
sourceType: llm
targetType: answer
id: 1730995241679-source-answer-target
source: '1730995241679'
sourceHandle: source
target: answer
targetHandle: target
type: custom
zIndex: 0 - data:
isInIteration: false
sourceType: document-extractor
targetType: llm
id: 1730994818842-source-1730994854289-target
source: '1730994818842'
sourceHandle: source
target: '1730994854289'
targetHandle: target
type: custom
zIndex: 0 - data:
isInIteration: false
sourceType: llm
targetType: llm
id: 1730994854289-source-1730995241679-target
source: '1730994854289'
sourceHandle: source
target: '1730995241679'
targetHandle: target
type: custom
zIndex: 0 - data:
isInIteration: false
sourceType: llm
targetType: llm
id: 1730994952059-source-1730995241679-target
source: '1730994952059'
sourceHandle: source
target: '1730995241679'
targetHandle: target
type: custom
zIndex: 0
nodes: - data:
desc: ''
selected: false
title: 开始
type: start
variables:- allowed_file_extensions: []
allowed_file_types:- image
allowed_file_upload_methods: - local_file
- remote_url
label: file
max_length: 48
options: []
required: true
type: file
variable: file
height: 90
id: '1730994694827'
position:
x: -122.33815460561607
y: 239.74853493583367
positionAbsolute:
x: -122.33815460561607
y: 239.74853493583367
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- image
- allowed_file_extensions: []
- data:
answer: '{{#1730995241679.text#}}'
desc: ''
selected: false
title: 直接回复
type: answer
variables: []
height: 103
id: answer
position:
x: 1507.4714864776458
y: 230.51667545618076
positionAbsolute:
x: 1507.4714864776458
y: 230.51667545618076
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244 - data:
desc: ''
is_array_file: true
selected: false
title: 文档提取器
type: document-extractor
variable_selector:- sys
- files
height: 94
id: '1730994818842'
position:
x: 198.13851897026086
y: 239.74853493583367
positionAbsolute:
x: 198.13851897026086
y: 239.74853493583367
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
context:
enabled: true
variable_selector:
- '1730994694827'
- file
desc: ''
model:
completion_params:
temperature: 0.1
mode: chat
name: Pro/Qwen/Qwen2-VL-7B-Instruct
provider: openai_api_compatible
prompt_template:- id: cb8bcd12-345d-4b95-8f48-e3360269ec60
role: system
text: 请提取这张照片的内容,其中内容格式‘发票号码’、'开票日期’、'‘出发时间’、‘始发站’、‘终点站’、‘车次’、‘票价’、‘身份证号’、‘姓名’、‘电子客票号’、‘购买方名称’、‘统一社会信用代码’字段返回信息,返回的结果信息以json格式返回
selected: false
title: 发票提取模型1
type: llm
variables: []
vision:
configs:
detail: high
variable_selector:- '1730994694827'
- file
enabled: true
height: 98
id: '1730994854289'
position:
x: 530.598669101873
y: 132.04920892271053
positionAbsolute:
x: 530.598669101873
y: 132.04920892271053
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- id: cb8bcd12-345d-4b95-8f48-e3360269ec60
- data:
context:
enabled: true
variable_selector:
- '1730994694827'
- file
desc: ''
model:
completion_params:
temperature: 0.1
mode: chat
name: glm-4v-plus
provider: zhipuai
prompt_template:- id: ee1b5d7d-3303-44cf-9b5c-d29b22d3f798
role: system
text: 请提取这张照片的内容,其中内容格式‘发票号码’、'开票日期’、'‘出发时间’、‘始发站’、‘终点站’、‘车次’、‘票价’、‘身份证号’、‘姓名’、‘电子客票号’、‘购买方名称’、‘统一社会信用代码’字段返回信息,返回的结果信息以json格式返回 - id: 827c3f3c-0c19-48c3-b40b-fc15bdfb0407
role: user
text: '1'
selected: false
title: 发票提取模型2
type: llm
variables: []
vision:
configs:
detail: high
variable_selector:- '1730994694827'
- file
enabled: true
height: 98
id: '1730994952059'
position:
x: 517.2073813384065
y: 407.5975395538644
positionAbsolute:
x: 517.2073813384065
y: 407.5975395538644
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- id: ee1b5d7d-3303-44cf-9b5c-d29b22d3f798
- data:
context:
enabled: false
variable_selector: []
desc: ''
model:
completion_params:
temperature: 0.1
mode: chat
name: Qwen/Qwen2.5-72B-Instruct
provider: siliconflow
prompt_template:-
id: 0a4409ea-6bb9-4c3f-9f9e-91e6eaa478aa
role: system
text: "{\n "Role": "JSON 数据比对专家",\n\n "Profile": {\n "专长":\
\ "精确比较和分析 JSON 数据",\n "经验": "多年处理各种结构化数据的丰富经验",\n "技能"\
: ["准确识别差异", "使用颜色高亮标注", "详细的比对报告生成"]\n },\n\n "Goals": [\n\
\ "逐行比较两个 JSON 数据的内容",\n "识别并标记所有存在的差异",\n "使用颜色(红色)高亮显示不同之处"\
,\n "生成清晰、易读的比对结果报告",\n "准确报告完全相同的数据"\n ],\n\n "Rules":\
\ [\n "必须逐个键值对进行比较,不遗漏任何字段",\n "只标注存在差异的部分,相同部分保持原样",\n "\
使用红色作为差异标注的唯一颜色",\n "对于数值型差异,需要考虑精度问题",\n "对于字符串差异,需要考虑大小写和空白字符"\
,\n "保持 JSON 的结构完整性,不改变原有的格式和顺序",\n "如果两个 JSON 完全相同,明确报告无差异"\
\n ],\n\n "Workflows": [\n "接收并解析两个待比对的 JSON 数据",\n "确保两个\
\ JSON 数据结构一致,如果不一致,报告结构差异",\n "逐一比对每个键值对:",\n " - 如果键不同,标记为新增或缺失"\
,\n " - 如果值不同,使用红色高亮标注",\n " - 如果完全相同,不进行标注",\n "生成详细的比对报告,包括:"\
,\n " - 总体差异统计(如果有)或无差异声明",\n " - 每个差异项的具体描述(如果有)",\n "\
\ - 高亮显示的 JSON 数据(如果有差异)"\n ],\n\n "OutputFormat": {\n "type"\
: "json",\n "structure": {\n "summary": "总体比对结果摘要",\n\
\ "differences": [\n {\n "key": "差异字段名",\n\
\ "value1": "第一个 JSON 中的值",\n "value2": "第二个\
\ JSON 中的值",\n "highlightColor": "red"\n }\n \
\ ],\n "highlightedJSON": "包含红色高亮的完整 JSON 数据(如果有差异)"\n }\n\
\ },\n\n "Examples": [\n {\n "input": {\n "json1"\
: {\n "价税合计(小写)": "263.00",\n "收款人": "段欣冉"\n\
\ },\n "json2": {\n "价税合计(小写)": "213.00"\
,\n "收款人": "段牛冉"\n }\n },\n "output":\
\ {\n "summary": "发现 2 处差异",\n "differences": [\n\
\ {\n "key": "价税合计(小写)",\n "value1"\
: "263.00",\n "value2": "213.00",\n "highlightColor"\
: "red"\n },\n {\n "key": "收款人",\n\
\ "value1": "段欣冉",\n "value2": "段牛冉",\n\
\ "highlightColor": "red"\n }\n ],\n \
\ "highlightedJSON": {\n "价税合计(小写)": "263.00"\
,\n "收款人": "段欣冉"\n }\n }\n },\n\
\ {\n "input": {\n "json1": {\n "发票号码":\
\ "243491194230000002",\n "开票日期": "2024-09-29",\n \
\ "购买方名称": "xx股份有限公司",\n "统一社会信用代码": "913401001492097421"\
\n },\n "json2": {\n "发票号码": "243491194230000002"\
,\n "开票日期": "2024-09-29",\n "购买方名称": "xx股份有限公司"\
,\n "统一社会信用代码": "913401001492097421"\n }\n },\n\
\ "output": {\n "summary": "两个 JSON 数据完全相同,没有发现任何差异。"\
,\n "differences": [],\n "highlightedJSON": null\n \
\ }\n }\n ]\n}" -
id: 5725207a-3967-4cb0-9220-fc269ec64a58
role: user
text: '{{#1730994854289.text#}}{{#1730994952059.text#}}'
selected: false
title: 基于文本发票比对模型
type: llm
variables: []
vision:
enabled: false
height: 98
id: '1730995241679'
position:
x: 1078.5929830262382
y: 262.4842834334896
positionAbsolute:
x: 1078.5929830262382
y: 262.4842834334896
selected: true
sourcePosition: right
targetPosition: left
type: custom
width: 244
viewport:
x: 122.11439198427718
y: 168.52395441087617
zoom: 0.5743491774985177
-
chatflow调试及发布
完成以上配置后就可以点击调试及发布了,当然如果你比较偷懒,也可以直接导入我的DSL 直接就可以搞定了。
导入DSL后,是需要修改工作流中的模型就可以了。如果大家没有硅基流动的账号,可以点击https://cloud.siliconflow.cn/i/e0f6GCrN 地址来注册,目前硅基的政策是新户注册送14块钱,14块钱够玩一阵子了。
下面我们就感受一下测试效果
发布
点击工作流左上角发布按钮对外提供发布
我们将分享的地址发送给其他小伙伴
我们点击 start chat 就可以使用了。
总结
今天我们就带大家在上个文章中多个发票识别功能基础上再扩展一个新的发票比对功能的业务场景,来感受一下dify的强大。感兴趣的小伙伴可以持续关注我的文章,今天的分享就到这里,我们下个文章见。
项目体验地址
http://101.126.84.227:88/chat/4J2Nau4TuNDJb5ep
往期文章