ラベル LM Studio の投稿を表示しています。 すべての投稿を表示
ラベル LM Studio の投稿を表示しています。 すべての投稿を表示

2025年1月28日火曜日

APEX 24.2のデータ・モデルの生成をLM StudioとDeepSeek-R1 Distill Llama 70Bで実行する

Oracle APEX 24.2に、AIに問い合わせてデータ・モデルを作成する機能が追加されました。この機能の動作確認を、最近話題のDeepSeek-R1を使って実施してみます。オンラインでAPIを呼び出す代わりに、LM Studioのローカル・サーバーにモデルをロードし、OpenAI互換のChat Completions APIで呼び出します。

Apple Macbook ProのM4 Max/128GBで動作する最大のモデルdeepseek-r1-distill-llama-70bを使います。性能は変わるとは思いますが、10GBメモリ以下で動かせるDeepSeek-R1の蒸留モデルもあるようです。

作業結果について、OpenAI GPT-4oとほとんど同じ回答が得られる印象です。ただし、すごい時間がかかる - これはモデルの問題というより、ローカルの環境が遅いから - また、処理時間での制限のため、エラーが発生して回答が得られないことがあります。Chat Completions APIはAPEX_WEB_SERVICE.MAKE_REST_REQUESTで呼び出していますが、その引数p_transfer_timeoutのデフォルト値が180秒のため、それ以上処理に時間がかかるとタイムアウトが発生するようです。

OpenAIのo1でも同様ですが、推論モデルは回答を生成するまでに比較的長い時間がかかります。また、Oracle APEXではデータベース・サーバーからリクエストを送信するため、ストリーミングを扱うことができません。結果として、問い合わせが難しいとAPIリクエストのタイムアウトが発生する可能性が高くなります。

以下より実際に行った作業を紹介します。

使用したLM Studioのバージョンは0.3.8です。

Discoverのアイコンをクリックして、Model Searchを開き、DeepSeek-R1を検索します。LM Studioを動かしているホストで実行可能なモデルをダウンロードします。

本記事ではmlx-community/DeepSeek-R1-Distill-Llama-70B-8bitを使用することにしました。MシリーズのMacでLM Studioを実行しているため、MLXフォーマットのモデルを優先しています。


Developerアイコンをクリックします。Settingsを開き、Server Portを設定します。本記事では8080にしています。他のモデルが自動的にロードされてメモリが不足しないように、Just-in-Time Model LoadingOffにします。

Statusを切り替え、Runningにします。


Select a model to loadをクリックし、ダウンロード済みのモデルDeepSeek R1 Distill Llama 70Bをロードします。


Context Lengthに最大値の131072を設定し、Load Modelを実行します。


モデルのステータスがREADYになれば、APIから呼び出せます。


Oracle APEXの設定に移ります。Oracle APEX 24.2は同じMacのpodmanで動作しています。Oracle APEX環境の構築手順については、こちらの記事で紹介しています。

Oracle APEXのワークスペースにサインインし、ワークスペース・ユーティリティ生成AIを開きます。


作成済みの生成AIサービスが一覧されます。作成をクリックします。


識別AIプロバイダとしてOpenAIを選択します。名前DeepSeek-R1 Distilled Llama 70Bとします。

ユーティリティAIを使用したデータ・モデルの作成から呼び出せるように、アプリケーション・ビルダーで使用オンにします。ベースURLは、コンテナで実行されているOracle Databaseから外の環境にアクセスするため、以下を指定します。

http://host.containers.internal:8080/v1

認証は不要ですが資格証明が必須項目であるため、設定だけは行います。資格証明- 新規作成 -を選択し、APIキーとして適当な文字列を設定します。

AIモデルdeepseek-r1-distill-llama-70bを設定します。


ここに設定するモデル名はLM StudioのAPI Usageのセクションに、This model's API identifierとして表示されています。


追加属性をクリックして開き、JSON属性として以下を記述します。

{ "max_tokens": 131072 }

以上でダイアログを閉じます。


接続のテストをクリックして、接続に成功することを確認します。

作成をクリックします。


以上で、生成AIサービスとしてDeepSeek-R1(の蒸留モデル)を呼び出せるようになりました。


SQLワークショップユーティリティAIを使用したデータ・モデルの作成を開きます。


APEXアシスタントのダイアログが開きます。SQL形式としてOracle SQLを選びます。


以下のメッセージを入力します。

国名、ISO3166-1 Alpha-2コード、首都名、首都の位置の経度と緯度を保存する表をEBAJ_COUNTRIESとして作成してください。日本語で会話してください。

Oracle APEXのプロンプトは英語なので(結果として英語と日本語が混在したメッセージがLLMに送信される)、回答が英語になることもあります。その点を考慮して、日本語で回答するように指示しています。


2分強の処理時間を費やした後、以下の回答が得られました。


生成されたSQLスクリプトは以下です。実行すると定義通りに表が作成されます。
-- create tables
create table ebaj_countries (
    id                            number         generated by default on null as identity
                                                constraint ebaj_countries_pk primary key,
    country_name                  varchar2(255)  not null,
    iso_code                      varchar2(2)    not null
                                                constraint ebaj_countries_iso_code_uk unique,
    capital_name                  varchar2(255),
    longitude                     number(9,6),
    latitude                      number(9,6),
    row_version                   integer        not null,
    created_on                    date           not null,
    created_by                    varchar2(255)  not null,
    updated_on                    date           not null,
    updated_by                    varchar2(255)  not null )
/
-- triggers
create or replace trigger ebaj_countries_biu
    before insert or update
    on ebaj_countries
    for each row
begin
    :new.updated_on := sysdate;
    :new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
    if inserting then
        :new.row_version := 1;
        :new.created_on := :new.updated_on;
        :new.created_by := :new.updated_by;
    elsif updating then
        :new.row_version := nvl(:old.row_version, 0) + 1;
    end if;
end ebaj_countries_biu;
/
今回の用途では、列ISO_CODEを主キーとして、監査列も行バージョンの列も不要です。これらの列は、Oracle APEXが生成するプロンプトで作成するよう指示されているため、SQLスクリプトに含まれています。

これらを削除します。

列ISO_CODEを主キーとして、列IDは削除してください。

3分弱考えて、以下の回答が返されました。


生成されたDDLは以下です。指示通りに列IDが削除され、列ISO_CODEが主キーになっています。
-- create tables
create table ebaj_countries (
    iso_code                      varchar2(2)    not null
                                                constraint ebaj_countries_pk primary key,
    country_name                  varchar2(255)  not null,
    capital_name                  varchar2(255),
    longitude                     number(9,6),
    latitude                      number(9,6),
    row_version                   integer        not null,
    created_on                    date           not null,
    created_by                    varchar2(255)  not null,
    updated_on                    date           not null,
    updated_by                    varchar2(255)  not null )
/
-- triggers
create or replace trigger ebaj_countries_biu
    before insert or update
    on ebaj_countries
    for each row
begin
    :new.updated_on := sysdate;
    :new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
    if inserting then
        :new.row_version := 1;
        :new.created_on := :new.updated_on;
        :new.created_by := :new.updated_by;
    elsif updating then
        :new.row_version := nvl(:old.row_version, 0) + 1;
    end if;
end ebaj_countries_biu;
/
監査列と列ROW_VERSIONを削除します。

監査列と列row_versionを削除してください。

3分弱考えて、以下の回答が返されました。


生成されたDDLは以下です。監査列と列ROW_VERSIONが削除されています。監査列を正しく認識しているのもそうですが、トリガーも削除されています。
-- create tables
create table ebaj_countries (
    iso_code                      varchar2(2)    not null
                                                constraint ebaj_countries_pk primary key,
    country_name                  varchar2(255)  not null,
    capital_name                  varchar2(255),
    longitude                     number(9,6),
    latitude                      number(9,6) )
/
これで完成なので、SQLスクリプトの生成を実行します。

このまま継続してサンプル・データの生成まで行おうとすると、表を作成するDDLが保存されれずに、サンプル・データのINSERT文に置き換わることがありました。少なくとも、LM StudioとDeepSeek-R1を使う場合は、DDLの作成で終了した方が良さそうです。


スクリプト・エディタが開きます。

スクリプト名を入力し、実行します。


即時実行します。


表が作成されます。

ここからアプリケーションの作成を行うことで、AIによるデータ・モデルの作成からAPEXアプリケーションの作成まで、一気に実行できます。


今回の確認作業については、以上になります。

Oracle APEXからLM StudioにDDLを生成するように指示をしたときに、LM Studio(DeepSeek-R1)から、推論の経過が<think>...</think>で囲んで返されています。

最初の指示に対して、以下の推論をしています。

よし、Oracleデータベースに「EBAJ_COUNTRIES」という名前のテーブルを作成する必要があります。このテーブルは、国名、ISO3166-1 Alpha-2コード、首都名、およびその経度と緯度を格納します。要件を整理します。

まず、テーブルには次のカラムが必要です:国名、ISOコード、首都名、経度、緯度。ガイドラインに従い、テーブル名には「ebaj」というプレフィックスを付けます。

主キーは数値型のIDで、デフォルトで生成されるものとします。このカラム名を「id」とし、オートインクリメントにはアイデンティティを使用します。

国名と首都名は文字列なので、varchar2(255)型にします。一方、ISOコードは短い文字列なので、varchar2(2)型が適切です。

経度と緯度は数値型とし、小数点が必要なので精度を9、スケールを6に設定します。

監査用カラムとして、created_on、created_by、updated_on、updated_byを追加します。これらは、データがいつ、誰によって挿入または更新されたかを記録します。

また、行のバージョン管理のためにrow_versionカラムを追加します。これは初期値を1とし、行が更新されるたびにインクリメントされるようにします。

トリガーも作成し、挿入や更新の前にcreatedおよびupdatedのタイムスタンプやユーザーを設定するようにします。これにより、両方のケースに対応できます。

インデックスについては、外部キーのためのものが必要ですが、今回外部キーはないため、もしISOコードが頻繁に検索される場合にはiso_codeにインデックスを追加するのが良いでしょう。

次の指示では、以下の推論をしています。

よし、ユーザーからのSQLテーブル変更のリクエストに対応する必要があります。以前、ユーザーはebaj_countriesというテーブルを作成し、IDを主キーとした特定のカラムを含むよう求めていました。しかし、今回はIDカラムを削除し、代わりにISO_CODEを主キーにしたいというリクエストです。

まず、この変更の理由を理解する必要があります。ISO_CODEを主キーとして使用するのは理にかなっています。なぜなら、これは各国を一意に識別する固定値であり、クエリや結合を簡素化できるからです。また、IDカラムを削除するのは、冗長性を減らし、テーブル構造をシンプルに保つためかもしれません。

元のSQLコードを見ると、IDカラムは主キーとして定義されており、アイデンティティシーケンスを使用しています。このカラムを削除するには、IDカラムとその主キー制約を削除し、代わりにISO_CODEを主キーとして設定する必要があります。

次に、ISO_CODEカラムは現在一意制約(UNIQUE)が付いていますが、これを主キーに変更します。このためには、一意制約を主キー制約に変更し、正しくインデックスが設定されていることを確認します。

依存関係も考慮する必要があります。IDが削除されるため、このテーブル内ではIDを参照する外部キーがないので、その点は簡単です。ただし、他のテーブルがebaj_countries.idを参照している場合、その参照をISO_CODEに変更する必要があります。ただし、ユーザーが他のテーブルについて言及していないため、今回はこのテーブルに集中します。

テーブル作成スクリプトを調整し、IDカラムを削除してISO_CODEを主キーに設定します。その後、トリガーを確認します。このトリガーはIDカラムをrow_versionで参照していますが、待てよ……トリガーはテーブル構造に基づく:new:oldを使用しているので、IDが削除されても問題ありません。row_versionはそのまま残っているので、トリガーは正常に動作するはずです。

次に、SQLコードを以下のように再構築します。IDカラムとその制約を削除し、ISO_CODEの制約を主キーに変更し、その他のカラムはそのままにします。トリガーについては、挿入や更新の前に動作するようになっており、IDカラムに直接依存していないので変更は不要です。

この変更の影響も考慮する必要があります。ISO_CODEが主キーになるため、必ずNOT NULLかつ一意(UNIQUE)である必要があります。これは元々の設定と一致しているので問題ありません。一意制約は主キー制約が代わりに適用されるため削除できます。

最後に、更新されたSQLコードを提示し、変更内容を説明します。IDを削除し、新しい主キーを設定したことを要約し、ユーザーのニーズに合致しているか、さらに調整が必要か確認するためのフォローアップ質問をします。

これで十分だと思います。更新されたコードは、ユーザーの要件を正確に反映するはずです。


最後の指示では、以下の推論をしています。


ユーザーがEBAJ_COUNTRIESというテーブルを作成してほしいと依頼しています。このテーブルには、国名、ISO 3166-1 Alpha-2コード、首都名、そしてその首都の経度と緯度を格納する必要があります。また、会話は日本語で進めてほしいとのことです。

まず、ユーザーの要件をデータモデルに変換する必要があります。Oracleでは、キーワードやカラム名は小文字を使用し、予約語を避けるのがベストプラクティスです。また、各テーブルには主キーを設定する必要があり、できればシーケンスやトリガーを使用したIDカラムを自動生成するのが望ましいです。

まず、IDを主キーとしたテーブルを作成します。IDはOracleのアイデンティティ機能を使用して生成します。この機能は、従来のシーケンスを使用する方法よりも簡単です。その後、国名をvarchar2(255)、ISOコードを2文字なのでvarchar2(2)、首都名もvarchar2(255)として追加します。

次に、経度と緯度については、精度とスケールを設定した数値型を使用します。座標は最大6桁の小数点を含む可能性があるため、number(9,6)として定義します。

さらに、監査用のカラムを追加します:created_oncreated_byupdated_onupdated_byです。これらはデータが挿入または更新された日時と操作したユーザーを記録します。また、row_versionカラムを追加して楽観的同時実行制御をサポートします。これにより、複数のユーザーが変更を上書きしないようにできます。

ガイドラインに従い、監査用カラムやrow_versionを自動的に更新するトリガーを作成します。このトリガーは挿入または更新の前に実行され、適切なタイムスタンプとユーザー情報を設定します。

テーブル作成後、インデックスも作成する必要があります。主キーはすでにインデックス化されているため、再度インデックスを作成する必要はありません。ただし、外部キーや頻繁に検索されるカラムがある場合、それらに対するインデックスを作成します。

次に、ユーザーの2回目のメッセージを見ると、ISO_CODEを主キーにしてIDカラムを削除したいとのことです。ISOコードは一意性があり、自然キーとして利用できるため、この変更は合理的です。そのため、テーブルを調整し、IDカラムを削除してISO_CODEを主キーとして設定します。

さらに、3回目のやり取りでは、監査用カラムとrow_versionを削除したいとのリクエストがあります。この場合、これらのカラムを削除し、トリガーもそれらを処理しないように更新する必要があります。

このプロセス全体を通じて、Oracleのベストプラクティスを守る必要があります。適切なデータ型や命名規則、制約を使用し、テーブル作成後にトリガーを設定するなど、正しい順序で作業を進めます。

最後に、各変更後にはSQLを検証し、エラーがないか確認します。また、パフォーマンスやデータの整合性を最適化することにも注意を払います。


追記1


LM Studioのチャットに指示を入力すると、長考してもタイムアウトは発生しません。ただし、Oracle APEXが挿入しているプロンプトが無いため、生成されるDDLの形式が今ひとつです。

表名や列名のルール、型の選択ルールなどを指示に含める必要があるでしょう。


Oracle APEXから指示しているときは、プロンプトは英語と日本語の混合だったためか、推論の過程は英語で出力されていました。LM Studioのチャットより日本語だけの指示を与えると、推論の過程が中国語で出力されました。



追記2


開発ツールのデバッグは以下の手順で実施します。

管理ツールのアクティビティのモニターを開きます。


アクティブ・セッションを開きます。


デバッグ対象のセッションを見つけて開きます。


デバッグ・レベル情報に変更します。変更の適用をクリックします。デバッグ・レベルトレースはアプリケーションがとても遅くなるため、本当に必要な場合に限り設定します。


エラーが発生した処理を再実行します。


先ほどデバッグ・レベルを情報に設定した画面を開きます。

エラーが補足できていれば、デバッグ・レベル無効に戻します。変更の適用をクリックします。

該当するエラーのデバッグIDを開きます。


デバッグ・メッセージからエラーの原因を探します。


デバッグ・メッセージより、上記のエラーの原因はORA-29276: 転送がタイムアウトしましたであることがわかります。


この手順は開発ツールのデバッグに限らず、一般ユーザーのセッションを対象にして実施することもできます。

追記終了

2024年10月28日月曜日

Microsoft AI Chat Protocolのクライアントを使ってブラウザよりローカルLLMを呼び出す

Oracle APEXのページにMicrosoft AI Chat Protocolのクライアントを読み込んで、ローカルLLMをブラウザから直接呼び出してみます。

作成したAPEXアプリケーションは以下のように動作します。

macOSのLM Studioに、モデルとしてmmnga/Llama-3-ELYZA-JP-8B-gguf/Llama-3-ELYZA-JP-8B-Q8_0.ggufをロードしてローカルLLM(ローカル・サーバー)を実行しています。

Oracle APEXのアプリケーションは別のホストよりロードしているため、localhostで動作しているLLMを呼び出そうとするとCORSのエラーが発生します。そのため、LM Studioの設定のCross-Origin-Resource-Sharing (CORS)ONにしています。


LM Studioは上記の設定によりAPEXのアプリケーションからローカルLLMを呼び出せたのですが、OllamaではCORSを許可する方法が分かりませんでした。

せっかくブラウザから直接LLMを呼び出すのでストリーミングを実装するつもりだったのですが(Microsoft AI Chat ProtocolはgetStreamedCompletion APIでストリーミングをサポートしています)、コードに以下のように記載があり、エンドポイントURLの末尾が/streamであることが必須のようでした。そのため、ストリーミングは断念してgetCompletion APIを呼び出しています。
    const response = await asStream(
      this.client.path(`${this.basePath}/stream`).post(request),
    );
以下より実装について紹介します。

空のAPEXアプリケーションを作成し、チャットの呼び出しはデフォルトで作成されるホーム・ページに実装しています。

ローカルLLMとやり取りするダイアログは、インライン・ダイアログとして実装しています。

タイプ静的コンテンツレイアウトスロットDialogs, Drawers and Popupsを選択し、外観テンプレートInline Dialogを選択します。これで、動的アクションでリージョンを開くまでは、画面にダイアログは表示されません。

テンプレート・オプションAuto Heightをチェックし、SizeとしてMedium (600x400)を選択しています。


ページ・プロパティJavaScriptCSSに、Microsoft AI Chat Protocolのクライアントを呼び出すために必要な設定や、メッセージの見かけを良くするためのCSSクラスを設定します。

JavaScriptファイルURLに以下を指定します。Microsoft AI Chat Protocolを扱うクライアント・ライブラリを読み込みます。

https://cdn.jsdelivr.net/npm/@microsoft/ai-chat-protocol/dist/iife/index.min.js

ファンクションおよびグローバル変数の宣言に以下を記述します。
var client;      /* Microsoft AI Chat Protocolのクライアント */
var messages;    /* 送信するメッセージの配列 */
const contentDiv = document.getElementById("content"); /* チャット履歴を表示する要素 */

/* 表示されたチャット履歴を初期化 */
function removeAllChildren(element) {
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
}
CSSインラインに以下を記述します。
.bubble-left {
  max-width: 70%;              /* 吹き出しの幅 */
  padding: 10px 15px;          /* 内側の余白 */
  margin: 10px 0;              /* 吹き出しの上下の間隔 */
  background-color: #e1f5fe; /* 吹き出しの背景色 */
  text-align: left;            /* 文字を左寄せ */
  position: relative;          /* 吹き出しの位置を相対的に */
  border-radius: 15px 15px 15px 15px; /* 角を丸める */
}

.bubble-right {
  max-width: 70%;               /* 吹き出しの幅 */
  padding: 10px 15px;           /* 内側の余白 */
  margin: 10px 0;               /* 吹き出しの上下間隔 */
  background-color: #c8e6c9;  /* 吹き出しの背景色(薄い緑色) */
  text-align: left;             /* 文字は左寄せ */
  position: relative;           /* 吹き出しの位置を相対的に */
  margin-left: auto;            /* 吹き出しを右寄せ */
  border-radius: 15px 15px 15px 15px; /* 角を丸める */
}

インライン・ダイアログのリージョンを開くボタンOPENを作成し、ボタンをクリックしたときに実行される動的アクションを作成します。


ボタンOPENを押した時に、最初に以下のJavaScriptコードを実行します。メッセージの履歴を消去し、ローカルLLMとのチャットを初期化します。

以下のURLよりLM StudioのローカルLLMを呼び出します。

http://localhost:8080/v1/chat/completions
/* AI Chat Protocolのクライアントを作成する */
client = new ChatProtocol.AIChatProtocolClient(
    "http://localhost:8080/v1/chat/completions",
    {
        "allowInsecureConnection": true
    }
);

/* メッセージとチャット履歴を表示するDIV要素も初期化。 */
messages = [];
removeAllChildren(contentDiv);

初期化を行なった後に、リージョンChatを開きます


リージョンChatには、チャットのやり取りを表示するリージョンとしてContent、ユーザーのメッセージを入力するページ・アイテムとしてP1_MESSAGE、メッセージをローカルLLMに送信するボタンSUBMIT、および、ダイアログを閉じるボタンCLOSEを作成します。

リージョンContent静的コンテンツとして作成し、ソースHTMLコードとして以下を記述します。

<div id="content"></div>

修飾を最低限にするため、外観テンプレートとしてBlank with Attributesを選択します。CSSクラスh400を設定し、リージョンの高さを400ピクセルに固定します。

詳細カスタム属性としてstyle="overflow-y: scroll;"を設定し、400ピクセルで表示できないメッセージはスクロールさせて表示するようにします。


メッセージを入力するページ・アイテムP1_MESSAGEは、テキスト領域として作成しています。


ボタンSUBMITをクリックしたときに、以下のJavaScriptコードを実行します。



ボタンCLOSEをクリックしたしたときは、TRUEアクションとしてリージョンを閉じるを呼び出すようにします。


以上でアプリケーションは完成です。実行すると記事の先頭のGIF動画のように動作します。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-microsoft-ai-chat-protocol-call-local-llm.zip

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

2024年5月27日月曜日

Oracle Database 23aiのベクトル検索を使ったOracle APEXアプリを作る

Oracle Database 23aiのベクトル検索を使うAPEXのサンプル・アプリを作ってみました。以下の処理を実装しています。
  1. ファイルをアップロードする。
  2. アップロードしたファイルからテキストを取り出す。
  3. 取り出したテキストをチャンクに分割する。
  4. 分割したチャンクよりエンべディングを生成する。
  5. 問合せ文字列から、類似度の高いチャンクを一覧する。
  6. 問合せ文字列と類似度の高いチャンクをプロンプトに組み込み、言語モデルを使って回答文を生成する。
アプリケーションは基本的なワークフローの実装を目的としていて、実用性や精度は求めません。すべてローカルで実行し、費用の発生やセキュリティ上の心配は不要にします。

作成したアプリケーションは以下のように動作します。


アプリケーションの作成と実行には、以下の環境を使っています。
  1. エンべディングの生成と言語モデルの実行に、LM Studioを使います。
  2. 言語モデルとして、mmnga/ELYZA-japanese-Llama-2-7b-fast-instruct-ggufをロードします。
  3. エンべディングには、nomic-ai/nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.f16.ggufをロードします。
  4. Oracle Database 23aiはMacbook上のDocker/Colimaのx86_64エミュレーション環境で実行します。非常に遅いです。
  5. 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は以下になります。



後はDDLを実行し、2つの表を作成します。


アプリケーションの作成を開始します。

名前Sample Vector Searchとし、アプリケーションの作成ウィザードを使用します。


アプリケーション作成ウィザードが開きます。

ページの追加をクリックし、表EBMJ_FILESフォーム付き対話モード・レポートのページを追加します。


対話モード・レポートを選択します。


ページ名Filesとします。表またはビューとしてEBMJ_FILESを選択し、フォームを含めるチェックを入れます。

ページの追加をクリックします。


EBMJ_FILESをソースとしたフォーム付き対話モード・レポートのページが追加されます。


同様の手順で、表EBMJ_CHUNKS対話モード・レポートのページを追加します。手作業での編集は行わないため、フォームは付けません。

ページ名Chunks表またはビューとしてEBMJ_CHUNKSを選択します。

ページの追加をクリックします。


以上のページ構成で、アプリケーションの作成をクリックします。


アプリケーションが作成されます。

これから、それぞれのページに修正を加えていきます。

最初にページ番号の表EBMJ_FILES対話モード・レポートを修正します。

ページ・デザイナで開きます。


アップロードしたファイルから取り出したテキストを保存する列TEXTは長文なので、レポートでは表示できません。コメント・アウトして変更を保存します。


ページ番号の、表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に保存します。



以上で、ファイルのアップロードからエンべディングを生成するまでの、一連の処理が実装できました。

アプリケーションを実行し、いくつかファイルをアップロードします。

その前にページ番号の表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といった外部サービスを呼び出す必要があるでしょう。


最後にホーム・ページに、問合せ文字列から類似検索したチャンクを含めたプロンプトを作成し、言語モデルを呼び出して回答文を生成する機能を実装します。

ページ・デザイナで、ページ番号ホーム・ページを開きます。

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のアプリケーション作成の参考になれば幸いです。