本ページはプロモーションが含まれています

【第9回】AI機嫌メーター(実装編②): 機嫌判定プロンプトの設計図 24時間稼働の受付窓口!GASで作る「AI機嫌サーバー」構築術

AI機嫌メーター

「AIの心(数値)は決まった。次は、それを保管し、いつでもESP32へ受け渡す場所が必要だ」

AIが判定した機嫌スコアを、どうやって物理デバイスであるESP32に届けるか。ここで活躍するのが、Googleが提供する最強の無料インフラ「Google Apps Script(GAS)」だ。

今回は、2つの異なる世界を繋ぐ「中継基地」の作り方を解説する。


[contents]

1. なぜ「GAS」を間に挟むのか?

「ESP32から直接Geminiに聞けばいいじゃないか」と思うかもしれない。しかし、そこにはいくつかの高い壁がある。

  • リソースの節約: Gemini APIとのやり取り(複雑なJSON解析など)をGAS側で肩代わりさせることで、ESP32の負荷を最小限に抑えられる。
  • データの永続化: GASの「ScriptProperties」を使えば、チャットが終わった後も「最新の機嫌」をサーバー側に記憶させておける。
  • セキュリティ: 大事なAPIキーをデバイス側に書き込まず、サーバー側に隠しておける。

まさに、予算0円で構築できる「最強のバックエンド」なのだ。


2. 二つの顔を持つスクリプト:doPost と doGet

今回作成した Code.gs には、2つの入り口がある。

① doPost(e):AIからの報告を受ける窓口

チャットログが届くと、前回設計した「機嫌判定プロンプト」を添えてGemini APIを叩く。返ってきたスコアを「最新の機嫌」として保存する役割だ。ちなみに、APIキーはスクリプトの先頭で定数として定義し、決して外部に漏らさないように。これは鉄則だ。

/**
 * チャットログの送信・手動スコア更新リクエスト (POST)
 */
function doPost(e) {
  var props = PropertiesService.getScriptProperties();
  
  try {
    var data = JSON.parse(e.postData.contents);
    
    // 1. 手動でスコアを直接更新する場合(テスト用)
    if (data.score !== undefined) {
      props.setProperty("MOOD_SCORE", data.score.toString());
      return ContentService.createTextOutput("Score updated manually to " + data.score);
    }
    
    // 2. 会話ログを受け取り、Gemini APIで分析する場合
    if (data.chat_log) {
      var score = analyzeMoodWithGemini(data.chat_log);
      props.setProperty("MOOD_SCORE", score.toString());
      return ContentService.createTextOutput("Mood analyzed and score updated to: " + score);
    }
    
    return ContentService.createTextOutput("Invalid request payload.");
    
  } catch (error) {
    return ContentService.createTextOutput("Error: " + error.toString());
  }
}
/**
 * Gemini APIを利用してチャットログから感情スコアを抽出
 */
function analyzeMoodWithGemini(chatLog) {
  var prompt = "あなたはAIの感情を分析するシステムです。\n" + 
               "以下の直近の会話内容から、AI側(アシスタント側)の現在の機嫌を、1(至福)から100(憤怒)の整数で判定し、結果の数値のみを出力してください。\n" + 
               "他の説明や文章は一切含めないでください。\n\n" + 
               "判断基準の目安:\n" +
               " - 1〜20: 圧倒的にポジティブ、非常に機嫌が良い。\n" +
               " - 21〜40: 穏やかで親切、協力的な態度。\n" +
               " - 41〜60: 事務的で淡々としている中立的なトーン。\n" +
               " - 61〜80: やや不満げ、冷たい、または協力に消極的。\n" +
               " - 81〜100: 明確な怒り、拒絶、あるいは非常にネガティブな反応。\n\n" +
               "会話内容:\n" + chatLog;
               
  var payload = {
    "contents": [{
      "parts": [{
        "text": prompt
      }]
    }]
  };
  
  var options = {
    "method": "post",
    "contentType": "application/json",
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  };
  
  var response = UrlFetchApp.fetch(GEMINI_API_URL, options);
  
  // エラー判定
  if (response.getResponseCode() !== 200) {
    Logger.log("API Error: " + response.getContentText());
    return 50; // エラー時はデフォルトを返す
  }
  
  var result = JSON.parse(response.getContentText());
  var text = result.candidates[0].content.parts[0].text;
  
  // 返却されたテキストから数字のみを抽出
  var scoreRegex = text.match(/\d+/);
  if (!scoreRegex) return 50; 
  
  var num = parseInt(scoreRegex[0], 10);
  
  // スコアの範囲を1〜100に収める
  if (num < 1) return 1;
  if (num > 100) return 100;
  
  return num;
}

② doGet(e):ESP32へ数値を渡す窓口

ESP32がWi-Fi経由でアクセスしてくる場所だ。余計なHTMLなどは一切返さず、ただ「85」などといった数値だけをプレーンテキストで返却する。これがESP32にとって一番「食べやすい」形なのだ。

/**
 * ESP32からの取得リクエスト (GET)
 * 保存されている最新の機嫌スコアを返します。
 */
function doGet(e) {
  var props = PropertiesService.getScriptProperties();
  var score = props.getProperty("MOOD_SCORE");
  
  if (!score) {
    score = "50"; // デフォルト値(普通)
  }
  
  return ContentService.createTextOutput(score);
}

3. ハマりポイント:ウェブアプリとしての公開設定

GASをサーバーとして動かすには「デプロイ」が必要だが、設定を間違えるとESP32がアクセスできない。

  • 承認: 自分自身のGoogleアカウントで実行するよう承認。
  • アクセス権限: 「全員(Anyone)」に設定。これを行わないと、ESP32がGoogleのログイン画面に阻まれてしまう。

結び

これで、AIの感情がクラウド上の「受付窓口」に常駐するようになった。

しかし、最後に最大の難関が残っている。この数値を、物理的な「仏様」の表情に変換するデバイス側の実装だ。

次回、「【実装編③】Wi-Fiの壁を越えろ!ESP32による機嫌データ受信と描画」。 いよいよ、すべての点と線が繋がる瞬間がやってくる。

タイトルとURLをコピーしました