我们在实际工作中发现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": {
"价税合计(小写)": "<red>263.00</red>",
"收款人": "<red>段欣冉</red>"
}
}
}
\]
}
-
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
- 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
- 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
- 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 \\"价税合计(小写)\\": \\"<red>263.00</red>\\"\\
,\\n \\"收款人\\": \\"<red>段欣冉</red>\\"\\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 就可以使用了。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。