2024年10月29日火曜日

OpenAIのChat Completions APIでStructured Outputsを指定する

以前の記事「Oracle APEXのアプリからいろいろなローカルLLMを呼び出してみる」にて、OpenAI Chat Completions APIを呼び出すAPEXアプリケーションを作成しています。

上記の記事で作成したAPEXアプリケーションを、Structured Outputsの指定ができるように改変しました。API呼び出しを実装しているパッケージUTL_OPENAI_CHAT_APIも、Structured Outputsに対応するために変更しています。ただしFunction Callingを実装していたので、改変部分は非常に少ないです。

パッケージ定義
https://gist.github.com/ujnak/93c9d427ccef53e30dd89e623f08c880

パッケージ本体
https://gist.github.com/ujnak/a6126f7e9ee264d0e384103cea0379b5

以下は、Structured Outputsを指定してOpenAIのChat Completions APIを呼び出した結果です。

与えているシステム・プロンプトとユーザーのメッセージは、OpenAIのStructured outputsのドキュメントにExampleとして掲載されていたものを与えています。

システム・プロンプト
「You are a helpful math tutor. Guide the user through the solution step by step.」
ユーザー・メッセージ
「how can I solve 8x + 7 = -23」


モデルgpt-4o-miniは、以下の結果をJSON形式で返してきました。
{
  "steps" :
  [
    {
      "explanation" : "Start with the original equation: 8x + 7 = -23",
      "output" : "8x + 7 = -23"
    },
    {
      "explanation" : "To isolate the term with x, subtract 7 from both sides of the equation.",
      "output" : "8x + 7 - 7 = -23 - 7"
    },
    {
      "explanation" : "Simplifying both sides gives us: 8x = -30",
      "output" : "8x = -30"
    },
    {
      "explanation" : "Next, divide both sides by 8 to solve for x.",
      "output" : "x = -30 / 8"
    },
    {
      "explanation" : "Simplifying -30/8 gives us -15/4 or -3.75.",
      "output" : "x = -3.75 or x = -15/4"
    }
  ],
  "final_answer" : "x = -15/4 or x = -3.75"
}
Structured Outputsに対応したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/chat-with-generative-ai-so.zip

以下より、アプリケーションの変更点を紹介します。

最初に、Structured Outputsの指定に含めるJSONスキーマを保存する表を作成します。クイックSQLの以下のモデルより、表OPENAI_RESPONSE_FORMATSを作成しています。
# prefix: openai
response_formats
    format_name vc80 /nn
    format_type vc20 /check text,json_object,json_schema
    json_schema json
    description vc4000

クイックSQLで列のタイプにjsonを指定すると、DDLでの列定義はclob check (json_schema is json)になります。本当はCLOB型ではなくBLOB型にformat osonの指定を加えるか、23aiであればJSON型とする方が、容量面でも性能面でも有利です。今回はAPEXのアプリケーションの作りやすさを優先して、CLOB型のままとします。Oracle APEXのウィザードは、CLOB型についてはテキスト領域を自動生成し、BLOB型についてはファイル選択を生成します。

作成した表OPENAI_RESPONSE_FORMATSのデータを操作するため、フォーム付き対話モード・レポートのページを作成します。


対話モード・レポート、フォームともに、ソースとしてOPENAI_RESPONSE_FORMATSが指定されている、ごく一般的なページになります。


OPENAI_RESPONSE_FORMATSには、設定の名前である列FORMAT_NAME、形式の指定である列FORMAT_TYPE、出力形式を指定するJSONスキーマの列JSON_SCHEMAと、補足説明を記述する列DESCRIPTIONがあります。

FORMAT_TYPEとして指定できる値はtextjson_objectと、Structured Outputsの指定であるjson_schemaがあります。これらの値は、ページ・アイテムのタイプ選択リストとし、静的値として設定しています。


JSON_SCHEMAには、正確に言うとOpenAIのChat Completions APIのメッセージの属性reponse_formatに与えるJSONデータを設定します。今回の実装例で参照しているOpenAIのExampleでは、以下の値がJSON Schemaとして設定する値になります。

schema属性のJSONデータが本来はJSONスキーマになりますが、namestrictschema属性を含むJSONオブジェクトをJSONスキーマとして設定しています。
{
  "name" : "math_reasoning",
  "schema" :
  {
    "type" : "object",
    "properties" :
    {
      "steps" :
      {
        "type" : "array",
        "items" :
        {
          "type" : "object",
          "properties" :
          {
            "explanation" :
            {
              "type" : "string"
            },
            "output" :
            {
              "type" : "string"
            }
          },
          "required" :
          [
            "explanation",
            "output"
          ],
          "additionalProperties" : false
        }
      },
      "final_answer" :
      {
        "type" : "string"
      }
    },
    "required" :
    [
      "steps",
      "final_answer"
    ],
    "additionalProperties" : false
  },
  "strict" : true
}
JSONデータを入力するページ・アイテムに計算を設定し、保存するデータをプリティ・プリントしています。

select json_serialize(:P5_JSON_SCHEMA pretty) from dual


以上で、Structured Outputsのresponse_formatを設定するページが作成できました。

ホーム・ページのTool Setの選択を行う選択リストの下に、Response Formatを選択するページ・アイテムを作成します。


ページ・アイテムのタイプ選択リストLOVSQL問合せとして、以下のSELECT文を記述します。

select format_name d, id r from openai_response_formats


Chat Completions APIの応答なるJSONデータが読みやすくなるように、本体拡張フォーマットオンにし、HTML式に以下を記述します。

<pre>&CLOB001.</pre>


ボタンSend Messageをクリックしたときに呼び出されるプロセスSend Messageは、パッケージUTL_OPENAI_CHAT_APIに含まれるプロシージャCHATを呼び出しています。

Structured Outputsに対応するため、引数p_response_formatの扱いを変えています。また、引数p_json_schemaが追加されています。

引数p_response_formatタイプSQL問合せ(単一の値を返す)に変更し、SQL問合せとして、以下を記述しています。ページ・アイテムP1_RESPONSE_FORMATで選択したフォーマット・タイプ(つまりtext、json_object、json_schemaのどれか)が、引数p_response_formatに渡されます。

select format_type from openai_response_formats where id = :P1_RESPONSE_FORMAT


新しく追加された引数p_json_schemaSQL問合せに、以下を記述しています。

select json_schema from openai_response_formats where id = :P1_RESPONSE_FORMAT

ページ・アイテムP1_RESPONSE_FORMATで選択したJSONスキーマが、引数p_json_schemaに渡されます。


APEXアプリケーションをStructured Outputsに対応させるために行なった変更は以上になります。

Ollamaにllama3.2をロードして、Structured Outputsに対応しているかどうか確認してみました。

指示通りに正解を返しますが、Strucured Outputsは無視されました。OllamaでサポートしているのはあくまでJSON mode(typeがjson_objectで、schemaまでは指定できない)までのようです。


llama3.2の回答です。
To solve for x, we need to isolate the variable x on one side of the equation.

Here's our starting point:

8x + 7 = -23

Step 1: Subtract 7 from both sides of the equation to get rid of the constant term on the left-hand side.

This will give us a new equation with no constant terms on the left-hand side:
8x = -23 - 7
= -30

Now we have:

8x = -30

Step 2: Divide both sides of the equation by 8 to isolate x. This is called "inverse operations".

Dividing by 8 will cancel out the coefficient (the number multiplied by the variable) on the left-hand side.

(-30) ÷ 8 = ?

= -3.75

And that's our solution for x!

Now you know: x = -3.75

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