ChatGPT入門: Chat Completions APIでオリジナルのチャットボットを作ろう！

## はじめに

#### 記事の対象者と目的

こんにちは！株式会社エンラプト開発チームです。本記事では、OpenAIのサービスを活用したアプリケーション開発に関心がある皆さんに向けて、Chat Completionsの特徴や具体的な使用方法、最後に利用するにあたっての注意事項を明確かつ分かりやすく解説していきます。

#### Chat Completionsの概要

Chat Completionsは、2023年3月1日にOpenAIによって公開された、チャット補完に特化した言語モデルと、それを利用するチャットベースの新しいAPIです。これは[ChatGPT](https://chat.openai.com/chat)と同じモデルを使用しているため、これを活用することで、独自のアプリケーションにChatGPTと同等の機能を実装することができます。

## 特徴

Text Completionsと比較してChat Completionsの特徴を見ていきましょう。

#### 専用のモデル

Text Completionsでは、代表的なGPT-3.5モデルとして**text-davinci-003**が提供されていましたが、Chat Completionsでは、チャット補完に最適化された新しいモデル**gpt-3.5-turbo**が追加されました。**text-davinci-003**は、高度な自然言語処理タスクでの利用が想定されている一方、 **gpt-3.5-turbo**は小規模で速度が速く、比較的簡単な自然言語処理タスクでの利用が想定されています。なお、これらに互換性はなく、Chat Completionsで**text-davinci-003**を使用することはできず、同様に、Text Completionsで**gpt-3.5-turbo**を使用することはできません。

#### 一連の会話に基づいた補完

Text Completionsでは単一のプロンプトを入力しますが、Chat Completionsでは**一連の会話（誰がどんな発言をしたのか）をメッセージのリスト**として入力できます。これにより、以前の会話のコンテキストに基づいた補完が実現可能です。たとえば、「猫の名前は？」という質問について、Text Completionsの場合、どの猫を指しているかは分かりません。しかし、Chat Completionsの場合、過去の会話から「吾輩は猫である」というコンテキストに基づいて回答します。

> user: 夏目漱石の小説に登場する動物は？
> assistant: 『吾輩は猫である』には猫が登場します。
> user: 猫の名前は？
> assistant: 『吾輩は猫である』の主人公の猫には、名前がありません。

#### 低価格

2023年3月20日現在、Text Completionsの**text-davinci-003**の価格は **$0.020/ 1K tokens** ですが、Chat Completionsの**gpt-3.5-turbo**は **$0.002 / 1K tokens**と、1/10の価格になっており、コスト面でもText Completionsより使いやすくなっています。

#### Fine-tuning未対応

Text CompletionsではFine-tuningしたカスタムモデルの作成が可能でしたが、Chat CompletionsではFine-tuningができません。そのため、独自のコンテキストを考慮した補完を実現する場合は、Few-shot Learningなどの手法を用いる必要があります。
なお、大量のコンテキストから関連性の高い情報のみを抽出し、Few-shot Learningを行う方法について知りたい方は、以下の記事をご覧ください。

https://qiita.com/enrapt/items/68c8142c7916850c4a0d

## 使用方法

Chat CompletionsはREST APIとして提供されています。また、PythonやNode.jsなどの一部の言語にはクライアントライブラリが提供されているので、それらを活用することで簡単に使用できます。ここではAPIの必須リクエストパラメータについてのみ解説します。より詳しく知りたい方は[公式ドキュメント](https://platform.openai.com/docs/api-reference/chat/create)を参照してください。なお、APIを使用するには[Open AIのAPI Key](https://platform.openai.com/account/api-keys)が必要です。

#### model

使用するモデルのIDを指定します。GPT-3.5モデルとしては2023年3月20日現在、以下の2つが利用可能です。

- **gpt-3.5-turbo**: 最も能力が高いGPT-3.5モデルで、チャットに最適化されています。最新のモデルイテレーションで更新されており、2023年3月20日現在ではgpt-3.5-turbo-0301になります。

- **gpt-3.5-turbo-0301**: 2023年3月1日のgpt-3.5-turboのスナップショットです。

常に最新のGPT-3.5モデルを使用したい場合は、gpt-3.5-turboを、逆に特定のモデルに固定したい場合は、gpt-3.5-turbo-0301のようにモデルIDを指定します。

#### messages

一連の会話をmessageオブジェクトのリストとして指定します。messageオブジェクトは、以下の2つの必須プロパティを持ちます。

- **role**: system, user, assistantのいずれかを指定します。通常、会話は最初にsystemメッセージでアシスタントの動作を定義し、その後にuserメッセージとassistantメッセージが交互に続きます。

- **content**: メッセージの内容を指定します。

```
[
  {"role": "system", "content": "You are a helpful assistant."},
  {"role": "user", "content": "Who won the world series in 2020?"},
  {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
  {"role": "user", "content": "Where was it played?"}
]
```

余談ですが、内部的には[ChatML](https://github.com/openai/openai-python/blob/main/chatml.md)という構造化された形式でメッセージを表現しているようです。

では、いよいよChat Completionsを使ってアプリケーションを作っていきましょう。

## チャットボット実装例

UIを作成するのは面倒なので、今回は[SlackのBolt](https://slack.dev/bolt-js/ja-jp/tutorial/getting-started)を使ってChat Completionsによる応答を返すSlackアプリを作成していきます。

#### 仕様

以下の通りシンプルなチャットボットを実装します。

- ユーザーからのメンションメッセージ、またはダイレクトメッセージに対して反応する。

- 返信内容をChat Completionsで生成してスレッドのリプライとして投稿する。

- スレッドのコンテキストに基づいて返信内容を生成する。

#### 実装

完全なソースコードは[こちらのglitch.comのプロジェクト](https://glitch.com/edit/#!/butternut-cold-guavaberry)をご覧ください。

###### 必要ライブラリのインストール

今回はNode.jsで実装するのでnpmで[OpenAIのAPIライブラリ](https://www.npmjs.com/package/openai)をインストールします。なお、Chat Completions APIはv3.2.0で実装されているのでそれ以降のバージョンをインストールしてください。また、併せてメッセージの[トークン数をカウント](https://www.npmjs.com/package/gpt-3-encoder)するためのライブラリもインストールします。

```
npm install openai@3.2.1 gpt-3-encoder
```

###### Chat Completions APIを実行する関数の作成

ここは特に説明することはないですね。openai.createChatCompletion()を呼び出してChat Completions APIを実行する関数を作成します。
ただし、messages が最大トークンを超えないように古いメッセージは除外して、messagesの先頭はシステムロールによる動作定義を行うメッセージを追加しています。

```javascript
const createCompletion = async(allMessages) => {
  try {
    // メッセージのフィルタリング
    const systemPrompt = CHAT_GPT_SYSTEM_PROMPT;
    let length = encode(systemPrompt).length;
    let messages = [];
    for (const msg of allMessages.reverse()) {
      length += encode(msg.content).length;
      if (length >= MAX_TOKEN) break;
      messages = [msg, ...messages];
    }
    messages = [
      // システムロールによる動作定義を先頭に追加
      {role: 'system', content: systemPrompt}, 
      ...messages
    ];
    console.log(messages);
    return await openai.createChatCompletion({
      model: 'gpt-3.5-turbo',
      messages,
      temperature: 0,
    });
  } catch (e) {
    console.error(e?.response?.data || e);
    return e.response;
  }
}
```

###### Chat Completions APIの結果をSlackに投稿

Slack APIのコードが混ざっているので少しややこしく見えますが、やっていることはシンプルです。

1. 受信したメッセージのスレッドに紐づく全リプライを取得

2. 1の結果をmessageオブジェクトのリストに変換

3. 2を引数に先ほど作成したcreateCompletion()関数をコール

4. 3の結果を該当スレッドに投稿

```javascript
app.event('message', async ({ event, client }) => {
  const { channel, ts, thread_ts, channel_type, user: user_id } = event

  // 1. 受信したメッセージのスレッドに紐づく全リプライを取得
  const threadMessagesResponse = await client.conversations.replies({
    channel,
    ts: thread_ts || ts,
  });
  const messages = threadMessagesResponse.messages?.sort((a, b) => Number(a.ts) - Number(b.ts));
  const { user_id: bot_user_id } = await client.auth.test();
  if (channel_type !== 'im' && (!messages.length || !messages[0].text.includes(`@${bot_user_id}`))) {
    // ダイレクトメッセージではない、かつ最初のメッセージにメンションがない場合は無視する
    return;
  }
  // 2. 1の結果をmessageオブジェクトのリストに変換
  const allMessages = messages.map(m => ({ role: m.user === bot_user_id ? 'assistant': 'user', content: m.text}));

  // 3. 2を引数に先ほど作成したcreateCompletion()関数をコール
  const response = await createCompletion(allMessages);
  const completion = response.data?.error
    ? `エラーが発生しました\n\`\`\`\n${JSON.stringify(response?.data.error, null, ' ')}\`\`\``
    : response?.data?.choices[0]?.message?.content;

  // 4. 3の結果を該当スレッドに投稿
  await client.chat.postMessage({
    channel,
    thread_ts: thread_ts || ts,
    text: completion,
  });
});
```

#### 実行結果

<!-- wp:image {"id":630,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://labs.enrapt.jp/wp-content/uploads/2023/03/slack-chatgpt-test.png" alt="" class="wp-image-630"/></figure>
<!-- /wp:image -->

期待通り以前の会話を考慮した応答を返してくれました。

補足すると、最初のメッセージ送信ではChat Completionsには以下のメッセージリストがパラメーターとして渡されています。

```
[
  { role: 'system', content: '長いので省略'},
  { role: 'user', content: '夏目漱石の小説に登場する動物は？' }
]
```

そして、2回目のメッセージ送信時には該当メッセージだけでなく、最初に送信しメッセージ（userロール）とそのメッセージに対するChat Completionsのcompletion（assistantロール）が一緒にChat Completionsに渡されていることがわかります。

```
[
  { role: 'system', content: '長いので省略'},
  { role: 'user', content: '夏目漱石の小説に登場する動物は？' },
  { role: 'assistant', content: '夏目漱石の小説には、『吾輩は猫である』という小説で猫が登場します。' },
  { role: 'user', content: '猫の名前は？' }
]
```

# 利用するにあたっての注意

Chat Completionsはまだまだ生まれたての技術であり、多くの課題があります。特に商用の場合、以下の注意事項を理解した上で利用することが重要です。

- **まだベータ版**: Chat Completionsはまだベータ版です。今後、APIの仕様が変更される可能性、不具合が発生する可能性があります。

- **SLAがない**: OpenAIのサービスが停止した場合には、利用できなくなります。そして残念ながら現状それは頻繁に起こります。[AzureのOpenAI Service](https://azure.microsoft.com/ja-jp/products/cognitive-services/openai-service)を検討してください。

- **Completionの信頼性**: Text Completionsと同様、幻覚と呼ばれる現象により正確でない情報や誤った推論を生成することがあります。今後、新しいモデルがリリースされることで改善される可能性がありますが、現段階で完全に信用するのは危険です。

- **トークン数の上限**: 1度に利用できる合計トークン数（prompt + completion）の上限は4096トークンです。そのため、上限を超えないように適宜メッセージリストの量を減らす必要があります。これにより過去のコンテキストを忘れてしまう場合があります。

- **コスト**: Text Completionsと比べれば安価になりましたが、それでも上限4096トークンの場合で約 [$0.008](https://www.google.com/search?q=%240.008) / call かかります。また、会話が長くなると必然的にトークン数も多くなることに注意が必要です。

- **機密情報の取扱い**: メッセージはOpenAIのサーバーに送信され、その後30日間保持されます。学習データ等に使われることはないとのことですが、機密情報を取り扱う場合は留意が必要です。

## まとめ

この記事では、Chat Completionsの特徴と基本的な使用方法を紹介しました。さらに応用すれば、独自のコンテキスト（製品マニュアルなど）に関するFAQボットや特定のタスク（英会話の先生など）に特化したボットアプリケーションを作ることもできます。ぜひ、これを機にChat Completionsをお試しください。
また、株式会社エンラプトでは、OpenAIのサービスを使ったソリューションの研究を日々行っています。もし、弊社のサービスや採用に関心があればお気軽にお問い合わせください。[ホームページ](https://enrapt.jp/)にはOpenAIサービスを利用したFAQチャットもご用意しています。

それでは、最後までご覧いただきありがとうございました！