- ファイルをアップロードする。
- アップロードしたファイルからテキストを取り出す。
- 取り出したテキストをチャンクに分割する。
- 分割したチャンクよりエンべディングを生成する。
- 問合せ文字列から、類似度の高いチャンクを一覧する。
- 問合せ文字列と類似度の高いチャンクをプロンプトに組み込み、言語モデルを使って回答文を生成する。
アプリケーションは基本的なワークフローの実装を目的としていて、実用性や精度は求めません。すべてローカルで実行し、費用の発生やセキュリティ上の心配は不要にします。
作成したアプリケーションは以下のように動作します。
アプリケーションの作成と実行には、以下の環境を使っています。
- エンべディングの生成と言語モデルの実行に、LM Studioを使います。
- 言語モデルとして、mmnga/ELYZA-japanese-Llama-2-7b-fast-instruct-ggufをロードします。
- エンべディングには、nomic-ai/nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.f16.ggufをロードします。
- Oracle Database 23aiはMacbook上のDocker/Colimaのx86_64エミュレーション環境で実行します。非常に遅いです。
- Oracle APEXは、23.2をOracle Database 23ai上で実行します。
以下より、サンプル・アプリケーションの作成手順を紹介します。
最初にアップロードしたファイルと、そのファイルに含まれる文字列を分割したチャンクを保存する表を作成します。
ファイルを保存する表はEBMJ_FILES、チャンクを保存する表はEBMJ_CHUNKSとして作成します。ファイルを保存する表EBMJ_FILESには、ファイルから取り出した文字列を列TEXTに保存します。チャンクを保存する表EBMJ_CHUNKSには、チャンクから生成したエンべディングを列EMBED_VECTORに保存します。
クイックSQLの以下のモデルを元に、これらの表を作成します。
# prefix: ebmj
files
title varchar2(200) /nn
content file
text clob
chunks /cascade
chunk_id num /nn
chunk_offset num /nn
chunk_length num /nn
chunk_data clob
embed_vector vector
レビューおよび実行を行います。
Oracle APEX 23.2のクイックSQLは、ベクトル型を認識しません。そのため、列EMBED_VECTORについては、以下の記述に置き換えます。
embed_vector vector
実行するDDLは以下になります。
名前はSample Vector Searchとし、アプリケーションの作成ウィザードを使用します。
アプリケーション作成ウィザードが開きます。
ページの追加をクリックし、表EBMJ_FILESのフォーム付き対話モード・レポートのページを追加します。
ページ名はFilesとします。表またはビューとしてEBMJ_FILESを選択し、フォームを含めるにチェックを入れます。
ページの追加をクリックします。
表EBMJ_FILESをソースとしたフォーム付き対話モード・レポートのページが追加されます。
同様の手順で、表EBMJ_CHUNKSの対話モード・レポートのページを追加します。手作業での編集は行わないため、フォームは付けません。
ページ名はChunks、表またはビューとしてEBMJ_CHUNKSを選択します。
ページの追加をクリックします。
アプリケーションが作成されます。
これから、それぞれのページに修正を加えていきます。
最初にページ番号2の表EBMJ_FILESの対話モード・レポートを修正します。
ページ・デザイナで開きます。
アップロードしたファイルから取り出したテキストを保存する列TEXTは長文なので、レポートでは表示できません。コメント・アウトして変更を保存します。
ページ番号3の、表EBMJ_FILESを対象としたフォームのページを開きます。
こちらも列TEXTをソースとしたページ・アイテムP3_TEXTをコメント・アウトします。
アップロードしたファイルより、テキストの取り出し、チャンク分割、エンべディングの生成までを行うプロセスを実装します。
左ペインでプロセス・ビューを表示します。プロセスを作成します。
識別の名前はExtract, Chunking and Embeddingとします。タイプとして実行チェーンを選択します。
サーバー側の条件のタイプにリクエストは値に含まれるを選択し、値としてCREATE SAVEを指定します。
作成した実行チェーンに子プロセスを追加します。
識別の名前はExtract、ソースのPL/SQLコードとして以下を記述します。アップロードしたファイルを列CONTENTから読み出し、取り出したテキストを列TEXTに保存します。
子プロセスを追加します。識別の名前はChunking、ソースのPL/SQLコードとして以下を記述します。ファイルから取り出したテキストをチャンクに分割し、表EBMJ_CHUNKSに保存します。ファンクションVECTOR_CHUNKSに与えているパラメータについては、調整の余地が多分にあります。
子プロセスを追加します。識別の名前はEmbedding、ソースのPL/SQLコードに以下を記述します。チャンクからエンべディングを生成し、表EBMJ_CHUNKSに保存します。
以上で、ファイルのアップロードからエンべディングを生成するまでの、一連の処理が実装できました。
アプリケーションを実行し、いくつかファイルをアップロードします。
その前にページ番号4の表EBMJ_CHUNKSの対話モード・レポートより、列EMBED_VECTORをコメント・アウトします。Oracle APEX 23.2のレポートでは、ベクトル型を扱うことができません。
アプリケーションを実行し、Filesのページを開きます。
作成をクリックします。
Titleを入力し、アップロードするファイルを選択します。
作成をクリックします。
ファイルがアップロードされます。
Chunksを開き、分割されたチャンクを確認します。エンべディングは表示されませんが、エラーが発生していなければ正常に生成されています。また、LM Studioのサーバー・ログからも確認できます。
単純にファイルからテキストを取り出して、単語数(日本語だとほとんど文字数)でチャンクに分割すると、さすがに精度がでない感じで分割されるな、とは思います。
ページ番号4の表EBMJ_CHUNKSの対話モード・レポートで、問合せ文字列との類似検索を実装します。
類似検索を行う文字列を入力するページ・アイテムを作成します。
識別の名前はP4_TEXT、タイプはテキスト・フィールド、ラベルはTextとします。
設定の[Enter]を押すと送信をオンにします。
類似検索を行うように、対話モード・レポートのソースを置き換えます。
ソースのタイプをSQL問合せに変更し、SQL問合せとして以下を記述します。FETCH句の部分が決めうちになっています。類似したチャンクを検索するにあたって、調整が必要になるでしょう。
ページ・アイテムP4_TEXTが空白のときは、SELECT文でエラーが発生します。そのため、サーバー側の条件のタイプにアイテムはNULLではないを選択し、アイテムにP4_TEXTを指定します。
以上で、ページ・アイテムP4_TEXTに入力した文字列に類似しているチャンクを検索できます。
選択したエンべディング・モデルが日本語に対応していないのだろうと思いますが、あまり関連の無さそうなチャンクが検索されています。LM Studioで利用可能なエンべディング・モデルに選択肢がほぼ無いので、仕方がありません。実用を考えると、OpenAIやCohereといった外部サービスを呼び出す必要があるでしょう。
最後にホーム・ページに、問合せ文字列から類似検索したチャンクを含めたプロンプトを作成し、言語モデルを呼び出して回答文を生成する機能を実装します。
ページ・デザイナで、ページ番号1のホーム・ページを開きます。
Body以下にあるリージョンのページ・ナビゲーションを削除します。
問合せ文字列を入力するページ・アイテムを作成します。
識別の名前はP1_QUERY、タイプはテキスト・フィールド、ラベルはQueryとします。
設定の[Enter]を押すと送信をオンにします。
言語モデルを呼び出して生成した回答文を表示するページ・アイテムを作成します。
識別の名前はP1_ANSWER、タイプはテキスト領域、ラベルはAnswerとします。セッション・ステートのデータ型としてCLOBを選択します。
デバッグのために、回答文の生成に使用したプロンプトを表示するページ・アイテムを作成します。
識別の名前はP1_PROMPT、タイプはテキスト領域、ラベルはPromptとします。セッション・ステートのデータ型としてCLOBを選択します。
プロセス・ビューを開き、問合せ文字列から回答を生成するプロセスを作成します。
識別の名前はGenerate Answerとします。タイプはコードの実行、ソースのPL/SQLコードとして、以下を記述します。
サーバー側の条件のタイプにアイテムはNULLではないを選択し、アイテムとしてP1_QUERYを指定します。
以上でアプリケーションは完成です。アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。
ほとんど質問と関連のないチャンクがプロンプトに含まれているにもかかわらず、それっぽい回答文が生成されるので、少々驚きました。言語モデルにELYZAを選択しているからかもしれません。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-vector-search.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完