2025年6月24日火曜日

マルチモーダル検索アプリケーションをAI Vector Searchを使うように更新する

ベクトル・データベースPineconeを使ってマルチモーダル検索を行うAPEXアプリケーションを、Oracle Database 23aiのベクトル検索の機能で置き換えます。

元記事は以下の2本です。
ユーザー・インターフェスは変更しません。以下の内部の処理を置き換えます。
  1. Flaskサーバーで行っていたrinna/japanese-cloob-vit-b-16によるエンべディングの生成を、openai/clip-vit-large-patch14のONNXモデルを作成し、データベースにロードしてを生成するようにします。
  2. Pineconeで行っていたエンべディングの保存と検索を、Oracle Database 23aiのベクトル型の列にエンべディングを保存して検索するように変更します。
Pineconeでベクトル検索を行っていたときは、Pineconeから取得した検索結果をAPEXコレクションに保存するなど、PL/SQLで色々な手続きを記述していました。今回はローカルのデータベースにエンべディングを保存するため、SELECT文だけで画像やテキストの類似検索を実行できます。

そのため、ボタンFind ImagesやFindといったボタンをクリックしたときに実行されるプロセスは削除し、検索処理はすべてリージョンのソースに記述するSELECT文に置き換えます。

データベースにONNXモデルをロードしてエンべディングを生成することでもコードがシンプルになりますが、今回使用しているモデルopenai/clip-vit-large-patch14は日本語に対応していません。Hugging Faceには日本語も扱えるマルチモーダルCLIPモデルがいくつかありますが、OML4Py 2.1を使ってONNXモデルにする手順は見つけられませんでした。そのため、事前構成されているモデルopenai/clip-vit-large-patch14を使用しています。


エンべディングを生成するONNXモデルの準備



以前の記事「OML4Py ServerとOML4Py ClientをOracle Database Freeのコンテナにインストールする」で作成したコンテナoml4py-dbを使って作業します。

コンテナoml4py-dbに接続します。

podman exec -it oml4py-db bash

% podman exec -it oml4py-db bash

bash-4.4$ 


OML4Pyのクライアント側の環境を参照するように、環境変数を設定します。

export PYTHONHOME=$HOME/python
export PATH=$PYTHONHOME/bin:$PATH
export LD_LIBRARY_PATH=$PYTHONHOME/lib:$LD_LIBRARY_PATH
unset PYTHONPATH


bash-4.4$ export PYTHONHOME=$HOME/python

bash-4.4$ export PATH=$PYTHONHOME/bin:$PATH

bash-4.4$ export LD_LIBRARY_PATH=$PYTHONHOME/lib:$LD_LIBRARY_PATH

bash-4.4$ unset PYTHONPATH

bash-4.4$ 


pythonを起動します。

python3

bash-4.4$ python3

Python 3.12.6 (main, Jun 20 2025, 05:48:10) [GCC 8.5.0 20210514 (Red Hat 8.5.0-26.0.1)] on linux

Type "help", "copyright", "credits" or "license" for more information.

>>> 


以下のコードを実行し、モデルopenai/clip-vit-large-patch14をONNX形式でエクスポートします。

import oml
from oml.utils import ONNXPipeline
pipeline = ONNXPipeline(model_name="openai/clip-vit-large-patch14")
pipeline.export2file("openai_clip_vit",output_dir="./work")


>>> import oml

>>> from oml.utils import ONNXPipeline

>>> pipeline = ONNXPipeline(model_name="openai/clip-vit-large-patch14")

>>> pipeline.export2file("openai_clip_vit",output_dir="./work")

tokenizer_config.json: 100%|███████████████████████████████| 905/905 [00:00<00:00, 5.34MB/s]

vocab.json: 100%|████████████████████████████████████████| 961k/961k [00:00<00:00, 2.19MB/s]

merges.txt: 100%|████████████████████████████████████████| 525k/525k [00:00<00:00, 1.18MB/s]

special_tokens_map.json: 100%|█████████████████████████████| 389/389 [00:00<00:00, 1.77MB/s]

tokenizer.json: 100%|██████████████████████████████████| 2.22M/2.22M [00:00<00:00, 5.65MB/s]

config.json: 100%|█████████████████████████████████████| 4.52k/4.52k [00:00<00:00, 14.8MB/s]

model.safetensors: 100%|███████████████████████████████| 1.71G/1.71G [03:02<00:00, 9.35MB/s]

preprocessor_config.json: 100%|████████████████████████████| 316/316 [00:00<00:00, 1.81MB/s]

/home/oracle/python/lib/python3.12/site-packages/transformers/models/clip/modeling_clip.py:282: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!

  if seq_length > max_position_embedding:

/home/oracle/python/lib/python3.12/site-packages/transformers/modeling_attn_mask_utils.py:88: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!

  if input_shape[-1] > 1 or self.sliding_window is not None:

/home/oracle/python/lib/python3.12/site-packages/transformers/modeling_attn_mask_utils.py:164: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!

  if past_key_values_length > 0:

/home/oracle/python/lib/python3.12/site-packages/torch/onnx/symbolic_opset9.py:5383: UserWarning: Exporting aten::index operator of advanced indexing in opset 17 is achieved by combination of multiple ONNX operators, including Reshape, Transpose, Concat, and Gather. If indices include negative values, the exported graph will produce incorrect results.

  warnings.warn(

UserWarning:Batch inference not supported in quantized models. Setting batch size to 1.

TracerWarning:Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!

>>> 


プロンプトが戻れば、ONNX形式でのファイル出力は完了です。pythonを終了したのち、コンテナから抜けます。

コンテナ内の/home/oracle/workはホストのファイル・システムにマウントされています。そのため、ホストからモデル・ファイルopenai_clip_vit_img.onnxopenai_clip_vit_txt.onnxが作成されていることを確認できます。

ls openai_clip_vit_*

>>> 

bash-4.4$ exit

exit

oml4py-on-db23ai-free % ls -l openai_clip_vit_*

-rw-r--r--  1 **********  staff  306374876  6 20 17:27 openai_clip_vit_img.onnx

-rw-r--r--  1 **********  staff  125759349  6 20 17:26 openai_clip_vit_txt.onnx

oml4py-on-db23ai-free %    


作成されたopenai_clip_vit_img.onnxopenai_clip_vit_txt.onnxを、データベースにロードします。

ロードする対象のデータベースはローカルのコンテナで動作しているOracle Database Free 23ai 23.8です。ONNXファイルはあらかじめ、コンテナの/opt/oracle/apexにマウントされたホスト上のディレクトリにコピーしておきます。

ホスト・マシンのSQLclを実行し、ユーザーSYSで接続します。

sql sys/[SYSのパスワード]@localhost/freepdb1 as sysdba

% sql sys/*******@localhost/freepdb1 as sysdba



SQLcl: 金 6月 20 17:41:03 2025のリリース25.1 Production


Copyright (c) 1982, 2025, Oracle.  All rights reserved.


接続先:

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free

Version 23.8.0.25.04


SQL> 


APEXのワークスペースに紐づいたスキーマで、ONNXモデルのインポートや利用ができるようにします。本記事ではスキーマとしてWKSP_APEXDEVを使用します。

create or replace directory onnx_import as '/opt/oracle/apex';
grant read,write on directory onnx_import to wksp_apexdev;
grant create mining model to wksp_apexdev;
grant execute on ctxsys.ctx_ddl to wksp_apexdev;

SQL> create or replace directory onnx_import as '/opt/oracle/apex';


Directory ONNX_IMPORTは作成されました。


SQL> grant read,write on directory onnx_import to wksp_apexdev;


Grantが正常に実行されました。


SQL> grant create mining model to wksp_apexdev;


Grantが正常に実行されました。


SQL> grant execute on ctxsys.ctx_ddl to wksp_apexdev;


Grantが正常に実行されました。


SQL> exit

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free

Version 23.8.0.25.04から切断されました

% 


ONNXモデルをロードします。最初はイメージ・モデルを対象にします。
begin
    dbms_vector.load_onnx_model(
        directory => 'ONNX_IMPORT'
        ,file_name => 'openai_clip_vit_img.onnx'
        ,model_name => 'OPENAI_CLIP_VIT_IMG'
    );
end;
/
SQLワークショップSQLコマンドから実行します。


続いてテキスト・モデルをロードします。
begin
    dbms_vector.load_onnx_model(
        directory => 'ONNX_IMPORT'
        ,file_name => 'openai_clip_vit_txt.onnx'
        ,model_name => 'OPENAI_CLIP_VIT_TXT'
    );
end;
/

ロードされたONNXモデルを確認します。

select MODEL_NAME, ALGORITHM from user_mining_models;


ONNXモデルの属性を確認します。特に列VECTOR_INFOよりONNXモデルが生成するエンべディングの次元数とタイプ(openai/clip-vit-large-patch14VECTOR(768,FLOAT32))を確認します。

 select * from user_mining_model_attributes


以上でエンべディングの生成にONNXモデルを使用できるようになりました。


マルチモーダル検索を行うAPEXアプリケーションの更新



更新するAPEXアプリケーションのエクスポートは以下にあります。
https://github.com/ujnak/apexapps/blob/master/exports/multimodal-search.zip

APEXアプリケーションを実装する上で、以下の前提があります。
  • Oracle Cloudのオブジェクト・ストレージに作成したバケットに、検索対象となるJPEGの画像データが保存されている。
  • オブジェクト・ストレージにアクセスするためのOCIのユーザー、ポリシー、クリデンシャルなどが作成済みである。
以下より、APEXアプリケーションの更新作業を紹介します。

APEXアプリケーションが元々使用していた表を作り直します。表VEC_IMAGES_INDEXESおよびVEC_TEXTSに列EMBEDDINGを追加します。データ型は、モデルopenai/clip-vit-large-patch14が生成するエンべディングに合わせてVECTOR(768,FLOAT32)とします。

SQLワークショップSQLスクリプトで実行します。



作成をクリックするとスクリプト・エディタが開きます。スクリプト名を入力し、上記のDDLを貼り付けて実行をクリックします。


確認のダイアログが開くので即時実行をクリックします。記述されている6行のDDLが成功していると、表の準備は完了です。


ワークスペースにmultimodal-search.zipをインポートします。

アプリケーション・ビルダーからインポートを実行します。


インポートするファイルとしてmultimodal-search.zipを選択し、へ進みます。


確認画面が表示されます。そのままアプリケーションのインストールをクリックします。


インポートされるリモート・サーバーが表示されます。

リモート・サーバーのベースURLとして以下が設定されます。Oracle Cloudのオブジェクト・ストレージにアクセスする(古い形式の)エンドポイントです。リージョンとして北米アッシュバーン・リージョンが決め打ちになっています。

https://objectstorage.us-ashburn-1.oraclecloud.com/

リージョンが異なる場合は、ここで設定を変更できます(アプリケーションのインポート後に変更することも可能です)。

へ進みます。


アプリケーションのインポートが完了します。

今のままではアプリケーションを実行できないため、アプリケーションの編集を実行します。


最初にテーマのリフレッシュを実行します。APEXの新機能を組み込む場合は、あらかじめテーマのリフレッシュが必要な場合があります。


テーマのリフレッシュが完了しました。


アプリケーション定義置換より、置換文字列G_NAMESPACEにオブジェクト・ストレージのネーム・スペースG_BUCKETに画像データが保存されているバケット名を設定します。

Pineconeは使わないので置換文字列G_INDEXの設定は不要になりました。また、ONNXモデルを使うので置換文字列G_EMBEDの設定も不要になりました。

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


ワークスペース・ユーティリティWeb資格証明を開きます。


Web資格証明としてOCIネイティブ認証OCI API Accessが、アプリケーションのインポートと同時にインポートされています。

これを開いて編集します。


オブジェクト・ストレージへのアクセス権限および事前承認URLを生成する権限を持つユーザーのWeb資格証明を設定します。

OCIユーザーIDOCI秘密キーOCIテナンシIDOCI公開キー・フィンガープリントを設定します。

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


Web資格証明の準備ができました。


ワークスペース・ユーティリティリモート・サーバーを開きます。


リモート・サーバーの名前はOCI Object Storage Endpoint - US Ashburnとなっていますが、宛先を他のリージョンに変更できます。


オブジェクト・ストレージのバケットがあるリージョンに合わせて、エンドポイントURLを更新します。


アプリケーション・ビルダーに戻り、アプリケーションの更新作業を始めます。

今回は作業コピーを作成してアプリケーションを更新します。

作業コピーの作成をクリックします。


作業コピー名23ai vector searchとします。

作業コピーの作成をクリックします。


作業コピー23ai vector searchが作成されます。この作業コピーで行った変更は、一括で破棄できます。


共有コンポーネントアプリケーションの計算G_PREAUTH_URLを開きます。

計算のコードに書かれている事前承認URLのエンドポイントURLなどが、us-ashburn-1に決め打ちになっています。必要に応じて画像データを保存しているバケットがあるリージョンに変更します。


アプリケーションを実行します。


ボタンLoad From Bucketをクリックします。画像データを含むバケットのファイル一覧を表VEC_IMAGESに同期します。


ボタンUpsert Vectorsをクリックしたときに実行されるコードを、以下に置き換えます。

画像データをオブジェクト・ストレージより取り出し、ONNXモデルで生成したエンべディングを表VEC_IMAGE_INDEXSに保存しています。


プロセスUpsert VectorsPL/SQLコードを置き換えたのち、ボタンUpsert Vectorsをクリックします。

画像の数に依存して時間がかかりますが、エンべディングが生成されるとIs IndexedYと表示されます。


ボタンFind Imagesをクリックしたときに実行されるプロセスは削除します。画像を検索するSELECT文は、レポートのソースに設定します。


リージョンResultsソースSQL問合せを以下に変更します。



Questionに検索対象の画像に合わせた文字列を入力し(今回の例ではpolar bear)、Top K2を入力して、Find Imagesをクリックします。

北極グマの写真が検索できているので、適切にマルチモーダルで検索できていることが確認できました。


ボタンDelete Vectorsをクリックしたときに実行されるPL/SQLコードを、以下に置き換えます。

実質1行です。



以上でページ番号1の、テキストから画像を検索するページの更新は完了です。

次にページ番号2の、画像から画像およびテキストを検索するページを更新します。

対話グリッドを使って、Textを何行か入力します。


ボタンUpsert Vectorsをクリックしたときに実行されるPL/SQLコードを、以下に置き換えます。

入力したテキストのエンべディングを生成しています。実質1行です。



プロセスUpsert VectorsPL/SQLコードを置き換えたのち、ボタンUpsert Vectorsをクリックします。

テキストにエンべディングが生成されると、Is IndexedYと表示されます。


ボタンFindをクリックしたときに実行されるプロセスFind Images and Texts削除します。


検索条件である画像はオブジェクト・ストレージにアップロードされず、APEX_APPLICATION_TEMP_FILESに保持されています。

そのため、ページ・アイテムP2_PICTURE設定基準SQL文で戻されたBLOB列に変更し、SQL文として以下を設定します。
select blob_content, null, filename, mime_type
from apex_application_temp_files
where name = :P2_IMAGE

リージョンResult ImagesソースSQL問合せを以下に変更します。


リージョンResult TextsソースSQL問合せを以下に変更します。



Image File画像を選択しTop K数値を設定して、ボタンFindをクリックします。

似た画像とテキストが検索できることを確認します。


ボタンDelete Vectorsをクリックしたときに実行されるコードを、以下に置き換えます。



以上でマルチモーダル検索を行うAPEXアプリケーションを、Oracle Database 23aiのAI Vector Searchを使うように更新できました。

作業コピー23ai vector searchでの動作確認が完了した時点で、メインにマージします。

その前に変更の比較を実行してみます。


PAGE:1とPAGE:2が変更されているとリストされます。差分をクリックしています。


ページの変更点の差分が表示されます。

コードが削除されていることが確認できます。


メインにマージを実行します。


差分が表示されます。

へ進みます。


最初にターゲット・アプリケーションをバックアップマージ後に作業用コピーを削除の両方にチェックを入れます。

マージの確認をクリックします。

多分、英語はConfirm Mergeだと思うので、実際はマージの実行です。


マージの確認をクリックすると、作業コピーでの変更がメインのアプリケーションにマージされます。


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

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

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