2024年4月17日水曜日

Llama.cppでCommand R+をローカルのMacbookで動かしAPEXアプリから呼び出す

巷で話題のCohere(Cohere For AI)のCommand R+を手元のMacbookで動かして、同じく手元のMacbookで動かしているAPEXアプリからアクセスしてみました。

動かしたMacbookのスペックは少し古いですが、M1 Max (64GB)です。

Apple MシリーズのマシンでLlama.cppを使ってCommand R+を動かしてみようと思い立ったきっかけは、npakaさんの以下の記事です。

Llama.cpp で Command R+ を試す

npakaさんの記事ではM3 Max (128GB)とのことです。実行するマシンがそれよりスペックが低いので、4ビット量子化のモデルの代わりに2ビット量子化のモデル(Q2_K - command-r-plus-Q2_K.gguf)を使っています。

以下のように動作しました。


APEXの環境はColimaを使って実行しています。

Oracle Database 23c FreeのコンテナだけでOracle APEXを実行する
データベース単体のコンテナへのOracle APEXのインストールを自動化する

以下の記事で作成したAPEXアプリケーションを使っています。

llama_cpp.serverをAmpere A1のインスタンス上で動かしてみる
https://apexugj.blogspot.com/2023/07/llamacppserver-on-ap.html

APIサーバーは以下のコマンドで実行しています。

Llama.cppをコンパイルしたディレクトリより、

./server --model models/command-r-plus-Q2_K.gguf -c 2048

APEXアプリケーションでは置換文字列G_SERVERとして、APIのエンドポイントを設定します。Dockerコンテナからホストで動作しているサーバーを呼び出すため、サーバーとしてhost.docker.internalを指定します。

結果としてG_SERVERの置換値はhttp://host.docker.internal:8080になります。


API呼び出しができるように、データベースにACEを追加します。
begin
  DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
    host => '*',
    ace => xs$ace_type(
        privilege_list => xs$name_list('connect'),
        principal_name => 'APEX_230200',
        principal_type => xs_acl.ptype_db));
  commit; 
end;
/

% docker exec -it apexdb sqlplus sys/********@localhost/freepdb1 as sysdba


SQL*Plus: Release 23.0.0.0.0 - Production on Wed Apr 17 08:44:29 2024

Version 23.3.0.23.09


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



Connected to:

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

Version 23.3.0.23.09


SQL> begin

  DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(

    host => '*',

    ace => xs$ace_type(

        privilege_list => xs$name_list('connect'),

        principal_name => 'APEX_230200',

        principal_type => xs_acl.ptype_db));

  commit; 

end;

/  2    3    4    5    6    7    8    9   10  


PL/SQL procedure successfully completed.


SQL> exit

Disconnected from Oracle Database 23c Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free

Version 23.3.0.23.09

% 


今回の作業は以上です。

Dockerコンテナで動かしたAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/chat-gpt-app-docker.zip

MLXでも試してみたのですが、そちらの方はCommand R+を動かすにはリソースが足りず動きませんでした。Llama.cppではかろうじて動く感じです。



チャートのプロパティを動的に変更する

Oracle APEXはチャートを描画するコンポーネントとして、Oracle JETを使用しています。Oracle JETのコンポーネントが持つプロパティを変更することにより、チャートの描画条件を動的に変更することができます。

簡単な棒グラフを表示するチャート・リージョンを作成し、Y軸の最大値を動的に変更してみます。

確認のために以下のように動作するアプリケーションを作成します。


チャートを表示するソースとして、サンプル・データセットEMP/DEPTに含まれる表EMPを使用します。

空のアプリケーションを作成し、ページ作成ウィザードを起動します。


作成するページのタイプとしてチャートを選択します。


チャートのタイプとしてバーを選択します。


ページ番号2、ページの名前Salaryとします。データ・ソース表/ビューの名前としてEMPを指定します。

へ進みます。


向き垂直ラベル列としてENAME(Varchar2)値列としてSAL(Number)を選択します。

ページの作成をクリックします。


チャート・リージョンを含んだページが作成されます。

作成されたページを実行します。


バー・チャートのY軸の最大値はデフォルトでは指定が無く、最大値は自動的に調整されます。

以下の例では最も多いサラリーが5000なので、最大値は6000になっています。


Y軸の最大値は、Y軸の最大として設定します。

最大10000に設定し、チャートの表示を確認してみます。


Y軸の最大値が10000になります。


ページ・デザイナで設定するY軸の最大はアプリケーションの開発時に設定し、アプリケーションの実行中に変更することはできません。

以下より、この値をアプリケーションから変更します。

最初にY軸の最大値を保持するページ・アイテムを作成します。

識別名前P2_MAX_Yタイプテキスト・フィールドとします。ラベルMax Yとします。設定[Enter]を押すと送信オンにし、Enterの入力でY軸の最大値を確定します。

ページの送信後にP2_MAX_Yの値が維持されるように、セッション・ステートストレージセッションごと(永続)を選択します。


チャートに限りませんが、大抵のOracle APEXに含まれるJavaScriptによって実装されたコンポーネントは、初期化JavaScriptファンクションを記述できます。

チャート・リージョンの初期化JavaScriptファンクションに以下を記述し、設定可能なオプションを確認します。
function( options ) {
    console.log( options );
}

アプリケーションを実行(ページを再ロード)するとチャートが初期化されるので、初期化JavaScriptファンクションが実行されます。

JavaScriptコンソールの出力を確認すると、yAxismaxとして10000が設定されていることが確認できます。


ページ・アイテムP2_MAX_Yに指定された値をY軸の最大とするように、初期化JavaScriptファンクションを書き直します。
function( options ) {
    // console.log( options );
    if ( apex.items.P2_MAX_Y.value ) {
        options.yAxis.max = apex.items.P2_MAX_Y.value;
    }
    return options;
}
Y軸の最大値を指定しEnterを押すと(ページが送信された後、ページが再描画され)、チャートのY軸の最大値が変更されます。


ページの送信をせずにチャートのY軸の最大を変更するには、JavaScriptのコードを実行しコンポーネントの属性を直接変更します。

チャート・リージョンをJavaScriptのコードから参照するため、静的IDを割り当てます。以下ではチャート・リージョンの静的IDとしてSALARYを割り当てています。


ページ・アイテムP2_MAX_Yに動的アクションを作成し、値が変更されたときに以下のJavaScriptコードを実行します。
apex.region("SALARY").widget().ojChart( { "yAxis" : { "max" : apex.items.P2_MAX_Y.value }});

動的アクションは値の変更が認識されれば実行されます。値の確定にEnterの入力の代わりにタブを入力することで、動的アクションによるY軸の最大値の変更を確認できます。


Y軸の最大を変更するにあたって動的アクションを使うような場合は、ページ・アイテムP2_MAX_Yセッション・ステートストレージとしてリクエストごと(メモリーのみ)を選択することができます。


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

現行のOracle APEXが組み込んでいるOracle JETのコンポーネントは、ほぼチャートのみです。Oracle JETのチャートについてのドキュメントは、以下より参照できます。


Oracle APEXにバンドルされているOracle JETのバージョンは、リリース・ノートを確認するか、またはチャートを組み込んだページがロードしているOracle JETのファイルから確認します。ロードされるファイルのパスに、組み込まれているOracle JETのバージョンが含まれています。


バージョンを選択し、Cookbookを開きます。


今回例に採用したバー・チャートは、VisualizationsChartに含まれます。


Bar Chartを開きます。


バー・チャートのプロパティを確認するためAPI DocojChartを開きます。


Element oj-chartが開きます。Attributesからy-axisを探します。


y-axisを開きます。


oj.ojChart.YAxisを開きます。


YAxisとして設定可能なプロパティの一覧が表示されます。プロパティmaxについて、ここで確認することができます。


簡単なアプリケーションですが、今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-jet-chart-property-change.zip

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

2024年4月11日木曜日

テーマ・ローラーによるレイアウト調整とページ別設定について

X(旧Twitter)を見ていたら、テーマ・ローラーレイアウトボディ・コンテンツの最大幅という設定があるけど、これをページごとに設定するにはどうしたら?というポストがありました。

まず、テーマ・ローラーレイアウトで調整できる項目を確認してみます。以下の項目があります。
  • ヘッダーの高さ
  • ナビゲーション・ツリー
  • アクション列
  • 左側の列
  • ボディ・コンテンツの最大幅

ヘッダーの高さは、名前の通りヘッダーの高さを設定します。


ナビゲーション・ツリーは、左にあるナビゲーション・メニューの幅を設定します。


アクション列は、ページ・テンプレートLeft and Right Side Columnsを選んだときの右側のカラムの幅を設定します。


左側の列は、ページ・テンプレートLeft Side ColumnまたはLeft and Right Side Columnsを選んだときの左側のカラムの幅を設定します。


そしてボディ・コンテンツの最大幅は、中央の本文領域の最大幅を設定します。


テーマ・ローラーで設定するとアプリケーション全体に適用されます。これをページ単位で設定するには、ページ・プロパティCSSインラインに以下を記述します。
:root {
    --ut-body-content-max-width: 768px;
}
以上でページ単位でボディ・コンテンツの最大幅が設定できます。


Oracle APEXをずいぶん長く使っている海外の開発者でも「知らなかった」とコメントしていました。Universal Themeのリファレンスに書かれていないので、知らないとしても仕方がないと思います。

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

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

複合主キーを持つ表のメンテナンスを行なう

複合主キーが設定されている表のメンテナンスを行なう画面を作成してみます。以下のスキーマ定義を例に取ります。

TEST_CATEGORIESにカテゴリーのコード(列CATEGORY_CD)と名前(列CATEGORY_NAME)を定義し、表TEST_PARTSの主キーは、表TEST_CATEGORIESに定義されているCATEGORY_CDPART_IDの複合主キーとして定義します。

create table test_categories (
    category_cd      varchar2(2 char) not null,
    category_name    varchar2(80 char) not null,
    constraint test_categories_pk primary key(category_cd)
);

create table test_parts (
    category_cd    varchar2(2 char)
                   constraint test_parts_category_cd_fk references test_categories,
    part_id        varchar2(2 char) not null,
    part_name      varchar2(80 char) not null,
    constraint test_parts_pk primary key(category_cd, part_id)
);
空のAPEXアプリケーションを作成します。名前複合主キーとします。


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

最初に表TEST_CATEGORIESのメンテナンスを行なう対話モード・レポートとフォームのページを作成します。

ページの作成をクリックします。


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


対話モード・レポートのページの名前Categoriesとします。フォーム・ページを含めるオンにします。フォーム・ページ名Categoryとします。

データ・ソースは表TEST_CATEGORIESですが、主キー列CATEGORY_CDには、データを登録する人が意味のあるコードを割り当てます(いわゆる自然キーまたはナチュラルキー)。

Oracle APEXのページ生成ウィザードは主キーは自動生成される(いわゆる代理キーまたはサロゲートキー)ことを前提としてるため、データ・ソースに表TEST_CATEGORIESを指定し、主キーとしてCATEGORY_CDを選択すると、対話モード・レポートでは列CATEGORY_CDは非表示列となり、また、フォームのページでは列CATEGORY_CDに対応するページ・アイテムが非表示かつ変更不可になります。

ここでは、主キー列CATEGORY_CDを通常の列と同様に扱えるように、ROWIDを主キーとして扱うように対話モード・レポートとフォームを構成します。

データ・ソースソース・タイプSQL問合せを選択し、SQL SELECT文を入力に以下を記述します。

select rowid, category_cd, category_name from test_categories

主キー列CATEGORY_CDは通常の列と同じ扱いになるため、作成されたフォームでは一度割り当てたコードを変更することも可能です。一度割り当てたコードを変更させたくない場合などは、フォームのページが作成された後にページ・アイテムのタイプ表示のみにするか、読取り専用の条件を適用します。

へ進みます。


主キー列1ROWID(Rowid)を選択します。

ページの作成をクリックします。


TEST_CATEGORIESを編集するページが作成されました。


同様に、表TEST_PARTSのメンテナンスを行なう対話モード・レポートとフォームのページを作成します。

データ・ソースのSELECT文は以下になります。

select rowid, category_cd, part_id, part_name from test_parts


主キー列1は表TEST_CATEGORIESと同様にROWID(Rowid)を選択し、ページの作成を実行すると、表TEST_PARTSを編集するページが作成されます。


カテゴリーのコードと名前のLOVを、共有コンポーネントとして作成します。

共有コンポーネントLOVを開きます。


作成をクリックします。


LOVの作成最初から行います。そのままへ進みます。


作成するLOVの名前CATEGORIES_LOVとします。タイプDynamicを選択します。

へ進みます。


LOVソース表/ビューの名前としてTEST_CATEGORIESを選択します。

へ進みます。


列マッピング戻り列としてCATEGORY_CD表示列としてCATEGORY_NAMEを選択します。

LOVを作成します。


LOVとしてCATEGORIES_LOVが作成されました。


カテゴリのコードと名前を入れ替るLOVが作成されたので、表TEST_PARTSの対話モード・レポートとフォームのページに使用します。

TEST_PARTS対話モード・レポートのページを開き、列CATEGORY_CDを選択します。

識別タイププレーン・テキスト(LOVに基づく)に変更します。LOVに先ほど作成したCATEGORIES_LOVを選択します。ヘッダーはCategory CdだったのでCdを除いてCategoryのみに変更します。


TEST_PARTSフォームのページを開き、ページ・アイテムP5_CATEGORY_CDを選択します。

識別タイプポップアップLOVに変更します。ラベルCategoryとします。LOVタイプ共有コンポーネントを選択し、LOVとしてCATEGORIES_LOVを設定します。

追加値の表示オフNULL値の表示オンにしてNULL表示値として- Select Category -を記述します。


以上で複合主キーを持つ表TEST_PARTSのメンテナンス画面が作成できました。

アプリケーションを実行し、実際にデータの入力を行います。

Categoriesのページを開き、以下のデータを作成します。

コード CL - 名前 Cloud
コード DB - 名前 Database


表TEST_PARTSにデータを作成します。

対話モード・レポートの列Categoryには、CATEGORIES_LOVよりCATEGORY_NAMEの値が表示されています。


一般的には、ソースのSELET文に複合主キーをそのまま表示する列を追加することが多いように思います。

対話モード・レポートのソースSQL問合せを以下に変更します。

select rowid, category_cd || part_id part_cd, category_cd, part_id, part_name from test_parts


ソースのSELECT文に後から追加した列は非表示列になるため、アクションから表示列に追加する必要があります。追加した列Part Cdは以下のように表示されます。

フォームを開くリンクはROWIDを引数としているため、対話モード・レポートの列の追加や修正の影響は受けません。


編集フォームのCategoryはポップアップLOVに変わっています。


複合主キーであってもフォームとしての扱いは通常の列なので、CategoryおよびPart Idの項目は変更可能です。ただし、同じ主キーが登録済みであればORA-00001の一意制約違反のエラーが発生します。


主キーとして設定されている値を変更することはあまり無いため、これらは値が設定済みであれば変更不可とすることが望ましいでしょう。

例えばページ・アイテムP5_CATEGORY_CDであれば、読取り専用タイプアイテムはNULLではないを選択し、アイテムとしてP5_CATEGORY_CDを指定します。


このように設定することにより、一度登録した部品についてはカテゴリを変更不可にできます。


Oracle APEXのページ作成ウィザードを使って、表TEST_PARTSの対話モード・レポートとフォームを作ると、どのようになるか確認してみます。

ROWIDを使ったページと見分けるために、ページ名は大文字にします。対話モード・レポートのページはPARTS、フォームのページはPARTです。

データ・ソース表/ビューの名前としてTEST_PARTSを指定します。


主キー列1CATEGORY_CD(Varchar2)主キー列2PART_ID(Varchar2)となります。


作成された対話モード・レポートのページを確認します。

CATEGORY_CDについては、タイププレーン・テキスト(LOVに基づく)LOVとしてCATEGORIES_LOVが設定されています。主キー列のタイプはデフォルトで非表示列ですが、複合種キーの場合は異なるのかもしれません。


PART_IDについてはタイプ非表示列となっていたため、表示されるようにプレーン・テキストに変更します。


フォームを開く際に、一意検索をするために主キー列である列CATEGORY_CDPART_IDの値が必要です。このとき、列CATEGORY_CDの表示値がCATEGORY_CDの値として渡されます。表示値ではなく戻り値を引数として渡すために、レポートのソースに列CATEGORY_CDの値をそのまま参照するための列を追加します。

対話モード・レポートのソースタイプSQL問合せに変更し、SQL問合せとして以下を記述します。列CATEGORY_CD_Vを追加しています。
select CATEGORY_CD,
       PART_ID,
       PART_NAME,
       CATEGORY_CD CATEGORY_CD_V
  from TEST_PARTS

追加した列CATEGORY_CD_Vタイプ非表示列にします。


対話モード・レポートの属性を開き、リンクターゲットを修正します。


ページ・アイテムP7_CATEGORY_CDに渡す値を\#CATEGORY_CD_V#\に変更します。


以上で対話モード・レポートについては、複合主キーの列もレポートに表示されるようになりました。


編集アイコンをクリックしてフォームを開くと、表示されるのはPart Nameのみです。主キーのページ・アイテムのタイプはデフォルトで非表示になります。


ページ・アイテムP7_CATEGORY_CDP5_CATEGORY_CDと同じく、ポップアップLOVとして設定します。


ページ・アイテムP7_CATEGORY_CDは主キー項目なので、ソース主キーオンセキュリティセッション・ステート保護チェックサムが必要 - セッション・レベルが設定されています。この設定はページ生成ウィザードによって行われます。ページ・アイテムP7_PART_IDも同様の設定になっています。


初期化フォームのプロセスは主キーがオンになっているページ・アイテムの値を、一意検索の条件とします。そのため複合主キーを構成する列に対応するページ・アイテムは、主キーオンである必要があります。


ページ・アイテムP7_PART_IDも表示されるよう、タイプテキスト・フィールドに変更します。ラベルPart Idとし、検証必須の値オンにします。


以上の変更で、ROWIDを使ったメンテナンス画面とほぼ同様な画面が作成できました。

ただし、主キー列の指定が異なるため、ROWIDを使った場合と複合主キーを使った場合では、特に更新処理で発生するエラーが異なります。主キーをオンにしたページ・アイテムの値は変更できません。ページ・アイテムとして変更ができても、更新時にエラーが発生するか更新処理が空振りします。

すでに主キーが登録済みの行がある場合は、エラーが発生します。


これは行の自動処理のプロセスの失われた更新の防止によって保護されています。逆に言うと、これをオフにすると既存の行が上書きできてしまいます。


主キーが存在しない場合は、更新処理が空振りします。これはUPDATE文に以下のような条件句がついているためです。対象行が存在しないため、更新処理は発生しません。

WHERE CATEGORY_CD = :P7_CATEGORY_CD and PART_ID = :P7_PART_ID

主キーがROWIDのときは、ROWIDが条件句に現れるため、列CATEGORY_CDおよびPART_IDの更新は可能です。

今回の記事は以上です。

説明に使用したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-composite-key-form.zip

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