dify案例分享-基于多模态模型的发票比对

技术

上期文章我们提到使用dify基于多模态模型实现多种发票识别工作流的智能体。我们在实际工作中发现OCR识别的发票准确性要求比较高。基于多模态大模型OCR识别这块可能会遇到对票面信息识别不准的问题,这样会财务报销就会产生很大的问题。基于以上的问题我们来实现一个发票比对工作流。

下面我们首先介绍一下整体功能。

picture.image

这里面主要功能:用户上传一张发票图片,发票会经过文档提取器。文档提取器提取用户上传的发票传递给2个llm多模态模型,两个多模态模型是实现发票票面信息的提取功能。然后将提取的发票票面信息发送给第三个基于llm文本的大模型,它充当模型裁判功能。主要的功能是将2个模型输出的JSON格式的数据比对,比对的结果输出给客户。从而实现发票识别比对判断功能。

实现的效果如下:

数据有差异的效果:

picture.image

数据无差异效果:

picture.image

下面我们重点介绍一下这个工作流是如何实现的。

创建工作流或者chatflow

接着来到Dify中按下图顺序依次点击并点击创建(注:chatflow和工作流配置基本差不多,下面我们就以chatflow讲解)

picture.image

开始

   开始节点点开后我们需要添加一个文件上传输入参数。点击开始节点输入字段,点击右边的“+”


   ![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/7deb60aed8504da9aa0176ac34b2a1fc~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1754709040&x-signature=mDyvAskYP9plEyc7oZEO%2B%2BEkAgI%3D)

我们选择单个文件,输入变量名称、支持的文件类型我们这里就选择图片。其他都可以默认,输入完成后,点击保存按钮

picture.image

以上步骤完成开始节点设置。

picture.image

文档提取器

接下来我们在工作流画布中,选择文档提取器和开始节点连接,去掉llm和开始节点连接

picture.image

picture.image

我们在文档提取器,输入变量中选中 sys.files 变量

picture.image

llm(多模态发票识别)

接下来我们将文档提取器的连接线和llm大语言模型连接。然后按照以下几个步骤设置

1.模型选择,模型我们在模型下拉列表中选择自定义OpenAI-API-compatible Qwen/Qwen2-VL-72B-Instruct模型;模型最大标记4096

2.上下文,这里设置开始节点file 属性值

3.SYSTEM 提示词 我们输入如下内容

请提取这张照片的内容,其中内容格式‘发票号码’、'开票日期’、'‘出发时间’、‘始发站’、‘终点站’、‘车次’、‘票价’、‘身份证号’、‘姓名’、‘电子客票号’、‘购买方名称’、‘统一社会信用代码’字段返回信息,返回的结果信息以json格式返回

4.视觉 点击右边按钮开启多模态

5 视觉输入变量 选择节点filefiles 变量

以上完成llm模型的设置

picture.image

以上我们需要再设置第二个多模态发票模型来对上传的图片进行发票识别,操作和上面一样,这里就不重复讲解,区别在于我们需要选择另外一个多模态模型。这里我们选择了智普的glm-4v-plus 来实现

picture.image

         主要需要注意的地方是 智普模型对用户输入的提示词需要有值作为输入参数,这里我们为了让工作流运行起来,我们填写“1”


          ![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/a19a12c7d9f84aaab2c058a2a471d798~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1754709040&x-signature=PZdsGfDHOHlduYW9gX7JC8GzeGI%3D)


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


![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/69928a9129494026b0e7cccc8fe74201~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1754709040&x-signature=3OHN5THxCZtaUiCApad9MoZrxsk%3D)

基于文本发票比对模型

 接下来我们需要将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",
"收款人": "段欣冉"
}
}
}
]
}

  1. user 提示词 我们需要输入上面2个模型的输出结果。

完整的模型配置如下图

picture.image

picture.image

直接回复

           这个地方设置比较简单,在回复设置一下llm text文本输出,把比对的结果输出给用户即可。


          ![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/9acd8e10df7847ad8763d1edcdbad0d2~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1754709040&x-signature=ajoLknuDl10sUYF6T6p1tIAP0MA%3D)

完整的流程图如下:

picture.image

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 "价税合计(小写)": "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 直接就可以搞定了。

picture.image

picture.image

导入DSL后,是需要修改工作流中的模型就可以了。如果大家没有硅基流动的账号,可以点击https://cloud.siliconflow.cn/i/e0f6GCrN 地址来注册,目前硅基的政策是新户注册送14块钱,14块钱够玩一阵子了。

下面我们就感受一下测试效果

picture.image

发布

点击工作流左上角发布按钮对外提供发布

picture.image

我们将分享的地址发送给其他小伙伴

picture.image

我们点击 start chat 就可以使用了。

picture.image

总结

今天我们就带大家在上个文章中多个发票识别功能基础上再扩展一个新的发票比对功能的业务场景,来感受一下dify的强大。感兴趣的小伙伴可以持续关注我的文章,今天的分享就到这里,我们下个文章见。

项目体验地址

http://101.126.84.227:88/chat/4J2Nau4TuNDJb5ep

往期文章

dify案例分享-基于多模态模型的发票识别2-多种发票识别

dify案例分享-基于多模态模型的发票识别

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
VikingDB:大规模云原生向量数据库的前沿实践与应用
本次演讲将重点介绍 VikingDB 解决各类应用中极限性能、规模、精度问题上的探索实践,并通过落地的案例向听众介绍如何在多模态信息检索、RAG 与知识库等领域进行合理的技术选型和规划。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论