2024年4月25日木曜日

OpenAI Chat Completions APIのツール呼び出しを使ってPL/SQLファンクションを呼び出す

こちらの記事「OpenAIのChat Completions APIを呼び出すAPEXアプリを作成する」で作成したAPEXアプリケーションに、ツール呼び出し(以前のFunction Calling)の機能を追加します。このアプリケーションが呼び出しているパッケージUTL_OPENAI_CHAT_APIには、すでにツール呼び出しをハンドリングするコードが含まれているため、主な作業はユーザー・インターフェースの追加になります。

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

プロンプトとして「あなたはお天気お兄さんです。」を設定し、メッセージ「現在の東京とパリの天候を教えてください。」を送信します。

LLMはtool_callsとして引数をTokyoとしたツール呼び出しと、Parisを引数としたツール呼び出しを返します。ツールの並列呼び出しは2023年11月に追加された機能です。

TokyoとParisの現在の天候をOpenMeteo.comに問合せて回答を返し、最終的に「現在の東京とパリの天候はどちらも晴れです。」という回答を得ています。

以下より追加した実装について紹介します。

はじめに呼び出すツールに関する設定を保存する表OPENAI_TOOLSを操作するために、対話モード・レポートとフォームのページを作成します。

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


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


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

データ・ソース表/ビューの名前OPENAI_TOOLSを選択します。

へ進みます。


主キー列1ID(Number)を選択します。

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


以上でツールの設定画面が作成できました。

ツール呼び出しに登録するツールは、PL/SQLのストアド・ファンクションとして作成します。OpenAIではLLMのレスポンスとしてツール呼び出し(roletool_calls)を返す時は、呼び出すファンクション名とその引数となるJSONドキュメントをレスポンスに含めます。

レスポンスとしてツール呼び出しを受け取った場合は、ファンクション名として返されたPL/SQLファンクションにJSONドキュメントを引数として渡して、動的にファンクションを呼び出します。呼び出したファンクションの戻り値(これもJSONドキュメント)をツールの応答(roletool)としてLLMに戻します。

ページを実行し、指定した都市(首都)の天候を取得するファンクションget_current_weatherを設定します。

作成をクリックします。


Tool SetWeatherとします。Tool Typefunctionを指定します。現時点で指定できるTool Typeはfunctionのみのようです。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を作成します。

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

また、weathercodeについては「気象関連コード表」に記載されているWMO 4501のHTML表を表WMO4501、WMO 4677のHTML表を表WMO4677として、データをロードします。双方の表は列CODE(NUMBER)、列DESCRIPTION(VARCHAR2(4000))の列を持ちます。データ・ワークショップを使ってHTML表をコピペし、それぞれの表にロードします。

ファンクションget_current_weatherの作成は以上です。

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

リージョンSystem Promptに、チャット呼び出しの際に含めるTool Setを選択するページ・アイテムを作成します。

識別名前P1_TOOL_SETタイプ選択リストラベルTool Setとします。

LOVタイプSQL問合せを選択し、SQL問合せとして以下を記述します。
select tool_set d, tool_set r from openai_tools group by tool_set
追加値の表示オフNULL値の表示オンとし、NULL表示値-- Select Tool Set --とします。


左ペインでプロセス・ビューを開き、プロセスSend Messageのパラメータp_tool_setを選択します。

タイプアイテムに変更し、アイテムとしてP1_TOOL_SETを指定します。


以上でツール呼び出しの組み込みは完了です。

アプリケーションを実行すると、記事の先頭にあるGIF動画のように動作します。

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

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