kintoneとChatGPTを使ってAIチャットボット型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@1.12.0/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 (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で動作を確認しています。