ChatGPTのEmbeddingのお試し実装

自然言語処理はずいぶん進化しました。それに伴い、あらゆるウェブサイトでチャットボットを見かけるようになりました。実装してみたくなりますね。というわけで、実装してみましょう。

## 何を使って何を作るか

企業のウェブサイトのホームページを想定し、質問するとそれに回答してくれるボットを作ります。

Q＆Aに必要なことは以下の通りです。

- 企業の知識をチャットボットに教えることができる

- 質問を解釈し、得た知識を元に回答することができる

チャットボットの様に、文脈に対しての応答を返すOpenAiのCompletionというものがあります。これはあらかじめ持っている知識を動員して、文章を生成してくれます。

このCompletionに、さらにいくつかの知識を覚えさせることでチャットボットが完成するわけですね。このいくつかの知識のみで学習させる技術をfew-shotと言います。

しかし、OpenAiのCompletionに教えることができる情報量には制限があります。無限に教えることはできません。

よって、質問との距離が近い知識だけを選択する必要があります。これを実現するための魔法がembeddingです。

embeddingは、文章をベクトルに変換することができます。これは概念を数字で表すことができたということ。つまり、2つの文章をベクトルに変換し内積をとると、似ているものほど大きくなります。ソートすれば質問に対して近しい知識を選択できるという寸法。

質問と近い概念の文章を学んだCompletionは、正しい回答ができるようになるでしょう。

## 実装

知識を準備します。各知識は、一つのテーマに対しての一段落程度の文章が適しています。

今回は以下の文章を用意しました。

- 株式会社エンラプトの設立は 2011年9月 です。

- 株式会社エンラプトの資本金は 9,000,000円 です。

- 株式会社エンラプトの代表者は ジェイソン　パーク と ケント　ホーング です。

- 株式会社エンラプトの従業員数は 約30名 です。

- 株式会社エンラプトの住所は 東京都港区六本木1-9-10 アークヒルズ仙石山森タワー25階 です。

- 株式会社エンラプトの連絡先は 電話:03-5544-8218、メール:info@enrapt.jp です。

- エンラプトは計画立案からサービス公開へ至るプロジェクトライフサイクルの全てのエリアに対してITコンサルティングサービスをご提供しております。

- エンラプトはプロジェクト計画をサービスとして提供しています。具体的にはプロジェクトの範囲、深度とスケジュール作成、ソリューションアーキテクチャの定義と実現可能性の検証、プロジェクト管理スタイル（アジャイル/スクラム）の設定とプロジェクトチームのアサインを行います。

- エンラプトはプロジェクト運営をサービスとして提供しています。具体的には開発スケジュール作成と報告会議体の設定、プロダクトバックログ作成、設計ドキュメント作成、アウトプットの定義を行います。

- エンラプトはラピッド開発をサービスとして提供しています。具体的にはプロダクトバックログに基づく設計ドキュメントの詳細化、インクリメンタル開発とテストを行います。

### Vectorに変換

文章をvectorに変換するコードは以下です。

```javascript
const { Configuration, OpenAIApi } = require('openai');

const configuration = new Configuration({
  apiKey: '{取得したAPI Key}',
});
const openai = new OpenAIApi(configuration);

const createEmbedding = async(input) => {
  try {
    const embedding = await openai.createEmbedding({
      model: 'text-embedding-ada-002',
      input
    });
    return embedding.data.data[0].embedding;
  } catch(e) {
    console.error(e);
    throw e;
  }
}

const createChishikiVector = async () => {
  const chishiki = [
    '株式会社エンラプトの設立は 2011年9月 です。',
    '株式会社エンラプトの資本金は 9,000,000円 です。',
    '株式会社エンラプトの代表者は ジェイソン　パーク と ケント　ホーング です。',
    '株式会社エンラプトの従業員数は 約30名 です。',
    '株式会社エンラプトの住所は 東京都港区六本木1-9-10 アークヒルズ仙石山森タワー25階 です。',
    '株式会社エンラプトの連絡先は 電話:03-5544-8218、メール:info@enrapt.jp です。',
    'エンラプトは計画立案からサービス公開へ至るプロジェクトライフサイクルの全てのエリアに対してITコンサルティングサービスをご提供しております。',
    'エンラプトはプロジェクト計画をサービスとして提供しています。具体的にはプロジェクトの範囲、深度とスケジュール作成、ソリューションアーキテクチャの定義と実現可能性の検証、プロジェクト管理スタイル（アジャイル/スクラム）の設定とプロジェクトチームのアサインを行います。',
    'エンラプトはプロジェクト運営をサービスとして提供しています。具体的には開発スケジュール作成と報告会議体の設定、プロダクトバックログ作成、設計ドキュメント作成、アウトプットの定義を行います。',
    'エンラプトはラピッド開発をサービスとして提供しています。具体的にはプロダクトバックログに基づく設計ドキュメントの詳細化、インクリメンタル開発とテストを行います。',
  ];
  const promised = chishiki.map(async i => {
    return {
      text: i,
      vector: await createEmbedding(i)
    };
  });
  return await Promise.all(promised);
}
```

知識(text)とそのベクトル(vector)を生成しました。

(OpenAIではこの変換の処理に対して料金がかかるので、実際のリリースではキャッシュするなどが必要です。)

### 質問に近い概念の知識を探す

次は質問もvectorに変換して、関連度が高いものを見つけ出しましょう。

```javascript
const getRelevanceList = async (chishikiVector, input) => {
  const dot = (a, b) => a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n);
  const inputV = await createEmbedding(input);
  return chishikiVector.map(i => {
    return {
      ...i,
      similarity: dot(inputV, i.vector)
    };
  }).sort((a, b) => b.similarity - a.similarity).slice(0,3).map(i => i.text);
}
```

たくさんの知識から、関連度が高いものから3つを取り出しました。これをチャットボットに覚えさせて、回答を得ます。

### チャットボットを生成

チャットはcompletionを使用します。few-shotという手法で教育するモデルであり、文脈の形で指示を送ります。

```javascript
const createCompletion = async(prompt) => {
  try {
    const completion = await openai.createCompletion({
      max_tokens: 1000,
      model: 'text-davinci-003',
      prompt,
      stop: [' END', ' ->'],
    });
    return completion.data.choices[0].text;
  } catch (e) {
    console.error(e);
  }
}

const chatbot = async (input) => {
  const chishikiVector = await createChishikiVector();
  const relevanceList = await getRelevanceList(chishikiVector, input);

  const prompt = `以下の制約条件に従って、株式会社エンラプトのお問い合わせ窓口チャットボットとしてロールプレイをします。

---
# 制約条件:
- エンラプトの情報を基に質問文に対する回答文を生成してください。
- 回答は見出し、箇条書き、表などを使って人間が読みやすく表現してください。

---
# エンラプトの情報:
${relevanceList.join('\n\n')}

---
# 質問文:
${input}

---
# 回答文:
`
  console.log(prompt);
  const completion = await createCompletion(prompt);
  console.log(completion);
}

const input = process.argv[2];
chatbot(input);
```

与えた知識を元に回答するようにチャットボットに求め、その後に回答の候補となる知識を追加します。

知識を教えた後に質問すると、それに対応する回答をしてくれます。

### 質問してみましょう

これで弊社の知識を持つチャットボットができました。では質問してみます。

![sc2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3293508/77a1c7e7-b0e2-6efb-9700-fc0d8388848a.png)

![sc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3293508/f2775170-6cd7-24a2-caca-37e9c979df5f.png)



回答を得られました。

AIを使用したQ＆Aの実装において、コアな部分はたったこれだけです。これにvectorのキャッシュなどを従来工法で追加することで実用的なものになるでしょう。チャットボット実装の足掛かりになれば幸いです。

参考文献：[github](https://github.com/openai/openai-cookbook/blob/main/examples/Question_answering_using_embeddings.ipynb?ref=mlq-ai)