OpenAIとDeepLのAPIキーを利用して、文章生成AI「GPT-4o-mini」と画像生成AI「DALL-E3」、更にDeepLの翻訳機能(英訳)を同時に使用できるLINE BotのベースコードをGASで作りました。
英語への翻訳は、GPT-4o-mini経由でもできますが、トークンを使用する為、DeepLの無料アカウントで使用できるAPIキーを使用して、その部分は無料化することにしました。
下図のように、文章生成AIと画像生成AIとDeepLを一つのアカウントで同時に使用することが出来ます。
ベースコードでは、「こんにちはGPT」という文字が「含まれる(部分一致)」する入力があった場合に、GPT-4o-miniと会話でき、
「絵を描いて」という文字が「含まれる(部分一致)する入力があった場合に、DALL-E3が絵を描いてくれ、
「英訳して」という文字が「含まれる(部分一致)する入力があった場合に、DeepLが英訳してくれるコード構成にしています。
それ以外の入力に対しては、単に「A」というテキストが返されるIF文構成になっています。
トリガーとなるキーワードや、仮に「A」としてある返しテキストに関しては、ご自身の環境に合わせて、自由に編集してください。
まず始めに、下のリンクからGSSファイルを開き、メニューファイルから「Make a copy」を選択して、ご自身のGoogle Driveにコピーしてください。
https://docs.google.com/spreadsheets/d/1MI0NPYix3bPwz9CF97xp2wFbYom5MKZgE3Tf4rp-QMg/edit?usp=sharing
GSSファイルをコピーしたら、下図のようにスクリプトを開いてください。
スクリプトエディタを開いたら、所定の位置にLINEのアクセストークンとOpenAIのAPIキー、DeepLのAPIキーを入力してください。
それだけで、後はデプロイと初回認証を行い、LINE developersのWebhookにリンクさせるだけで使用できます。
DeepLのキーを入力しなくても、英訳機能以外のLINE Bot自体は動作します。
(GPT-3.5Turboとの会話機能の中でも、英訳自体は可能です。トークンは消費しますが)
尚、参考までにmain.gsファイルのコードは下のように作成しております。
// LINE Bot 設定
const CHANNEL_ACCESS_TOKEN = 'ここに入力する';
// AI設定
const openAIApiKey = "ここに入力する";
//英語翻訳機能を使う為のDEEL API Key
const DEEPL_API_KEY = 'ここに入力する';
const logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('log');
const DALLE_API = "https://api.openai.com/v1/images/generations";
var replyToken, json;
function doPost(e) {
json = JSON.parse(e.postData.contents);
replyToken = json.events[0].replyToken;
if (typeof replyToken === 'undefined') {
return;
}
var userMessage = json.events[0].message.text;
var messages;
if (userMessage.includes("こんにちはGPT")) {
var replyText; // AIからの応答を保存する変数
if (/こんにちはGPT、.*駅発、.*駅着/.test(userMessage)) {
// 交通手段と費用に関するプロンプトを作成
var prompt = userMessage + "前述の合理的な交通手段と費用を算出して1000字以内で出力してください。読みやすいように箇条書きにして、改行してください";
replyText = getAIChatAnswer(prompt); // 関数を呼び出し、promptを渡す
}else if (/.*、レシピ/.test(userMessage)) {
// 交通手段と費用に関するプロンプトを作成
var prompt = userMessage + "前述の合理的なレシピと予想される費用を概算で良いので算出し、1000字以内で出力してください。読みやすいように箇条書きにして、改行してください";
replyText = getAIChatAnswer(prompt); // 関数を呼び出し、promptを渡す
} else {
// 通常の応答
replyText = getAIChatAnswer(userMessage); // 関数を呼び出し
}
messages = [{'type': 'text', 'text': replyText}]; // LINEメッセージオブジェクトを作成
} else if (userMessage.startsWith("絵を描いて")) {
// 絵を描くリクエストに対して画像を生成
messages = getAIImageAnswer(userMessage);
} else if (userMessage.includes("英訳して")) {
var textToTranslate = userMessage.replace("英訳して", "");
var translatedText = translateTextWithDeepL(textToTranslate, "EN");
messages = [{'type': 'text', 'text': translatedText}];
} else {
// それ以外のメッセージには「A」と返信
messages = [{'type': 'text', 'text': 'A'}];
}
// LINEに返信する処理を呼び出す
const linebotClient = new LineBotSDK.Client({ channelAccessToken: CHANNEL_ACCESS_TOKEN });
try {
linebotClient.replyMessage(replyToken, messages);
} catch (e) {
log_to_sheet("A", e);
}
return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}
//DALL-E3に関する関数
function getAIImageAnswer(text) {
var imageURL = generateImageURL(text);
var messages = [
{'type':'text', 'text': '画像ができました!'},
{'type':'image', 'originalContentUrl': imageURL, 'previewImageUrl': imageURL}
];
return messages;
}
function generateImageURL(text) {
var options = {
"method": "post",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + openAIApiKey
},
"payload": JSON.stringify({
"prompt": text,
"model": "dall-e-3",
"response_format": "url"
})
};
var response = UrlFetchApp.fetch(DALLE_API, options);
var data = JSON.parse(response.getContentText());
return data.data[0].url;
}
//GPT-4o-miniに関する関数
function getAIChatAnswer(prompt, userId) {
var requestOptions = {
"method": "post",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + openAIApiKey
},
"payload": JSON.stringify({
"model": "gpt-4o-mini",
"messages": [
{"role": "user", "content": prompt}
]
})
};
var response = UrlFetchApp.fetch("https://api.openai.com/v1/chat/completions", requestOptions);
var responseText = response.getContentText();
var json = JSON.parse(responseText);
return json.choices[0].message.content.trim();
}
function log_to_sheet(column, text) {
var lastRow;
if(logSheet.getRange(column + "1").getValue() == ""){
lastRow = 0;
} else if(logSheet.getRange(column + "2").getValue() == ""){
lastRow = 1;
} else {
lastRow = logSheet.getRange(column + "1").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
if(lastRow >= 1000){
logSheet.getRange(column + "1:" + column + "1000").clearContent();
lastRow = 0;
}
}
var putRange = column + String(lastRow + 1);
logSheet.getRange(putRange).setValue(text);
}
DeepL機能を動作させる為の関数は、別途deepl.gsファイルとして下のように作成しています。
//自動英語翻訳機能を成立させる為の追加関数
function translateTextWithDeepL(text, targetLang) {
const deepLUrl = `https://api-free.deepl.com/v2/translate?auth_key=${DEEPL_API_KEY}&text=${encodeURIComponent(text)}&target_lang=${targetLang}`;
const response = UrlFetchApp.fetch(deepLUrl, {
'method': 'post'
});
const jsonResponse = JSON.parse(response.getContentText());
if (jsonResponse.translations && jsonResponse.translations.length > 0) {
return jsonResponse.translations[0].text;
} else {
return "翻訳に失敗しました。";
}
}
また、GPT-4o-miniに対する応用的な用途として、ユーザーの入力に対して、プロンプトを追加してGPT-4o-miniに送信するコードも2つ、実装しています。
それぞれ、ご自身の用途に合わせてプロンプトやトリガー条件を工夫してみてください。
その2つのコードに関しては上のコードからご確認いただけますが、
まず1つめは、こんにちはGPT、〇〇駅発、〇〇駅着という形式の入力があった時に、「前述の合理的な交通手段と費用を算出して1000字以内で出力してください。読みやすいように箇条書きにして、改行してください」というプロンプトを追加して送信するものです。
2つめは、こんにちはGPT、〇〇(料理名)、レシピという形式の入力があった時に、「前述の合理的なレシピと予想される費用を概算で良いので算出し、1000字以内で出力してください。読みやすいように箇条書きにして、改行してください」というプロンプトを追加して送信するものです。
これらのコードは、「ユーザーの入力に対して、管理者がプロンプトを追加してGPT-4o-miniに送信する」形を構築する為のサンプルとして実装しています。
...
2024年5月13日、更に、改良を加えたGPT-4omniへの切り替え機能を持つベースコードも合わせて作成しました。
OpenAIによれば、GPT4ominはGPT-4Turboと同等の精度を持ち、かつトークン代はGPT-4Turboの更に半額に抑えられているモデルです。
改良版については、下のリンクからGSSファイルを開き、メニューファイルから「Make a copy」を選択して、ご自身のGoogle Driveにコピーしてください。
https://docs.google.com/spreadsheets/d/1EhpTnlfhI7sYf8Z2ixFGGmxtsY7d3gMOZi0kQ_rwuPg/edit?usp=sharing
その後の設定に関しては、上記解説した通常版と同じです。
参考までに、改良版のmain.gsファイルのコードは下のように作成しております。
// LINE Bot 設定
const CHANNEL_ACCESS_TOKEN = 'ここに入力する';
// AI設定
const openAIApiKey = "ここに入力する";
//英語翻訳機能を使う為のDEEL API Key
const DEEPL_API_KEY = 'ここに入力する';
const logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('log');
const DALLE_API = "https://api.openai.com/v1/images/generations";
var replyToken, json;
function doPost(e) {
json = JSON.parse(e.postData.contents);
replyToken = json.events[0].replyToken;
if (typeof replyToken === 'undefined') {
return;
}
var userMessage = json.events[0].message.text;
var messages;
if (userMessage.includes("こんにちはGPT")) {
var replyText; // AIからの応答を保存する変数
var model = "gpt-4o-mini"; // デフォルトモデル
if (userMessage.includes("こんにちはGPT4")) {
model = "gpt-4o-2024-05-13"; // GPT-4モデルを指定
}
if (/こんにちはGPT[4]、.*駅発、.*駅着/.test(userMessage)) {
var prompt = userMessage + "前述の合理的な交通手段と費用を算出して1000字以内で出力してください。読みやすいように箇条書きにして、改行してください";
replyText = getAIChatAnswer(prompt, model); // 関数を呼び出し、promptとモデルを渡す
} else if (/.*、レシピ/.test(userMessage)) {
var prompt = userMessage + "前述の合理的なレシピと予想される費用を概算で良いので算出し、1000字以内で出力してください。読みやすいように箇条書きにして、改行してください";
replyText = getAIChatAnswer(prompt, model); // 関数を呼び出し、promptとモデルを渡す
} else {
replyText = getAIChatAnswer(userMessage, model); // 関数を呼び出し
}
messages = [{'type': 'text', 'text': replyText}]; // LINEメッセージオブジェクトを作成
} else if (userMessage.startsWith("絵を描いて")) {
messages = getAIImageAnswer(userMessage);
} else if (userMessage.includes("英訳して")) {
var textToTranslate = userMessage.replace("英訳して", "");
var translatedText = translateTextWithDeepL(textToTranslate, "EN");
messages = [{'type': 'text', 'text': translatedText}];
} else {
messages = [{'type': 'text', 'text': 'A'}];
}
const linebotClient = new LineBotSDK.Client({ channelAccessToken: CHANNEL_ACCESS_TOKEN });
try {
linebotClient.replyMessage(replyToken, messages);
} catch (e) {
log_to_sheet("A", e);
}
return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}
//DALL-E3に関する関数
function getAIImageAnswer(text) {
var imageURL = generateImageURL(text);
var messages = [
{'type':'text', 'text': '画像ができました!'},
{'type':'image', 'originalContentUrl': imageURL, 'previewImageUrl': imageURL}
];
return messages;
}
function generateImageURL(text) {
var options = {
"method": "post",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + openAIApiKey
},
"payload": JSON.stringify({
"prompt": text,
"model": "dall-e-3",
"response_format": "url"
})
};
var response = UrlFetchApp.fetch(DALLE_API, options);
var data = JSON.parse(response.getContentText());
return data.data[0].url;
}
//GPTに関する関数
function getAIChatAnswer(prompt, model) {
var requestOptions = {
"method": "post",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + openAIApiKey
},
"payload": JSON.stringify({
"model": model,
"messages": [
{"role": "user", "content": prompt}
]
})
};
var response = UrlFetchApp.fetch("https://api.openai.com/v1/chat/completions", requestOptions);
var responseText = response.getContentText();
var json = JSON.parse(responseText);
return json.choices[0].message.content.trim();
}
function log_to_sheet(column, text) {
var lastRow;
if(logSheet.getRange(column + "1").getValue() == ""){
lastRow = 0;
} else if(logSheet.getRange(column + "2").getValue() == ""){
lastRow = 1;
} else {
lastRow = logSheet.getRange(column + "1").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
if(lastRow >= 1000){
logSheet.getRange(column + "1:" + column + "1000").clearContent();
lastRow = 0;
}
}
var putRange = column + String(lastRow + 1);
logSheet.getRange(putRange).setValue(text);
}
この改良版では、下図のように「こんにちはGPT」と部分一致するテキスト入力をした際にはGPT-4o-miniと会話し、「こんにちはGPT4」と部分一致するテキスト入力をした際にはGPT-4omniと会話する分岐を行います。
ユーザーの入力に対して、プロンプトを追加してGPTに送信する2つのコードにおいても、GPT-4o-miniとGPT-4omniを切り替えて使用することが出来ます。
まず1つ目の機能では、下図のように「こんにちはGPT、〇〇駅発、〇〇駅着」と入力した時は、GPT-4o-miniが回答し、「こんにちはGPT4、〇〇駅発、〇〇駅着」と入力した時は、GPT-4omniが回答します。
料理のレシピを出力させる機能についても下図のように、同様に使用モデルをGPT-4o-miniとGPT-4omniを切り替えることが出来ます。