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: 転送がタイムアウトしましたであることがわかります。


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

追記終了