kintone と ChatGPT を使って FAQ チャットシステムを作ろう

目次

はじめに

kintone と ChatGPT を使った連携例として、手軽に AI チャットボットを開発できる方法を紹介します。
kintone のレコード一覧画面に独自のチャットボット UI を組込み、さらに kintone と ChatGPT API を連携させることで、自社サービスやツールに AI を利用したチャットボット機能を導入できます。

例として、営業支援ツール「セールストラッカー」という架空のサービスに組み込まれる FAQ チャットシステムを考えます。
セールストラッカーの FAQ アプリに蓄積された情報を活用し、ChatGPT を通じてユーザーからの問い合わせに対し、オペレーターの回答をサポートできるしくみを構築します。

ChatGPT 含む AI による応答は、必ずしも正しいものとは限りませんので、ここでのサンプルはあくまで回答をサポートするためのものとお考えください。

完成イメージ

今回のカスタマイズを適用することで、kintone のレコード一覧画面に、JavaScript カスタマイズで作成したチャットボット UI を表示させることができます。

次のようにメッセージを入力すると、チャットボットが質問に回答してくれます。

FAQ に使うだけでなく、FAQ のデータを活用した質問も対応可能です。

下記では、提案内容の例を出してもらっていますが、FAQ 内に記載されている特徴もピックアップしています。

注意事項

  • 今回の例はあくまでサンプルです。
    AI の特徴として、必ずしも正しい答えを導き出せるとは限りません。
  • 今回のサンプルでは、プロンプトに情報を記述してそこから情報を回答してもらう方法をとっており、扱えるデータ(トークン)の量に限界があります。
    今回のサンプルでは FAQ アプリのレコード数が 30 件程度です。
    大量のデータを取り扱う場合、別の方法で ChatGPT に学習させたりする必要があります。

アプリの準備

FAQ アプリを次のように作成します。
今回は、検証用のため、質問フィールドと回答フィールドのみを用意します。
作成日時などは必要に応じて設置してください。

フィールドの種類 フィールドコード 備考
文字列(複数行) 質問 FAQ の質問を記載するためのフィールドです。
文字列(複数行) 回答 FAQ の回答を記載するためのフィールドです。

サンプルコード

以下の JavaScript ファイルと CSS ファイルを設定画面より指定してください。
指定方法はヘルプの JavaScriptやCSSでアプリをカスタマイズする (External link) を参考してください。

  • JavaScript
    1. https://unpkg.com/kintone-ui-component/umd/kuc.min.js(kintone UI Component)
    2. chat.js
      後述するコードを JavaScript ファイルとして保存し、アップロードしてください。
  • CSS ファイル
    1. chat.css
      後述するコードを CSS ファイルとして保存し、アップロードしてください。

chat.js

ChatGPT との連携に必要な API キーの取得方法については後述します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
 * ChatGPT sample program
 * Copyright (c) 2023 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
*/
(() => {
  'use strict';

  // ChatGPT APIのエンドポイントURL
  const apiUrl = 'https://api.openai.com/v1/chat/completions';
  // ChatGPT APIキーの指定
  const apiKey = 'your api key';
  // kintone UI Componentの利用
  const kuc = Kucs['1.12.0'];

  // メッセージの要素を作成するための関数
  const createMessageElement = (sender, message) => {
    // 送信者+メッセージを表示する枠の要素を作成
    const messageWrapper = document.createElement('div');
    messageWrapper.classList.add('message-wrapper');

    // メッセージの送信者を表示する要素を作成
    const senderElement = document.createElement('div');
    senderElement.classList.add('sender');
    senderElement.innerText = sender;
    messageWrapper.appendChild(senderElement);

    // メッセージを表示する要素を作成
    const messageElement = document.createElement('div');
    messageElement.classList.add('message');
    messageWrapper.appendChild(messageElement);
    const messageTextElement = document.createElement('p');
    messageTextElement.innerText = message;
    messageElement.appendChild(messageTextElement);

    return messageWrapper;
  };

  // ChatGPT APIにリクエストを送信する関数
  const sendChat = async (records, message) => {
    // チャットボットの役割を定義
    const botRoleContent = `あなたはFAQチャットボットです。
      当社が開発・販売している営業管理ツール「セールストラッカー」についてのFAQを回答してください。
      受けた質問に対して、適切な回答をユーザーに示してください。
      `;

    // ChatGPTに渡すkintoneのデータを用意
    const faqData = records.map((r) => {
      return [
        {role: 'user', content: r.質問.value},
        {role: 'assistant', content: r.回答.value}
      ];
    }).flat();

    // ChatGPT APIにリクエストを送信
    const response = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`
      },
      body: JSON.stringify({
        model: 'gpt-3.5-turbo',
        messages: [
          {role: 'system', content: botRoleContent},
          ...faqData,
          {role: 'user', content: message}
        ]
      })
    });

    const parsedResponse = await response.json();

    // ChatGPT APIのレスポンスをチェック
    if (response.ok === false) {
      const error = parsedResponse.error;
      throw new Error(`${error.code}: ${error.message}`);
    }

    // 問題なければ、チャットボットの回答を返す
    const botResponse = parsedResponse.choices[0].message.content;
    return botResponse;
  };

  // レコード一覧画面の表示イベントが発生した時に実行される関数
  kintone.events.on('app.record.index.show', (event) => {

    // チャット全体の要素を作成
    const chatContainer = document.createElement('div');
    chatContainer.id = 'chat-container';
    const headerSpace = kintone.app.getHeaderSpaceElement(); // レコード一覧画面上部の要素を取得
    headerSpace.appendChild(chatContainer);

    // チャットのログ部分の要素を作成
    const chatLog = document.createElement('div');
    chatLog.id = 'chat-log';
    chatContainer.appendChild(chatLog);

    // チャットの入力フォームの要素を作成
    const chatForm = document.createElement('form');
    chatForm.id = 'chat-form';
    chatForm.autocomplete = 'off';
    chatContainer.appendChild(chatForm);

    // チャットの入力のためのテキストボックスを作成
    const userInput = new kuc.Text({
      placeholder: 'メッセージを入力してください',
      id: 'text-input',
      visible: true,
      disabled: false,
    });
    chatForm.appendChild(userInput);

    // チャットの送信のためのボタンを作成
    const sendButton = new kuc.Button({
      text: '送信',
      type: 'submit',
      id: 'send-button',
      visible: true,
      disabled: false,
    });
    chatForm.appendChild(sendButton);

    // ボタン押下時のイベントを定義
    sendButton.addEventListener('click', () => {
      chatForm.requestSubmit(); // formのsubmitを行う
    });
    // 送信ボタンのクリックイベントを設定
    // 入力フィールドでEnterキーが押された場合も送信する
    chatForm.addEventListener('submit', async (ev) => {
      // デフォルトのフォーム送信を無効化
      ev.preventDefault();

      const message = userInput.value;
      if (!message) {
        // ユーザーの入力をチャットログに表示
        chatLog.appendChild(createMessageElement('🤖', 'メッセージを指定してください。'));
        return;
      }

      // ユーザーの入力をチャットログに表示
      chatLog.appendChild(createMessageElement('👱', message));

      // ChatGPT APIにリクエストを送信
      try {
        // データ送信中はform無効化
        userInput.disabled = true;
        sendButton.disabled = true;
        userInput.value = '送信中...';

        // sendChat関数からの戻り値をチャットログに表示
        const botResponse = await sendChat(event.records, message);
        chatLog.appendChild(createMessageElement('🤖', botResponse));
        userInput.value = '';
      } catch (error) {
        // エラーが発生した場合は、エラーをチャットログに表示
        console.error('エラー:', error);
        chatLog.appendChild(createMessageElement('❌', `エラーが発生しました ${error.message}`));
        userInput.value = message;
      } finally {
        // データ送信完了後はform有効化
        userInput.disabled = false;
        sendButton.disabled = false;
      }
    });
  });
})();

chat.css

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@charset "UTF-8";

#chat-container {
  padding: 8px;
}

#chat-log {
  white-space: pre-wrap;
}

#chat-form {
  padding-top: 6px;
  display: flex;
  align-items: center;
}

#text-input {
  --kuc-text-input-width: 400px;
}

#send-button {
  margin-left: 8px;
}

.message-wrapper {
  display: flex;
  align-items: center;
}

.sender {
  font-size: xx-large;
  flex-grow: 0; /* 右側のDivに幅を占有させない */
  flex-shrink: 0; /* 幅の縮小を禁止 */
}

.message {
  position: relative;
  display: block;
  margin: 4px 0 4px 20px;
  padding: 7px 10px;
  max-width: 100%;
  color: #555;
  font-size: 16px;
  background: #e0edff;
  border-radius: 15px;
}

.message:before {
  content: "";
  position: absolute;
  top: 50%;
  left: -22px;
  margin-top: -15px;
  border: 15px solid transparent;
  border-right: 15px solid #e0edff;
}

.message p {
  margin: 0;
  padding: 0;
}

サンプルコードの解説

以下、コードを具体的に解説します。

ChatGPT を利用するための準備

カスタマイズで ChatGPT を利用するために、まずは ChatGPT の API キーを取得する必要があります。
OpenAI の API ページ (External link) にアクセスしてアカウントを作成し、そちらで API キーを生成できます。
この記事では取得方法の詳細の説明を割愛します。
ChatGPT API のエンドポイント URL と上記で取得した API キーを次のように定義します。

1
2
3
4
// ChatGPT APIのエンドポイントURL
const apiUrl = 'https://api.openai.com/v1/chat/completions';
// ChatGPT APIキーの指定
const apiKey = 'your api key';

API の利用には所定の料金が発生しますのでご注意ください。
詳細は OpenAI (External link) の「Pricing」を参照してください。

caution
警告

API キーは秘密情報なので、絶対に公開しないでください。
API キーの情報が漏洩すると API を自由に実行できてしまうため、 kintone の JavaScript の開発においても慎重に扱ってください。

kintone UI Component の利用

今回のサンプルではメッセージ入力欄とボタンに kintone UI Component を利用しています。

15
16
// kintone UI Componentの利用
const kuc = Kucs['1.12.0'];
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 中略
// チャットの入力のためのテキストボックスを作成
const userInput = new kuc.Text({
  placeholder: 'メッセージを入力してください',
  id: 'text-input',
  visible: true,
  disabled: false,
});
chatForm.appendChild(userInput);

// チャットの送信のためのボタンを作成
const sendButton = new kuc.Button({
  text: '送信',
  type: 'submit',
  id: 'send-button',
  visible: true,
  disabled: false,
});
chatForm.appendChild(sendButton);

kintone UI Component の詳細については、 kintone UI Component v1 を確認してください。
この記事では説明を割愛します。

メッセージの要素を作成する関数

ユーザーが入力したメッセージや ChatGPT から返ってくるメッセージを表示することは何回も繰り返しますので、関数にします。

ここでは、あくまで要素を作り、append(追加)はしません。
作った要素を戻り値として返すまでとします。

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// メッセージの要素を作成するための関数
const createMessageElement = (sender, message) => {
  // 送信者+メッセージを表示する枠の要素を作成
  const messageWrapper = document.createElement('div');
  messageWrapper.classList.add('message-wrapper');

  // メッセージの送信者を表示する要素を作成
  const senderElement = document.createElement('div');
  senderElement.classList.add('sender');
  senderElement.innerText = sender;
  messageWrapper.appendChild(senderElement);

  // メッセージを表示する要素を作成
  const messageElement = document.createElement('div');
  messageElement.classList.add('message');
  messageWrapper.appendChild(messageElement);
  const messageTextElement = document.createElement('p');
  messageTextElement.innerText = message;
  messageElement.appendChild(messageTextElement);

  return messageWrapper;
};

画面との対応はこのようになっています。

ChatGPT API にリクエストを送信する関数

ChatGPT にリクエストを送信する部分も関数にします。

前提として、今回は Chat Completions API というチャット補完に特化した API を利用します。
Chat Completions API では一連の会話を先にインプットしておくことで、そのコンテキストに基づいて ChatGPT が応答してくれます。
リクエスト・レスポンスの仕様などの詳細は、 ChatGPT API のリファレンス (External link) を確認ください。

今回のカスタマイズでは、FAQ のレコードにある質問をユーザーの発言として、回答をボットの発言として送信するようにしています。
また、チャットボットの役割については、仮想サービスのセールストラッカー向けの FAQ ボットという文脈で定義していますが、利用用途に応じて定義を変更してください。

FAQ の内容、チャットボットの役割を定義したうえで、最後にユーザーが実際にメッセージボックスに入力した質問を付け加えて送ります。

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// ChatGPT APIにリクエストを送信する関数
const sendChat = async (records, message) => {
  // チャットボットの役割を定義
  const botRoleContent = `あなたはFAQチャットボットです。
      当社が開発・販売している営業管理ツール「セールストラッカー」についてのFAQを回答してください。
      受けた質問に対して、適切な回答をユーザーに示してください。
      `;

  // ChatGPTに渡すkintoneのデータを用意
  const faqData = records.map((r) => {
    return [
      {role: 'user', content: r.質問.value},
      {role: 'assistant', content: r.回答.value}
    ];
  }).flat();

  // ChatGPT APIにリクエストを送信
  const response = await fetch(apiUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${apiKey}`
    },
    body: JSON.stringify({
      model: 'gpt-3.5-turbo',
      messages: [
        {role: 'system', content: botRoleContent},
        ...faqData,
        {role: 'user', content: message}
      ]
    })
  });

  const parsedResponse = await response.json();

  // ChatGPT APIのレスポンスをチェック
  if (response.ok === false) {
    const error = parsedResponse.error;
    throw new Error(`${error.code}: ${error.message}`);
  }

  // 問題なければ、チャットボットの回答を返す
  const botResponse = parsedResponse.choices[0].message.content;
  return botResponse;
};

kintone イベントハンドラーの定義

今回はレコード一覧画面にチャットを表示していますので、 レコード一覧画面を表示した後のイベント を利用します。

 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// レコード一覧画面の表示イベントが発生した時に実行される関数
kintone.events.on('app.record.index.show', (event) => {

  // チャット全体の要素を作成
  const chatContainer = document.createElement('div');
  chatContainer.id = 'chat-container';
  const headerSpace = kintone.app.getHeaderSpaceElement(); // レコード一覧画面上部の要素を取得
  headerSpace.appendChild(chatContainer);

  // チャットのログ部分の要素を作成
  const chatLog = document.createElement('div');
  chatLog.id = 'chat-log';
  chatContainer.appendChild(chatLog);

  // チャットの入力フォームの要素を作成
  const chatForm = document.createElement('form');
  chatForm.id = 'chat-form';
  chatForm.autocomplete = 'off';
  chatContainer.appendChild(chatForm);

  // チャットの入力のためのテキストボックスを作成
  const userInput = new kuc.Text({
    placeholder: 'メッセージを入力してください',
    id: 'text-input',
    visible: true,
    disabled: false,
  });
  chatForm.appendChild(userInput);

  // チャットの送信のためのボタンを作成
  const sendButton = new kuc.Button({
    text: '送信',
    type: 'submit',
    id: 'send-button',
    visible: true,
    disabled: false,
  });
  chatForm.appendChild(sendButton);

  // ボタン押下時のイベントを定義
  sendButton.addEventListener('click', () => {
    chatForm.requestSubmit(); // formのsubmitを行う
  });
  // 送信ボタンのクリックイベントを設定
  // 入力フィールドでEnterキーが押された場合も送信する
  chatForm.addEventListener('submit', async (ev) => {
    // デフォルトのフォーム送信を無効化
    ev.preventDefault();

    const message = userInput.value;
    if (!message) {
      // ユーザーの入力をチャットログに表示
      chatLog.appendChild(createMessageElement('🤖', 'メッセージを指定してください。'));
      return;
    }

    // ユーザーの入力をチャットログに表示
    chatLog.appendChild(createMessageElement('👱', message));

    // ChatGPT APIにリクエストを送信
    try {
      // データ送信中はform無効化
      userInput.disabled = true;
      sendButton.disabled = true;
      userInput.value = '送信中...';

      // sendChat関数からの戻り値をチャットログに表示
      const botResponse = await sendChat(event.records, message);
      chatLog.appendChild(createMessageElement('🤖', botResponse));
      userInput.value = '';
    } catch (error) {
      // エラーが発生した場合は、エラーをチャットログに表示
      console.error('エラー:', error);
      chatLog.appendChild(createMessageElement('❌', `エラーが発生しました ${error.message}`));
      userInput.value = message;
    } finally {
      // データ送信完了後はform有効化
      userInput.disabled = false;
      sendButton.disabled = false;
    }
  });
});

ここでも、チャットを表示するための要素を作って、設置しています。
画面との対応は次になります。

また、次のコードでは、ボタン押下時(フォーム送信時)の処理を記述しています。
ここでは、先ほど定義した sendChat 関数を使って ChatGPT にリクエストを送り、そのレスポンスを createMessageElement 関数に渡してメッセージを作成し表示することを行っています。

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// ボタン押下時のイベントを定義
sendButton.addEventListener('click', () => {
  chatForm.requestSubmit(); // formのsubmitを行う
});
// 送信ボタンのクリックイベントを設定
// 入力フィールドでEnterキーが押された場合も送信する
chatForm.addEventListener('submit', async (ev) => {
  // デフォルトのフォーム送信を無効化
  ev.preventDefault();

  const message = userInput.value;
  if (!message) {
    // ユーザーの入力をチャットログに表示
    chatLog.appendChild(createMessageElement('🤖', 'メッセージを指定してください。'));
    return;
  }

  // ユーザーの入力をチャットログに表示
  chatLog.appendChild(createMessageElement('👱', message));

  // ChatGPT APIにリクエストを送信
  try {
    // データ送信中はform無効化
    userInput.disabled = true;
    sendButton.disabled = true;
    userInput.value = '送信中...';

    // sendChat関数からの戻り値をチャットログに表示
    const botResponse = await sendChat(event.records, message);
    chatLog.appendChild(createMessageElement('🤖', botResponse));
    userInput.value = '';
  } catch (error) {
    // エラーが発生した場合は、エラーをチャットログに表示
    console.error('エラー:', error);
    chatLog.appendChild(createMessageElement('❌', `エラーが発生しました ${error.message}`));
    userInput.value = message;
  } finally {
    // データ送信完了後はform有効化
    userInput.disabled = false;
    sendButton.disabled = false;
  }
});

以上、kintone と ChatGPT を使って kintone の FAQ チャットシステムを作るためのコードを解説しました。

このコードを適切に kintone に組み込むことで、ユーザーが質問を入力すると、それに対する回答を自動的に生成して表示するチャットシステムを実現できます。

おわりに

本記事では、kintone と ChatGPT を融合させた FAQ チャットシステムの開発手法について紹介しました。
このシステムは、開発者の皆さんが比較的簡単に導入できるため、kintone と ChatGPT を駆使して業務効率化を実現できるというのをご理解いただけたかと思います。

ChatGPT は、高度な自然言語処理能力を有し、人間らしい対話文を生成できます。
これにより、ユーザーは自然なコミュニケーションの流れで情報を得ることができ、卓越したユーザーエクスペリエンスを実現できます。

kintone と ChatGPT を組み合わせて、カスタマーサポートだけでなく、内部の情報共有や社内教育など、多岐にわたるシーンで利用できうると思います。
この記事が AI を使った業務効率化の一例としてご参考になると幸いです。

information

この Tips は、2023 年 08 月版 kintone で動作を確認しています。