新しい記事で更新されています。
OpenAI Chat Completions APIのツール呼び出しを使ってPL/SQLファンクションを呼び出すhttps://apexugj.blogspot.com/2024/04/chat-with-generative-ai-fc-sample-app.html
以下は過去の内容です。
以下より追加した実装について紹介します。
ファンクションget_current_weatherが登録されました。このget_current_weatherは必ずPL/SQLのファンクションとして、CLOBの引数が1つ(JSONが渡されることを想定)、戻り値はCLOB(こちらもJSONが返されることを想定)であることを前提としています。
Functionとして先ほど作成したget_current_weatherを選択します。Function Set NameはWeatherとします。
ファンクション・セットWeatherが登録されました。
以上でFunction Callingの機能追加は完了です。アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。
以下は過去の内容です。
以前にOpenAIのChatGPTのAPIを呼び出すサンプル・アプリケーションを作成しました。そのときの記事は「OpenAIのChatGPTのAPIを呼び出すAPEXアプリを作る」です。
今回は、以前の記事で作成したAPEXアプリケーションにFunction Callingを扱うコードを追加してみました。
OpenAIのブログ記事「Function calling and other API updates」を元にサンプルを実装しています。この記事では、都市を指定して現在の天気を質問しています。OpenAIの記事での質問は「What is the weather like in Boston?」ですが、今回作成したサンプル・アプリケーションには、「現在の東京の天気を教えてください。」と質問しています。
最初に質問を投げたときは、Function Set(これはOpenAIのAPIの引数ではなく、今回のサンプル・アプリケーションで作った設定です)の指定が無いため、API呼び出しに属性functionsが含まれず、ChatGPTからは「申し訳ありませんが、私は情報を提供することができません。」と返されています。2回目はFunction Callingが行われて外部の天気APIサービス(Open-Meteo.comを呼び出しています)より現在の東京の天気を取得しているため、「現在の東京の天気は「晴れ」です。」と回答されています。
上記のAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/chatgpt-app-fc.zip
最初にfunction callingの呼び出し対象となるファンクションを登録する表を作成します。
クイックSQLの以下のモデルより、表OPENAI_FUNCTIONSとOPENAI_FUNCTION_SETを作成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# prefix: openai | |
functions | |
function_name vc200 /nn | |
description vc4000 /nn | |
parameters json /nn | |
function_set | |
function_set_name vc80 /nn | |
function_id num /nn /fk functions |
表OPENAI_FUNCTIONSには、ファンクションの名前(APIでは属性name)、ファンクションの説明(属性description)およびファンクションのパラメータ定義(属性parameters)を定義します。手っ取り早く開発するため、parametersにはJSON形式のドキュメントをそのまま記述します。
表OPENAI_FUNCTION_SETでは、表OPENAI_FUNCTIOINSに定義したファンクションをひとまとめにします。ChatGPTのAPIを呼び出す際に、ここで設定したファンクションセット名を指定することで、そのセットに含まれるファンションをAPIの属性functionsに含めます。
今回は表OPENAI_FUNCTIONSおよびOPENAI_FUNCTION_SETにデータが投入できるアプリケーションがあればよいので、クイックSQLによる表の生成からアプリケーションの作成までを、続けて実施します。
生成されたSQLスクリプトを即時実行した後、アプリケーションの作成を開始します。次に開く確認画面でも、アプリケーションの作成をクリックします。
アプリケーション作成ウィザードが起動します。表OPENAI_FUNCTIONS、OPENAI_FUNCTION_SETのレポートとフォームのページが追加済みになっています。
アプリケーションの名前はOpenAI Function Callingとし、アプリケーションの作成を実行します。
アプリケーションが作成されます。
アプリケーションを実行し、ChatGPTから呼び出すファンクションを設定します。
OpenAIのブログ記事に従って、ファンクションget_current_weatherを定義します。
Functionsを開きます。
作成をクリックします。
Function nameとしてget_current_weather、DescriptionとしてGet the current weather in a given location、Parametersには以下のJSONドキュメントを記述します。
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": [
"celsius",
"fahrenheit"
]
}
},
"required": [
"location"
]
}
ファンクションget_current_weatherのコードは以下です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
/* | |
* 都市名から緯度経度の情報を取り出す。 | |
* アマノ技研さんより提供されている「世界の百万都市の位置データ 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 || '¤t=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 | |
*/ | |
l_weathercode := l_response_json.get_object('current').get_number('weathercode'); | |
select description into l_weather from wmo4501 where code = l_weathercode; | |
return '{ "weather" : "' || l_weather || '" }'; | |
exception | |
when no_data_found then | |
return '{ "error": "no data found" }'; | |
end get_current_weather; | |
/ |
都市名から緯度経度を求めるために、アマノ技研様が公開している「世界の百万都市の位置データ Location Data of Megacities」のCSVデータをダウンロードし、表AMANO_CITY_LOCATIONS(新規作成)にロードしています。データのロードには、SQLワークショップのデータ・ワークショップを使用しています。
また、weathercodeについては「気象関連コード表」に記載されているWMO 4501のHTML表をデータ・ワークショップにコピペし、表WMO4501(新規作成)にロードしています。
ファンクションの登録は以上です。
Function Setのページを開き、作成をクリックします。
作成をクリックします。
以前に作成したChatGPTのAPIを呼び出すアプリケーションに機能を追加します。
最初にプロシージャcall_openai_apiを作成します。以前は動的アクションのサーバー側のコードを実行にコードを直書きしていましたが、コードが長くなりすぎ制限を超えてしまいました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace procedure call_openai_api( | |
p_collection_name in varchar2 default 'CHATGPT' | |
,p_user_message in varchar2 | |
,p_model_name in varchar2 default 'gpt-3.5-turbo-0613' | |
,p_function_set_id in number default null | |
,p_credential_static_id in varchar2 default 'OPENAI_API_KEY' | |
,p_request_out out varchar2 | |
,p_response_out out varchar2 | |
) | |
as | |
l_request json_object_t; | |
l_request_clob clob; | |
l_messages json_array_t; | |
l_message json_object_t; | |
/* function callingの対応 */ | |
l_functions json_array_t; | |
l_function json_object_t; | |
l_parameters json_object_t; | |
l_function_call json_object_t; | |
l_finish_reason varchar2(4000); | |
l_function_name varchar2(200); | |
l_function_args clob; | |
l_dynamic_sql varchar2(4000); | |
l_function_out clob; | |
/* | |
* OpenAIのドキュメントより、APIの応答例を参照。 | |
* 参考: https://platform.openai.com/docs/guides/chat | |
*/ | |
l_response_clob clob; | |
l_response json_object_t; | |
l_choices json_array_t; | |
l_choice0 json_object_t; | |
l_role varchar2(80); | |
l_content clob; | |
l_usage json_object_t; | |
e_openai_api_exception exception; | |
begin | |
/* | |
* ユーザーによる直近のメッセージをコレクションに追記する。 | |
*/ | |
apex_collection.add_member( | |
p_collection_name => p_collection_name | |
,p_c001 => 'user' | |
,p_clob001 => p_user_message | |
); | |
/* | |
* APEXコレクションより、作成時刻の昇順でメッセージの配列にする。 | |
*/ | |
l_messages := json_array_t(); | |
for r in (select c001, clob001 from apex_collections where collection_name = p_collection_name order by seq_id) | |
loop | |
l_message := json_object_t(); | |
l_message.put('role' ,r.c001); | |
l_message.put('content',r.clob001); | |
l_messages.append(l_message); | |
end loop; | |
l_request := json_object_t(); | |
l_request.put('model',p_model_name); | |
l_request.put('messages', l_messages); | |
/* | |
* Function Setが指定されていれば、functionの配列を | |
* リクエストに含める。 | |
*/ | |
if p_function_set_id is not null then | |
l_functions := json_array_t(); | |
for r in ( | |
select * from openai_functions f join openai_function_set s on f.id = s.function_id | |
where s.id = p_function_set_id | |
) | |
loop | |
l_function := json_object_t(); | |
l_function.put('name', r.function_name); | |
l_function.put('description', r.description); | |
l_parameters := json_object_t(r.parameters); | |
l_function.put('parameters', l_parameters); | |
l_functions.append(l_function); | |
end loop; | |
l_request.put('functions', l_functions); | |
end if; | |
/* | |
* temprature, top_pなどのパラメータを設定するとしたら、ここでputする。 | |
*/ | |
l_request_clob := l_request.to_clob(); | |
p_request_out := l_request_clob; | |
/* | |
* OpenAIのChatGPTのAPIを呼び出す。 | |
*/ | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Content-Type','application/json',p_reset => false); | |
l_response_clob := apex_web_service.make_rest_request( | |
p_url => 'https://api.openai.com/v1/chat/completions' | |
,p_http_method => 'POST' | |
,p_body => l_request_clob | |
,p_credential_static_id => p_credential_static_id | |
); | |
p_response_out := l_response_clob; | |
if apex_web_service.g_status_code <> 200 then | |
apex_debug.info(l_response_clob); | |
raise e_openai_api_exception; | |
end if; | |
/* ドキュメントに記載されているレスポンスで処理を継続する。 */ | |
l_response := json_object_t(l_response_clob); | |
l_choices := l_response.get_array('choices'); | |
l_choice0 := treat(l_choices.get(0) as json_object_t); | |
/* finish_reaonがfunction_callで終わっているか、後で確認する。 */ | |
l_finish_reason := l_choice0.get_string('finish_reason'); | |
l_message := l_choice0.get_object('message'); | |
l_role := l_message.get_string('role'); | |
l_content := l_message.get_clob('content'); | |
/* finish_reasonがfunction_callであれば、指定されたファンクションを呼び出す。 */ | |
if l_finish_reason = 'function_call' then | |
l_function_call := l_message.get_object('function_call'); | |
if l_function_call is not null then | |
l_function_name := l_function_call.get_string('name'); | |
l_function_args := l_function_call.get_clob('arguments'); | |
/* ストアド・プロシージャを動的に呼び出す。 */ | |
l_dynamic_sql := 'begin :a := ' || l_function_name || '(:b); end;'; | |
execute immediate l_dynamic_sql using in out l_function_out, l_function_args; | |
l_content := l_function_out; | |
end if; | |
end if; | |
/* usageも取り出す。 */ | |
l_usage := l_response.get_object('usage'); | |
/* | |
* ChatGPTからの応答をAPEXコレクションに追記する。 | |
*/ | |
apex_collection.add_member( | |
p_collection_name => p_collection_name | |
,p_c001 => l_role | |
,p_clob001 => l_content | |
,p_n001 => l_usage.get_number('prompt_tokens') | |
,p_n002 => l_usage.get_number('completion_tokens') | |
,p_n003 => l_usage.get_number('total_tokens') | |
); | |
end call_openai_api; | |
/ |
送信するアイテムは、P1_USER_MESSAGE、P1_FUNCTION_SETです。戻すアイテムはP1_REQUEST、P1_RESPONSEです。戻すアイテムは両方ともデバッグ用途で、無くても問題ありません。
ファンクション・セットを指定するページ・アイテムを作成します。
識別の名前はP1_FUNCTION_SET、タイプは選択リストです。ラベルはFunction Setとします。LOVのタイプにSQL問合せを選択し、SQL問合せとして以下を記述します。
select function_set_name d, id r from openai_function_set
追加値の表示はオフ、NULL値の表示はオンとして、NULL表示値に-- No function set assigned --を記述します。
デバッグ用にタイプが表示のみのページ・アイテムP1_REQUESTおよびP1_RESPONSEを作成します。
今回の記事は以上です。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完