🍵

OpenAIの Create chat completion APIを使ってChatGPTからJSONの応答を得る方法

2024/11/27に公開

はじめに

OpenAIの Create chat completion APIを使ってChatGPTからJSONの応答を得る方法を3つ紹介します。

  1. response_formatを使ってJSONの応答を得る … オススメ
  2. toolsのfunctionを使ってJSONの応答を得る … JSON使ってAPI呼び出すならこれ
  3. messagesのみでJSONの応答を得る(非推奨) … ちょっと応答を得たいだけならこれでも良い

この記事ではTypeScriptで実装を行っています。
例として、架空の人物のプロフィールを作成して、以下のTypeScriptの型に当てはまるJSONを出力する実装を行いました。

type ProfileType =  {
  name: string;
  age: number;
  gender: "男" | "女" | "その他";
};

参考資料

OpenAI の Create chat completion の API Referenceは以下です。
https://platform.openai.com/docs/api-reference/chat/create

1. response_formatを使ってJSONの応答を得る

まずは、response_formatを使った方法を紹介します。単にJSONの応答を得たい場合には、今回紹介する3つの方法の中で最もお勧めの方法です。もし、得られたJSONを使ってAPIなどを呼び出したい場合には、第2章で紹介するtoolsのfunctionを使った方法が適していると思います。

1.1 response_formatを使った実装

response_formatを使ったTypeScriptの実装は以下の通りです。

const generateProfileWithResponseFormat = async () => {
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      messages: [
        {
          role: "user",
          content: "架空の人物のプロフィールを作成してください。",
        },
      ],
      response_format: {
        type: "json_schema",
        json_schema: {
          name: "profile",
          description: "人物のプロフィールです。",
          schema: {
            type: "object",
            properties: {
              name: {
                type: "string",
                description: "名前。",
              },
              gender: {
                type: "string",
                description: "性別。",
                enum: ["男", "女", "ノンバイナリー"],
              },
              age: {
                type: "integer",
                description: "年齢。",
              },
            },
            required: ["name", "gender", "age"],
            additionalProperties: false,
          },
          strict: true,
        },
      },
    }),
  });

  const responseJson = await response.json();
  const message = responseJson.choices[0].message;
  console.log(JSON.stringify(message, null, 2));
}

特にポイントとなるのは以下のプロパティです。

プロパティ名 説明
strict trueを指定することで、厳密なスキーマ遵守を有効にしています。
required name、gender、ageを指定することで全てのプロパティを必須としています。
additionalProperties falseを指定することで未定義のプロパティが追加されることを禁止しています。

このように厳格なスキーマ遵守と必須プロパティ指定、未定義プロパティの禁止をしても、期待する結果が得られないことがある点に注意してください。応答はZodのsafeParseなどを使って型チェックをすることをお勧めします。

1.2 response_formatを使った場合の応答

response_formatを使った場合の応答は以下の通りです。ここでは見やすさのためにchoices[0].messageのみを出力しています。
contentに期待したJSONの応答が得られているのが分かります。

{
  "content": "{\"name\":\"山田太郎\",\"gender\":\"男\",\"age\":20}",
  "role": "assistant"
}

2. toolsのfunctionを使ってJSONの応答を得る

次に、toolsのfunctionを使った方法を紹介します。こちらの方法は第1章で紹介したresponse_formatが搭載される前に使われていた方法です。
toolsのfunctionはAIに関数を選択してもらって、その関数を呼び出すためのパラメータを生成してもらう機能です。そのため、得られたJSONを使ってAPIなどを呼び出したい場合にはこちらを使うのが適していると思います。

2.1 toolsのfunctionを使った実装

toolsのfunctionを使ったTypeScriptの実装は以下の通りです。

const generateProfileWithTools = async () => {
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      messages: [
        {
          role: "user",
          content: "架空の人物のプロフィールを作成してください。",
        },
      ],
      tools: [
        {
          type: "function",
          function: {
            name: "postProfile",
            description: "人物のプロフィールを登録します。",
            parameters: {
              type: "object",
              properties: {
                name: {
                  type: "string",
                  description: "名前。",
                },
                gender: {
                  type: "string",
                  description: "性別。",
                  enum: ["男", "女", "ノンバイナリー"],
                },
                age: {
                  type: "integer",
                  description: "年齢。",
                },
              },
              required: ["name", "gender", "age"],
              additionalProperties: false,
            },
            strict: true,
          },
        },
      ],
      tool_choice: {
        type: "function",
        function: { name: "postProfile" },
      },
    }),
  });

  const responseJson = await response.json();
  const message = responseJson.choices[0].message;
  console.log(JSON.stringify(message, null, 2));
}

特にポイントとなるのは以下のプロパティです。

プロパティ名 説明
function 生成したプロフィールを登録する関数を指定します。こちらの例ではpostProfileという関数を定義しています。単にJSONの応答を得たい場合には、こちらの関数はダミーの関数になります。AIにはこちらの関数を呼び出すための引数として、指定した型のJSONを作ってもらいます。
tool_choice functionに定義した関数postProfileを指定することで、関数呼び出しを必須にしています。つまり、AIに対してこの関数を呼び出すためのパラメータとなるJSONの値を生成することを強制しています。
parameters 第1章のresponse_formatを使った方法のschemaと同じ値になっています。
strict trueを指定することで、厳密なスキーマ遵守を有効にしています。
required name、gender、ageを指定することで全てのプロパティを必須としています。
additionalProperties falseを指定することで未定義のプロパティが追加されることを禁止しています。

注意点は第1章のresponse_formatを使った方法と同様です。関数呼び出しを必須にして、厳密なスキーマ遵守を有効にしていても、期待する結果が得られないことがある点に注意してください。応答はZodのsafeParseなどを使って型チェックをすることをお勧めします。

2.2 toolsのfunctionを使った場合の応答

toolsのfunctionを使った場合の応答は以下の通りです。ここでは見やすさのためにchoices[0].messageのみを出力しています。
tool_calls.function.argumentsに期待したJSONの応答が得られているのが分かります。

{
  "content": null,
  "role": "assistant",
  "tool_calls": [
    {
      "function": {
        "arguments": "{\"name\":\"山田太郎\",\"gender\":\"男\",\"age\":20}",
        "name": "postProfile"
      },
      "id": "call_abc123",
      "type": "function"
    }
  ]
}

3. messagesのみでJSONの応答を得る(非推奨)

最後に、messagesのみを使った方法を紹介します。こちらは非推奨です。ちょっとした応答をもらう際には手軽で良い方法だと思いますが、システムの開発でこちらの方法を使うのは止めた方が良いでしょう。

3.1 messagesのみを使った実装

messagesのみを使ったTypeScriptの実装は以下の通りです。

const generateProfileWithNothing = async () => {
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      messages: [
        {
          role: "user",
          content: `架空の人物のプロフィールを作成してください。
応答は必ず以下のTypeScriptの型に当てはまるJSONのみを返してください。
トリプルクォートと改行とインデントは不要です。
\`\`\` json
type Profile = {
  name: string;
  age: number;
  gender: "男" | "女" | "その他";
};
\`\`\``,
        },
      ],
    }),
  });

  const responseJson = await response.json();
  const message = responseJson.choices[0].message;
  console.log(JSON.stringify(message, null, 2));
};

ポイントとしては、contentに期待するJSONの型を指定している点です。今回はTypeScriptで実装しているため、TypeScriptの型定義でJSONの型を指定しました。この代わりにJSON Schemaを使ってJSONの型を指定しても良いでしょう。
トリプルクォートと改行とインデントは不要というメッセージを入れることで、応答のJSONが第1章と第2章の実装で得られるものと同じになるようにしました。

3.2 messagesのみを使った場合の応答

messagesのみを使った場合の応答は以下の通りです。ここでは見やすさのためにchoices[0].messageのみを出力しています。
contentに期待したJSONの応答が得られているのが分かります。

{
  "content": "{\"name\":\"山田太郎\",\"gender\":\"男\",\"age\":20}",
  "role": "assistant"
}

おわりに

記事を読んでいただいてありがとうございました。今回はOpenAIの Create chat completion APIを使ってChatGPTからJSONの応答を得る方法を3つ紹介しました。実装の具体例として、どなたかの参考になれば嬉しいです。この記事が良かったら「いいね」もいただけたら嬉しいです。よろしくお願いします!

Discussion