2024年10月23日水曜日

Oracle APEXのアプリよりOllamaのTool support を呼び出す

ある程度のスペック(32GB以上のメモリなど)のスペックは必要ですが、Apple Silicon(MシリーズのCPU)搭載のMacであれば、ローカルでLLMを実行できます。

さらにLM StudioもしくはOllamaを使用することで、OpenAI互換のAPIサーバーを実行することができます。つまり、APIのエンドポイントを変更することにより、コードを変更せずにOpenAIとローカルLLMを切り替えることができます。

また、オラクルよりARM64版のOracle Database 23ai Freeがリリースされたことにより、Oracle APEXを手元のMacで実行できるようになりました。以前はIntelのエミュレータ(Colima)での実行だったため、非常に遅くアプリケーションの開発に使うには苦しい状況でした。ARM64のOracle Database 23ai Freeで作成したOracle APEX環境では、ほとんどストレスなくアプリケーション開発を行えます。

Ollamaのブログにて、Tool supportについて案内されていました。いわゆるOpenAIのFunction Callingです。OllamaのOpenAI互換サーバーでも、OpenAIと同様のメッセージの仕様がサポートされているようです。

以下より、Apple MシリーズのMacでOracle APEXを動かし、OllamaのローカルLLMのTool supportを確認してみます。

最終的に、以下のGIF動画のように動作しました。


Apple MシリーズのMacでOracle APEXの環境を作る手順については、以下の記事を参考にします。

Oracle Database 23ai FreeとOracle REST Data Servicesともに、コンテナ・イメージを使ってOracle APEXの環境を作成します。APEXワークスペースもスクリプトを使って作成します。
[参考記事] podmanを使ってOracle Database FreeとOracle REST Data Servicesをコンテナとして実行する
[参考記事] APEXのワークスペースを作成するコードを記述する

Oracle APEXの環境を作成する方法としては、VirtualBoxを使う方法や、Oracle Database 23ai FreeのコンテナにORDSを実装する方法も紹介しています。今回は、これらの手順は採用しません。
[参考記事] Apple M Series + VirtualBox + Oracle Database Freeの組み合わせでOracle APEXを構成する
[参考記事] Apple M SeriesのpodmanでOracle Database Freeのコンテナを作成しOracle APEXを実行する

OpenAIのAPIを呼び出すAPEXのサンプル・アプリケーションの作成に関する記事です。

OpenAIのChat Completions APIを呼び出すAPEXアプリケーションの作成記事です。

上記のアプリケーションにツール呼び出し(ファンクション・コーリング)の実装を追加しています。
[参考記事] OpenAI Chat Completions APIのツール呼び出しを使ってPL/SQLファンクションを呼び出す

これまでのアプリに、ローカルLLMへの対応を追加しています。Ollamaで動作を確認しています。この記事で紹介されているAPEXアプリケーションを、本記事では使用します。
[参考記事] Oracle APEXのアプリからいろいろなローカルLLMを呼び出してみる
アプリケーションのエクスポート:
https://github.com/ujnak/apexapps/blob/master/exports/chat-with-generative-ai-hc.zip

LM Studioで動作確認をした記事です。本記事ではLM Studioは使いませんが、Oracle APEXの環境作成から、表OPENAI_TOOLSの作成およびパッケージUTL_OPENAI_CHAT_APIまでは同じ手順になります。

以下より、実際の作業について紹介します。Oracle APEXの環境、ワークスペースの作成およびOllamaのインストールまでは完了しているところから始めます。

最初に表OPENAI_TOOLSを作成します。

CREATE TABLE "OPENAI_TOOLS"
( "ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
"TOOL_SET" VARCHAR2(20 CHAR) NOT NULL ENABLE,
"TOOL_TYPE" VARCHAR2(20 CHAR) DEFAULT ON NULL 'function' NOT NULL ENABLE,
"DESCRIPTION" VARCHAR2(4000 CHAR) NOT NULL ENABLE,
"TOOL_NAME" VARCHAR2(80 CHAR) NOT NULL ENABLE,
"PARAMETERS" CLOB,
CHECK (parameters is json) ENABLE,
CONSTRAINT "OPENAI_TOOLS_ID_PK" PRIMARY KEY ("ID")
USING INDEX ENABLE
) ;

単一のコマンドなのでSQLコマンドに貼り付けて実行します。複数のコマンドの場合は、SQLスクリプトを作成して実行します。


パッケージUTL_OPENAI_CHAT_APIについても、同様にSQLコマンドに貼り付けて作成します。

パッケージ定義
https://gist.github.com/ujnak/93c9d427ccef53e30dd89e623f08c880
パッケージ本体
https://gist.github.com/ujnak/a6126f7e9ee264d0e384103cea0379b5

エクスポートされたアプリケーションをダウンロードし、ワークスペースにインポートします。
https://github.com/ujnak/apexapps/blob/master/exports/chat-with-generative-ai-hc.zip

アプリケーション・ビルダーからインポートを実行します。


インポートするファイルとしてchat-with-generative-ai-hc.zipを選択します。

へ進みます。


アプリケーションのインストールを実行します。


アプリケーションがインポートされました。

接続先となるLLMを設定するため、アプリケーションの編集をクリックします。


アプリケーションの編集画面が開きます。

アプリケーション定義の編集を開きます。


アプリケーション定義置換タブを開きます。

置換文字列のG_API_ENDPOINTの置換値は以下です。
http://host.containers.internal:11434/v1/chat/completions

G_MODEL_NAMEにはllama3.2を設定します。Ollamaでツール呼び出しをサポートしているモデルです。これらの値はアプリケーション上で変更できます。


設定の変更を適用し、アプリケーションを実行します。

記事「OpenAI Chat Completions APIのツール呼び出しを使ってPL/SQLファンクションを呼び出す」に記載されている、指定した都市(首都)の天候を取得するファンクションget_current_weatherを設定します。

Toolsページを開き、作成をクリックします。


Tool SetWeatherとします。Tool Typefunctionを指定します。DescriptionGet the current weather in a given locationTool Nameget_current_weatherParametersには以下のJSON Schemaを記述します。
{
  "type": "object",
  "properties": {
    "location": {
      "type": "string",
      "description": "The city name, e.g. Tokyo"
    }
  },
  "required": [
    "location"
  ]
}
以上で、作成をクリックします。


PL/SQLファンクションget_current_weatherが、呼び出し可能なツールとして登録されました。


実際に呼び出されるPL/SQLファンクションget_current_weatherを作成します。

都市名から緯度経度を求めるために、アマノ技研様が公開している「世界の百万都市の位置データ Location Data of Megacities」のCSVデータをダウンロードし、表AMANO_CITY_LOCATIONS(新規作成)にロードしています。データのロードには、SQLワークショップデータ・ワークショップを使用しています。

ダウンロードしたファイルasti-dats59r03wm.zipに含まれるs59r03megacities_utf8.csvを、新規表AMANO_CITY_LOCATIONSにロードします。


また、weathercodeについては「気象関連コード表」に記載されているWMO 4501のHTML表を表WMO4501、WMO 4677のHTML表を表WMO4677として、データをロードします。双方の表は列CODE(NUMBER)、列DESCRIPTION(VARCHAR2(4000))の列を持ちます。以下のDDLを実行して表を作成します。
create table wmo4501(code number primary key, description varchar2(4000));
create table wmo4677(code number primary key, description varchar2(4000));
データ・ワークショップを呼び出し、HTML表をコピペします。


既存表を選択し、コピペした表に応じてWMO4501またはWMO4677をロード先のとして選択します。表を選択した後、構成をクリックします。


ソース列COL001のマップ先としてCODE(Number)COL002マップ先としてDESCRIPTION(Varchar2)を選択します。

変更の保存をクリックします。


構成を決定したのち、データのロードをクリックします。


表WMO4501にデータがロードされます。


表WMO4677についても同様に、WebページからHTML表をコピペしたデータを表WMO4677にロードします。

WMO4677については、00から99までのコードと説明をコピペします。表へのロード手順はWMO4501と同じです。


ファンクションget_current_weatherのコードは以下です。

create or replace function get_current_weather(
p_args in clob
)
return clob
as
l_args json_object_t;
l_location varchar2(400);
l_location_tokens apex_t_varchar2;
l_lat number;
l_lon number;
l_url varchar2(4000);
l_response clob;
l_response_json json_object_t;
l_weathercode number;
l_weather varchar2(4000);
begin
l_args := json_object_t(p_args);
l_location := l_args.get_string('location');
/* get first part (city name) from the location */
l_location_tokens := apex_string.split(l_location, '[ ,]');
l_location := l_location_tokens(1);
apex_debug.info('location = %s', l_location);
/*
* 都市名から緯度経度の情報を取り出す。
* アマノ技研さんより提供されている「世界の百万都市の位置データ Location Data of Megacities」の
* CSVファイルを表AMANO_CITY_LOCATIONSにロードしています。
* 参照: https://amano-tec.com/data/megacities.html
*/
select lat, lon into l_lat, l_lon
from amano_city_locations
where upper(capital_en) = upper(l_location) or capital_jp = l_location;
/*
* Open-Meteo.comのAPIを呼び出し、指定した座標の現在の天気を取得しています。
* 参照: https://open-meteo.com
*/
l_url := 'https://api.open-meteo.com/v1/forecast?latitude=' || l_lat || '&longitude=' || l_lon || '&current=weathercode';
l_response := apex_web_service.make_rest_request(
p_url => l_url
,p_http_method => 'GET'
);
if apex_web_service.g_status_code <> 200 then
return '{ "error": "open-meteo.com api call failed, response = ' || l_response || '" }';
end if;
l_response_json := json_object_t(l_response);
/*
* Weather Code (WMO 4501)のコードの説明は以下の気象関係コード表のWMO 4501の部分を
* 表WMO4501として読み込んでいます。
* 参照: https://www.jodc.go.jp/data_format/weather-code_j.html
*
* たまにWMO 4677を返しているのでは?と思えるときもあります。なのでコードが10以上の場合は、WMO4677として
* 取り扱います。
*/
l_weathercode := l_response_json.get_object('current').get_number('weathercode');
if l_weathercode < 10 then
select description into l_weather from wmo4501 where code = l_weathercode;
else
select description into l_weather from wmo4677 where code = l_weathercode;
end if;
return '{ "weather" : "' || l_weather || '" }';
exception
when no_data_found then
return apex_string.format('{ "error": %s }', l_response);
end get_current_weather;
/

これをSQLコマンドに貼り付けて実行します。


以上で、ファンクションget_current_weatherを呼び出せるようになりました。

アプリケーションを実行し、動作を確認してみます。

llama3.2は日本語を(あまり)学習していないとのことなので、確認は英語で行います。

ボタンStart New Conversationをクリックし、チャットを初期化します。Tool SetWeatherを選択し、Promptとして「You are a weather forecaster.」を記述します。

以上でボタンSet Promptをクリックします。

systemメッセージとして、You are a weather forecaster. が設定されます。この時点では、OllamaのAPI呼び出しは行われていません。


Messageに「Could you tell me the current weather in Tokyo?」と記述して、ボタンSend Messageをクリックします。


チャット履歴より、正常にツールが呼び出されていることが確認できます。


以上で、Ollamaを使ったローカルLLMでのツール呼び出しの確認は終了です。

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