https://github.com/ujnak/apexapps/blob/master/exports/multimodal-search.zip
以下のように動作します。
動物(ホッキョクグマとタヌキ)の写真を選択し、そのベクトル埋め込みを生成してPineconeで検索しています。オブジェクト・ストレージに保存している画像とオラクル・データベースの表に保存しているテキストからベクトル埋め込みを生成し、その両方をPineconeのインデックスに保存しています。
テキストを一覧するリージョンとしてResult Textsを作成します。タイプは対話モード・レポート、ソースのSQL問合わせとして以下を記述します。
以下よりアプリケーションの実装について説明します。
検索対象のテキストの準備
検索対象となるテキストを表VEC_TEXTSに保存します。以下のDDLを実行し、表を作成します。
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
create table vec_texts ( | |
id varchar2(32 byte) default on null sys_guid() | |
constraint vec_texts_id_pk primary key, | |
text varchar2(4000 char) not null, | |
is_indexed varchar2(1 char) constraint vec_texts_is_indexed_ck | |
check (is_indexed in ('Y','N')) | |
) | |
; |
表VEC_TEXTSをソースとした対話グリッドを作成し、検索対象となる(つまり、ベクトル埋め込みを生成しPineconeに保存する対象となる)テキストの入力を行います。
ベクトル埋め込みを生成しPineconeのインデックスにUpsertするボタンを作成します。
ボタン名はUPSERT_VECTORS、ラベルはUpsert Vectors、動作のアクションはページの送信とします。
ボタンUPSERT_VECTORSを押下したときに実行されるプロセスをUpsert Vectorsとして作成します。
ソースのPL/SQLコードに以下を記述します。
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_request clob; | |
l_request_json json_object_t; | |
l_vectors json_array_t; | |
l_vector json_object_t; | |
l_embedding json_array_t; | |
l_response clob; | |
l_response_json json_object_t; | |
C_UPSERT constant varchar2(100) := :G_INDEX || '/vectors/upsert'; | |
e_upsert_exception exception; | |
begin | |
/* | |
* Pinconeのindexに未登録のテキストを登録する。 | |
*/ | |
l_vectors := json_array_t(); | |
for r in ( | |
select id, text from vec_texts where is_indexed is null | |
) | |
loop | |
/* テキストのembeddingを生成する. */ | |
select json_object( | |
key 'text' value r.text | |
) into l_request | |
from dual; | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Accept','application/json',p_reset => false); | |
apex_web_service.set_request_headers('Content-Type','application/json',p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => :G_EMBED || '/embed-text' | |
,p_http_method => 'POST' | |
,p_body => l_request | |
); | |
l_response_json := json_object_t(l_response); | |
l_embedding := l_response_json.get_array('embedding'); | |
l_vector := json_object_t(); | |
l_vector.put('id', r.id); | |
l_vector.put('values', l_embedding); | |
/* このベクトルがtextから生成されたことを、metadataとして追加する。 */ | |
l_vector.put('metadata', json_object_t('{ "type":"text" }')); | |
l_vectors.append(l_vector); | |
update vec_texts set is_indexed = 'Y' where id = r.id; | |
end loop; | |
if l_vectors.get_size = 0 then | |
/* 追加するテキストがないので終了 */ | |
return; | |
end if; | |
l_request_json := json_object_t(); | |
l_request_json.put('vectors', l_vectors); | |
l_request := l_request_json.to_clob; | |
/* | |
* PineconeのUpsertを呼び出す。 | |
*/ | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Accept','application/json',p_reset => false); | |
apex_web_service.set_request_headers('Content-Type','application/json',p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => C_UPSERT | |
,p_http_method => 'POST' | |
,p_body => l_request | |
,p_credential_static_id => 'PINECONE_API' | |
); | |
/* レスポンス本文は解析不要。 */ | |
apex_debug.info(l_response); | |
if apex_web_service.g_status_code <> 200 then | |
raise e_upsert_exception; | |
end if; | |
end; |
Pineconeのインデックスから、テキストより生成したベクトルを削除するボタンを作成します。
ボタン名はDELETE_VECTORS、ラベルはDelete Vectors、動作のアクションはページの送信とします。
ボタンDELETE_VECTORSを押下したときに実行されるプロセスをDelete Vectorsとして作成します。
ソースのPL/SQLコードに以下を記述します。
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_request clob; | |
l_response clob; | |
C_DELETE constant varchar2(100) := :G_INDEX || '/vectors/delete'; | |
e_delete_exception exception; | |
begin | |
/* | |
* typeがtextのベクトルを削除する。 | |
*/ | |
select json_object( | |
key 'deleteAll' value 'false', | |
key 'filter' value json_object( | |
key 'type' value 'text' | |
) | |
) into l_request from dual; | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Accept','application/json',p_reset => false); | |
apex_web_service.set_request_headers('Content-Type','application/json',p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => C_DELETE | |
,p_http_method => 'POST' | |
,p_body => l_request | |
,p_credential_static_id => 'PINECONE_API' | |
); | |
/* レスポンス本文は解析不要。 */ | |
apex_debug.info(l_response); | |
if apex_web_service.g_status_code <> 200 then | |
raise e_delete_exception; | |
end if; | |
/* インデックス済みのフラグを初期化。 */ | |
update vec_texts set is_indexed = null; | |
end; |
以上で検索対象となるテキストの入力、ベクトル埋め込みの生成とインデックスへの更新、インデックスからの削除ができるようになりました。
画像を指定した検索
検索に使用する画像を選択するページ・アイテムはP2_IMAGEです。
識別のタイプにファイル参照...を指定します。ラベルはImage Fileとしています。設定の記憶域タイプとしてTable APEX_APPLICATION_TEMP_FILESを選択します。ファイルをパージするタイミングは、End of SessionおよびEnd of Requestのどちらを選択しても動作に違いはありません。
ページ・アイテムP2_TOP_Kにて、検索結果の数を指定します。
ページ・アイテムP2_TYPEの選択リストでは、無指定、image、textのどれかを選択します。Pineconeのインデックスの検索条件のmetadataに含めることで、検索対象を画像とテキストの両方(無指定)、画像のみ(image)、テキストのみ(text)に限定します。
ページ・アイテムP2_PICTUREに、検索に使った画像を表示します。
設定の基準にImage URL stored in Page Item Valueを選択します。ページ・アイテムの値は、ファイルをアップロードしたときに実行されるプロセス内で設定します。
検索はボタンFINDをクリックして実行します。
ボタン名はFIND、ラベルはFind、動作のアクションはページの送信です。
ボタンFINDを押下したときに実行されるプロセスをFind Images and Textsとして作成します。
ソースのPL/SQLコードに以下を記述します。
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 | |
C_BASE_URL constant varchar2(200) := 'https://objectstorage.us-ashburn-1.oraclecloud.com/n/' || :G_NAMESPACE || '/b/' || :G_BUCKET || '/o/'; | |
l_filename apex_application_temp_files.filename%type; | |
l_mime_type apex_application_temp_files.mime_type%type; | |
l_blob_content blob; | |
l_request clob; | |
l_response clob; | |
l_response_json json_object_t; | |
l_embedding json_array_t; | |
/* Pinecone */ | |
l_query_json json_object_t; | |
l_matches json_array_t; | |
l_vector json_object_t; | |
/* search result */ | |
l_id varchar2(400); | |
l_metadata json_object_t; | |
l_type varchar2(8); | |
l_score number; | |
C_QUERY constant varchar2(80) := :G_INDEX || '/query'; | |
e_query_exception exception; | |
e_upload_failed_exception exception; | |
begin | |
/* | |
* 選択したファイルをオブジェクト・ストレージにアップロードする。 | |
*/ | |
select filename, mime_type, blob_content into l_filename, l_mime_type, l_blob_content | |
from apex_application_temp_files where name = :P2_IMAGE; | |
apex_web_service.set_request_headers('Content-Type', l_mime_type); | |
l_response := apex_web_service.make_rest_request( | |
p_url => C_BASE_URL || utl_url.escape(l_filename, false, 'AL32UTF8') | |
,p_http_method => 'PUT' | |
,p_body_blob => l_blob_content | |
,p_credential_static_id => 'OCI_API_ACCESS' | |
); | |
if apex_web_service.g_status_code <> 200 then | |
apex_debug.info(l_response); | |
raise e_upload_failed_exception; | |
end if; | |
/* | |
* アップロードした画像のembeddingを生成する。 | |
*/ | |
:P2_PICTURE := :G_PREAUTH_URL || l_filename; | |
select json_object( | |
key 'url' value :G_PREAUTH_URL || l_filename | |
) into l_request | |
from dual; | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Accept','application/json', p_reset => false); | |
apex_web_service.set_request_headers('Content-Type','application/json', p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => :G_EMBED || '/embed-image' | |
,p_http_method => 'POST' | |
,p_body => l_request | |
); | |
-- apex_debug.info(l_response); | |
l_response_json := json_object_t.parse(l_response); | |
l_embedding := l_response_json.get_array('embedding'); | |
/* | |
* Pineconeに問い合わせる。 | |
*/ | |
l_query_json := json_object_t(); | |
/* 検索対象に制限をかける。 */ | |
if :P2_TYPE is not null then | |
l_query_json.put('filter', json_object_t('{ "type":"' || :P2_TYPE || '"}')); | |
end if; | |
l_query_json.put('includeValues', false); | |
l_query_json.put('includeMetadata', true); | |
l_query_json.put('vector', l_embedding); | |
l_query_json.put('topK', :P2_TOP_K); | |
l_request := l_query_json.to_clob; | |
/* queryを呼び出す。 */ | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Accept','application/json',p_reset => false); | |
apex_web_service.set_request_headers('Content-Type','application/json',p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => C_QUERY | |
,p_http_method => 'POST' | |
,p_body => l_request | |
,p_credential_static_id => 'PINECONE_API' | |
); | |
if apex_web_service.g_status_code <> 200 then | |
apex_debug.info(l_response); | |
raise e_query_exception; | |
end if; | |
/* | |
* レスポンスをAPEXコレクションに保存する。 | |
*/ | |
apex_collection.create_or_truncate_collection('IMAGES_AND_TEXTS'); | |
l_response_json := json_object_t.parse(l_response); | |
l_matches := l_response_json.get_array('matches'); | |
for i in 1..l_matches.get_size | |
loop | |
l_vector := json_object_t(l_matches.get(i-1)); | |
l_id := l_vector.get_string('id'); | |
l_score := l_vector.get_number('score'); | |
l_metadata := l_vector.get_object('metadata'); | |
l_type := ''; | |
if l_metadata is not null then | |
l_type := l_metadata.get_string('type'); | |
end if; | |
apex_collection.add_member( | |
p_collection_name => 'IMAGES_AND_TEXTS' | |
,p_c001 => l_id | |
,p_c002 => l_type | |
,p_n001 => l_score | |
); | |
end loop; | |
end; |
検索結果を一覧するリージョンを作成します。
画像を一覧するリージョンとしてResult Imagesを作成します。タイプは対話モード・レポート、ソースのSQL問合わせとして以下を記述します。
select c001, n001 from apex_collections where collection_name = 'IMAGES_AND_TEXTS' and c002 = 'image'
列C001に画像が表示されるよう、列の書式のHTML式として以下を記述します。
<img src="&G_PREAUTH_URL.#C001#" width="200"></img>
select t.id, t.text, c.n001 from vec_texts t join (
select c001, n001 from apex_collections where collection_name = 'IMAGES_AND_TEXTS' and c002 = 'text'
) c
on t.id = c.c001
アプリケーションの実装の説明は以上になります。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完