Vertex AI Searchと オセロアプリを連携してみた

この記事は[NTTコムウェア Advent Calendar 2023](https://qiita.com/advent-calendar/2023/nttcomware)の24日目の記事です。
## はじめに
みなさ～～～～ん！！！
**Vertex AI Search**、使ってますか！？

こんにちは。NTTコムウェア2年目の石川です。
みなさん、**Vertex AI Search**、使ってますか？

Vertex AI SearchとはGoogle Cloudが提供するマネージドサービスです。大規模言語モデル（LLM）に対し、情報を外部からインプットすることで、LLMが学習していない知識を回答させることができるアーキテクチャをRAG（Retrieval Augmented Model）と言いますが、Vertex AI Searchを使うとRAGを簡単に実現することができます。
以下にVertex AI Searchの簡単なイメージを示します。

![vertex_ai_search概要2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/858447e8-4a84-b188-fcbe-cddab688de61.png)

通常、RAGを実現させるためには「embedding」と呼ばれる、自然言語をベクトル化する工程や、ベクトル化した文章とインプットした文章を比較し類似した文章を検索する「ベクトル検索」が必要です。**Vertex AI Searchではこれらの要素技術が隠蔽されている**ので、簡単なRAG構築が可能になります。

私は最近業務でVertex AI Searchを少々触っており、今回は簡単なオセロのWebアプリケーションとVertex AI Searchを連携してみましたので、みなさんにVertex AI Searchの機能や使い方の一端をご紹介できればと思います。

## オセロアプリとVertex AI Searchの連携

ではやっていきましょう。
まずはReactで簡単なオセロのWebアプリケーションを用意します。オセロアプリは[こちらのGitHub](https://github.com/tjmtmmnk/react-othello/tree/main)を大変参考にさせていただきました。ありがとうございます。

現時点ではオセロアプリは以下のような画面になっています。2人のプレイヤーが交互に石を置いていくと、各色の数が右に表示される仕様です。リセットボタンを押すと最初の盤面に戻ります。
![ocello1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/484dc19f-9d50-b79c-6018-dc16600d8cc2.png)

次にVertex AI Searchのアプリケーションを作成します。使用するドキュメントはGCS（Google Cloud Storage）に保存しておき、アプリにインポートすることで使用可能になります。今回は[Wikipediaのオセロの記事](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%82%BB%E3%83%AD_(%E3%83%9C%E3%83%BC%E3%83%89%E3%82%B2%E3%83%BC%E3%83%A0))をPDF化し、読み込ませてあります。

さて、ここからどうオセロアプリに連携させるかですが、2つ方法があります。

### Widgetによるオセロアプリ連携
1つはWidgetを使う方法です。左のメニューから「統合」を選択し、さらに「WIDGET」タブをクリックします。認証タイプは「公開アクセス」を選択します。ドメインは、今回ローカルでしか実行しないので`localhost`と入力して保存しておきます。
![vertex_ai_search_widget1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/4bd05628-0557-335d-cf5b-dad2ea216af9.png)

次に、JavaScriptのコードスニペットが生成されるので、コピーしてオセロアプリの`index.html`の一番下に貼り付けます。
 ![vertex_ai_search_widget2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/78bdc4c1-4baa-5447-6803-4d27adaa3e35.png)
 
オセロアプリを実行してみると、以下のように画面の最下部に検索窓が表示されます。
![vertex_ai_search_widget3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/c5693b3f-0f53-749c-6400-e36f27a10c6a.png)

この検索窓をクリックし、適当に「オセロについて教えて」と入力してみると回答が返ってきました。[事前にインプットしたWikipedia](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%82%BB%E3%83%AD_(%E3%83%9C%E3%83%BC%E3%83%89%E3%82%B2%E3%83%BC%E3%83%A0))を確認すると、しっかりと回答がインプット文書から生成されていることがわかります。
![vertex_ai_search_widge4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/5e75e83d-5e76-b945-86df-f526aca9bbea.png)

このように、最低限で良いのであれば、たったこれだけでアプリとVertex AI Searchを連携することができます。
ただ、これだけでは柔軟性に欠けるかと思います。そこで、もう一つの連携方法も試してみましょう。

### APIを用いたオセロアプリ連携
もう一つの連携方法はAPIを叩くことです。「API」タブを選択し、検索クエリを入力すると、Cloud ShellでAPIを叩くためのcurlコマンドが自動生成されます。「CLOUD SHELLで実行」ボタンを押すとCloud Shellが起動するので、コピーしておいたcurlコマンドを実行します。
![vertex_ai_search_api1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/619bdc9f-1a38-ffc5-2c0f-8c41cc8f0f3d.png)


すると、以下のようにレスポンスが返ってきます。
```
{
  "summary": {
    "summaryText": "オセロは、1890年に...",
  ...

  "results": [
    {
      "id": "xxxxxxxxxxxxxxx",
      "document": {
        "name": "projects/xxxxxxxxx/locations/global/collections/default_collection/dataStores/xxxxxxxxxxx/branches/0/documents/xxxxxxxxxx",
        "id": "xxxxxxxxxxx",
        "derivedStructData": {
          "snippets": [
            {
              "snippet": "... \u003cb\u003eについて\u003c/b\u003eも特許権や...",
              "snippet_status": "SUCCESS"
            }
          ],
          "extractive_answers": [
            {
              "content": "ロ』に由来する[18]。緑の平原が...",
              "pageNumber": "13"
            }
          ],
          "title": "オセロ_(ボードゲーム)",
          "link": "gs://xxxx-bucket/オセロ_(ボードゲーム).pdf"
        }
      }
    },
    ...
  ]
}
```

### ReactからAPIを呼び出す
しかしながら、勿論オセロアプリと連携させるのであれば、curlではなくReactからAPIを呼び出すことになると思いますので、今回はこの方法をご紹介します。

#### Googleのサービスアカウント作成
まずGoogleのサービスアカウントを作成する必要があります。今回は[公式ドキュメント](https://developers.google.com/identity/protocols/oauth2/service-account?hl=ja#creatinganaccount)を参考に作成しました。途中、IAMロールを指定する必要がありますが、「AI Platform サービス エージェント」と「Vertex AI サービス エージェント」の2つを指定しました。どのロールを指定すればよいかは、先ほど立ち上げたCloud Shellで、組織の IAM Policy 情報を取得する以下のコマンドを実行すれば確認できるかと思います。（筆者はここで数時間躓きました）
`gcloud projects get-iam-policy [Project ID or Number]`

割り当てたIAMロールは以下のように、「権限」タブで確認できます。
![vertex_ai_role.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/9ec6e2fb-fb2f-9171-de9f-72e040552de6.png)

#### アクセストークンの取得
次にアクセストークンを取得します。上の手順の最後で保存した秘密鍵を用いて署名済みJWTを作り、Google OAuth2.0 AuthorizationServerへリクエストを送信します。
（この手順は[こちらの記事](https://qiita.com/________________-_/items/838750d32ca3e2fe2b75)を参考にさせていただきました）
詳しい手順は上記URLを参考にしていただければと思いますが、手順のうちJWTの作成と署名は手動で実施するうえ、認証アサーションの有効期限を1時間以内に設定する必要があります。いちいち1時間ごとに手動でポチポチ生成するのは現実的ではないので、実際に導入するのであれば自動生成できるように工夫しましょう。（今回は~~めんどくさい~~時間がないのでやりませんでした）

署名済JWTが作成できたら、以下のパラメータをbodyに含めて`https://oauth2.googleapis.com/token`
にPOSTリクエストを送り、アクセストークンを取得します。
|  パラメータ名  |  値  |
| ---- | ---- |
|  `assertion`  |  署名済JWT  |
|  `grant_type`  |  `urn:ietf:params:oauth:grant-type:jwt-bearer`  |	

#### Vertex AI Search APIを実行
アクセストークンが取得出来たら、いよいよVertex AI SearchのAPIを呼び出します。今回はJavaScriptのaxiosを使ったので、axiosを使った場合のコード例を載せておきます。

```
  const response = await axios.post(
    "https://discoveryengine.googleapis.com/v1alpha/projects/xxxxxxxxx/locations/global/collections/default_collection/dataStores/xxxxxxxxxxx/servingConfigs/default_search:search",
    {
      query: message,
      pageSize: 10,
      queryExpansionSpec: { condition: "AUTO" },
      spellCorrectionSpec: { mode: "AUTO" },
      contentSearchSpec: {
        summarySpec: {
          summaryResultCount: 5,
          ignoreAdversarialQuery: true,
          includeCitations: true,
        },
        snippetSpec: { returnSnippet: true },
        extractiveContentSpec: { maxExtractiveAnswerCount: 1 },
      },
    },
    {
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
    }
  );
```

一部マスキングしてしまっていますが、Vertex AI SearchのAPI画面で自動生成されるcurlコマンドを確認すれば、リクエスト先のURIやbodyの値が確認できるかと思います。

### オセロアプリとVertex AI Search連携結果
これでようやくオセロアプリからVertex AI SearchのAPIを呼び出す準備ができました！

以下が実際に連携させてみた結果です。
![ocello3_pettern3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/01c68ce6-6cb5-91a2-368f-1c5dfb298214.png)
 
質問文を入力して黄色いボタンを押すと、Vertex AI Searchから答えが返ってきます。オセロのWikipediaを確認していただければ分かりますが、しっかりとインプットした文書から答えが生成されていることがわかるかと思います。
![ocello4_pettern3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/0f05c28e-da36-d579-28db-6622edd55353.png)

### 精度
上述の通り、答えはインプット文書から生成されていますが、正確とは限りません。例えば、今回の「オセロの歴史を教えてください」という質問に対し、「1974年に水戸で自身のゲームであるオセロを発売した」と書かれていますが、実際にはオセロの発売日は1973年であり、水戸で発売したとはインプット文書に書かれていません。
(もちろん質問の表現を変えれば正答する場合もあります)

また、表から回答させる場合も、正答したり誤答したりとまちまちです。

表から正確に回答を生成した例
![ocello_正答例_pettern3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/0aa53074-c824-d87c-e366-3a33002af860.png)

表から回答させる質問を誤答した例
![ocello_誤答例_pettern3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622162/cfda3878-e123-d816-e4b3-8255566d2ed2.png)

（正誤は[Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%82%BB%E3%83%AD_(%E3%83%9C%E3%83%BC%E3%83%89%E3%82%B2%E3%83%BC%E3%83%A0))でご確認ください）

このように回答の精度は全て信用できるものではないので、インプット文書に対し前処理を施すなどの工夫が必要です。

## 終わりに
皆様お気づきかと思いますが、今回実装したオセロアプリは単にウィジェットを組み込んだ場合とほとんど機能は変わりません・・・。本当は盤面の状態をうまく自然言語に変換し、検索クエリに投げることで、次の一手のアドバイスができるオセロアプリを開発したかったのですが、時間がありませんでした。悔しいので次の機会にチャレンジして、オセロの「神の一手」を極めていきたいと思います。

この記事がこれからVertex AI Searchを始める方々のお力に少しでもなれたら幸いです。
引き続き[NTTコムウェアのアドベントカレンダー](https://qiita.com/advent-calendar/2023/nttcomware)をお楽しみください！

※記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。