2024年1月15日月曜日

Google Geminiに動画について説明してもらう

Google GeminiのAPIを呼び出して、動画について説明してもらいます。説明してもらう動画ファイルはGoogle Cloud Storageにアップロードし、 そのURIをAPIリクエストのfileDatafileUriとして指定します。APIリクエストにファイルをそのまま埋め込む場合は、inlineDataとしてbase64でエンコーディングした文字列を指定できますが、APIリクエストの最大サイズに制限されます。

Gemini APIでGoogle Cloud Storage上のファイルを扱うには、Vertex AIのGemini APIを呼び出す必要があります。APIの認証はAPIキーではなく、サービス・アカウントによる認証を使います。

以下のGIF動画では、キリンの動画をGoogle Cloud Storageにアップロードし、動画の説明をお願いしています。


以下は静止画ですが、動画ではもぐもぐ草を食べています。


作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/google-vertex-gemini.zip

今回の作業で追加、変更した機能は以下になります。
  1. APIの呼び出しをGoogle AIからVertex AIに変更しています。
  2. Google Cloud Storageの操作画面を追加しています。操作画面の作成方法は、記事「Google Cloud StorageにCloud Storage JSON APIでアクセスする」にて紹介しています。
  3. Gemini APIの呼び出しにあたって、Google Cloud Storage上のファイルをリクエストに含めることができるページを作成しています。
  4. ベクトル埋め込み(embedding)の生成に、Vertex AIのモデルmultimodalembeddingを使うように変更しました。
Google Gemini APIのAPIエンドポイントは以下です。(モデルがGemini Proの場合)
https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent

Vertex AI Gemini APIのAPIエンドポイントは以下です。(同等のモデルの場合)
https://{REGION}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{REGION}/publishers/google/models/{MODEL_ID}:streamGenerateContent

Vertex AIではgenerateContentではなくstreamGenerateContentを呼び出します。送信するJSONのリクエストの仕様には差異は無さそうですが、streamGenerateContentでは単一の応答の代わりに応答の配列が返されます。

Googleの以下のドキュメントでは、属性candidatesを含みJSONドキュメントが応答として記載されています。

実際には属性candidatesを含むJSONオブジェクトの配列が返されます。
[{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "* **成人式とは?**\n\n 成人式とは、毎年1月1"
          }
        ]
      },
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE"
        }
      ]
    }
  ]
}
,
{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "5日に行われる日本の国民的行事で、20歳になった男女を祝"
          }
        ]
      },
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE"
        }
      ]
    }
  ]
}
,
/* この繰り返しなので省略します。 */
{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "、日本の伝統的な衣装であり、成人式に振袖を着ることで、成人になったことを祝う意味があります。振袖は、未婚女性の礼装であり、成人式だけでなく、結婚式や卒業式などにも着られます。\n\n\n* **成人式の過ごし方**\n\n 成人式は、20歳になったことを祝うため、友人や家族と食事をしたり、旅行に出かけたりして過ごす人が"
          }
        ]
      },
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE"
        }
      ]
    }
  ]
}
,
{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "多いです。また、成人式の前には、成人式の記念写真を撮影したり、成人式の準備をしたりして、成人式を祝う準備をします。"
          }
        ]
      },
      "finishReason": "STOP",
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE"
        }
      ]
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 8,
    "candidatesTokenCount": 373,
    "totalTokenCount": 381
  }
}
]
Googleから提供されているSDKを使ってGemini APIを呼び出していると、低レベルの動作を気にする必要はないのですが、PL/SQLではそうも行きません。GoogleのAPIのドキュメントには、レスポンスのフォーマットについて詳細が記載されていないため、今回の実装ではレスポンスの属性candidatesの配列の要素は1つだけ、属性partsの配列の要素も1つだけ、そして属性textも1つだけ含まれる、という前提でAPIのレスポンスを処理しています。

Gemini APIを呼び出すパッケージをUTL_GOOGLE_GEMINI_APIとして作成しています。これを少し変更してVertex AIのGemini APIを呼び出すようにしたパッケージUTL_VERTEX_AI_GEMINI_APIを作成しました。コードは記事の末尾に添付しています。

APIのエンドポイントをVertex AIに変更し、上記のレスポンスの扱いも変更しているため、同名のプロシージャやファンクションでも引数は異なっています。

以下より、動画のfileUriをAPIのリクエストに含めて、マルチモーダルの問い合わせを行なうページを作成します。

APIエンドポイントを決める際にプロジェクトIDリージョンの指定が必要になります。そのため、アプリケーション定義置換置換文字列としてG_PROJECT_IDG_REGIONを作成し、値を設定します。


最初にGoogle Cloud Storage上のファイルを選択する際に使用する、共有コンポーネントLOVを作成します。


作成済みのLOVが一覧されます。作成をクリックします。


LOVの作成最初からです。

へ進みます。


名前Google Cloud Storage Filesとします。タイプDynamicを選択します。

へ進みます。


データ・ソースとしてRESTデータ・ソースを選択します。RESTデータ・ソースGoogle Cloud Storage JSON APIを選択します。このデータ・ソースは、Google Cloud StorageのファイルをアクセスするページをAPEXアプリケーションに追加する際に作成しています。

へ進みます。


戻り列表示列ともにNAMEを選択します。

作成をクリックします。


LOVとしてGOOGLE CLOUD STORAGE FILESが作成されます。

編集するために、リンクをクリックします。


RESTソース・パラメータbucket鉛筆アイコンをクリックし、bucketのを指定します。


値タイプ静的静的値として&G_BUCKET_NAME.を設定します。

変更の適用をクリックします。


追加表示列列の選択をクリックします。

NAMEに加えて列CONTENTTYPEも表示されるようにします。


CONTENTTYPE(Varchar2)を選択し、更新をクリックします。


追加表示列としてCONTENTTYPEが追加されます。

NAMEは最初から表示列となっていますが、列NAME戻り列として設定されているため、ヘッダーに値がありません。また表示可能などもNoとなっています。

NAMEヘッダーNameを設定し、表示可能および検索可能Yesに変更します。

変更の適用をクリックします。


以上でLOVの作成は完了です。

Gemini APIを呼び出すページは、ページ番号Imageのページをコピーして作成します。

ページの作成をクリックします。


コピーとしてのページの作成をクリックします。


次のコピーとしてのページを作成として、このアプリケーションのページを選択します。

へ進みます。


コピー元ページとして3. Imageを選択します。新規ページ番号10新規ページ名Movieとします。

ページにブレッドクラムを作るため、ブレッドクラムを選択します。親エントリなしエントリ名Movieとします。

へ進みます。


ナビゲーションのプリファレンスとして新規ナビゲーション・メニュー・エントリの作成を選びます。

新規ナビゲーション・メニュー・エントリMovie親ナビゲーション・メニュー・エントリとして- 親が選択されていません -を選択します。

へ進みます。


ラベルImageMovieに変更します。

コピーをクリックします。


ページがコピーされます。

コンテント・タイプを保持するページ・アイテムを作成します。

識別名前P10_CONTENT_TYPEタイプテキスト・フィールドラベルContent Typeとします。


デバッグのためにAPIのレスポンスをそのまま表示するページ・アイテムを作成します。

識別名前P10_RESPONSE_RAWタイプテキスト領域ラベルはResponse Rawとします。

大きなデータも扱えるように、セッション・ステートデータ型CLOBを選択します。


ページ・アイテムP10_IMAGEを選択し、ローカルのコンピュータ上のファイルを選択するページ・アイテムから、Google Cloud Storage上のファイルを選択するページ・アイテムに変更します。

識別名前P10_MOVIEに変更します。タイプポップアップLOVに変更します。ラベルMovieに変更済みです。

追加出力としてCONTENTTYPE:P10_CONTENT_TYPEを設定します。APIリクエストのfileDataは複数(最大16個まで)にすることができますが、今回の実装では1つだけにしています。複数の値オンにすると追加出力ができなくなるため、複数のファイルを選択するには列CONTENT_TYPEの値を取る追加の実装が必要になります。

LOVタイプ共有コンポーネントを選択し、LOVとしてGOOGLE CLOUD STORAGE FILESを選択します。追加値の表示オフNULL表示値として- ファイルを選択 -を設定します。

セッション・ステートストレージとしてリクエストごと(メモリーのみ)を選択します。理由は不明ですが、セッションごと(永続)の場合エラーが発生しました


左ペインでプロセス・ビューを開き、プロセス画像を含む呼び出しを選択します。

名前動画を含む呼び出しに変更し、ソースPL/SQLコードも以下に入れ替えます。

保存をクリックします。


以上でアプリケーションは完成です。アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。

Googleのドキュメント「マルチモーダル プロンプト リクエストを送信する」を参照すると、色々と考慮しなければいけないことが書かれています。
https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/send-multimodal-prompts

Oracle APEXのアプリケーション作成の参考になれば幸いです。


パッケージUTL_VERTEX_AI_GEMINI_APIのコード: