この記事はCAMPFIRE Advent Calender 2023の21日目の記事です。
CAMPIFIREでマーケティングをしておりますmizutakuと申します。
今回は、はじめてのアドベントカレンダー投稿。どんなものを書こうか悩んだのですがせっかくならと思いGAS×GPT-3.5のAPIをつかって時間のかかる業務をざっと完了させる仕組みづくりをしたという話を書こうと思います。
ちなみに私のキャリアはずーっとマーケティング職で、コードを書く職種に付いたことがないため技術的な知見が手に入るかも的な期待せずお読みいただけると嬉しいです(予防線を張る)。あと文字だらけなので面白みにかけるなぁと思いましたのでいったん犬のイラストを差し込みます。
取り組んだこと
CAMPFIREのサービス回遊性を高めるためにプロジェクト一つ一つに対してハッシュタグをつけているのですが、プロジェクトに最適な「ハッシュタグを選ぶ」という作業をSQL×GAS×GPT-3.5を用いて行いました。
ハッシュタグを選ぶ作業とは
CAMPFIREでは毎日何件ものクラウドファンディングのプロジェクトが立ち上がっています。
クラウドファンディングのプロジェクト情報は基本的に「タイトル」「概要文」「本文」から構成されており、それらを読み込んだ上で、事前に用意されているハッシュタグのなかから最適なものを選ぶといったことをしております(1巡目)。そしてもう一度プロジェクト情報を読み込み、本当にそのタグが最適なのか?という2巡目のチェックをした上で公開されています。
そのプロジェクト情報はクラウドファンディングの肝であるが故に情報量がとても多いため、1件1件読み込んでハッシュタグをつける、という(先ほどの工程でいう)1巡目が大変でした。なので1巡目の工程をGPTに任せて「どんなプロジェクトなのか」を把握した上で2巡目のチェックに入りたいな、と。
GASでやろうとしたことの概要
① 更新性の高い情報(プロジェクト情報やハッシュタグの一覧)をスプレッドシートに出力
② GASを使ってGPTを呼び出し、最適なハッシュタグを3つ選ぶ
③ 選んだハッシュタグをスプレッドシートに書き込む
というとてもシンプルなものです。
②のみのGASは下記の通りです。※①のSQLは割愛
大きく3つのファイルから構成されているのですが「ハッシュタグを選ぶための情報をGPTに渡す&選択されたタグをスプレッドシートに書き込む」「GPTの呼び出し」のコードを共有します。
function loadProjects() {
var sheet = ss.getSheetByName('プロジェクト別タグ数_タグ付与なし');
var lastRow = sheet.getLastRow();
var range = sheet.getRange(3, 5, lastRow-2, 2);
//タイトル、概要文の読み込み
var projectValues = range.getValues();
Logger.log(projectValues.length);
for (var i = 0; i < 20 ; i++){
var title = projectValues[i][0]; // タイトル
var description = projectValues[i][1]; // 概要文
var assignedTags = callOpenAI(title, description);
//出力を配列に変換
var tagsArray = assignedTags.split(',').map(function(item) {
return item.trim();
});
Logger.log(title+":"+tagsArray);
// 各タグをスプシの対応する列に出力
for (var j = 0; j < tagsArray.length; j++) {
sheet.getRange(3+i, 11+j).setValue(tagsArray[j]); // 11はK列、12はL列、13はM列
}
// 負荷対策
Utilities.sleep(2000); // 2秒待つ
}
}
const api_key = PropertiesService.getScriptProperties().getProperty('api_key');
//プロンプトに使う材料
var tags = loadTags();
var order = "タイトル、概要文を読み、必ずタグ一覧にあるものから適切なタグを3つ選んで出力してください。"
var output = "カンマ区切りで必ずそのタグのみアウトプットしてください。"
//OpenAIの呼び出し
function callOpenAI(title, description) {
var url = "https://api.openai.com/v1/chat/completions";
var headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + api_key
};
var messages = [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": order+output+"【タイトル】"+title+"【概要文】"+description+"【タグ一覧】"+tags
}
];
var options = {
"method" : "post",
"headers": headers,
"payload" : JSON.stringify({
"model": "gpt-3.5-turbo",
"messages": messages
})
};
// Logger.log(messages);
var response = UrlFetchApp.fetch(url, options);
var jsonResponse = JSON.parse(response.getContentText());
// Logger.log(jsonResponse['choices'][0]['message']['content']);
return jsonResponse['choices'][0]['message']['content'];
}
大変だったこと
[1] 処理が重いということ
負荷がかかりすぎてよくスクリプトが止まってしまうことが多々発生してしまってました。
なのでその負荷を軽減するためにも繰り返し処理が始まってから2秒後に次の繰り返し処理を進めるみたいなことを突貫でいれました。
[2] GPTの応答制度
GPT-4.0を使えていればもしかしたら変わったのかもなと思うのですがスプレッドシートに出力をする際、区切り文字の判別をつけるために「カンマ区切りで必ずそのタグのみアウトプットしてください。」と書いているにも関わらず「カンマなしでアウトプットしてくるパターン」や「わかりました。ではそのプロジェクトに最適なタグを・・・」といった喋りだし?の文書が帰ってくるケースも結構ありました。これに関しては一番いいプロンプトを探っていき、GPTと仲良くなるしか手はないかもしれません。
さいごに
まだまだこの取り組みは完成したものではないので、上記の通りプロンプトを変えるなどをして返答の精度を高めたいなーと思ってたりします。あとはマーケターのキャリアを形成していく上で、コードを書けるマーケターになりたいなと常に考えているのでそういった機会を自分で作ってスキルアップに繋げて行けたらいいなぁと思ってます。
ということで味気ない文章になってしまいちょっと寂しいのでもういっかい犬のイラストをあげておきます。
それでは、よい年末を!