簡単なAPEXアプリケーションを作成し、Responses APIの組み込みツールのWeb検索、previous_response_idを指定した会話の継続、およびReasoningモデルの呼び出しを確認してみます。
作成したアプリケーションは以下のように動作します。
上記のAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-openai-responses-api.zip
機能はすべてホーム・ページに実装しています。
モデルを選択するページ・アイテムとしてP1_MODELを作成しています。タイプは選択リストです。
選択リストの戻り値を、APIリクエストのパラメータmodelに与えています。
会話を継続するためのパラメータprevious_response_idとして与えるIDを保存するページ・アイテムとしてP1_RESPONSE_IDを作成しています。Responses APIを呼び出した後に、レスポンスに含まれるidを取り出しP1_RESPONSE_IDに設定します。
OpenAIのAPI Referenceによると、"Even when using previous_response_id, all previous input tokens for responses in the chain are billed as input tokens in the API.”とのことなので、input配列にメッセージ履歴を含めるのと、費用面では変わらないようです。
パラメータinstructionsとして指定する文字列をページ・アイテムP1_INSTRUCTIONSに指定します。実際にはinstructionsの代わりに、roleをdeveloperとしたcontentとしてinput配列に含めています。
指定がなければAPIリクエストには含めません。
ユーザーによるプロンプトをページ・アイテムP1_INPUTに入力します。タイプはテキスト領域です。roleがuserのcontentになります。
ボタンSUBMITをクリックすると、Responses 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 openai_responses_api( | |
p_model in varchar2 | |
,p_input in varchar2 | |
,p_instructions in varchar2 | |
,p_response_id in out varchar2 | |
,p_output_text out clob | |
,p_annotations out clob | |
,p_usage out clob | |
,p_endpoint in varchar2 default 'https://api.openai.com/v1/responses' | |
,p_credential_static_id in varchar2 default 'OPENAI_API_KEY' | |
) | |
as | |
/* リクエストの作成に使用する */ | |
l_request clob; | |
l_request_json json_object_t; | |
l_tools json_array_t; | |
l_tool json_object_t; | |
l_reasoning json_object_t; | |
l_input json_array_t; | |
l_input_obj json_object_t; | |
e_openai_api_call_failed exception; | |
/* レスポンスの処理に使用する。 */ | |
l_response clob; | |
l_response_json json_object_t; | |
l_object varchar2(40); | |
l_status varchar2(20); /* completed, failed, in_progress, incomplete */ | |
l_output json_array_t; | |
l_output_size integer; | |
l_output_obj json_object_t; | |
l_output_obj_type varchar2(40); | |
l_output_obj_status varchar2(40); | |
l_output_obj_role varchar2(40); | |
l_content json_array_t; | |
l_content_size integer; | |
l_content_obj json_object_t; | |
l_content_obj_type varchar2(20); | |
-- l_content_obj_text clob; | |
/* | |
* typeがoutput_textのcontent | |
*/ | |
l_output_text clob; | |
l_annotations json_array_t; | |
/* | |
* usage | |
*/ | |
l_usage json_object_t; | |
begin | |
l_request_json := json_object_t(); | |
/* model指定 | |
* https://platform.openai.com/docs/api-reference/responses/create#responses-create-model | |
*/ | |
l_request_json.put('model', p_model); | |
/* | |
* reponse idが与えられていれば、会話を継続させる。 | |
* https://platform.openai.com/docs/api-reference/responses/create#responses-create-previous_response_id | |
*/ | |
if p_response_id is not null then | |
l_request_json.put('previous_response_id', p_response_id); | |
end if; | |
/* | |
* GPTモデルであれば、Web検索をtoolに含める | |
* https://platform.openai.com/docs/api-reference/responses/create#responses-create-tools | |
*/ | |
if p_model like 'gpt%' then | |
l_tools := json_array_t(); | |
l_tool := json_object_t(); | |
l_tool.put('type', 'web_search_preview'); | |
l_tools.append(l_tool); | |
l_request_json.put('tools', l_tools); | |
end if; | |
/* | |
* Reasoningモデルであれば、effortをmediumにする。 | |
* https://platform.openai.com/docs/api-reference/responses/create#responses-create-reasoning | |
*/ | |
if p_model like 'o3%' then | |
l_reasoning := json_object_t(); | |
l_reasoning.put('effort', 'medium'); | |
if p_model like 'o1%' then | |
l_reasoning.put('generate_summary', 'concise'); | |
end if; | |
l_request_json.put('reasoning', l_reasoning); | |
end if; | |
/* | |
* ユーザー・プロンプトの指定 | |
* https://platform.openai.com/docs/api-reference/responses/create#responses-create-input | |
*/ | |
l_input := json_array_t(); | |
l_input_obj := json_object_t(); | |
/* | |
* p_instructionsの指定があれば、developerロールのメッセージとして追加。 | |
*/ | |
if p_instructions is not null then | |
l_input_obj.put('role', 'developer'); | |
l_input_obj.put('content', p_instructions); | |
l_input.append(l_input_obj); | |
l_input_obj := json_object_t(); | |
end if; | |
/* | |
* ユーザー・ロールのメッセージを追加 | |
*/ | |
l_input_obj.put('role', 'user'); | |
l_input_obj.put('content', p_input); | |
l_input.append(l_input_obj); | |
l_request_json.put('input', l_input); | |
/* 送信するメッセージ */ | |
l_request := l_request_json.to_clob(); | |
apex_debug.info('request = %s', l_request); | |
/* OpenAIのResponses APIの呼び出し。 */ | |
apex_web_service.set_request_headers('Content-Type', 'application/json'); | |
l_response := apex_web_service.make_rest_request( | |
p_url => p_endpoint | |
,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 | |
raise e_openai_api_call_failed; | |
end if; | |
apex_debug.info('response = %s', l_response); | |
l_response_json := json_object_t(l_response); | |
p_response_id := l_response_json.get_string('id'); | |
l_object := l_response_json.get_string('object'); | |
l_status := l_response_json.get_string('status'); | |
l_usage := l_response_json.get_object('usage'); | |
/* outputの取り出し */ | |
l_output := l_response_json.get_array('output'); | |
apex_debug.info('output = %s', l_output.to_string()); | |
l_output_size := l_output.get_size(); | |
apex_debug.info('output length = %s', l_output_size); | |
l_output_text := ''; | |
l_annotations := json_array_t(); | |
for i in 1..l_output_size | |
loop | |
l_output_obj := treat(l_output.get(i-1) as json_object_t); | |
l_output_obj_type := l_output_obj.get_string('type'); | |
l_output_obj_status := l_output_obj.get_string('status'); | |
/* | |
* output_extはtypeがmessageのオブジェクトより取り出す。 | |
*/ | |
if l_output_obj_type = 'message' then | |
l_output_obj_role := l_output_obj.get_string('role'); /* assistantなはず */ | |
/* contentの取り出し */ | |
l_content := l_output_obj.get_array('content'); | |
apex_debug.info('content = %s', l_content.to_string()); | |
l_content_size := l_content.get_size(); | |
apex_debug.info('content length = %s', l_content_size); | |
for j in 1..l_content_size | |
loop | |
l_content_obj := treat(l_content.get(j-1) as json_object_t); | |
l_content_obj_type := l_content_obj.get_string('type'); | |
if l_content_obj_type = 'output_text' then | |
l_output_text := l_output_text || l_content_obj.get_string('text'); | |
apex_debug.info('output_text = %s', l_output_text); | |
l_annotations.append_all(l_content_obj.get_array('annotations')); | |
end if; | |
end loop; | |
end if; | |
end loop; | |
p_output_text := l_output_text; | |
p_annotations := l_annotations.to_clob(); | |
p_usage := l_usage.to_string(); | |
end openai_responses_api; | |
/ |
識別のタイプとしてAPIの呼出しを選択し、設定のタイプにPL/SQLプロシージャまたはファンクションを選択し、プロシージャまたはファンクションはOPENAI_RESPONSES_APIとします。
ボタンCLEARをクリックすると、タイプがセッション・ステートのクリアであるプロセスを呼び出します。
APIレスポンスからはマークダウンが返されることを想定しています。レスポンスを表示するページ・アイテムP1_OUTPUT_TEXTのタイプはMarkdownエディタ、読取り専用に常時を設定します。
JSON配列として返されるannotationsをクラシック・レポートに表示します。annotations自体は非表示のページ・アイテムP1_ANNOTATIONSに設定します。
ソースのSQL問合せに以下を記述します。
select j.type, j.title, j.url, j.start_index, j.end_index
from json_table(:P1_ANNOTATIONS, '$[*]'
columns
(
type varchar2(20) path '$.type',
title varchar2(400) path '$.title',
url varchar2(200) path '$.url',
start_index number path '$.start_index',
end_index number path '$.end_index'
)
) j
サーバー側の条件を設定し、annotationsが返されているときだけクラシック・レポートを表示するようにします。
declare
l_array json_array_t;
begin
if :P1_ANNOTATIONS is not null then
l_array := json_array_t(:P1_ANNOTATIONS);
if l_array.get_size() > 0 then
return true;
end if;
end if;
return false;
end;
usageはJSONをそのままページ・アイテムP1_USAGEに表示します。
作成したAPEXアプリケーションの説明は以上です。
作成したAPEXアプリケーションを使って、OpenAIのResponses APIを呼び出してみます。
最初にモデルにgpt-4o-miniを選択し、inputとして以下を入力します。
「これから開かれる大阪万博の期間を教えて。」
toolsとして組み込みのweb_search_previewを渡しているため、引用先も含めた回答が得られています。APIレスポンスに含まれるidがResponse Idに設定されます。
続けて以下を問合せます。Response Idが指定されているため、大阪万博に関する会話が継続します。
「日本からの展示にはどのようなものがありますか?」
「海外からのパビリオンではどのようなものがありますか?」
回答が日本語になったり、引用が無かったりしました。スイスとドイツは確認したところ、情報としては正しそうです。
モデルとしてo3-miniを選択し、以下を問い合わせました。
「万国博覧会の意義を教えて。」
Responses APIがChat Completions APIのように他の実装(vLLM、Ollamaやllama.cppといったところ)で採用されるかどうかは分かりませんが、Assistants APIよりは採用するハードルは低そうです。
とりあえず、previous_response_idの指定だけで会話を継続できるのは、大変便利です。
今回の記事は以上になります。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完