1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elastic Stack (Elasticsearch) Advent Calendar 2023Advent Calendar 2023

Day 15

Connectorを使ってWindowsにあるファイルをElasticsearchにインデックスする

Last updated at Posted at 2023-12-15

はじめに

生成AIの使い方の一つであるRAG(Retrieval Augmented Generation)について以下の記事を先日書きました。
https://qiita.com/takeo-furukubo/items/e5d43fa734e4338b895f
この利用方法では実はまずファイルにあるテキストデータを抽出してElasticsearchにインデックスする必要があります。
本記事では実際にWindowsにあるファイルからテキストを抽出してElasticsearchにインデックスを行います。

Connector

Connectorというのはユーザが検索したい元データ(PDF,Text File,Office系ドキュメントなど)からテキストを抽出して、Elasticsearchで全文検索できるようにするものです。
生成AIの使い方の一つであるRAG(Retrieval Augmented Generation)の初手になる部分です。

Elasticsearchにはテキストを入れますので、様々なファイルからテキストを抽出してから、属性情報を追加してインデックス化します。
スクリーンショット 2023-12-11 13.33.51.png
ここでConnectorには2種類あります。

  • Native Connector
    • Elastic側で管理していて、設定だけ入力するとそのまま利用可能
  • Connector clients
    • ユーザ側で運用・管理するもの

SharepointやGoogle Driveなどクラウド上にあるデータであればNative Connectorが便利ですが、オンプレミスにあるデータの場合は外部からのアクセスは難しいので、Connector clientsを使います。

今回はオンプレミスのWindowsからConnector clientsを使ってテキスト抽出する方法の解説です。

環境

Windows 10 Pro / 21H2 / 19044.2130
Docker Desktop for windows: v24.0.6
Elasticsearch: 8.11.1 (Elastic Cloud)

データを集めるConnectorをコンテナ上で動かします。WindowsにDocker Desktopをインストールしておいてください。

Elastic Cloud

(既にある場合は必要ありません)
こちらのブログをベースにElastic Cloudにクラスタを展開します。
https://qiita.com/tomo_s_el/items/3584d0b1fabb0bafa4fa

Connector clients設定

ドキュメントはこちらです。
https://www.elastic.co/guide/en/enterprise-search/current/connectors-network-drive.html#connectors-network-drive-connector-client-reference

KibanaメニューからSearch -> Contentと選び、右上のCreatea new indexをクリック
スクリーンショット 2023-12-11 17.04.32.png
次にUse a connectorをクリックします。因みにデータの抽出ツールは今回ご紹介するConnectorも入れて3種類用意しています。他の2つはこちらです。

  • Crawler
    • Web Siteからデータを引っ張ってくる
  • API
    • APIでデータを持ってくる

スクリーンショット 2023-12-11 17.07.03.png
次の画面でNetwork Driveを選択して、右下のContinueをクリック
因みにNetwork DriveはSMB v2/v3のみです。
スクリーンショット 2023-12-11 17.22.45.png
次の画面ではインデックスの名前を入力します。search-が必ず頭に付きます。以下の例だとsearch-windows-driveという名前のインデックスが作成されます。入力したら右下のCreate Indexをクリック。
Language Analyzerは日本語を選んでも形態素解析は行われないので、後ほど設定します。
スクリーンショット 2023-12-11 17.26.57.png

次の画面では右側にあるConvert connectorをクリックします。デフォルトではNative Connectorとして扱われるためです。
このような脅しが出てきますが、インデックス毎に変更できるので気にせずYesを押しましょう。
スクリーンショット 2023-12-11 17.33.09.png

最後に設定を入れていきます。
まずはGenerate API KeyをクリックするとAPI Keyが表示されるので、コピーして何処かに保存しておきましょう。この画面から移ると表示されなくなるので、保存していなかった場合は再作成する必要があります。
次にWindowsの認証情報を入れます。
Username,password,IP address,Pathを入力します。Portは必要に応じて変更してください。
最後にEnable document level securityをチェックしてSave configurationをクリックします。
スクリーンショット 2023-12-11 18.01.30.png

次にWindows上で動くConnector clientsを設定していきます。Deploy connectorの下にあるconnectors:から始まる部分をコピーしておきます。
ですが、その前に日本語対応の設定を行います。

日本語(形態素解析)設定

インデックスclose

日本語の全文検索には特別な処理が必要です。詳細は下記リンクを参照して下さい。
https://www.elastic.co/jp/blog/how-to-implement-japanese-full-text-search-in-elasticsearch
settingとmappingを変更する必要がありますが、インデックスを開いた状態では変更できませんので、まず一旦インデックスをcloseします。
Management->Dev Toolsと開いて、以下を入力して実行します。
POST search-windows-drive/_close
赤枠で囲った三角をクリックすると実行できます。右側に以下のように表示されると無事close出来ています。
スクリーンショット 2023-12-14 10.15.15.png

settings/mappings変更

まず形態素解析を指定しているsettingsを変更します。この設定を入れることで形態素解析としてkuromojiを使う設定になります。
右側の画面にerrorが出ていないことを確認してください。

PUT search-windows-drive/_settings
{
  "settings": {
    "index": {
      "analysis": {
        "char_filter": {
          "normalize": {
            "type": "icu_normalizer",
            "name": "nfkc",
            "mode": "compose"
          }
        },
        "tokenizer": {
          "ja_kuromoji_tokenizer": {
            "mode": "search",
            "type": "kuromoji_tokenizer"
          }
        },
        "analyzer": {
          "kuromoji_analyzer": {
            "tokenizer": "ja_kuromoji_tokenizer",
            "char_filter": ["normalize"],
            "filter": [
              "kuromoji_baseform",
              "kuromoji_part_of_speech",
              "cjk_width",
              "ja_stop",
              "kuromoji_stemmer",
              "lowercase"
            ]
          }
        }
      }
    }
  }
}

次にどのフィールドに対して適用するかをmappingで決めます。長いですが、以下をDev Toolsで実行します。

PUT search-windows-drive/_mapping
{
    "dynamic": "true",
    "dynamic_templates": [
      {
        "data": {
          "match_mapping_type": "string",
          "mapping": {
            "analyzer": "iq_text_base",
            "fields": {
              "prefix": {
                "search_analyzer": "q_prefix",
                "analyzer": "i_prefix",
                "type": "text",
                "index_options": "docs"
              },
              "delimiter": {
                "analyzer": "iq_text_delimiter",
                "type": "text",
                "index_options": "freqs"
              },
              "joined": {
                "search_analyzer": "q_text_bigram",
                "analyzer": "i_text_bigram",
                "type": "text",
                "index_options": "freqs"
              },
              "enum": {
                "ignore_above": 2048,
                "type": "keyword"
              },
              "stem": {
                "analyzer": "iq_text_stem",
                "type": "text"
              }
            },
            "index_options": "freqs",
            "type": "text"
          }
        }
      }
    ],
    "properties": {
      "_allow_access_control": {
        "type": "text",
        "fields": {
          "delimiter": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "iq_text_delimiter"
          },
          "enum": {
            "type": "keyword",
            "ignore_above": 2048
          },
          "joined": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "i_text_bigram",
            "search_analyzer": "q_text_bigram"
          },
          "prefix": {
            "type": "text",
            "index_options": "docs",
            "analyzer": "i_prefix",
            "search_analyzer": "q_prefix"
          },
          "stem": {
            "type": "text",
            "analyzer": "iq_text_stem"
          }
        },
        "index_options": "freqs",
        "analyzer": "iq_text_base"
      },
      "_subextracted_as_of": {
        "type": "date"
      },
      "_subextracted_version": {
        "type": "keyword"
      },
      "_timestamp": {
        "type": "date"
      },
      "body": {
        "type": "text",
        "fields": {
          "delimiter": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "iq_text_delimiter"
          },
          "enum": {
            "type": "keyword",
            "ignore_above": 2048
          },
          "joined": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "i_text_bigram",
            "search_analyzer": "q_text_bigram"
          },
          "prefix": {
            "type": "text",
            "index_options": "docs",
            "analyzer": "i_prefix",
            "search_analyzer": "q_prefix"
          },
          "stem": {
            "type": "text",
            "analyzer": "iq_text_stem"
          },
          "japanese": {
            "type": "text",
            "analyzer": "kuromoji_analyzer"
          }
        },
        "index_options": "freqs",
        "analyzer": "iq_text_base"
      },
      "created_at": {
        "type": "date"
      },
      "id": {
        "type": "keyword"
      },
      "path": {
        "type": "text",
        "fields": {
          "delimiter": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "iq_text_delimiter"
          },
          "enum": {
            "type": "keyword",
            "ignore_above": 2048
          },
          "joined": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "i_text_bigram",
            "search_analyzer": "q_text_bigram"
          },
          "prefix": {
            "type": "text",
            "index_options": "docs",
            "analyzer": "i_prefix",
            "search_analyzer": "q_prefix"
          },
          "stem": {
            "type": "text",
            "analyzer": "iq_text_stem"
          }
        },
        "index_options": "freqs",
        "analyzer": "iq_text_base"
      },
      "size": {
        "type": "long"
      },
      "title": {
        "type": "text",
        "fields": {
          "delimiter": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "iq_text_delimiter"
          },
          "enum": {
            "type": "keyword",
            "ignore_above": 2048
          },
          "joined": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "i_text_bigram",
            "search_analyzer": "q_text_bigram"
          },
          "prefix": {
            "type": "text",
            "index_options": "docs",
            "analyzer": "i_prefix",
            "search_analyzer": "q_prefix"
          },
          "stem": {
            "type": "text",
            "analyzer": "iq_text_stem"
          },
          "japanese": {
            "type": "text",
            "analyzer": "kuromoji_analyzer"
          }
        },
        "index_options": "freqs",
        "analyzer": "iq_text_base"
      },
      "type": {
        "type": "text",
        "fields": {
          "delimiter": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "iq_text_delimiter"
          },
          "enum": {
            "type": "keyword",
            "ignore_above": 2048
          },
          "joined": {
            "type": "text",
            "index_options": "freqs",
            "analyzer": "i_text_bigram",
            "search_analyzer": "q_text_bigram"
          },
          "prefix": {
            "type": "text",
            "index_options": "docs",
            "analyzer": "i_prefix",
            "search_analyzer": "q_prefix"
          },
          "stem": {
            "type": "text",
            "analyzer": "iq_text_stem"
          }
        },
        "index_options": "freqs",
        "analyzer": "iq_text_base"
      }
    }
}

titlebodyに以下の部分が追加されています。これにより、title.japanesebody.japaneseに対して検索をかけると形態素解析されたものになります。

  "japanese": {
    "type": "text",
    "analyzer": "kuromoji_analyzer"
  }

設定が正しく入っているかどうかは以下をDev Toolsで実行すると確認できます。

GET search-windows-drive/_settings
GET search-windows-drive/_mapping

問題なければ以下をDev Toolsで実行してインデックスを再度開きます。

POST search-windows-drive/_open

Connector clientを立ち上げる

config fileの作成

Windowsマシン上でどこか適当な作業ディレクトリを作ります。

mkdir connectors-config
curl https://raw.githubusercontent.com/elastic/connectors/main/config.yml --output ./connectors-config/config.yml

と実行します。config.ymlが保存されるので、notepadか何かで開きます。
先程保存した内容をconfig.ymlに貼り付けます。
スクリーンショット 2023-12-11 18.20.01.png

Docker(Connector client)起動

Windows上で以下のコマンドを実行
C:/Users/elastic/source/connectors/connectors-configの部分は環境に合わせて変更してください。

2024/08/02更新
imageは公式のリポジトリから確認してください
https://www.docker.elastic.co/r/enterprise-search/elastic-connectors

docker network create elastic
docker run -v C:/Users/elastic/source/connectors/connectors-config:/config --network "elastic" --tty --rm docker.elastic.co/enterprise-search/elastic-connectors:<version> /app/bin/elastic-ingest -c /config/config.yml

エラーなどが出ていないことを確認してください。

同期

connector clientが待機状態になっているので、同期を開始します。
Search->Contentから先程作ったインデックス(search-windows-drive)を選択し、Configurationタブをクリックして設定画面に戻ります。
スクリーンショット 2023-12-14 13.26.36.png
一番下のSyncをクリックしてFull Contentを選んでしばらく待ちます。
Dockerを実行した画面に以下のような出力があればデータの同期ができています。

[FMWK][15:18:52][INFO] [Connector id: kGe9Y4wBBgiEiYrEOH-5, index name: search-windows-drive, Sync job id: w2fCY4wBBgiEiYrEKX_i] Sync progress -- created: 400 | updated: 0 | deleted: 0
[FMWK][15:18:57][INFO] [Connector id: kGe9Y4wBBgiEiYrEOH-5, index name: search-windows-drive, Sync job id: w2fCY4wBBgiEiYrEKX_i] Sync ended with status completed -- created: 409 | updated: 0 | deleted: 0 (took 17 seconds)

同期スケジュール

今回は手動で同期をしていますが、通常は一定間隔で自動的に行うと思います。
こちらの画面でその設定を行うことが出来ます。
スクリーンショット 2023-12-15 14.49.06.png

同期ルール

フォルダを除外したり、拡張子で取り込むファイルの種類を決めたりすることができます。
ドキュメントはこちらです。
https://www.elastic.co/guide/en/enterprise-search/current/sync-rules.html
スクリーンショット 2023-12-15 14.57.35.png

制限

10MB以上のファイルはそのままではインデックス出来ません。
テキスト抽出を行うコンテナを別途立てる必要があります。
ドキュメントはこちらです。
https://www.elastic.co/guide/en/enterprise-search/current/connectors-content-extraction.html

ドキュメントの確認

先程の画面でDocumentsタブをクリックするとインデックスされたドキュメントが表示されます。
スクリーンショット 2023-12-14 14.06.23.png

アクセス権限情報

ドキュメントの右上にあるところ(赤枠で囲ったところ)をクリックすると全てのフィールドが表示されます。
_allow_access_controlというフィールドがそれです。WindowsのSIDが入っているのがわかるかと思います。
スクリーンショット 2023-12-15 17.44.09.png

Document Level Security

Windowsサーバの場合、権限情報を別のインデックスに保存することで簡単にドキュメントレベルでアクセス制限をかけることが出来ます。
ConfigurationタブのSyncをクリックして、今度はAccess Controlをクリックします。
無事に同期が終わったら情報を確認してみます。
Documentsタブに移動して、Browse Documentsの横にあるPull DownをクリックしてAccess control indexを選びます。
下図のように情報が入っていて、queryが入っているのは実際に検索する際にこのqueryを使ってアクセス権限を確認するためです。このあたりはまた別のブログで紹介します。
Document Level Securityのドキュメントはこちらです。
https://www.elastic.co/guide/en/enterprise-search/current/dls-e2e-guide.html
スクリーンショット 2023-12-14 14.29.49.png

Discoverでデータを確認する

おなじみのDiscoverでもデータを見てみます。
Stack Management->Index Managementから先ほど作成したsearch-windows-driveをクリックします。
スクリーンショット 2023-12-15 9.30.44.png
次の画面でDiscover indexをクリックするとDiscoverのページに飛びます。
スクリーンショット 2023-12-15 9.32.21.png

Discoverのページでbodytitleをクリックしてみましょう。
スクリーンショット 2023-12-15 9.55.11.png
kuromoji.analyzer用に追加したbody.japanese,title.japaneseがあるのが確認できます。

形態素解析

ファイルの用意

ではごく簡単に形態素解析の効果を確認してみます。
まずは東京都とだけ書いたテキストファイルをWindowsの同期できる場所に置きます。そしてSearch->Content->search-windows-drive->Configurationで設定画面を開いて一番下にあるSyncをクリックしてFull Contentを選んで同期待ちにしておきます。
コンテナが立ち上がったままであれば、自動的に同期されてDiscoverに表示されるはずです。

検索してみる

Discoverの検索ボックスに東京と入れてみると先程の文章が表示されますね。当然です。
では次に京都と入れてみてください。表示されてしまいましたね。
理由は東京都の後ろの「京都」に引っかかっているからです。
これは望んだ結果ではないはずです。

では次にbody.japanese:京都と入力してみてください。表示されませんね。
body.japaneseでは「東京都」は「東京|都」と分割されているので検索ではヒットしません。

スクリーンショット 2023-12-15 14.43.54.png

ただ辞書ベースの分割のみに頼ると新しい言葉に対応できなくなります。
このあたりは様々な知見が必要となりますので、是非弊社コンサルティングサービスをご利用ください。

まとめ

Connectorを使うと非常に簡単に色んな場所に散らばっている文書類をElasticsearchに取り込むことが出来ます。
ドキュメントの権限も設定することが出来るので、人事の書類を一般社員が見るなどの事故も起こりません。

全ての資料を一箇所に綺麗にまとめられているケースは少ないと思います。是非ElasticsearchのConnectorを使って社内文書の横断検索システムを構築して、従業員の生産性向上に役立ててみてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?