2023年11月8日水曜日

OpenAIのFiles APIを使ってファイルをアップロードする

OpenAIのFiles APIを呼び出して、ファイルの操作を行なうアプリケーションを作ってみました。

最近ベータ版が出たAssistants APIに含まれる機能にretrievalcode_interpreterがあります。これらの機能が使用するassistant fileは、purpose="assistants"Filesにアップロードされているファイルのfile_idassistant fileにアタッチするという仕様のようです。

Create assistant file
https://platform.openai.com/docs/api-reference/assistants/createAssistantFile

ファイルのアップロードができれば十分なので、あまり手の込んだ実装はしていません。


Downloadをクリックするとエラーが発生しました。エラー・メッセージを確認すると、そもそもpurposeがassistantsのファイルはダウンロード不可のようです。purposeがfine-tuneであればダウンロードできるのかもしれません。

body: {
  "error": {
    "message": "Not allowed to download files of purpose: assistants",
    "type": "invalid_request_error",
    "param": null,
    "code": null
  }
}

OpenAIのFiles APIを呼び出すパッケージUTL_OPENAI_FILES_APIを作成しています。上記のAPEXアプリケーションは、このパッケージに実装されたプロシージャを呼び出しています。クラシック・レポートのデータ・ソースだけはパッケージで実装せず、RESTデータ・ソースを定義しています。

create or replace package utl_openai_files_api
as
/**
* OpenAI Upload fileを呼び出す。
* 参照: https://platform.openai.com/docs/api-reference/files/create
*
* @param p_filename アップロードされるファイル名
* @param p_content_type ファイル形式
* @param p_file_content ファイル本体
* @param p_purpose OpenAIでのファイルの用途 fine-tuneかassistantsのどちらか
* @param p_credential_static_id Web資格証明
* @param p_file アップロードされたファイルのメタデータ JSON形式
* @return file_id
*/
function upload_file(
p_filename in varchar2
,p_content_type in varchar2
,p_file_content in blob
,p_purpose in varchar2 default 'assistants'
,p_credential_static_id in varchar2 default 'OPENAI_API_KEY'
,p_file out clob
)
return varchar2;
/**
* OpenAI Retrieve file、Retrieve file contentを呼び出す。
* 参照: https://platform.openai.com/docs/api-reference/files/retrieve
* 参照: https://platform.openai.com/docs/api-reference/files/retrieve-contents
*
* @param p_file_id file_id
* @param p_credential_static_id Web資格証明
* @param p_file ファイルのメタデータ JSON形式
* @param p_file_content ファイル本体 BLOB形式
* @return file_id
*/
function retrieve_file(
p_file_id in varchar2
,p_credential_static_id in varchar2 default 'OPENAI_API_KEY'
,p_file out clob
,p_file_content out blob
)
return varchar2;
/**
* OpenAI delete fileを呼び出す。
* 参照: https://platform.openai.com/docs/api-reference/files/delete
*
* @param p_file_id file_id
* @param p_credential_static_id Web資格証明
*/
function delete_file(
p_file_id in varchar2
,p_credential_static_id in varchar2 default 'OPENAI_API_KEY'
,p_file out clob
)
return varchar2;
end utl_openai_files_api;
/
create or replace package body utl_openai_files_api
as
/**
* ファイルのアップロード
*/
function upload_file(
p_filename in varchar2
,p_content_type in varchar2
,p_file_content in blob
,p_purpose in varchar2
,p_credential_static_id in varchar2
,p_file out clob
)
return varchar2
as
l_response clob;
l_multipart apex_web_service.t_multipart_parts;
l_multipart_request blob;
e_api_call_failed exception;
l_file json_object_t;
begin
apex_web_service.clear_request_headers;
/*
* アップロードするファイルを指定する。
*/
apex_web_service.append_to_multipart(
p_multipart => l_multipart
,p_name => 'file'
,p_filename => p_filename
,p_content_type => p_content_type
,p_body_blob => p_file_content
);
/*
* purposeを指定する。
*/
apex_web_service.append_to_multipart(
p_multipart => l_multipart
,p_name => 'purpose'
,p_content_type => 'text/plain'
,p_body => p_purpose
);
l_multipart_request := apex_web_service.generate_request_body(l_multipart);
l_response := apex_web_service.make_rest_request(
p_url => 'https://api.openai.com/v1/files'
,p_http_method => 'POST'
,p_body_blob => l_multipart_request
,p_credential_static_id => p_credential_static_id
);
if apex_web_service.g_status_code <> 200 then
apex_debug.info('status_code = %s, response = %s', apex_web_service.g_status_code, l_response);
raise e_api_call_failed;
end if;
p_file := l_response;
l_file := json_object_t(p_file);
return l_file.get_string('id');
end upload_file;
/**
* ファイルのダウンロード
*/
function retrieve_file(
p_file_id in varchar2
,p_credential_static_id in varchar2
,p_file out clob
,p_file_content out blob
)
return varchar2
as
l_url varchar2(400);
l_response clob;
e_api_call_failed exception;
l_file json_object_t;
begin
/* ファイルのメタデータの取得 */
l_url := 'https://api.openai.com/v1/files/' || p_file_id;
apex_web_service.clear_request_headers;
l_response := 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_code = %s, response = %s', apex_web_service.g_status_code, l_response);
raise e_api_call_failed;
end if;
/* ファイルのメタデータをp_fileとして返す。 */
p_file := l_response;
/* ファイルの内容をBOBとして取得する */
l_url := l_url || '/content';
apex_web_service.clear_request_headers;
p_file_content := apex_web_service.make_rest_request_b(
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_code = %s, failed to download file', apex_web_service.g_status_code);
raise e_api_call_failed;
end if;
l_file := json_object_t(p_file);
return l_file.get_string('id');
end retrieve_file;
/**
* ファイルの削除
*/
function delete_file(
p_file_id in varchar2
,p_credential_static_id in varchar2
,p_file out clob
)
return varchar2
as
l_url varchar2(400);
l_response clob;
e_api_call_failed exception;
l_file json_object_t;
begin
/* ファイルのメタデータの取得 */
l_url := 'https://api.openai.com/v1/files/' || p_file_id;
apex_web_service.clear_request_headers;
l_response := apex_web_service.make_rest_request(
p_url => l_url
,p_http_method => 'DELETE'
,p_credential_static_id => p_credential_static_id
);
if apex_web_service.g_status_code <> 200 then
apex_debug.info('status_code = %s, response = %s', apex_web_service.g_status_code, l_response);
raise e_api_call_failed;
end if;
/* 削除したファイルのメタデータをp_fileとして返す。 */
p_file := l_response;
l_file := json_object_t(p_file);
return l_file.get_string('id');
end delete_file;
end utl_openai_files_api;
/
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/openai-files-sample.zip

アップロードしたファイルは、OpenAIの画面からも確認できます。


簡単に実装について紹介します。

ファイルのアップロードは、ファイルを選択するページ・アイテムP1_FILE(タイプはファイルのアップロード)とボタンUPLOADで実装しています。ストレージタイプとして表APEX_APPLICATION_TEMP_FILESを選択します。


ボタンUPLOADをクリックしたときに実行されるプロセスには、以下のコードを記述しています。

declare
l_file clob;
l_id varchar2(400);
begin
for r in (
select * from apex_application_temp_files
where name = :P1_FILE
)
loop
l_id := utl_openai_files_api.upload_file(
p_filename => r.filename
,p_content_type => r.mime_type
,p_file_content => r.blob_content
,p_credential_static_id => :G_OPENAI_API_KEY
,p_file => l_file
);
:P1_RESPONSE := l_file;
end loop;
end;


ファイルのダウンロード削除はクラシック・レポートに追加した仮想列リンクを設定してプロセスを呼び出しています。


ターゲットの設定ではページはページ、つまり同じページに遷移させています。file_idをページ・アイテムのP1_IDに渡しています。リクエストの値は、ダウンロードの場合はDOWNLOAD、削除の場合はDELETEを渡し、レンダリング前に作成しているプロセスを呼び出す条件に使用します。


ファイルをダウンロードするプロセスは、レンダリング前ヘッダー前に作成しています。実行するコードとして、以下を記述します。

declare
l_id varchar2(400);
l_filename varchar2(400);
l_file clob;
l_file_content blob;
l_file_json json_object_t;
l_download apex_data_export.t_export;
begin
l_id := utl_openai_files_api.retrieve_file(
p_file_id => :P1_ID
,p_file => l_file
,p_file_content => l_file_content
,p_credential_static_id => :G_OPENAI_API_KEY
);
l_file_json := json_object_t(l_file);
l_filename := l_file_json.get_string('filename');
/* download */
l_download.file_name := l_filename;
l_download.as_clob := FALSE;
l_download.content_blob := l_file_content;
apex_data_export.download( p_export => l_download );
apex_application.stop_apex_engine;
end;
サーバー側の条件タイプリクエスト = 値を選択し、としてDOWNLOADを設定します。


削除もほぼ同じ実装です。削除を行なうプロセスのコードとして、以下を記述します。

declare
l_id varchar2(400);
l_filename varchar2(400);
l_file clob;
begin
l_id := utl_openai_files_api.delete_file(
p_file_id => :P1_ID
,p_file => l_file
,p_credential_static_id => :G_OPENAI_API_KEY
);
end;
サーバー側の条件タイプリクエスト = 値を選択し、としてDELETEを設定します。


OpenAIのFilesにアップロードされているファイルの一覧は、以下のURLを呼び出すことで取得できます。

https://api.openai.com/v1/files

最低1つでもファイルがアップロード済みであれば、RESTデータ・ソースの作成時にレスポンスを自動的に認識してくれます。


IdBytesObjectStatusPurposeFilenameCreated_AtStatus_Detailsが認識されています。


OpenAIのFiles APIを使ったAPEXアプリケーションの説明は以上です。

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