2025年3月13日木曜日

マルチモーダルLLMのGemma3をLM Studioで動かしAPEXアプリケーションから呼び出す

最近GoogleからリリースされたマルチモーダルLLMのGemma3がLM Studioで使えるようになっていたので、写真の説明をしてもらうAPEXアプリケーションを作ってみました。

作成したAPEXアプリケーションは以下のように動きます。ローカルLLMへのプロンプトに含めた画像が正しく解釈されるか確認するためのアプリなので、凝ったことはしていません。


イメージを含めたプロンプトはLM StudioのUIからも送信できるのでAPEXでアプリケーションを作らなくてもいいのですが、PL/SQLコードのテストにはなります。

作成したAPEXアプリケーションのエクスポートを以下に置きました。実質的にホーム・ページだけのアプリケーションです。
https://github.com/ujnak/apexapps/blob/master/exports/local-lm-studio-multimodal-gemma3.zip

以下にアプリケーションについて簡単に説明します。

すべての機能はホーム・ページに実装しています。

写真をアップロードするページ・アイテムとしてP1_PHOTOを作成しています。タイプイメージ・アップロードです。

ストレージタイプ表APEX_APPLICATION_TEMP_FILESファイルをパージするタイミングとしてリクエストの終わりを選択しています。セッション・ステートストレージリクエストごと(メモリーのみ)です。


プロンプトを入力するページ・アイテムとしてP1_PROMPTを作成しています。タイプテキスト領域です。

セッション・ステートデータ型VARCHAR2ストレージセッションごと(永続)として、一度入力したプロンプトをセッション・ステートに保存するようにしています。


ページ・アイテムP1_PHOTOに設定した写真とP1_PROMPTに記述したプロンプトをサーバーに送信するボタンとしてSUBMITを作成しています。動作アクションページの送信です。


LLMからの応答を表示するページ・アイテムとしてP1_RESPONSEを作成しています。タイプリッチ・テキスト・エディタです。

設定書式マークダウンを選択しています。セッション・ステートデータ型CLOBストレージセッションごと(永続)です。読取り専用常時を設定しています。


ボタンSUBMITをクリックしてページが送信されたときに実行されるプロセスとして、Explain Photoを作成しています。

タイプコードを実行ソースPL/SQLコードとして以下を記述します。

declare
/* Local LM Studio */
C_ENDPOINT constant varchar2(4000) := 'http://host.containers.internal:8080/v1/chat/completions';
/* LLM model name */
C_MODEL constant varchar2(200) := 'gemma-3-27b-it';
/* 写真に関する変数 */
l_image json_object_t;
l_image_url json_object_t;
l_mime_type apex_application_temp_files.mime_type%type;
l_blob_content apex_application_temp_files.blob_content%type;
/* 送信するメッセージ */
l_prompt json_object_t;
l_content json_array_t;
l_message json_object_t;
l_messages json_array_t;
l_request json_object_t;
l_request_clob clob;
/* 受信するメッセージ */
l_response clob;
l_response_json json_object_t;
e_call_api_failed exception;
l_choices json_array_t;
l_choice json_object_t;
l_response_message clob;
begin
/* 写真の取り出し */
select blob_content, mime_type into l_blob_content, l_mime_type
from apex_application_temp_files where name = :P1_PHOTO;
apex_debug.info('image type = %s, length = %s', l_mime_type, dbms_lob.getlength(l_blob_content));
/* イメージの準備 - base64で送信する */
l_image_url := json_object_t();
/*
* LM Studioではimage_urlオブジェクトに直接base64の文字列を入れるのは不可で、
* urlオブジェクトにbase64の文字列を入れて、image_urlに与える必要がある。
*/
l_image_url.put('url', 'data:' || l_mime_type || ';base64,'
|| apex_web_service.blob2clobbase64(l_blob_content, 'N', 'N')
);
l_image := json_object_t();
l_image.put('type', 'image_url');
l_image.put('image_url', l_image_url);
/* プロンプトの準備 */
l_prompt := json_object_t();
l_prompt.put('type', 'text');
l_prompt.put('text', :P1_PROMPT);
/* contentの作成 */
l_content := json_array_t();
l_content.append(l_prompt);
l_content.append(l_image);
/* messageの作成 - systemプロンプトは無し, userの単発のみ */
l_message := json_object_t();
l_message.put('role', 'user');
l_message.put('content', l_content);
/* messagesの作成 */
l_messages := json_array_t();
l_messages.append(l_message);
/* requestの作成 */
l_request := json_object_t();
l_request.put('model', C_MODEL); /* 使用するモデルはGemma 3 */
l_request.put('messages', l_messages);
-- l_request.put('max_tokens', p_max_tokens);
/* call OpenAI chat completions api */
l_request_clob := l_request.to_clob();
apex_web_service.clear_request_headers();
apex_web_service.set_request_headers('Content-Type', 'application/json', p_reset => false);
l_response := apex_web_service.make_rest_request(
p_url => C_ENDPOINT
,p_http_method => 'POST'
,p_body => l_request_clob
);
/* process response */
if apex_web_service.g_status_code <> 200 then
raise e_call_api_failed;
end if;
l_response_json := json_object_t(l_response);
l_choices := l_response_json.get_array('choices');
l_response_message := '';
for i in 1..l_choices.get_size()
loop
l_choice := treat(l_choices.get(i-1) as json_object_t);
l_message := l_choice.get_object('message');
l_response_message := l_response_message || l_message.get_clob('content');
end loop;
:P1_RESPONSE := l_response_message;
end;

サーバー側の条件ボタン押下時SUBMITを設定します。


作成したAPEXアプリケーションの説明は以上です。

LM StudioにはGemma 3 27B Instruct(APIに含めるモデル名としてはgemma-3-27b-it)をロードしています。Context Lengthの最大値128K(131072)を指定するとホスト・コンピュータの動作が不安定になるため、半分の65536を設定しています。


今回の記事は以上になります。

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