この記事では、OpenAI APIを使用し、ChatGPTを搭載したLINE BotをGoogle Apps Script(GAS)で自作する方法について解説する。
複数ターンの会話履歴考慮や、最新モデル「GPT-4」にも対応できるよう設計している。
既に100番煎じくらいだと思うが、イメージはこんな感じ↓
ChatGPTのAPIが公開されたので、早速LINEで使えるようにしてみた。
— PC人間 (@pcningen) March 2, 2023
LINE Dev+GASで誰でも簡単に使えますぞ〜!
これは夢が広がる。 pic.twitter.com/j1mQkGdynK
準備
ChatGPT LINE BotをGASで作成するためには、以下の3つのアカウントが必要。
OpenAI API Keyを取得
OpenAI Platformの管理画面から、API Keyを発行する。
「Create new secret key」をクリックし、出てきた文字列をメモしておく。
最初の3ヶ月間は、18ドル分のクレジットが付与されているため、ある程度無料で楽しめるだろう。
LINE Developersの登録
LINE Developersコンソールに、LINEアカウントでログインする。初回は開発者名とメールアドレスを求められるので、入力して開発者アカウントを作成する。
プロバイダー・チャネル作成
次に、プロバイダーを作成する。名前は何でもOK。
チャネル設定よりMessagins APIを選択し、新規チャネルの設定に進む。
各項目を埋める。チャネル名はボットのアカウント名となるので、それっぽい名称にしておくと良し。
Messaging API設定タブより「チャネルアクセストークン(長期)」を発行する。
API Keyと同様に後で必要になるので、こちらもメモしておく。
Google Apps Script
今回は、スプレッドシートをDB的に利用し、設定や応答を管理する。こちらより新規ファイルを作成。
シートを2つ用意し、1つめをData、2つめをSettingsとしておく。
SettingsのB列に、設定を入力する。
- モデル … 「gpt-3.5-turbo」または「gpt-4」
- ボットのキャラクター … AIにロールプレイングをさせる時の口調や条件等
- 最大ターン数 … 過去何ターンまでの会話を記憶するか
最大ターン数は、「プロンプト+応答」のセットを1としている。数値を大きくすれば、より多くの会話履歴を考慮した回答が返ってくるが、その分高コストとなるため注意が必要だ。
GPT-4のAPIは、2023年7月6日に一般開放が発表された。ただし、記事執筆時点では、一度でも支払い履歴のあるユーザーに限られる。年内には新規登録ユーザーも利用可能になるとのこと。参考:OpenAI
画面上部メニューの拡張機能 > Apps Scriptを押下すると、以下の画面が出現。
ここにコードを書いていく。
コード全文
以下のGASコードをコード.gsにコピペする。
OPENAI_APIKEYとLINE_ACCESS_TOKENは、先程取得した文字列に置き換える。
const OPENAI_APIKEY = "sk-xxxxxxxxxxxx"; // 自身のAPI Keyに書き換える
const LINE_ACCESS_TOKEN = "Qbxxxxxxxxxxxx="; // 自身のアクセストークンに書き換える
function doPost(e) {
let fromLineData = e.postData.contents;
let receiveData = JSON.parse(fromLineData);
// text部分を取り出す
let receiveMsg = receiveData.events[0].message.text;
// replyTokenを取り出す
let replyToken = receiveData.events[0].replyToken;
// ============= debug =============
let sheetSettings = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Settings");
sheetSettings.getRange(4, 2).setValue(receiveMsg);
sheetSettings.getRange(5, 2).setValue(fromLineData);
//==========================
// myFunctionを呼ぶ
let replyMessage = myFunction(receiveMsg);
// reply関数を呼ぶ
reply(replyToken, replyMessage);
// forgotData関数を呼ぶ
forgotData();
}
// LINEへの返信
function reply(replyToken, replyMessage){
let replyUrl = "https://api.line.me/v2/bot/message/reply";
let contents = {
replyToken: replyToken,
messages: [{ type: 'text', text: replyMessage }],
};
let options = {
method: 'post',
contentType: 'application/json',
headers: {
Authorization: 'Bearer ' + LINE_ACCESS_TOKEN
},
payload: JSON.stringify(contents)
};
UrlFetchApp.fetch(replyUrl, options);
}
function myFunction(receiveMsg) {
// スプレッドシートの読み込み
let spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
// シートの選択
let sheetSettings = spreadsheet.getSheetByName("Settings");
// セルの選択
let rangeSettings = sheetSettings.getRange("B2");
// セルの値を取得する(ボットのキャラクター設定)
let botCharacter = rangeSettings.getValue();
// セルの選択
rangeSettings = sheetSettings.getRange("B1");
// セルの値を取得する(モデル名)
let model = rangeSettings.getValue();
// setCellValueUserPrompt関数を呼び出す
setCellValueUserPrompt(receiveMsg);
// fncCreateMessage関数を呼び出す
let createdMessage = fncCreateMessage(botCharacter);
// fncCallApi関数を呼び出す
let botAnswer = fncCallApi(createdMessage, model);
// setCellValue関数を呼び出す
setCellValue(botAnswer);
console.log(botAnswer);
return(botAnswer);
}
// スプレッドシートに値を書き込む関数(ユーザーの入力)
function setCellValueUserPrompt(userPrompt) {
let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data");
let columnB = sheet.getRange("B:B").getValues();
for (let i = 0; i < columnB.length; i += 2) {
if (columnB[i][0] == "") {
sheet.getRange(i + 1, 2).setValue(userPrompt);
break;
}
}
}
// スプレッドシートに値を書き込む関数(ボットの応答)
function setCellValue(botAnswer) {
// 現在アクティブなシートを取得する
let sheet = SpreadsheetApp.getActiveSheet();
// 最終行数を取得する
let lastRow = sheet.getLastRow();
// 2列目の範囲オブジェクトを取得する
let range = sheet.getRange(2, 2, lastRow, 1);
// 2列目の値を取得する
let values = range.getValues();
// 2列目の値を順番に処理する
for (let i = 0; i < values.length; i++) {
// 空セルかどうかを判断する
if (values[i][0] === "") {
// 奇数行かどうかを判断する
if ((i + 2) % 2 === 1) {
// 繰り返し処理をスキップする
continue;
}
// 空セルの範囲オブジェクトを取得する
let cell = sheet.getRange(i + 2, 2);
// セルに値を代入する
cell.setValue(botAnswer);
// 繰り返し処理を終了する
break;
}
}
}
// 設定した最大ターン数に応じて、スプレッドシートの値を上に詰める
function forgotData() {
// シートの取得
let sheetSettings = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Settings");
// 設定された最大ターン数を取得
let maxNumberOfTurns = sheetSettings.getRange("B3").getValue();
// シートの取得
let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("data");
// 最終行数の取得
let lastRow = sheet.getRange("B:B").getValues().filter(String).length;
// 最大ターンを超える場合
if (lastRow > maxNumberOfTurns * 2) {
for (let i = 3; i <= lastRow; i++) {
let value = sheet.getRange(i, 2).getValue();
sheet.getRange(i - 2, 2).setValue(value);
}
// 指定行数以上の値を削除
sheet.getRange(maxNumberOfTurns * 2 + 1, 2, 2, 1).clearContent();
}
}
// メッセージ部分を作成する関数
function fncCreateMessage(botCharacter) {
// スプレッドシートの読み込み
let spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
// シートの選択
let sheetData = spreadsheet.getSheetByName("Data");
// 最終行数の取得
let lastRow = sheetData.getRange("B:B").getValues().filter(String).length;
// プロンプト・応答を格納する配列
let arrMessage = [];
// 行数分だけ要素を配列に追加する
for (let i = 1; i <= lastRow; i++) {
// セルの選択
let rangeData = sheetData.getRange("B" + i);
// セルの値を取得する
let cellVal = rangeData.getValue();
// 配列の末尾に要素を追加
arrMessage.push(cellVal);
}
// message配列 1項目めはsystemメッセージ(キャラクター設定)
let message = [{"role": "system", "content": botCharacter }];
// メッセージ部分を作成
for (let i = 0; i < arrMessage.length; i++) {
// 奇数行の場合は、roleを"assistant"に設定し、それ以外の場合は、"user"に設定する
let role = (i % 2 === 1) ? "assistant" : "user";
// message配列に新しいオブジェクトを追加する
message.push({
"role": role,
"content": arrMessage[i]
});
}
console.log(message);
// 作成したメッセージ(配列)を返す
return message;
}
// OpenAI APIを呼び出す関数
function fncCallApi(createdMessage, model) {
let requestUrl = "https://api.openai.com/v1/chat/completions";
let headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + OPENAI_APIKEY,
};
let payload = {
"model": model,
"messages": createdMessage
};
let options = {
"method": "POST",
"headers": headers,
"payload": JSON.stringify(payload)
};
// UrlFetchAppでAPIを呼び出し、レスポンスを取得
let response = UrlFetchApp.fetch(requestUrl, options).getContentText();
// レスポンスをJSONオブジェクトに変換
let objResponse = JSON.parse(response);
// choices[0]のmessage.contentを返す
return objResponse.choices[0].message.content;
}
半分くらいはChatGPTに書かせたので、所々冗長だったりするかも。
あと、エラーハンドリングが全く無いので、追加したほうが望ましい。
デプロイ
GAS画面右上のデプロイより「新しいデプロイ」を選択。
種類の選択 > ウェブアプリ
アクセスできるユーザーを全員に変更し、デプロイする。
初回は警告画面が出るが、気にせずに全ての権限を付与する。
「デプロイを更新しました」という画面に表示されているURLをコピーする。
Webhook設定
次に、LINE Developersコンソール側でWebhook URLを設定する。
先程コピーしたURLをWebhook URL欄に貼り付ける。「Webhookの利用」もオンにしておく。
表示されているQRコードから友だち登録すると、ボットを使用可能になる。
LINE公式アカウント機能の「応答メッセージ」は無効化しておくと良いだろう。
ボットに話しかけて、返信が来れば成功だ!
お疲れ様でした。
コメント