Azure OpenAI Service の公式情報などを見つつ「GPT-4 Turbo」の「Chat Completions の JSON Mode」を試す：【Node.js（JavaScript）で「@azure/openai」を利用】

## はじめに
本記事で扱う内容は、以下の記事や過去の他の記事でも書いてきた、Azure OpenAI Service に関するものです。

●Azure OpenAI Service の GPT-4 まわりの話を個人的にまとめてみる【2024/1/11時点】 #Azure - Qiita
　https://qiita.com/youtoy/items/c4b69c2891f8e77a92c5

その中でも、上記の記事の「サポートされているプログラミング言語」という項目で書いた JavaScript を使い、「GPT-4 Turbo の Chat Completions」を扱う話です。
せっかくなので、GPT-4 Turbo から利用できるようになった「JSON Mode」を使います。

## 参照する公式の情報
まずは、今回参照する公式の Azure OpenAI Service の情報源を記載します。

具体的には、[公式の Azure OpenAI Service でサポートされているプログラミング言語](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/supported-languages)というページに書かれている、以下のリンクです。

- azure-sdk-for-js/sdk/openai/openai at main · Azure/azure-sdk-for-js
   - https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/openai/openai
- @azure/openai - npm
   - https://www.npmjs.com/package/@azure/openai
- azure-sdk-for-js/sdk/openai/openai/samples at main · Azure/azure-sdk-for-js
   - https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/openai/openai/samples/

## この後を進めるための前提
ここから API を試していきますが、そのための前提条件をここで書いておきます。

まず、Azure OpenAI Service の GPT-4 が利用できることが大前提です。

また、[上で紹介していた過去記事](https://qiita.com/youtoy/items/c4b69c2891f8e77a92c5)ではいくつかのモデルをデプロイしてみていました。今回は、以下のモデル 2つをデプロイした状態で、この後の手順を進めてください。

- モデル名: gpt-4、モデル バージョン: 0613
- モデル名: gpt-4、モデル バージョン: 1106-Preview （＝ GPT-4 Turbo）

## GPT-4（Turbo や Turbo with visionではないもの）で chat completion を試す
まずは API利用のお試しの最初の一歩という感じで、GPT-4（Turbo ではないもの）の Chat Completions を、Node.js（JavaScript）で試します。

利用するパッケージは公式の「[@azure/openai](https://www.npmjs.com/package/@azure/openai)」です。

### サンプルを選ぶ
npm のページを見ると、以下の「Samples」からも、上で掲載していた GitHub のリポジトリにアクセスできるので、そこへアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/9909997c-3932-1dbc-3826-13ed8bcc2be8.png)

その中で、Typescript ではなく JavaScript のほうを選びます。

●azure-sdk-for-js/sdk/openai/openai/samples/v1-beta at main · Azure/azure-sdk-for-js
　https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/openai/openai/samples/v1-beta

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/125b3db7-7902-0047-dd87-625d4d14e3da.png)

そうすると、以下のサンプルが並んでいます。この中の「[chatCompletions.js](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/openai/openai/samples/v1-beta/javascript/chatCompletions.js)」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/7d67373c-d1d5-5897-3abb-f7f351a95f64.png)

### サンプルを使う準備
それでは、上記のサンプルを活用して API を使ってみます。
そのために、まずは npmコマンドを使って、パッケージをインストールします。

```bash
$ npm install @azure/openai
```

次に、JavaScript のプログラムを作ります。
上で書いていたサンプルの内容をまずは確認してみて、それを少し書きかえて使うことにします。

手を加える前は、以下の内容でした。

```javascript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/**
 * Demonstrates how to get chat completions for a chat context.
 *
 * @summary get chat completions.
 */

const { OpenAIClient, AzureKeyCredential } = require("@azure/openai");

// Load the .env file if it exists
require("dotenv").config();

// You will need to set these environment variables or edit the following values
const endpoint = process.env["ENDPOINT"] || "<endpoint>";
const azureApiKey = process.env["AZURE_API_KEY"] || "<api key>";

async function main() {
  console.log("== Chat Completions Sample ==");

  const client = new OpenAIClient(endpoint, new AzureKeyCredential(azureApiKey));
  const deploymentId = "gpt-35-turbo";
  const result = await client.getChatCompletions(deploymentId, [
    { role: "system", content: "You are a helpful assistant. You will talk like a pirate." },
    { role: "user", content: "Can you help me?" },
    { role: "assistant", content: "Arrrr! Of course, me hearty! What can I do for ye?" },
    { role: "user", content: "What's the best way to train a parrot?" },
  ]);

  for (const choice of result.choices) {
    console.log(choice.message);
  }
}

main().catch((err) => {
  console.error("The sample encountered an error:", err);
});

module.exports = { main };
```

#### プログラムを書きかえる
サンプルの内容を少し書きかえて以下とし、それを app.js という名前で保存します。

```javascript:app.js
const { OpenAIClient, AzureKeyCredential } = require("@azure/openai");

const endpoint = process.env["ENDPOINT"];
const azureApiKey = process.env["AZURE_API_KEY"];
const deploymentId = process.env["DEPLOYMENT_ID"];

async function main() {
  console.log("== Chat Completions Sample ==");

  const message = [
    { role: "system", content: "アシスタントとして丁寧に回答してください。" },
    { role: "user", content: "あなたは誰？" },
  ];
  console.log(`APIで送る内容`);
  console.log(message);

  console.log(`APIのレスポンス`);
  const client = new OpenAIClient(
    endpoint,
    new AzureKeyCredential(azureApiKey)
  );
  const result = await client.getChatCompletions(deploymentId, message);

  for (const choice of result.choices) {
    console.log(choice.message);
  }
}

main().catch((err) => {
  console.error("The sample encountered an error:", err);
});

module.exports = { main };
```

上記のプログラムの中では環境変数で、エンドポイントや APIキーの情報などを読み込んでいるため、プログラムを実行する前にそれらを設定します。

Mac の場合は、例えば以下のコマンドを実行すると、一時的に利用可能な環境変数が設定できます。
※ 今回は以下の環境変数を一時的に利用可能なものとしましたが、必要に応じて永続化などをしてください

```bash:Macの場合
export ENDPOINT='【エンドポイント（https://●●●.openai.azure.com）】'
export AZURE_API_KEY='【AzureのAPIキー】'
export DEPLOYMENT_ID='【DEPLOYMENT ID（デプロイ名）】'
```

それでは、node コマンドで app.js に書いた内容を実行します。

```bash
$ node app
```

以下が実行結果です。
エラーが発生することもなく、API のレスポンスとして以下のように回答が得られていることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d56378ad-8b8f-b727-d224-b0af3aaa514b.png)

## GPT-4 Turbo を試す
次に、GPT-4 Turbo で試してみます。

### JSON Mode ではないものを試す
まずは JSON Mode ではないものを試していきます。

GPT-4 のモデルをデプロイしたのと同じ条件で GPT-4 Turbo のモデルをデプロイしている場合（エンドポイント・APIキーが共通となっている場合）、上で試した内容をほぼそのままで試せます。
差分としては、 `【DEPLOYMENT ID（デプロイ名）】` のみ GPT-4 Turbo のものにすれば良いです。

自分は、同じリージョンで複数のモデルをデプロイしていたため、上記の状況になっていました。

ここで、デプロイ名を GPT-4 Turbo のモデルのものに変更して、先ほどと同じように API を使ってみます（先ほどと同じコマンドで、環境変数を 1つだけ上書きします）。

```bash:Macの場合
export DEPLOYMENT_ID='【DEPLOYMENT ID（デプロイ名）】'
```

プログラムを実行してみると、エラーが発生することなどもなく、無事にレスポンスが返ってきたのが確認できました。

### JSON Mode を試す
次に JSON Mode を試します。

Node.js で @azure/openai の「getChatCompletions()」を使う場合に、JSON Mode を指定する方法を確かめてみます。

検索などをしていると、以下の公式情報にたどり着きました。

●OpenAIClient class | Microsoft Learn
　https://learn.microsoft.com/ja-jp/javascript/api/@azure/openai/openaiclient?view=azure-node-preview#@azure-openai-openaiclient-getchatcompletions

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/8eb62dc5-43c8-32d3-6166-8cba262e8ab2.png)

どうやら `options GetChatCompletionsOptions` と書いてある部分で、何らかの指定をすれば良さそうです。

以下を見てみると、「responseFormat」という内容で、値を指定すれば良さそうでした。

●GetChatCompletionsOptions interface | Microsoft Learn
　https://learn.microsoft.com/ja-jp/javascript/api/%40azure/openai/getchatcompletionsoptions?view=azure-node-preview

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/0660a3be-6894-0644-dd82-e95c90747dfd.png)

#### JSON Mode をオプションで指定する
さらに指定方法について、情報を探してみました。

その結果、 `{ responseFormat: { type: "json_object" } }` という指定方法が見つかりました。

●@azure/openai - Set response_format for GPT-4 Turbo · Issue #27860 · Azure/azure-sdk-for-js
　https://github.com/Azure/azure-sdk-for-js/issues/27860

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/e5b34928-c137-e2c2-ad45-36f5266b0aea.png)

#### JSON Mode を指定したプログラムと実行結果
上記の `{ responseFormat: { type: "json_object" } }` という指定を行い、さらに API で送るメッセージを JSON というキーワードを含むものにします。

プログラム全体は以下のとおりです。

```javascript:app.js
const { OpenAIClient, AzureKeyCredential } = require("@azure/openai");

const endpoint = process.env["ENDPOINT"];
const azureApiKey = process.env["AZURE_API_KEY"];
const deploymentId = process.env["DEPLOYMENT_ID"];

async function main() {
  console.log("== Chat Completions Sample ==");

  const message = [
    { role: "system", content: "JSON形式で回答してください。" },
    { role: "user", content: "あなたはいつまでの知識をもっていますか？" },
  ];
  console.log(`APIで送る内容`);
  console.log(message);

  console.log(`APIのレスポンス`);
  const client = new OpenAIClient(
    endpoint,
    new AzureKeyCredential(azureApiKey)
  );
  const result = await client.getChatCompletions(
    deploymentId,
    message,
    {responseFormat: { type: "json_object" }}
  );

  for (const choice of result.choices) {
    console.log(choice.message);
  }
}

main().catch((err) => {
  console.error("The sample encountered an error:", err);
});

module.exports = { main };
```

上記を実行してみると、以下のように JSON形式のレスポンスが得られました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ac6666d5-5bda-212d-6e24-cd774d589ac9.png)

#### JSON Mode を使う際の注意点
上で掲載した結果のとおり、最終的にはうまくいったのですが、その前に 1つ失敗をしていました。
どんな失敗だったかというと、 `{ responseFormat: { type: "json_object" } }` という指定は行っていたものの、API で送るテキストの中に「JSON」というキーワードを含めていなかったことです。

その状態だと、API のレスポンスに、以下のような「送信するテキストに JSON というキーワードを入れるように」というメッセージが返ってきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d79ac22d-f571-351d-0368-25f27b081b4b.png)

#### JSON Mode を使う際に送信するテキストについて
なお、上で成功した例では `role: 'system'` のほうに JSON というキーワードを含めていましたが、以下のように `role: 'user'` のほうに含めても大丈夫でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3c0c50ee-3ee8-764c-1d27-bc7071cfe65d.png)

#### GPT-4 Turbo以前の GPT-4 でJSON Mode を使おうとした場合の話
余談ですが、JSON Mode は GPT-4（GPT-4 Turbo以前のもの）では使えないので、モデルを GPT-4 Turbo以前の GPT-4 にすると、以下のエラーが返ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/6d1fffe7-2117-72c9-6e75-62f62f859569.png)