Google Gemini Pro Visionを呼び出すために、以前の記事「Google Geminiを呼び出すAPEXアプリケーションを作る」で紹介しているパッケージUTL_GOOGLE_GEMINI_APIを使っています。Google AI Studioから取得したAPIキーをWeb資格証明として使っています。Google Geminiの扱いについてはすでにパッケージに実装済みであるため、本記事の内容には含まれていません。
本記事では主にSPA(Single Page Application)風のAPEXアプリケーションの作り方を紹介します。
作成したAPEXアプリケーションは以下のように動作します。
最初にAPEXのアプリからアップロードされる写真データを受け取って、Gemini Pro Visionを呼び出すORDSのRESTサービスを作成します。
モジュールとテンプレートを作成し、以下のコードをソースとしたPOSTメソッドのハンドラを作成します。今回の記事ではモジュール名はGoogle Gemini Pro Vision、モジュール・ベース・パスは/gemini-pro-vision/、テンプレートはanimalとしています。完全なURLはあらかじめ記録しておきます。後で、APEXアプリケーションの置換文字列のG_REQUEST_URLの置換値として設定します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
l_response clob; | |
l_candidates json_array_t; | |
l_prompt_feedback json_object_t; | |
l_role varchar2(8); | |
l_prompt clob; | |
begin | |
l_prompt := 'この画像に写っている動物の名前と生息地および生態を教えてください。'; | |
utl_google_gemini_api.generate_content( | |
p_text => l_prompt | |
,p_image => :body | |
,p_mimetype => :content_type | |
,p_candidates => l_candidates | |
,p_prompt_feedback => l_prompt_feedback | |
,p_response => l_response | |
,p_credential_static_id => 'GOOGLE_GEMINI_API_KEY' | |
,p_transfer_timeout => 60 | |
); | |
:status := 200; | |
htp.p(utl_google_gemini_api.get_first_text( | |
p_candidates => l_candidates | |
,p_role => l_role | |
)); | |
exception | |
when others then | |
htp.p(l_response); | |
:status := 400; | |
end; |
ORDSのRESTサービスはAPEXのセッションで保護します。
ロールとしてRESTful Services、モジュールとしてGoogle Gemini Pro Visionを割り当てた権限を作成します。
以上でRESTサービスの準備はできました。
アプリケーション作成ウィザードを起動します。作成するアプリケーションの名前はこの動物は何?としています。Google Gemini Pro Visionを呼び出す際にプロンプトとして「この画像に写っている動物の名前と生息地および生態を教えてください。」を与えていますが、プロンプトはどのように変更してもよいので、プロンプトに合わせてアプリの名前を変えても良いでしょう。
スマホにインストールして使用することを想定しているため、機能のプログレッシブWebアプリケーションのインストールはチェックします。
アプリケーションが作成されます。すべての機能はホーム・ページに実装します。
アプリケーション定義の置換を開き、置換文字列G_REQUEST_URLの置換値として、ORDSのRESTサービスの完全なURLを設定します。APEXアプリケーションとORDSのRESTサービスは同じワークスペースに実装されていることが前提なので(そうでないとAPEXセッションで認証できない)、プロトコルやホストを除いて設定します。
ページ・デザイナでホーム・ページを開きます。
ブレッドクラムはいらないので削除します。
外観のテンプレートとしてBlank with Attributes (No Grid)を選択し、CSSクラスとしてu-flex u-justify-content-centerを設定します。このCSSクラスはAPEXのUniversal Themeにより定義されています。説明は、Universal ThemeのLayout Modifiersのリファレンスを参照してください。
Gemini Pro Visionの応答を表示するページ・アイテムも画面中央に配置するため、同じ設定で静的コンテンツのリージョンを作成します。タイトルはResponseとします。
ボタンSUBMITを作成します。
ラベルは問い合わせるとします。外観のホットをオンにし、CSSクラスにはbutton-submitを設定します。このCSSクラスは後ほど定義し、問い合わせボタンを画面下部に固定します。
動作のアクションは動的アクションで定義を選択します。SPA風のアプリを作成するときは、ほとんどの処理を動的アクションとJavaScriptで実装することになります。
リージョンResponseの下に、Google Gemini Pro Visionの応答を保持するページ・アイテムP1_RESPONSEを作成します。タイプにリッチ・テキスト・エディタを選びます。
テンプレートにhiddenを選択します。テンプレート・オプションのLeft MarginとRight MarginにLargeを指定します。セッション・ステートのデータ型にCLOBを選択し、ストレージにリクエストごと(メモリーのみ)を選択します。ページが送信されなければ、セッション・ステートにページ・アイテムの値が保存される機会はありません。そのためストレージにセッションごと(永続)を選んでも動作は変わりません。
読取り専用のタイプに常時を指定します。
リージョンRequestの下に送信する写真を保持するページ・アイテムP1_IMAGEを作成します。タイプにイメージ・アップロードを選択します。
表示の表示形式としてアイコン・ドロップ・ゾーンを選択します。アイコンとして写真がプレビューされます。プレビュー・サイズは特大にします。キャプチャに使用にメイン・カメラを選択します。アプリがスマホで実行されているときは、ファイル選択のダイアログが開く代わりに、背面カメラを起動するようにします。
ストレージのファイルをパージするタイミングはリクエストの終わり、タイプとして表APEX_APPLICATION_TEMP_FILESを選択します。ただし、ページの送信は行わないため、イメージが表APEX_APPLICATION_TEMP_FILESに保存されることはありません。
サイズ変更の最大ファイル・サイズに1000を入力します。トリミングのトリミングの許可をオンにし、アスペクト比として1:1(正方形)を選択します。
トリミングをオンにしていると写真のデータはPNG形式に変換され最大ファイル・サイズに収まるように解像度を落としてサイズを縮小します(JPEGがPNGに変換されるのは現行のAPEXのバージョンの不具合かも知れず、今後のバージョンで動作が変わる可能性もあります)。Google Gemini Pro Visionにリクエストを送信する際に、イメージのサイズおよび送信するリクエストのサイズが制限を超えないようにしています。
外観のテンプレートとしてHidden、セッション・ステートのストレージとしてリクエストごと(メモリーのみ)を選択します。
ページ・アイテムP1_IMAGEに動的アクションを作成し、値が変更されたときにページ・アイテムP1_RESPONSEをクリアします。
動的アクションのタイミングは変更です。
アクションは表示、影響を受ける要素の選択タイプにボタン、ボタンとしてSUBMITを選択します。初期化時に実行はオフにします。
ボタンSUBMITをクリックしたときに実行される動的アクションを作成します。
タイミングのイベントはクリックです。
TRUEアクションとしてJavaScriptコードの実行を選択します。設定のコードに以下を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// スピナーを表示し、画面操作をブロックする。 | |
let spinner = apex.widget.waitPopup(); | |
// P1_IMAGEに設定されているファイルを取り出す。 | |
let file = document.getElementById("P1_IMAGE").files[0]; | |
// Gemini Pro VisionをORDSのREST API経由で呼び出す。 | |
const req = new XMLHttpRequest(); | |
req.open("POST", "&G_REQUEST_URL.", true); | |
// 現行のAPEXは画像のクロッピングを行うと、フォーマットがPNGに変わる。 | |
// この動作は将来のバージョンで変更される可能性がある。 | |
req.setRequestHeader('content-type','image/png'); | |
// APEXのセッションでORDS APIを認証するためにApex-Sessionヘッダーを設定している。 | |
const authHeader = apex.env.APP_ID.concat(',',apex.env.APP_SESSION); | |
req.setRequestHeader('Apex-Session', authHeader); | |
req.onload = (event) => { | |
// console.log(req.response); | |
// 影響を受ける要素にREST APIのレスポンスを設定する。 | |
let elem = this.affectedElements.toArray()[0]; | |
apex.item(elem).setValue(req.response); | |
// スピナーの停止および画面操作のブロックを解除する | |
spinner.remove(); | |
}; | |
// 要求の送信。 | |
req.send(file); |
影響を受ける要素の選択タイプにアイテム、アイテムとしてP1_RESPONSEを選択します。Gemini Pro Visionのレスポンスを表示するページ・アイテムを指定しています。
TRUEアクションを追加します。写真を送信した後は、ボタンSUBMITを非表示にします。
アクションは非表示、影響を受ける要素の選択タイプにボタン、ボタンとしてSUBMITを選択します。ページが初めて表示されたときに非表示とするため、初期化時に実行はオンにします。
以上でアプリケーションの動作の部分は実装できました。
フッター部分を非表示にするのと、ボタンを修飾するCSSを、ページ・プロパティのCSSのインラインに記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#t_Footer { | |
display: none; | |
} | |
.button-submit { | |
font-size: 2em; | |
width: 10em; | |
height: 3em; | |
position: fixed; | |
bottom: 5%; | |
left: 50%; | |
transform: translateX(-50%); | |
} |
ページ・プロパティの外観のページ・テンプレートをMinimal (No Navigation)に切り替えます。また、テンプレート・オプションのDeferred Page Renderingをオンにします。この設定により、ページ・ロード時に一瞬ボタンSUBMITが表示される現象を抑制します。
アプリケーションに移り、テーマ・ローラを開いて見かけをカスタマイズします。
プライマリ・アクセント色を白、ボタンの枠線の角の丸めを20px、ホットの色を緑に変更しています。このあたりはお好みで設定されるとよいでしょう。
変更したテーマを保存します。
写真のデータは一旦APEXにアップロードされ、その後、Geminiに渡されます。そのため、データはブラウザからORDSとオラクル・データベースを通ります。単にGeminiを呼び出すだけであれば、ブラウザから直接APIを呼び出せば、ORDSからデータベースへのアクティブな接続が不要になります。通信するデータ量が多いためCPU負荷は低くてもセッションは長時間アクティブなままになります。データベースへの負荷を減らすには、写真はオブジェクト・ストレージにアップロードして、ファイルの位置をAPEXに渡すといった実装も考慮する必要があります。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/ask-animal.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完