先日、GPT、Gemini を使った LINE bot をそれぞれ作りましたが、今回は Claude の API を使った LINE bot を GAS(Google Apps Script)で作ってみました。
モデルは、Claude 3.5 Sonnet を使っています。プログラミングのお供にめちゃくちゃ便利ですね。
詳解! Google Apps Script完全入門 [第3版]
by SimpleImageLink
それでは、作り方です。
LINE Messaging API チャンネルの作成とアクセストークンの取得。及び、Claude の API キーの取得は済んでる想定です。
それぞれ、GAS の「プロジェクトの設定」よりスクリプト プロパティとして追加・保存しておきます。プロパティ名は「Line_key」と「Claude_key」としています。
また、スプレッドシートを用意して、「chat」シート、「system」シート、「temperature」シートを作っておきます(サンプル)。(作り方は「GAS で ChatGPT とやりとりできる簡単な LINE bot の作り方」を参照)
★ 機能一覧
- 文頭に「続き)」を付けることで会話を継続。(例:続き)もっと詳細に教えて)
- 文頭に「昔話!」のように付けることで、スプレッドシートの「system」シートにあらかじめ登録したシステム指示を実行。(例:昔話!きのこ)
- 文頭に「画像)」で画像の処理モード。(例:画像)何ですか? → 画像を送信)
- 文末に「(厳密」「(創造」で temperature を指定。通常 0.5、厳密 0、創造 1。(例:3行で小噺を作って(創造)
コードは以下の通りです。
const spreadsheet = SpreadsheetApp.openById('スプレッドシート ID); // スプレッドシート ID を書き換え const chatSheet = spreadsheet.getSheetByName('chat'); let chatLastRow = chatSheet.getLastRow(); let chatRange = chatSheet.getRange(2, 1, chatLastRow, 2); const systemSheet = spreadsheet.getSheetByName('system'); const systemLastRow = systemSheet.getLastRow(); const systemWatchwordRange = systemSheet.getRange(2, 1, systemLastRow - 1, 1); const systemContentRange = systemSheet.getRange(2, 2, systemLastRow - 1, 1); const tempSheet = spreadsheet.getSheetByName('temperature'); const tempRange = tempSheet.getRange(1, 1); function doPost(e) { try { const event = JSON.parse(e.postData.contents).events[0]; let prompt = []; let replyMessage = ''; switch(event.message.type) { case 'text': // リクエストがテキストの場合 let messageContent = event.message.text; if (messageContent.startsWith('画像)')) { // 「画像)」に続いて指示で画像処理 // 画像処理の場合 messageContent = messageContent.replace('画像)', ''); claudeRequestText(messageContent); sendLineMessage(event.replyToken, '画像を送信してください'); } else { // 画像処理以外の場合 prompt = claudeRequestText(messageContent); replyMessage = getClaudeReply(prompt); // Claude で回答 setAiMessage('assistant', replyMessage); // AI の返答をスプレッドシートに'role': 'assistant'で格納 sendLineMessage(event.replyToken, replyMessage); } break; case 'image': // リクエストが画像の場合 prompt = claudeRequestImage(event.message.id); replyMessage = getClaudeReply(prompt); // Claude で回答 sendLineMessage(event.replyToken, replyMessage); break; default: // リクエストがサポート外のデータ形式の場合 sendLineMessage(event.replyToken, 'サポート外のデータ形式です'); } } catch { // エラー発生時 sendLineMessage(event.replyToken, '不明なエラーが発生しました'); } } // Claude でリクエストデータ(テキスト)を生成する関数 function claudeRequestText(messageContent) { // system ロールの配列を生成 const systemWatchwordArry = systemWatchwordRange.getValues().flat(); const systemContentArry = systemContentRange.getValues().flat(); let systemWatchword = ''; let systemContent = ''; // リクエスト内容の処理 if (messageContent.startsWith('続き)')) { // 文頭に「続き)」で会話を継続 messageContent = messageContent.replace('続き)', ''); chatSheet.getRange(chatLastRow + 1, 1, 1, 2).setValues([['user', messageContent]]); } else { // temperature を取得してスプレッドシートにセット // 文末に「(創造」「(厳密」で指定 if (messageContent.endsWith('(創造')) { messageContent = messageContent.replace('(創造', ''); tempRange.setValue(1); } else if (messageContent.endsWith('(厳密')) { messageContent = messageContent.replace('(厳密', ''); tempRange.setValue(0); } else { tempRange.setValue(0.5); } // スプレッドシートにあらかじめ登録した system ロールの読み込み // 文頭に「〇〇!」でsystem ロールを指定 for (let i = 0; i < systemWatchwordArry.length; i++) { if (messageContent.startsWith(systemWatchwordArry[i] + '!')) { systemWatchword = systemWatchwordArry[i] + '!'; systemContent = systemContentArry[i]; break; } } messageContent = messageContent.replace(systemWatchword, ''); chatRange.clear(); // 以前のチャット内容を削除 // リクエスト内容をスプレッドシートに格納 if (systemContent) { chatSheet.getRange(2, 1, 2, 2).setValues([['system', systemContent], ['user', messageContent]]); chatRange = chatSheet.getRange(2, 1, 2, 2); } else { chatSheet.getRange(2, 1, 1, 2).setValues([['user', messageContent]]); chatRange = chatSheet.getRange(2, 1, 1, 2); } } // リクエストデータを生成 let messages = []; let values = chatRange.getValues(); for(let i = 0; i < values.length; i++) { if(values[i][0] == 'system') { continue; } messages.push({'role': values[i][0], 'content': values[i][1]}); } return messages; } // Claude でリクエストデータ(画像)を生成する関数 function claudeRequestImage(id) { // 画像を取得 const response = UrlFetchApp.fetch('https://api-data.line.me/v2/bot/message/' + id + '/content',{ 'headers': { 'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('Line_key')}`, }, 'method': 'get' }); const imageBlob = response.getBlob().getAs('image/png'); // 画像を Base64 にエンコード const base64Image = Utilities.base64Encode(imageBlob.getBytes()); // リクエストデータを生成 let messages = []; let values = chatSheet.getRange(2, 1, chatLastRow - 1, 2).getValues(); for(let i = 0; i < values.length; i++) { if(values[i][0] == 'user') { messages.push({'role': 'user','content': [{'type': 'image','source': {'type': 'base64','media_type': 'image/png','data': base64Image}},{'type': 'text','text': values[i][1]}]}); } else { messages.push({'role': values[i][0], 'content': values[i][1]}); } } return messages; } // Claude から回答を得る関数 function getClaudeReply(prompt) { let system = ""; if(chatSheet.getRange(2, 1).getValue() == 'system') { system = chatSheet.getRange(2, 2).getValue(); } const payload = { 'model': 'claude-3-5-sonnet-20240620', // 使用するモデル, 'max_tokens': 1024, // 生成する文章の最大トークン数 'temperature': tempRange.getValue(), 'system': system, // システム指示 'messages': prompt }; const options = { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': PropertiesService.getScriptProperties().getProperty('Claude_key'), 'anthropic-version': '2023-06-01', }, payload: JSON.stringify(payload), }; try { const response = JSON.parse(UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', options).getContentText()); return response.content[0].text; // Claude の回答 } catch (error) { // エラー発生時 console.error('Error:', error); return 'エラー: Claude の回答が得られませんでした'; } } // AI の返答をスプレッドシートに格納する関数 function setAiMessage(role, replyMessage) { chatLastRow = chatSheet.getLastRow(); chatSheet.getRange(chatLastRow + 1, 1, 1, 2).setValues([[role, replyMessage]]); } // LINE メッセージを送信する関数 function sendLineMessage(replyToken, replyMessage) { const linePayload = { 'method': 'post', 'headers': { 'Content-Type': 'application/json', 'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('Line_key')}`, }, 'payload': JSON.stringify({ 'replyToken': replyToken, 'messages': [{ 'type': 'text', 'text': replyMessage, }], }), }; UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', linePayload); }
1行目の「スプレッドシート ID」は書き換えてください。
コード作成後にウェブアプリとして公開、URL を LINE Messaging API チャンネルで Webhook として設定して完成です。
***
生成AI、本当に日進月歩で性能が上がっていきますね。自身にとってのより良い活かし方を、日々探っていきたいですね。