Function Callingを使って東京の天気を問い合わせています。
https://github.com/ujnak/apexapps/blob/master/exports/sample-openai-assistant.zip
以下よりアプリケーションの作成手順を紹介します。
クイック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のフォーム付き対話モード・レポートのページを作成します。
対話モード・レポートのページ番号は9(8は別のページで使うので空けておきます)、名前はFunctions、フォームのフォーム・ページ番号は10、フォーム・ページ名はFunction Detailとします。データ・ソースの表/ビューの名前にOPENAI_FUNCTIONSを指定します。
それ以外はデフォルトで次に進み、主キー列1にID (Number)を指定してページの作成を実行します。
対話モード・レポートのページ番号は11、名前はFunction Set、フォームのフォーム・ページ番号は12、フォーム・ページ名はFunction Set Detailとします。データ・ソースの表/ビューの名前にOPENAI_FUNCTION_SETを指定します。
それ以外はデフォルトで次に進み、主キー列1にID (Number)を指定してページの作成を実行します。
表OPENAI_FUNCTIONSとOPENAI_FUNCTION_SETを編集するページができたので、記事「OpenAI ChatGPTのFunction Callingを使ってPL/SQLプロシージャを呼び出す」を参照して、PL/SQLファンクションGET_CURRENT_WEATERを作成します。表OPENAI_FUNCTIONSにファンクションとしてget_current_weatherを登録し、表OPENAI_FUNCTION_SETにファンクションget_current_weaterを含んだFunction SetとしてWeatherを作成します。
アシスタントを作成する際に上記のファンクションセットを選択できるように、共有コンポーネントのLOVを作成します。
作成するLOVの名前はLOV_FUNCTION_SETとします。タイプはDynamicです。
次へ進みます。
表ビューの名前としてOPENAI_FUNCTION_SET (表)を選択します。
次へ進みます。
戻り列はID、表示列はFUNCTION_SET_NAMEです。
作成をクリックすると、LOV_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
create or replace package utl_openai_tools_api | |
as | |
/** | |
* アシスタントを作成する引数toolsとして与えるJSONを生成する。 | |
* | |
* @param p_code_interpreter Code Interpreterを含む場合は真 | |
* @param p_retrieval Retrievalを含む場合は真 | |
* @param p_function_set_id 表OPENAI_FUNCTION_SETのID値。 | |
* 指定があればfunction callingの設定を含める。 | |
* return toolsに与えるJSON | |
*/ | |
function generate_tools_json( | |
p_code_interpreter in boolean | |
,p_retrieval in boolean | |
,p_function_set_id in openai_function_set.id%type | |
) | |
return clob; | |
/** | |
* Statusがrequire_actionのRunであることを確認し、tool_callsを取り出し | |
* 指定されてPL/SQLプロシージャの呼び出しを行う。 | |
* | |
* @param p_run_id run id | |
* @param p_thread_id thread id | |
* @param p_credential_static_id OpenAIのAPI呼び出しに使用するWeb資格証明の静的ID | |
* @param p_required_action Runオブジェクトのrequired_action - デバッグ用 | |
* @param p_tool_outputs PL/SQLプロシージャを呼び出しsubmit_tool_outputsとして送信した内容 - デバッグ用 | |
* @param p_response submit_tool_outputsの応答 - デバッグ用 | |
*/ | |
procedure submit_tool_outputs( | |
p_run_id in varchar2 | |
,p_thread_id in varchar2 | |
,p_credential_static_id in varchar2 default 'OPENAI_API_KEY' | |
,p_required_action out clob | |
,p_tool_outputs out clob | |
,p_response out clob | |
); | |
end utl_openai_tools_api; | |
/ | |
create or replace package body utl_openai_tools_api | |
as | |
function generate_tools_json( | |
p_code_interpreter in boolean | |
,p_retrieval in boolean | |
,p_function_set_id in openai_function_set.id%type | |
) | |
return clob | |
as | |
l_tool json_object_t; | |
l_tools json_array_t; | |
l_tools_clob clob; | |
l_function json_object_t; | |
l_parameters json_object_t; | |
begin | |
l_tools := json_array_t(); | |
/* | |
* Code Interpreterが有効。 | |
*/ | |
if p_code_interpreter then | |
l_tool := json_object_t('{ "type": "code_interpreter" }'); | |
l_tools.append(l_tool); | |
end if; | |
/* | |
* Retrievalが有効。 | |
*/ | |
if p_retrieval then | |
l_tool := json_object_t('{ "type": "retrieval" }'); | |
l_tools.append(l_tool); | |
end if; | |
/* | |
* function setの指定がある。 | |
*/ | |
if p_function_set_id is not null then | |
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_tool := json_object_t('{ "type": "function" }'); | |
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_tool.put('function', l_function); | |
l_tools.append(l_tool); | |
end loop; | |
end if; | |
l_tools_clob := l_tools.to_clob(); | |
return l_tools_clob; | |
end generate_tools_json; | |
procedure submit_tool_outputs( | |
p_run_id in varchar2 | |
,p_thread_id in varchar2 | |
,p_credential_static_id in varchar2 | |
,p_required_action out clob | |
,p_tool_outputs out clob | |
,p_response out clob -- Modified run object | |
) | |
as | |
l_run_clob clob; | |
l_run json_object_t; | |
l_status varchar2(80); | |
l_required_action json_object_t; | |
l_submit_tool_outputs json_object_t; | |
l_tool_calls json_array_t; | |
l_call json_object_t; | |
l_function json_object_t; | |
/* 動的SQL呼び出し */ | |
l_function_name varchar2(200); | |
l_function_args clob; | |
l_dynamic_sql varchar2(32767); | |
l_function_out clob; | |
l_tool_outputs json_array_t; | |
l_tool_output json_object_t; | |
/* OpenAI Assistants APIの呼び出し */ | |
l_url varchar2(400); | |
l_request_json json_object_t; | |
l_request clob; | |
l_response clob; | |
l_response_json json_object_t; | |
e_api_call_failed exception; | |
begin | |
/* | |
* 指定されたRunのStatusがrequires_actionかどうか確認する。 | |
*/ | |
l_url := 'https://api.openai.com/v1/threads/' || p_thread_id || '/runs/' || p_run_id; | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('OpenAI-Beta','assistants=v1',p_reset => false); | |
apex_web_service.set_request_headers('Content-Type','application/json',p_reset => false); | |
l_run_clob := apex_web_service.make_rest_request( | |
p_url => l_url | |
,p_http_method => 'GET' | |
,p_credential_static_id => p_credential_static_id | |
); | |
if apex_web_service.g_status_code <> 200 then | |
apex_debug.info('status = %s, reseponse = %s', apex_web_service.g_status_code, l_run_clob); | |
raise e_api_call_failed; | |
end if; | |
l_run := json_object_t(l_run_clob); | |
l_status := l_run.get_string('status'); | |
if l_status <> 'requires_action' then | |
/* 異なっている場合は何もしないで終了する */ | |
apex_debug.info('status is not requires_action, exit. %s', l_run_clob); | |
return; | |
end if; | |
/* | |
* Statusがrequires_actionであれば | |
* typeがsubmit_tool_outputsのrequired_actionがあるはず。 | |
* それを取り出す。 | |
*/ | |
l_required_action := l_run.get_object('required_action'); | |
p_required_action := l_required_action.to_clob(); | |
apex_debug.info('required_action %s', p_required_action); | |
if l_required_action.get_string('type') <> 'submit_tool_outputs' then | |
/* | |
* typeがsubmit_tool_outputsでなければ何もしない。 | |
* l_required_actionに値があるのにl_tool_outputに結果が返されない状態になる。 | |
*/ | |
apex_debug.info('type is not submit_tool_outputs, exit.'); | |
return; | |
end if; | |
l_submit_tool_outputs := l_required_action.get_object('submit_tool_outputs'); | |
l_tool_calls := l_submit_tool_outputs.get_array('tool_calls'); | |
l_tool_outputs := json_array_t(); | |
for i in 1..l_tool_calls.get_size() | |
loop | |
l_call := treat(l_tool_calls.get(i-1) as json_object_t); | |
if l_call.get_string('type') = 'function' then | |
/* | |
* 今の所Function Callingのtypeはfunctionのみなはず。 | |
* そうでなければRunはrequires_actionのステータスで停止する。 | |
*/ | |
l_function := l_call.get_object('function'); | |
l_function_name := l_function.get_string('name'); | |
l_function_args := l_function.get_clob('arguments'); | |
/* ストアド・プロシージャを動的に呼び出す。 */ | |
apex_debug.info('function calling name %s, args %s', l_function_name, l_function_args); | |
l_dynamic_sql := 'begin :a := ' || l_function_name || '(:b); end;'; | |
execute immediate l_dynamic_sql using in out l_function_out, l_function_args; | |
/* 実行結果をtool_outputsに追加 */ | |
apex_debug.info('function calling result %s', l_function_out); | |
l_tool_output := json_object_t(); | |
l_tool_output.put('tool_call_id', l_call.get_string('id')); | |
l_tool_output.put('output', l_function_out); | |
l_tool_outputs.append(l_tool_output); | |
end if; | |
end loop; | |
/* | |
* OpenAI Assistants APIのsubmit_tool_outputsを呼び出す。 | |
* Ref: https://platform.openai.com/docs/api-reference/runs/submitToolOutputs | |
*/ | |
l_url := l_url || '/submit_tool_outputs'; | |
l_request_json := json_object_t(); | |
l_request_json.put('tool_outputs', l_tool_outputs); | |
l_request := l_request_json.to_clob(); | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('OpenAI-Beta','assistants=v1',p_reset => false); | |
apex_web_service.set_request_headers('Content-Type','application/json',p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => l_url | |
,p_http_method => 'POST' | |
,p_body => l_request | |
,p_credential_static_id => p_credential_static_id | |
); | |
if apex_web_service.g_status_code <> 200 then | |
apex_debug.info('status = %s, reseponse = %s', apex_web_service.g_status_code, l_response); | |
raise e_api_call_failed; | |
end if; | |
p_response := l_response; | |
end submit_tool_outputs; | |
end utl_openai_tools_api; | |
/ |
アシスタントの作成時にツールとしてCode InterpreterとFunction Callingを設定できるようにします。
ページ・デザイナでページ番号5のAssistant Detailのフォームを開きます。
ページ・アイテムP5_TOOLSを選択します。現在はデフォルトの値を[{ "type": "retrieval" }]として、ツールとしてRetrievalを使用する設定になっています。
識別のタイプをチェック・ボックス・グループに変更します。設定の列の数を3にします。LOVのタイプに静的値を選択し、静的値として以下を設定します。戻り値はCODE_INTERPRETER、RETRIEVAL、FUNCTION_CALLINGのどれか、または全てを含みます。
追加値の表示はオフです。
デフォルトのタイプを- 選択 -に戻し、無指定にします。
ツールに含めるfunctionを決めるfunction setを選択するページ・アイテムを作成します。作成したページ・アイテムはページ・アイテムP5_FILE_IDSの下に配置します。
識別の名前はP5_FUNCTION_SET_ID、タイプはラジオ・グループです。レイアウトの新規行の開始をオフにし、ページ・アイテムP5_FILE_IDSの右隣に配置します。
LOVのタイプに共有コンポーネントを選択し、LOVとしてLOV_FUNCTION_SETを選びます。追加値の表示はオフ、NULL値の表示もオフです。
セッション・ステートのストレージはリクエストごと(メモリーのみ)とし、サーバー側の条件のタイプはアイテムはNULL、アイテムにP5_IDを指定し、アシスタントの作成時のみ設定できるようにします。
値のタイプをファンクション本体に変更し、PL/SQLファンクション本体として以下を記述します。
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
declare | |
l_tools json_array_t; | |
l_code_interpreter boolean; | |
l_retrieval boolean; | |
l_function_set_id number; | |
begin | |
if instr(:P5_TOOLS,'CODE_INTERPRETER') > 0 then | |
l_code_interpreter := true; | |
end if; | |
if instr(:P5_TOOLS,'RETRIEVAL') > 0 then | |
l_retrieval := true; | |
end if; | |
if instr(:P5_TOOLS, 'FUNCTION_CALLING') > 0 then | |
l_function_set_id := :P5_FUNCTION_SET_ID; | |
end if; | |
return utl_openai_tools_api.generate_tools_json( | |
p_code_interpreter => l_code_interpreter | |
,p_retrieval => l_retrieval | |
,p_function_set_id => l_function_set_id | |
); | |
end; |
現在の天気を確認するアシスタントを作成します。NameはMy Weather Forecast Assistant、Instructionsは「あなたは日本の天気を教えてくれるアシスタントです。」とします。ToolsのFunction Callingにチェックを入れ、Function SetとしてWeatherを選択します。
作成をクリックします。
APIを呼び出して作成したアシスタントも、OpenAIのAssistantsのPlaygroundで扱うことができます。
実行したスレッドはStatusがrequires_actionで停止します。
アシスタントがファンクションの呼び出しを要求していて、その結果を待っている状態です。Oracle APEX側で要求されているストアド・プロシージャを実行し、その実行結果をOpenAI Assistants APIのSubmit Tool Outputsを呼び出してアップロードする必要があります。
Runオブジェクトの内容を表示するページを作成し、そのページにSubmit Tool Outputsを呼び出すボタンを作成します。
ページの作成をクリックし、ページ作成ウィザードを開始します。
フォームを選択します。
ページ番号は8、名前はRun Detailとします。ページ・モードはドロワーを選択します。データ・ソースとしてRESTデータ・ソースを選択し、RESTデータ・ソースとしてList runsを選びます。
ドロワーなので、ナビゲーションは双方ともオフです。
次へ進み、主キー列1にID (Varchar2)を選択し、ページを作成します。
作成されたページを編集します。
ページ・アイテムP8_IDの除くすべてのページ・アイテムを選択し、識別のタイプを表示のみに変更します。
デバッグ用にページ・アイテムP8_RESPONSEを作成します。タイプはテキスト領域、ラベルはResponseとします。
セッション・ステートのデータ型はCLOB、ストレージはリクエストごと(メモリのみ)を指定します。
レンダリング前のヘッダーの前にあるプロセス初期化フォームRun Detailを選択します。
識別のタイプをAPIの呼出しに変更し、設定のパッケージとしてUTL_OPENAI_ASSISTANTS_API、プロシージャまたはファンクションとしてRETRIEVE_RUNを指定します。
サーバー側の条件のタイプにアイテムはNULLではないを選択し、アイテムとしてP8_IDを指定します。
パラメータp_run_idを選択し、値のアイテムとしてP8_IDを指定します。
パラメータファンクションの結果、p_tools、p_file_ids、p_metadataを選択し、パラメータの出力を無視をオンにします。
Assistants APIのSubmit Tool Outputsを発行するボタンを作成します。
識別のボタン名はSUBMIT_TOOL_OUTPUTS、ラベルはSubmit Tool Outputsとします。外観のホットをオン、テンプレート・オプションのWidthをStretchにします。
動作のアクションはデフォルトのページの送信です。
サーバー側の条件のタイプにアイテム = 値を選択し、アイテムとしてP8_STATUS、値にrequires_actionを指定します。
プロセス・ビューを開き、Submit Tool Outputsを発行するプロセスを作成します。作成したプロセスはダイアログを閉じるの上に配置します。
識別の名前はSubmit Tool Outputs、タイプとしてAPIの呼出しを選択します。設定のパッケージはUTL_OPENAI_TOOLS_API、プロシージャまたはファンクションとしてSUBMIT_TOOL_OUTPUTSを指定します。
サーバー側の条件のボタン押下時にSUBMIT_TOOL_OUTPUTSを指定します。
パラメータp_run_idの値のアイテムはP8_IDとします。
パラメータp_required_actionとp_tool_outputは、パラメータの出力を無視をオンにします。
フォームのページについては、以上で完成です。
ページ番号6のList runsをソースとした対話モード・レポートから、ドロワーを開く設定を追加します。
リージョンRunsを選択し、プロパティ・エディタの属性タブを開きます。リンクのターゲットをクリックし、リンク・ビルダーを開きます。
ターゲットのタイプとしてこのアプリケーションのページ、ページとして先ほど作成したドロワーのページである8を指定します。
アイテムの設定の名前はP8_ID(P7_IDから変更します)に値は\#ID#\、その次に名前はP8_THREAD_IDに値は\&P6_THREAD_ID.\を設定します。
リージョンRunsから開かれたドロワーが閉じた時に、対話モード・レポートがリフレッシュされるように、動的アクションをリージョンRunsに作成します。
作成した動的アクションの識別の名前はダイアログのクローズとします。タイミングのイベントとしてダイアログのクローズを選択し、選択タイプはリージョン、リージョンとしてRunsを指定します。
TRUEアクションとしてリフレッシュを選択します。影響を受ける要素の選択タイプをリージョン、リージョンとしてRunsを選択します。
以上で、OpenAIのAssistants APIからCode InterpreterとFunction Callingを呼び出す機能を追加できました。
Code Interpreterの処理の過程を確認するためには、Run Stepsを見る必要があります。
Run Stepsを参照するページを追加します。
RESTソース・カタログのOpenAI Assistants APIよりList run stepsを選択し、RESTデータ・ソースList run stepsを作成します。
RESTデータ・ソースList run stepsをソースとしたフォーム付き対話モード・レポートのページを作成します。主キー列1はID (Varchar2)を選択します。
対話モード・レポートのページに、RESTソースList run stepsの必須パラメータであるRun IDを保持するページ・アイテムP13_RUN_IDを作成します。
タイプはテキスト・フィールド、ラベルはRun ID、セッション・ステートのストレージはセッションごと(永続)とします。
同様に必須パラメータであるThread IDを保持するページ・アイテムP13_THREAD_IDを作成します。ラベルはThread IDになります。
リージョンRun Stepsのパラメータを設定します。
パラメータrun_idの値のタイプはアイテム、アイテムとしてP13_RUN_IDを指定します。
パラメータthread_idの値のタイプはアイテム、アイテムとしてP13_THREAD_IDを指定します。
リージョンRun Stepsを選択し、ソースの送信するページ・アイテムとしてP13_RUN_ID、P13_THREAD_IDを指定します。
アイテムの設定に、名前がP14_RUN_ID、値が\#RUN_ID#\と名前がP14_THREAD_ID、値が\#THREAD_ID#\の組み合わせを追加します。
Run Stepの詳細を確認するためにページ・アイテムを4つ作成します。タイプはすべて表示のみです。
作成するページ・アイテムはP14_RESPONSE、P14_STEP_DETAILS、P14_EXPIRE_AT、P14_METADATAです。セッション・ステートのデータ型はP14_EXPIRED_ATを除いてCLOB(P14_EXPIRED_ATはVARCHAR2)、ストレージはリクエストごと(メモリーのみ)とします。
ページ・アイテムP14_IDを除いた既存のページ・アイテムをすべて選択し、タイプを表示のみに変更します。
レンダリング前のヘッダーの前にあるプロセス初期化フォームRun Stepsを選択します。
識別のタイプをAPIの呼出しに変更し、設定のパッケージとしてUTL_OPENAI_ASSISTANTS_API、プロシージャまたはファンクションとしてRETRIEVE_RUN_STEPを指定します。
サーバー側の条件のタイプにアイテムはNULLではないを選択し、アイテムとしてP14_IDを指定します。
パラメータファンクションの結果のパラメータの出力を無視をオンにします。
ページ番号6のMessagesの対話モード・レポートからRun Stepsのページに遷移できるように、リンクを設定します。
リージョンMessagesの列RUN_IDを選択し、タイプをリンクに変更します。リンクのターゲットをクリックし、リンク・ビルダーを開きます。
ターゲットのタイプはこのアプリケーションのページ、ページは13を指定します。
アイテムの設定に、名前がP13_RUN_ID、値が#RUN_ID#と名前がP13_THREAD_IDと値が#THREAD_ID#の組み合わせを追加します。
以上でRun Stepsを表示するページも完成です。
以下はCode Interpreterを呼び出したGIF動画になります。
今回の記事は以上になります。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完