2024年12月9日月曜日

OllamaのStructured outputsの動作を確認する

OllamaがStructured Outputsをサポートしました。2024年12月6日付のブログ記事Structured outputsに紹介されています。

上記の記事の最後の方に、OpenAI compatibilityのセクションがあります。OpenAI互換のChat Completions APIでもStructured outputsをサポートしているとのことなので、以前に作成したOpenAIのChat Completions APIを呼び出すアプリを使って動作を確認してみます。

作業環境はApple Macbook ProのM4 Max/128GBです。大きなモデルを動かした方が良い結果が得られるとは思いますが、llama3.1:8bのモデルでも指定したJSONスキーマに従った回答が得られました。

日本語能力が高くないllama3.1に、日本語で「日本について教えて。」と聞いたせいか、languages日本語英語中国語が返されています。また、同じ質問でも回答がブレます。英語で「Tell me about Japan.」と聞くと、ほとんどは以下の回答になります。
{
  "capital" : "Tokyo",
  "languages" :
  [
    "Japanese"
  ],
  "name" : "Japan"
}
どちらでも指定したJSONスキーマに従ったレスポンスにはなっています。


OpenAIのChat Completions APIを呼び出すAPEXアプリケーションは、今までTool CallingやStructured Outputsの対応を後付けしてきたため、アプリケーションを動くようにするために、あちこちの記事を参照する必要があります。

以下にインストールまでの手順をまとめます。

1. ローカルPCへのAPEX環境作成


手元のPCにAPEX環境を作成する手順は以下の記事を参照します。Oracle Database 23ai FreeとOracle REST Data Servicesそれぞれのコンテナを作成して、APEX環境を作成しています。

podmanを使ってOracle Database FreeとOracle REST Data Servicesをコンテナとして実行する

Oracle Database 23ai FreeのコンテナにOracle REST Data Servicesをインストールする手順も紹介していますが(こちらの記事)、せっかくOracle社がソフトウェアをインストール済みのコンテナ・イメージを提供してくれているので、そちらを使用する方が作業は容易です。

2. 表OPENAI_TOOLSの作成


以下のDDLを実行して表OPENAI_TOOLSを作成します。この表はTool Callingの設定を保持します。

CREATE TABLE "OPENAI_TOOLS"
( "ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
"TOOL_SET" VARCHAR2(20 CHAR) NOT NULL ENABLE,
"TOOL_TYPE" VARCHAR2(20 CHAR) DEFAULT ON NULL 'function' NOT NULL ENABLE,
"DESCRIPTION" VARCHAR2(4000 CHAR) NOT NULL ENABLE,
"TOOL_NAME" VARCHAR2(80 CHAR) NOT NULL ENABLE,
"PARAMETERS" CLOB,
CHECK (parameters is json) ENABLE,
CONSTRAINT "OPENAI_TOOLS_ID_PK" PRIMARY KEY ("ID")
USING INDEX ENABLE
) ;

表OPENAI_TOOLSのDDLが掲載されている記事は以下です。
OpenAIのChat Completions APIを呼び出すAPEXアプリを作成する

APEXアプリケーションへのToll Callingの組み込みを紹介している記事は以下です。


3. 表OPENAI_RESPONSE_FORMATSの作成


以下のDDLを実行して表OPENAI_RESPONSE_FORMATSを作成します。この表はStructured Outputsの指定に必要なJSONスキーマを保持します。

create table openai_response_formats (
id number generated by default on null as identity
constraint openai_response_formats_id_pk primary key,
format_name varchar2(80 char) not null,
format_type varchar2(20 char) constraint openai_response_formats_format_type_ck
check (format_type in ('text','json_object','json_schema')),
json_schema clob check (json_schema is json),
description varchar2(4000 char)
);

表OPENAI_RESPOSE_FORMATSが掲載されている記事は以下です。
OpenAIのChat Completions APIでStructured Outputsを指定する


4. パッケージUTL_OPENAI_CHAT_APIの作成



記事の末尾に添付されているパッケージ定義部とパッケージ本体のコードを実行し、パッケージUTL_OPENAI_CHAT_APIを作成します。


5. APEXアプリケーションのインポート



以下のAPEXアプリケーションをワークスペースにインポートします。
https://github.com/ujnak/apexapps/blob/master/exports/chat-with-generative-ai-so.zip

APEXアプリケーションへのStructured Outputsの対応を紹介した記事は以下です。
OpenAIのChat Completions APIでStructured Outputsを指定する

以上で、ローカルのOllamaを呼び出してStructured Outputsの確認をするAPEXアプリケーションの準備ができました。

今回の作業で使用したOllamaのバージョンは0.5.1です。バージョンが古い場合は、アップデートしておきます。

ollama --version

% ollama --version

ollama version is 0.5.1

%



動作確認



本記事の本題の動作確認をします。

最初にOllamaのブログ記事にある例を動かしてみます。国の情報をJSONで返してもらいます。

Response Formatsのページを開きます。Format NamecountryFormat Typejson_schema、JSON Schemaは以下を登録します。
{
  "type" : "object",
  "properties" :
  {
    "name" :
    {
      "type" : "string"
    },
    "capital" :
    {
      "type" : "string"
    },
    "languages" :
    {
      "type" : "array",
      "items" :
      {
        "type" : "string"
      }
    }
  },
  "required" :
  [
    "name",
    "capital",
    "languages"
  ]
}

Ollamaでllama3.1:8bをpullします。

ollama pull llama3.1:8b

% ollama pull llama3.1:8b

pulling manifest 

pulling 667b0c1932bc... 100% ▕███████████████████████████████████████████████████████████████████████▏ 4.9 GB                         

pulling 948af2743fc7... 100% ▕███████████████████████████████████████████████████████████████████████▏ 1.5 KB                         

pulling 0ba8f0e314b4... 100% ▕███████████████████████████████████████████████████████████████████████▏  12 KB                         

pulling 56bb8bd477a5... 100% ▕███████████████████████████████████████████████████████████████████████▏   96 B                         

pulling 455f34728c9b... 100% ▕███████████████████████████████████████████████████████████████████████▏  487 B                         

verifying sha256 digest 

writing manifest 

success 

% 


APEXアプリケーションのホーム・ページを開きます。以下の操作を行い、LLMの出力を確認します。
  1. ボタンStart New Conversationをクリックする。
  2. Response Formatcountryを選択する。
  3. API Endpointhttp://host.containers.internal:11434/v1/chat/completionsを入力する。
  4. Model Namellama3.1:8bを入力する。
  5. MessageTell me about Japanを入力する。
  6. ボタンSend Messageをクリックする。
以上で、assistantから以下のメッセージが返されます。
{
  "capital" : "Tokyo",
  "languages" :
  [
    "Japanese"
  ],
  "name" : "Japan"
}

もう一つ、JSON Schemaを登録します。物語の登場人物と関係をJSONで表現します(Batch APIの記事で使っています)。

Format NamestoryFormat Typejson_schemaJSON Schemaとして以下を登録します。

{
"$schema" : "http://json-schema.org/draft-07/schema#",
"title" : "StoryCharactersSchema",
"type" : "object",
"properties" :
{
"characters" :
{
"type" : "array",
"items" :
{
"type" : "object",
"properties" :
{
"name" :
{
"type" : "string",
"description" : "The name of the character."
},
"role" :
{
"type" : "string",
"description" : "The role of the character in the story."
},
"relations" :
{
"type" : "object",
"additionalProperties" :
{
"type" : "string",
"description" : "The relationship of the character to another character."
},
"description" : "Relationships of the character with other characters."
}
},
"required" :
[
"name",
"role"
],
"additionalProperties" : false
}
}
},
"required" :
[
"characters"
],
"additionalProperties" : false
}


Response Formatstoryを選択し、Messageとして以下を入力します。白雪姫の物語をChatGPTに1000語で出力してもらいました。
Please represent the characters and their relationships in the following story in JSON format.
--------
[ChatGPTが出力した白雪姫の物語を添付]


assistantからのレスポンスは以下です。
{
  "characters" :
  [
    {
      "name" : "Queen",
      "role" : "Main Character",
      "relations" :
      {
        "spouse" : "",
        "children" : "Snow White"
      }
    },
    {
      "name" : "King (initial husband of Queen)",
      "role" : "Background/Dead",
      "relations" :
      {
        "wife" : "Queen"
      }
    },
    {
      "name" : "New queen (second wife of King)",
      "role" : "Antagonist",
      "relations" :
      {
        "husband" : "King"
      }
    },
    {
      "name" : "Snow White",
      "role" : "Protagonist/Princess",
      "relations" :
      {
        "mother" : "Queen (initial husband's wife)",
        "father" : "King (initial)"
      }
    },
    {
      "name" : "Huntsman",
      "role" : "Support Character",
      "relations" :
      {
      }
    },
    {
      "name" : "Mirror",
      "role" : "Plot Device",
      "relations" :
      {
      }
    },
    {
      "name" : "Seven Dwarfs (miner group)",
      "role" : "Support Characters/Homeowners",
      "relations" :
      {
      }
    },
    {
      "name" : "Prince",
      "role" : "Love Interest/Hero",
      "relations" :
      {
        "partner/wife" : "Snow White"
      }
    }
  ]
}
指定したJSONスキーマに規定されたレスポンスが返されていることが確認できます。

もう少し頑張ってプロンプトを書くと、レスポンスに含まれるroleやrelationsの精度は上がるように思います。日本語についてはモデル次第だろうとは思いますが、いくつか試したモデルでは、意味のある回答は得られませんでした。

今回の記事は以上になります。



create or replace package utl_openai_chat_api as
/**
* Change History:
* 2024/11/29 - ynakakos change name and strict included in the json_schema to arguments.
* 2024/10/29 - ynakakos add OpenAI Structured Outputs support.
*/
/**
* ツールセット名からJSON形式のツール定義を生成する。
*
* @param p_tool_set ツールセット名
* @return json_array_t toolsとして送信するファンクション定義
*/
function generate_tools(
p_tool_set in varchar2
) return json_array_t;
/**
* OpenAI Chat Completions APIおよび互換APIを呼び出す。
* OpenAIのエンドポイント https://api.openai.com/v1/chat/completionsと互換ががあること。
*
* @param p_content メッセージ本文
* @param p_collection_name チャット履歴を保存するAPEXコレクションの名前
* @param p_api_endpoint Chat Completions APIを呼び出すURL
* @param p_model_name モデル名(必須)
* @param p_max_tokens トークン数の上限
* @param p_stream streamingの許可 - APEXはstreamingが扱えないためデフォルトでfalse
* @param p_credential_static_id Web資格証明の静的ID
* @param p_tool_set 表GENAI_TOOLSのTOO_SET列の名前
* @param p_response_format text、json_object または json_schema
* @param p_json_schema_name respons_formatがjson_schematのときのname属性の値
* @param p_json_schema_strict 同上のstrict属性の値
* @param p_json_schema 同上のschema属性の値 - リクエストに含めるJSON schema
* @param p_request_out デバッグ用 - Chat APIに送信したリクエスト本文
* @param p_response_out デバッグ用 - Chat APIから受信したレスポンス本文
* @param p_transfer_timeout Chat APIの応答のタイムアウト - 秒
* @param p_recursive_calL_count ツール呼び出しを行った後に許可する再帰呼び出しの回数。
*/
procedure chat(
p_content in clob default null
,p_collection_name in varchar2
,p_api_endpoint in varchar2
,p_model_name in varchar2
,p_max_tokens in number default null
,p_stream in boolean default false
,p_credential_static_id in varchar2 default null
/* function calling & Structured Outputs 向け */
,p_tool_set in varchar2 default null
,p_response_format in varchar2 default null
/* p_response_formatがjson_schemaのときに有効 */
,p_json_schema_name in varchar2 default null
,p_json_schema_strict in boolean default true
,p_json_schema in clob default null
/* 以下、デバッグ用 */
,p_request_out out clob
,p_response_out out clob
/* 無視しても良いパラメータ */
,p_transfer_timeout in number default 360 -- 10 min
,p_recursive_call_count in number default 0
);
end utl_openai_chat_api;
/

create or replace package body utl_openai_chat_api as
/**
* APEXコレクションに保存されたチャット履歴からメッセージを生成する。
* ツールからの応答だけを送信する場合は、p_contentをnullにする。
*
* @param p_collection_name チャット履歴を保存するAPEXコレクション名
* @param p_role チャット履歴の最後に追加するメッセージのロール - デフォルトuser
* @param p_content チャット履歴の最後の追加するメッセージ - デフォルトnull
* @return json_array_t 送信するメッセージの配列
*/
function init_message_from_collection(
p_collection_name in varchar2
,p_role in varchar2 default 'user'
,p_content in clob default null
)
return json_array_t
as
l_messages json_array_t;
l_message json_object_t;
begin
/*
* ユーザーからの入力があれば、Chat履歴の最後に追加する。
*/
if p_content is not null then
apex_collection.add_member(
p_collection_name => p_collection_name
,p_c001 => p_role
,p_clob001 => p_content
);
end if;
/*
* 生成AIのChat APIに送信するメッセージを作成する。
  * APEXコレクションより、作成順の昇順でメッセージの配列にする。
*/
l_messages := json_array_t();
for r in (
/*
* contentが空であればメッセージに含めない。
* ロールがsystemでは、メッセージが空のときがある。
*/
select c001, c002, c003, clob001 from apex_collections
where collection_name = p_collection_name and dbms_lob.getlength(clob001) > 0
order by seq_id
)
loop
l_message := json_object_t();
l_message.put('role' ,r.c001);
if r.c001 = 'tool' then -- c001はrole
l_message.put('tool_call_id', r.c002);
l_message.put('name', r.c003);
end if;
if r.c002 = 'tool_calls' then -- c002はfinish_reason
l_message.put('tool_calls', json_array_t(r.clob001));
else
l_message.put('content', r.clob001);
end if;
l_messages.append(l_message);
end loop;
return l_messages;
end init_message_from_collection;
/**
* ツールセット名からJSON形式のツール定義を生成する。
*/
function generate_tools(
p_tool_set in varchar2
) return json_array_t
as
l_tools json_array_t;
l_tool json_object_t;
l_function json_object_t;
l_parameters json_object_t;
begin
l_tools := json_array_t();
for r in (
/* ツールの指定は表OPENAI_TOOLSに保存されている。 */
select * from openai_tools where tool_set = p_tool_set
)
loop
if r.tool_type = 'function' then
/* only function is supported as of 19, Apr. 2024 */
l_tool := json_object_t();
l_tool.put('type','function');
l_function := json_object_t();
l_function.put('name', r.tool_name);
l_function.put('description', r.description);
/* parametersにはJSON Schemaがそのまま設定されている */
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 if;
end loop;
return l_tools;
end generate_tools;
/**
* メッセージにツールの指定を含める。
*
* @param p_request Chat APIのリクエスト本文 in/out
* @param p_tool_set toolとして含めるツールを選択するtool_set名(GENAI_TOOLS.TOOL_SET)
* @param p_response_format text、json_object または json_schema
* @param p_json_schema_name respons_formatがjson_schematのときのname属性の値
* @param p_json_schema_strict 同上のstrict属性の値
* @param p_json_schema 同上のschema属性の値 - リクエストに含めるJSON schema
*/
procedure configure_tools(
p_request in out json_object_t
,p_tool_set in varchar2 default null
,p_response_format in varchar2 default null
,p_json_schema_name in varchar2 default null
,p_json_schema_strict in boolean default true
,p_json_schema in clob default null
)
as
l_tools json_array_t;
l_response_format json_object_t;
l_json_schema json_object_t;
begin
/*
* p_tool_setの指定があれば、toolの定義を送信する。
*/
if p_tool_set is not null then
l_tools := generate_tools(
p_tool_set => p_tool_set
);
p_request.put('tools', l_tools);
end if;
/* response_format */
if p_response_format is not null then
l_response_format := json_object_t();
l_response_format.put('type', p_response_format); /* text, json_object or json_schema */
if p_response_format = 'json_schema' then
/*
* json_schemaとして与えられたJSON SchemaをStructured Outputsに
* 必要なname, schema, strictでラップする。
*/
l_json_schema := json_object_t();
l_json_schema.put('name', p_json_schema_name);
l_json_schema.put('schema', json_object_t(p_json_schema));
l_json_schema.put('strict', p_json_schema_strict);
l_response_format.put('json_schema', l_json_schema);
end if;
p_request.put('response_format', l_response_format);
end if;
end configure_tools;
/**
* ツール呼び出し(function calling)の処理を行う。
* tool_callsを解釈しファンクションを呼び出し、ロールをtoolとして呼び出したファンクションの応答を
* APEXコレクションに追記する。
*
* @param p_collectiuon_name Chat履歴を保存するAPEXコレクション名
* @param p_message tool_callsを含んだレスポンス本文
*/
procedure process_tool_calls(
p_collection_name in varchar2
,p_message in json_object_t
)
as
l_tool_calls json_array_t;
l_tool_call json_object_t;
l_tool_call_id varchar2(80);
l_function_call json_object_t;
l_function_name varchar2(160);
l_function_args clob;
l_function_arg_obj json_object_t;
l_dynamic_sql varchar2(4000);
l_function_out clob;
begin
l_tool_calls := p_message.get_array('tool_calls');
for i in 1..l_tool_calls.get_size()
loop
l_tool_call := treat(l_tool_calls.get(i-1) as json_object_t);
if l_tool_call.get_string('type') = 'function' then
/* only function is supported as type */
l_tool_call_id := l_tool_call.get_string('id');
l_function_call := l_tool_call.get_object('function');
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');
if l_function_args is null then
/* OpenAI returns arguments as CLOB but could be json_object */
l_function_arg_obj := l_function_call.get_object('arguments');
if l_function_arg_obj is not null then
l_function_args := l_function_arg_obj.to_clob();
end if;
end if;
/* ストアド・プロシージャを動的に呼び出す。 */
apex_debug.info('Calling %s with %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;
apex_collection.add_member(
p_collection_name => p_collection_name
,p_c001 => 'tool'
,p_c002 => l_tool_call_id
,p_c003 => l_function_name
,p_clob001 => l_function_out
);
end if;
end if;
end loop;
end process_tool_calls;
/**
* Batch APIの呼び出しも使えるように、メッセージの作成部分をプロシージャCHATより
* 分離した。
*/
function generate_chat_message(
p_content in clob
,p_collection_name in varchar2
,p_model_name in varchar2
,p_max_tokens in number
,p_stream in boolean
/* function calling向け */
,p_tool_set in varchar2
,p_response_format in varchar2
,p_json_schema_name in varchar2
,p_json_schema_strict in boolean
,p_json_schema in clob
)
return clob
as
l_request json_object_t;
l_request_clob clob;
l_messages json_array_t;
l_message json_object_t;
begin
/*
* LLMに送信するメッセージをl_requestとして作成する。
*/
l_request := json_object_t();
/* modelは必ず必要 */
l_request.put('model', p_model_name);
if p_max_tokens is not null then
l_request.put('max_tokens', p_max_tokens);
end if;
/* APEXではstreamingは処理できないので、基本常にfalse */
if p_stream is not null then
l_request.put('stream', p_stream);
end if;
/*
* APIで送信するメッセージをチャット履歴から初期化する。
*/
l_messages := init_message_from_collection(
p_collection_name => p_collection_name
,p_content => p_content
);
l_request.put('messages', l_messages);
/*
* ツール・セットp_tool_setの指定があれば、toolの構成を
* リクエストに含める。
*/
configure_tools(
p_request => l_request
,p_tool_set => p_tool_set
,p_response_format => p_response_format
,p_json_schema_name => p_json_schema_name
,p_json_schema_strict => p_json_schema_strict
,p_json_schema => p_json_schema
);
/*
* temprature, top_pなどのパラメータを設定するとしたら、ここでputする。
*/
/*
* 生成AIのChat APIを呼び出す。
*/
l_request_clob := l_request.to_clob();
return l_request_clob;
end generate_chat_message;
/**
* Chat APIの呼び出し。
*/
procedure chat(
p_content in clob
,p_collection_name in varchar2
,p_api_endpoint in varchar2
,p_model_name in varchar2
,p_max_tokens in number
,p_stream in boolean
,p_credential_static_id in varchar2
/* function calling向け */
,p_tool_set in varchar2
,p_response_format in varchar2
,p_json_schema_name in varchar2
,p_json_schema_strict in boolean
,p_json_schema in clob
/* 以下、デバッグ用 */
,p_request_out out clob
,p_response_out out clob
/* 無視しても良いパラメータ */
,p_transfer_timeout in number
,p_recursive_call_count in number
)
as
l_request_clob clob;
l_message json_object_t;
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;
/* usage */
l_n001 number;
l_n002 number;
l_n003 number;
/* response_format */
l_response_format json_object_t;
l_finish_reason varchar2(4000);
e_to_many_recursive_calls exception;
/* Ollama Functiion Calling */
l_tools_string clob;
l_tools_obj json_object_t;
l_seq number;
r_member apex_collections%rowtype;
/* JSON構文エラー */
e_bad_json_syntax exception;
pragma exception_init(e_bad_json_syntax, -40441);
begin
/*
* ツールの再帰呼び出しは1回までに限定する。
*/
if p_recursive_call_count > 1 then
raise e_to_many_recursive_calls;
end if;
/*
* 送信メッセージの作成。
*/
l_request_clob := generate_chat_message(
p_content => p_content
,p_collection_name => p_collection_name
,p_model_name => p_model_name
,p_max_tokens => p_max_tokens
,p_stream => p_stream
,p_tool_set => p_tool_set
,p_response_format => p_response_format
,p_json_schema_name => p_json_schema_name
,p_json_schema_strict => p_json_schema_strict
,p_json_schema => p_json_schema
);
p_request_out := l_request_clob; -- デバッグ用
/*
* Chat Completions 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 => p_api_endpoint
,p_http_method => 'POST'
,p_body => l_request_clob
,p_credential_static_id => p_credential_static_id
,p_transfer_timeout => p_transfer_timeout
);
p_response_out := l_response_clob;
l_response := json_object_t(l_response_clob);
/*
* 生成AIからの応答を処理する。
*/
l_choices := l_response.get_array('choices');
if l_choices is not null then
/* OpenAI flavor */
l_choice0 := treat(l_choices.get(0) as json_object_t);
l_message := l_choice0.get_object('message');
/* finish_reaonは、後で確認する。 */
l_finish_reason := l_choice0.get_string('finish_reason');
else
/* Ollama flavor */
l_message := l_response.get_object('message');
end if;
l_role := l_message.get_string('role');
if l_finish_reason = 'tool_calls' then
l_content := l_message.get_array('tool_calls').to_clob();
else
l_content := l_message.get_clob('content');
end if;
/* usageも取り出す。 */
l_usage := l_response.get_object('usage');
if l_usage is not null then
/* OpenAI flavor */
l_n001 := l_usage.get_number('prompt_tokens');
l_n002 := l_usage.get_number('completion_tokens');
l_n003 := l_usage.get_number('total_tokens');
else
/* Ollama flavor */
l_n001 := l_response.get_number('prompt_eval_count');
l_n002 := l_response.get_number('eval_count');
end if;
/*
* 生成AIからの応答をAPEXコレクションに追記する。
*
* response_formatの指定がjson_objectまたはjson_schemaの
* 場合は、pretty printする。
*/
if p_response_format in ('json_object', 'json_schema') then
begin
select json_serialize(l_content pretty) into l_content from dual;
exception
when e_bad_json_syntax then
/* 出力を見ると分かるので、構文エラーは無視 */
null;
end;
end if;
apex_collection.add_member(
p_collection_name => p_collection_name
,p_c001 => l_role
,p_c002 => l_finish_reason
,p_clob001 => l_content
,p_n001 => l_n001
,p_n002 => l_n002
,p_n003 => l_n003
);
/*
* finish_reasonがtool_callsであれば、指定されたツールを呼び出す。
*/
if l_finish_reason = 'tool_calls' then
process_tool_calls(
p_collection_name => p_collection_name
,p_message => l_message
);
/*
* ツールを呼び出した結果をLLMに送信する。
*/
chat(
p_collection_name => p_collection_name
,p_api_endpoint => p_api_endpoint
,p_model_name => p_model_name
,p_max_tokens => p_max_tokens
,p_stream => p_stream
,p_credential_static_id => p_credential_static_id
,p_tool_set => p_tool_set
,p_response_format => p_response_format
,p_request_out => p_request_out
,p_response_out => p_response_out
,p_transfer_timeout => p_transfer_timeout
,p_recursive_call_count => (p_recursive_call_count + 1)
);
end if;
end chat;
end utl_openai_chat_api;
/