🤖

Dify × PDF:スキャンPDFをAIに読ませるためのシンプル解決策

に公開

作ったもの

DifyのプラグインPDF to Images Converter Pluginを作成しました!
晴れて公式マーケットプレイスに登録されましたので、プラグインの紹介とマーケットページには書ききれない従来の問題点の説明のために記事を作成します。

はじめに

DifyでPDFを読み込みAIモデルに内容を渡して何かしらの処理を行うシーンで活用するプラグインです。
その際PDFは大きくは以下の2種類に大きく分かれます。
(pythonのライブラリを使う場合など、Difyに限らずシステム・プログラムで扱う場合はこの点のケアが必要になると考えています。)

  1. テキスト埋め込みPDF
    PDF内部に文字情報が埋め込まれているもの。要するにマウスでテキストをコピーできるものですね。
    (下記の「Write for yourself」の部分のようにテキストを選択できる。)

    Zennとは」を「Zennとは?.pdf」で保存したもの

  2. スキャンPDF(画像ベースPDF、非テキスト埋め込みPDF、など)
    PDF内部に文字情報はなく、ページ全体が画像として格納されているPDFです。このケースでは文字を読み取れないため、OCRや画像認識が必要になります。
    (正式名称がAIに聞いてもわからなかったので「スキャンPDF」と呼びます。)

従来の問題点

解決策の前に、それぞれのパターンでどのようになるのか、問題点を共有させてください。
DifyでPDFを処理する際は「ファイルアップロード」[1]機能と「テキスト抽出」[2]ノードを使うのが仕様に沿った使い方になります。

テキスト埋め込みPDFの場合は標準機能でOK

以下は「Zennとは?.pdf」を読み取る簡単なDifyのフローです。
真ん中下段の「text」の部分の通り、pdfの内容を読み取れていることが確認できます。

もしもこんな最小構成でも使いたい方がいましたら以下をインポートしてご利用ください。

test.yml
app:
  description: ''
  icon: 🤖
  icon_background: '#FFEAD5'
  mode: advanced-chat
  name: test
  use_icon_as_answer_icon: false
dependencies:
- current_identifier: null
  type: marketplace
  value:
    marketplace_plugin_unique_identifier: langgenius/openai:0.2.3@5a7f82fa86e28332ad51941d0b491c1e8a38ead539656442f7bf4c6129cd15fa
kind: app
version: 0.3.1
workflow:
  conversation_variables: []
  environment_variables: []
  features:
    file_upload:
      allowed_file_extensions:
      - .JPG
      - .JPEG
      - .PNG
      - .GIF
      - .WEBP
      - .SVG
      allowed_file_types:
      - image
      - document
      allowed_file_upload_methods:
      - local_file
      enabled: true
      fileUploadConfig:
        audio_file_size_limit: 50
        batch_count_limit: 5
        file_size_limit: 15
        image_file_size_limit: 10
        video_file_size_limit: 100
        workflow_file_upload_limit: 10
      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
        isInLoop: false
        sourceType: start
        targetType: document-extractor
      id: 1754876217921-source-1756044569689-target
      source: '1754876217921'
      sourceHandle: source
      target: '1756044569689'
      targetHandle: target
      type: custom
      zIndex: 0
    - data:
        isInIteration: false
        isInLoop: false
        sourceType: document-extractor
        targetType: llm
      id: 1756044569689-source-1756044579087-target
      source: '1756044569689'
      sourceHandle: source
      target: '1756044579087'
      targetHandle: target
      type: custom
      zIndex: 0
    - data:
        isInIteration: false
        isInLoop: false
        sourceType: llm
        targetType: answer
      id: 1756044579087-source-1756044632703-target
      source: '1756044579087'
      sourceHandle: source
      target: '1756044632703'
      targetHandle: target
      type: custom
      zIndex: 0
    nodes:
    - data:
        desc: ''
        selected: false
        title: 開始
        type: start
        variables: []
      height: 54
      id: '1754876217921'
      position:
        x: 429.3783617376292
        y: 23.778184703671002
      positionAbsolute:
        x: 429.3783617376292
        y: 23.778184703671002
      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: '1756044569689'
      position:
        x: 476.8571428571429
        y: 105.35714285714283
      positionAbsolute:
        x: 476.8571428571429
        y: 105.35714285714283
      selected: false
      sourcePosition: right
      targetPosition: left
      type: custom
      width: 244
    - data:
        context:
          enabled: false
          variable_selector: []
        desc: ''
        model:
          completion_params:
            temperature: 0.7
          mode: chat
          name: gpt-4o-mini
          provider: langgenius/openai/openai
        prompt_template:
        - id: 4a44c71b-bde2-4500-998d-7a159fb9c46d
          role: system
          text: ''
        - id: 949bf395-1a93-4c3c-b7c8-58040033ede2
          role: user
          text: '{{#sys.query#}}


            {{#1756044569689.text#}}'
        selected: false
        title: LLM
        type: llm
        variables: []
        vision:
          enabled: false
      height: 90
      id: '1756044579087'
      position:
        x: 554.4313111598581
        y: 225.6386357299544
      positionAbsolute:
        x: 554.4313111598581
        y: 225.6386357299544
      selected: false
      sourcePosition: right
      targetPosition: left
      type: custom
      width: 244
    - data:
        answer: '{{#1756044579087.text#}}'
        desc: ''
        selected: false
        title: 回答
        type: answer
        variables: []
      height: 105
      id: '1756044632703'
      position:
        x: 609.7149753261024
        y: 338.058419526812
      positionAbsolute:
        x: 609.7149753261024
        y: 338.058419526812
      selected: false
      sourcePosition: right
      targetPosition: left
      type: custom
      width: 244
    viewport:
      x: -451.4914715123374
      y: 204.4258469818957
      zoom: 1.0051611574364074

スキャンPDFの場合は標準機能で読み取れない

一方スキャンタイプで同じページを保存した「Zennとは?_scan.pdf」では「text」が空欄になってしまい、情報を取得できていないことがわかります。テキスト抽出の仕様上、これは仕方のないことです。


スキャンPDFの内容。こちらはテキストをコピーできない


スキャンPDFの内容をテキスト抽出ノードで抽出できない

解決策

私が作成したDifyのプラグインPDF to Images Converter Pluginを使った様子がこちらです。
スキャンPDFの「Zennとは?_scan.pdf」の内容を画像としてAI(LLMノード)に渡しており、しっかりと内容を答えてくれています。

画像変換後の「Zennとは?_scan_page_1.png」がAIへの入力に使われている

AI処理用の「LLM」ノードでは、「ビジョン」欄にプラグインが作成した画像ファイルを指定しています。こうすることで開発者側が画像からテキスト情報を取り出すことなく、AI自身のOCR機能を利用して読み取らせることが出来ています。

設定の下部に「(x)pdf変換ツール/(x)files Array[Files]」の形で設定している

使い方

マーケットからインストール出来るので以下の手順で簡単導入可能です。

  1. Difyクラウド版にログイン
  2. プラグインマーケットから pdf-to-images をインストール
  3. 以下画像やymlファイルを参考にワークフローを作成

    Githubの画像

フロー作成後に以下のファイルをインポートすることで、Githubの画像のようなノード配置を簡単にできますのでお試しください!
こちらは解決策で見せたものから一歩進んで、画像でもテキスト埋め込みPDFでもスキャンPDFでも、何が来てもAIが内容を読み取って処理できるフローになっています!
https://github.com/aToy0m0/dify-customplugin_pdf-to-images/blob/main/docs/pdf-to-images_common_en.yml

まとめ

  • PDFは「テキスト埋め込みPDF」と「スキャンPDF」に分かれる ※自分の勝手な呼称
  • テキスト埋め込みPDFは「テキスト抽出ノード」で簡単に処理可能
  • スキャンPDFは pdf-to-imagesプラグイン + LLMのvision機能 でOCR代替

これで、どんなPDFをアップロードしてもAIが内容を読み取ることができます!🥳 🙌

脚注
  1. https://docs.dify.ai/ja-jp/guides/workflow/file-upload#ファイルタイプ ↩︎

  2. https://docs.dify.ai/ja-jp/guides/workflow/node/doc-extractor#入力変数 ↩︎

Discussion