ドキュメント書いて自動的に検索システムに反映されたら素敵やん

恥ずかしながら Azure の Form Recognizer というのを今週初めて知ったのですが、流行りのGPTと組み合わせるともっと用途広がりそうですよね。
例えば、ドキュメント書いたら自動的にQAシステムに反映されるとテンション↑↑

ということで、試してみました。

# これできるんじゃない？

1. AzureのStorageなどに何らかの文章が書かれたファイルを置く
1. 1.をトリガーにFunctionsが動き、ファイルを Form Recognizer に投げる
1. Form Recognizer の結果を OpenAI の EmbeddingsのAPI に投げる
1. Embeddings の結果をDB（またはファイル）に書き出す
1. FAQシステム等で4.の結果を利用して検索結果を返す

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/8abfc8cd-bdc6-09ad-8c7e-20ec8df2c267.png)


これが理想的に動くとしたら、かなり幅広い業種で活用できるのではないでしょうか？
今回、実際にこの流れを構築したわけではないのですがそれぞれのステップを確認していきました。



# 対象データの選定

AI屋さんではないし、こういった作業はしたことなかったのですがいざやろうと思うとテストデータ探すので面倒ですね・・・
ChatGPTさんにお願いしてみたものの、コピペで増し増しにしろと言って作ってくれない・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/89837d72-abf9-40c5-80c9-06bfce2e4604.png)

色々調べた結果、個人情報保護委員会のFAQがPDFになっていたのでこれを使うことにしました。
（このコンテンツは出典の記載があれば自由に使って問題ないものです）
https://www.ppc.go.jp/personalinfo/faq/APPI_QA/

こんな内容です。
人の目で読むには良いですが、ここからテキストを抽出しようと思うと辟易します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/7a351ba8-9bfe-b9b3-048d-450e6b5c1884.png)


# Form Recognizer に投げる

調べたら色々情報あると思いますのでここで詳細は記述しませんが、Azure のサービスで、PDFやOfficeなどのファイルからテキストを抽出してくれるサービスです。
お試しは無料枠でいけます。

Azure上で Form Recognizer のリソースを作ると、 Form Recognizer Studio という機能へのリンクがあります。
Form Recognizer Studio を開くと、サンプルの画像が既にアップされた状態になっていますので、サンプルに対して「Analyze」を実行してみた結果が以下です。
文書からテキストが抽出されてますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/9abd6003-f1ff-64e2-ad85-9c6bfecc33f8.png)

右の方にある「Result」を選択すると、ダウンロードボタンがあるので、お試しであれば Form Recognizer Studio で良いと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/56ceeb47-6c9d-98b0-518b-753f39157ef5.png)


REST APIも提供されています。
https://westus.dev.cognitive.microsoft.com/docs/services/form-recognizer-api-2022-08-31/operations/AnalyzeDocument

prebuilt-document のモデルも試したいところなのですが日本語はサポートされてないので prebuilt-read で試します。
若干面倒なのは、Analyze の APIの応答では結果は返ってこなくて（202が返ってくる）、別URLに対してGETしに行く必要があります。

cURLで投げてみた結果はこんな感じです。
レスポンスで operation-location というヘッダが返ってくるので、この値に対してGETします。

````shell
// お試しなので1ページだけ指定してリクエスト投げる
curl -X POST -H "Content-Type: application/octet-stream" -H "Ocp-Apim-Subscription-Key: ここにKeyを書く" -F "file=@PDFへのパス" -D - "https://ここにEndpointを書く/formrecognizer/documentModels/prebuilt-read:analyze?api-version=2022-08-31&pages=34" 
````

````shell
HTTP/2 202 
content-length: 0
operation-location: https://指定したEndpointが入る/formrecognizer/documentModels/prebuilt-read/analyzeResults/リクエストIDが返ってくる?api-version=2022-08-31
x-envoy-upstream-service-time: 126
apim-request-id: リクエストIDが返ってくる
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-content-type-options: nosniff
x-ms-region: Japan West
date: Wed, 19 Apr 2023 14:35:11 GMT
````

結果を取得します。

````shell
curl -H "Ocp-Apim-Subscription-Key: ここにKeyを書く" -D - "https://ここにEndPointを書く/formrecognizer/documentModels/prebuilt-read/analyzeResults/リクエストIDを書く?api-version=2022-08-31"
````

実際に使うのは、応答の中にある paragraphs の部分かと思いますが、確認すると結果が格納されてます。
良い感じですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/e3a763a6-034f-f405-894d-6a9853281c09.png)


# Embeddingsする

（「Embeddingsする」という日本語で正しいですかね？）
取得したデータを paragraphs の単位で Embeddings していきます。
ただ、適当に拾ってきたPDFを投げただけなので、こんな感じでQとAがバラバラだし、変なところで文章が切れちゃってます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/433befd6-5604-68cb-08f0-c26bb2d42e66.png)

やっつけプログラムですが、QとA合わせて1個の文章となるように結果を結合させて Embeddings させます。
````javascript
      for (let i = 0; i < inputJson.analyzeResult.paragraphs.length; i++) {
        const text = inputJson.analyzeResult.paragraphs[i].content;

        if (text.indexOf("Ｑ") != -1) {
          if (detectQ) {
            texts.push(question + " " + answer);
          }
          detectQ = true;
          question = text;
          answer = "";
        } else if (detectQ) {
          answer += " " + text;
        }
        console.log(`finish load: ${i}`);
      }
````

Embeddingsに投げる部分自体の説明は割愛しますが、text-embedding-ada-002 を使ってベクトルを取得します。
結果はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/7d5532e0-7828-ca3a-6c62-7522a42f17ff.png)

# 検索システムから利用する

Embeddingsの結果をどこかしらに保存して検索に利用する。
検索の仕組みは以下です。

1. ユーザーからの入力テキストを Embeddings する
1. 事前に保存していおいた Embeddings の結果とコサイン類似度を計算して、類似度が近いものをピックアップする

パフォーマンスチューニングの工夫はあるかもしれませんが、コサイン類似度の計算はChatGPTさんに聞けば教えてくれるので割愛します。
結果はこんな感じ。
````shell
$ node cosin_similarity.js "カメラ画像は個人情報の対象か"
Cosine similarities: [
  {
    text: 'Ｑ１－13 防犯目的のために取得したカメラ画像やそこから得られた顔認証データをマ ーケティング等の商業目的に利用することを考えています。個人情報保護法との関係 で、どのような措置を講ずる必要がありますか。 当初防犯目的のために取得したカメラ画像やそこから得られた顔認証データを、 マーケティング等の商業目的のために利用する場合には、あらかじめ本人の同意を得る 必要があります（法第18条第１項）。 なお、当初から商業目的のためにカメラ画像や顔認証データを取得する場合について は、Ｑ１－12を参照のこと。  Ａ１－13 （令和３年９月更新） （個人情報）',
    similarity: 0.8761461623026948
  },
  {
    text: 'Ｑ１－31 ある人の犯罪行為を撮影した防犯カメラ映像は、要配慮個人情報に該当しま すか。  Ａ１－31 単に防犯カメラの映像等で、犯罪行為が疑われる映像が映ったのみでは、犯罪の 経歴にも刑事事件に関する手続が行われたことにも当たらないため、要配慮個人情報に 該当しません。',
    similarity: 0.8734243695007344
  },
  {
    text: 'Ｑ１－12 店舗等に防犯カメラを設置し、撮影したカメラ画像やそこから得られた顔認 証データを防犯目的で利用することを考えています。個人情報保護法との関係で、どの ような措置を講ずる必要がありますか。  Ａ１－12 本人を判別可能なカメラ画像やそこから得られた顔認証データを取り扱う場合、 個人情報の利用目的をできる限り特定し、当該利用目的の範囲内でカメラ画像や顔認証 データを利用しなければなりません。また、個人情報の利用目的をあらかじめ公表するか、 又は個人情報の取得後速やかに本人に通知若しくは公表する必要があります。 具体的には、店舗等に設置した防犯カメラによりカメラ画像を取得し、そこから顔認証 データを抽出してこれを防犯目的で利用する場合、本人においてかかる取扱いが行われ るとは合理的に予測・想定できないと考えられ、また、顔認証データはマーケティング等 他の目的にも利用され得る個人情報であることから、防犯のためにカメラ画像及び顔認 証技術を用いた顔認証データの取扱いが行われることを本人が予測・想定できるように 利用目的を特定し、これをあらかじめ公表又はその取得後速やかに通知・公表する必要が あると考えられます。 また、防犯カメラが作動中であることを店舗等の入口や設置場所等に掲示する等、防犯 カメラに
// 以下略
````

ChatGPTさんと組み合わせることもできるので、先ほどの検索結果をChatGPTさんに投げるとこんな感じ。

````javascript
 // プロンプト部分の実装抜粋
 // role: system
`please answer question from user in Japanese.
 related documents are selected from faq documents using embeddings and cosine similarity.
 please summarize.`,
 [
  // role: user
  `## user question
   ${inputText}

   ## related documents, it's sorted by similarity
   ${similarities.join(" ")}`,
 ]
````

> ユーザーの質問は、「カメラ画像は個人情報の対象か」というものです。関連する文書は、個人情報保護法における防犯カメラやビデオカメラでの撮影に関する指針や、カメラ画像を使用する際に講ずる必要がある措置についてのものです。文書では、個人情報として扱うためには、カメラ画像を取得する目的を特定し、また、防犯カメラで取得された顔認証データの利用は、事前に本人の同意を得る必要がある旨が述べられています。また、カメラ画像を体系的に構成して個人情報データベースに格納する場合は、適切な取扱いが必要であり、防犯カメラが作動中であることを周知する措置が講じられる必要があるとされています。


せっかくなので、LINE bot 風に画面を用意するとこんな感じ。
あえて文書に含まれていない「肉声」という単語を使ってみましたが、それっぽい回答が来ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201603/1a08ecdf-b7a1-0d33-b9e8-029c702e2feb.png)




# まとめ

なんとなくドキュメント作成 → 検索システムへの反映はできそうな雰囲気です。
Embeddingsは8Kトークンまでなので「良い感じ」の単位でベクトル化していく必要がありますし、最後にChatGPTで要約しようと思うとGPT-3.5だと4Kトークンまでなので苦しいですし、GPT-4なら上限上がってるのでトークン的には余裕ができますが、コスト的に苦しい・・・と、実際にプロダクト開発をする場合は色々工夫はいると思います。

ちなみに近い内容は公式にあるので、トリガーやFunctionsと組み合わせてこの流れを自動化することは可能と思います。

　[チュートリアル: Azure Functions と Python を使用して格納済みドキュメントを処理する](https://learn.microsoft.com/ja-jp/azure/applied-ai-services/form-recognizer/tutorial-azure-function?view=form-recog-3.0.0)