
LINEに「明日の13時に歯医者」と送るだけで、Googleカレンダーにその予定が自動で登録される。 そんな“自分専用のAI執事”を、Pythonと無料のAPIだけで実現するシリーズ第4弾です。
これまでの構成では、「LINEから予定を確認する」まででしたが、今回はついに“予定を追加できる”ステージに進みます。
自然な文章から日時と予定名を抽出し、Googleカレンダーに直接登録してくれるこの仕組みは、日々の小さなタスク管理を圧倒的に効率化します。
スマホを開かずに予定を登録したい。 PCや手帳に向かう時間も惜しい。 そんなあなたのための“本物の時短ツール”を一緒に作っていきましょう。
この記事で作るものと進化ポイント

この記事では、LINEに自然文を送るだけで、予定をGoogleカレンダーに自動登録してくれる「進化型AI執事ボット」を構築します。
これまでの記事では“予定を確認する”までの構成でしたが、今回は“予定を作る”という能動的な処理へと進化します。 それにともない、コード構成も大きく進化し、より実践的なスタイルへと移行しています。
LINEで送るだけで予定を登録できるAIボット
本記事で構築するAIボットは、ユーザーがLINEに送信した「明日10時に会議」などの自然な文章を受け取り、 Googleカレンダーに予定として自動登録してくれる仕組みです。
これまでと違い、カレンダーに対して“確認する”のではなく“書き込む”側へと回ることで、 単なる便利ツールではなく「生活に溶け込む自動化パートナー」へと機能が進化します。
予定“確認”だけでは物足りない
これまでの記事(第1〜2弾)では、LINEからの自然文に対して「ChatGPTで返信」や「Googleカレンダーの予定を確認」する機能を作ってきました。 しかし、実際の利用シーンでは「予定を知る」だけでなく「予定を入れる」ニーズの方が圧倒的に多く、 このステップがなければAIボットは“ただの便利なメモ代わり”に留まってしまいます。
本記事では、その物足りなさを解消し、ユーザーの入力をトリガーに「予定を自動生成・登録」する 真の意味で実用性の高いAI執事へと昇華させていきます。
前回の「予定参照」Botとの違い
前回の記事①では、「今日の予定は?」と聞くと、Googleカレンダーから当日の予定を返してくれるAI執事を構築しました。 これは“確認”までの機能であり、ユーザーのスケジュールを参照することが目的でした。
今回はその続編として、“予定を登録する”機能を追加します。 たとえば、「明日の15時に歯医者の予約を入れて」とLINEに送るだけで、Googleカレンダーに登録されます。

この進化により、AI執事は単なるリマインダーから“スケジュール管理の実行役”へと進化を遂げました。
なぜ構成を分けるのか(自然な進化)
予定の登録処理は、自然文の解析・日時の抽出・Google APIとのやり取りなど、複数の複雑な処理が関わるため、 これまでのようにすべてを1つのファイル(app.py)に詰め込むと、メンテナンス性が著しく低下します。
そのため、この記事からは「処理の役割に応じてファイルを分割する」構成へと進化します。 構造が整理されることで、コード全体の見通しがよくなり、今後の拡張(削除・リマインド・代理登録など)にも耐えられる実装になります。
これは“技術的な都合”ではなく、「予定を登録できるようになったからこそ、構成も進化するのが自然である」という設計思想に基づいた判断です。
モジュール構成の全体像
今回のボットは、自然文から予定を解析してGoogleカレンダーに登録するという、複数の処理が組み合わさった構成になります。 そのため、すべての処理を1つのファイルに押し込むのではなく、処理の責任ごとに分けて設計することで、コード全体の見通しを明確にしています。
ファイル構成一覧と役割
以下は、本記事で使用するPythonファイルとディレクトリ構成の一覧です。各ファイルには明確な役割があり、将来的な機能追加や修正にも対応しやすい構成になっています。
project_root/
├── app.py # Flask本体・Webhookの受信と分岐
├── .env # APIキーや認証設定(非公開)
├── logic/
│ ├── chatgpt_logic.py # ChatGPT応答・意図分類ロジック
│ └── calendar_utils.py # Googleカレンダー予定確認+登録
└── credentials.json # Googleサービスアカウント認証ファイル
モジュールごとの責務(役割分担が読める表形式)
各ファイルが担う役割と、他モジュールとの関係性を以下の表にまとめます。読者がコードを拡張・保守する際に、どこを見ればよいかが一目でわかる構成になっています。
ファイル名 | 役割 | 主な処理内容 |
---|---|---|
app.py | 司令塔(Flask本体) | LINEからのメッセージを受け取り、意図を分類して処理を分岐 |
chatgpt_logic.py | 応答処理(AI担当) | ChatGPTへの問い合わせと自然文応答の生成、意図の判定 |
calendar_utils.py | 予定管理 | 今日の予定取得、自然文から日時を抽出しGoogleカレンダーに登録 |
.env | 環境設定 | APIキー、カレンダーID、認証ファイルパスなどの秘匿変数 |
必要なライブラリと前提環境
本記事では、LINE Botによる「予定登録」機能を実装するにあたり、従来の環境に加えていくつかのライブラリが新たに必要となります。 以下に、今回追加でインストールすべきライブラリと、その導入方法をまとめます。
使用するPythonライブラリ一覧
今回の機能(予定登録)に最低限必要なライブラリは以下の通りです。
ライブラリ名 | 用途 |
---|---|
flask | Webhookサーバ構築 |
python-dotenv | .envファイルから環境変数を読み込む |
line-bot-sdk | LINE Messaging APIの操作(v3対応) |
openai | ChatGPT応答(予定確認以外の会話処理) |
google-api-python-client | Google Calendar APIの操作 |
google-auth | Google APIの認証用 |
requirements.txt の記述例
これらを requirements.txt にまとめておくことで、再インストールや別環境構築がスムーズになります。
flask
python-dotenv
line-bot-sdk>=3.0.0
openai
google-api-python-client
google-auth
コピペで動く完成コード(予定登録対応)
このセクションでは、前提知識や背景説明よりも先に「とにかく動かしてみたい!」という読者のために、すぐに実行可能なコード一式を提示します。 すべてのファイルはモジュール分割されており、1つずつ貼り付けることでそのまま再現できます。
app.py(本体:Webhook + 処理分岐)
このファイルはFlaskアプリケーションのエントリーポイントであり、LINEからのWebhookを受け取り、メッセージの意図に応じて各処理へ振り分ける“司令塔”です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import os from flask import Flask, request, abort from dotenv import load_dotenv from linebot.v3.messaging import MessagingApi, Configuration, ApiClient from linebot.v3.webhook import WebhookHandler from linebot.v3.webhooks import MessageEvent, TextMessageContent from linebot.v3.messaging.models import ReplyMessageRequest, TextMessage from logic.chatgpt_logic import askChatgpt # .envファイルを読み込む load_dotenv() # Flaskアプリケーション初期化 app = Flask(__name__) # LINE Messaging API設定 configuration = Configuration(access_token=os.getenv("LINE_CHANNEL_ACCESS_TOKEN")) handler = WebhookHandler(os.getenv("LINE_CHANNEL_SECRET")) # Webhookエンドポイント @app.route("/callback", methods=["POST"]) def callback(): line_signature = request.headers.get("X-Line-Signature", "") request_body = request.get_data(as_text=True) print("📦 Webhook受信ボディ:", request_body) try: handler.handle(request_body, line_signature) except Exception as error: print("❌ Webhook handling failed:", error) abort(400) return "OK" # LINEメッセージ受信処理 @handler.add(MessageEvent, message=TextMessageContent) def handleMessage(event): user_message = event.message.text print("✅ メッセージイベント発火! 📩", user_message) try: reply_text = askChatgpt(user_message) print("🧠 応答内容:", reply_text) except Exception as error: reply_text = f"応答処理エラー: {error}" with ApiClient(configuration) as api_client: messaging_api = MessagingApi(api_client) messaging_api.reply_message( ReplyMessageRequest( reply_token=event.reply_token, messages=[TextMessage(text=reply_text)] ) ) # Flaskサーバ起動 if __name__ == "__main__": app.run(host="0.0.0.0", port=5000) |
logic/chatgpt_logic.py(ChatGPT応答・意図分類)
このファイルは、ユーザーからの自然文メッセージに対して、ChatGPTを用いた応答や“予定”などの意図分類を担当するモジュールです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
import os import json from datetime import datetime from openai import OpenAI from logic.calendar_utils import ( registerSchedule, getScheduleByOffset ) # 🔍 ユーザーの発言から意図を判定(登録・更新・削除・予定確認など) def classifyIntent(user_input): user_input = user_input.lower() if "削除" in user_input: return "delete" elif "更新" in user_input or "変更" in user_input: return "update" elif "入れて" in user_input or "登録" in user_input or "追加" in user_input: return "register" elif "明後日" in user_input and "予定" in user_input: return "schedule+2" elif "明日" in user_input and "予定" in user_input: return "schedule+1" elif "今日" in user_input and "予定" in user_input: return "schedule+0" elif "予定" in user_input or "スケジュール" in user_input: return "schedule+0" elif "天気" in user_input: return "weather" elif "疲れた" in user_input or "やる気" in user_input: return "mental" else: return "general" # 📤 ChatGPTを使って予定のタイトルと(必要なら)開始時刻を抽出する def extractNewEventDetails(user_input, require_time=True): today = datetime.now().strftime("%Y-%m-%d") if require_time: system_content = ( f"あなたは自然文から予定の日時とタイトルを抽出するアシスタントです。\n" f"今日の日付は {today} です。『明日』『明後日』も正しく認識してください。\n" f"※タイトルには「の予定」「予約」などの余計な表現を含めず、純粋な予定名のみを抽出してください。\n" f"出力形式はJSONで {{\"title\": \"予定名\", \"start_time\": \"2025-04-30 15:00:00\"}} にしてください。" ) else: system_content = ( f"あなたは自然文から予定のタイトルだけを抽出するアシスタントです。\n" f"今日の日付は {today} です。『明日』『明後日』も正しく認識してください。\n" f"入力文が命令文(例:予定を削除して)の場合でも、予定の名前だけを抽出してください。\n" f"出力形式はJSONで {{\"title\": \"予定名\"}} にしてください。" ) messages = [ {"role": "system", "content": system_content}, {"role": "user", "content": user_input} ] client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) response = client.chat.completions.create( model="gpt-3.5-turbo", messages=messages ) content = response.choices[0].message.content print("📤 ChatGPTの返答(更新情報):", content) parsed = json.loads(content) title = parsed.get("title", "").strip() title = title.replace("の予定", "").replace("の予約", "").replace("予約", "").strip() if require_time: start_time = parsed.get("start_time") return {"title": title, "start_time": start_time} else: return {"title": title} # 🗓️ 予定登録用:ChatGPTで抽出 → 登録処理 → 成功メッセージ返却 def registerScheduleFromText(user_message, client): try: new_event = extractNewEventDetails(user_message, require_time=True) title = new_event["title"] start_time = datetime.strptime(new_event["start_time"], "%Y-%m-%d %H:%M:%S") registerSchedule(title, start_time) return f"予定『{title}』を登録しました。" except Exception as error: print("❌ 予定登録エラー:", error) return "日付とタイトルの解析に失敗しました。" # 🎯 メイン処理:ユーザーの意図に応じて処理分岐し、結果を返す def askChatgpt(user_message): client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) intent = classifyIntent(user_message) print(f"🎯 intent 判定: {intent}") if intent.startswith("schedule+"): day_offset = int(intent.split("+")[1]) return getScheduleByOffset(day_offset) elif intent == "register": return registerScheduleFromText(user_message, client) elif intent == "delete": try: new_event = extractNewEventDetails(user_message, require_time=False) title = new_event["title"] return deleteEvent(title) except Exception as error: print("❌ 削除エラー:", error) return "削除中にエラーが発生しました。" elif intent == "update": try: new_event = extractNewEventDetails(user_message, require_time=True) title = new_event["title"] start_time = datetime.strptime(new_event["start_time"], "%Y-%m-%d %H:%M:%S") return updateEvent(title, {"title": title, "start_time": start_time}) except Exception as error: print("❌ 更新エラー:", error) return "更新中にエラーが発生しました。" messages = [ {"role": "system", "content": "あなたは親切で柔軟なAIアシスタントです。"}, {"role": "user", "content": user_message} ] try: response = client.chat.completions.create( model="gpt-3.5-turbo", messages=messages ) return response.choices[0].message.content except Exception as error: print("❌ ChatGPT応答エラー:", error) return "現在AI応答が制限されています。" |
logic/calendar_utils.py(予定確認 + 予定登録)
のファイルは、Googleカレンダーと連携して「今日・明日・明後日など任意の日付の予定を取得」したり、「自然文から予定を登録する」処理をまとめたユーティリティモジュールです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
import os from datetime import datetime, timedelta import pytz from googleapiclient.discovery import build from google.oauth2 import service_account # 🔐 認証情報を取得 def getCredentials(): credentials_path = os.getenv("GOOGLE_SERVICE_ACCOUNT_JSON") if not credentials_path: raise ValueError("GOOGLE_SERVICE_ACCOUNT_JSON が未設定です") credentials = service_account.Credentials.from_service_account_file( credentials_path, scopes=["https://www.googleapis.com/auth/calendar"] ) return credentials # 📅 Googleカレンダーへ予定を登録 def registerSchedule(title, start_time): try: credentials = getCredentials() service = build("calendar", "v3", credentials=credentials) end_time = start_time + timedelta(minutes=30) event = { "summary": title, "start": {"dateTime": start_time.isoformat(), "timeZone": "Asia/Tokyo"}, "end": {"dateTime": end_time.isoformat(), "timeZone": "Asia/Tokyo"} } calendar_id = os.getenv("GOOGLE_CALENDAR_ID", "primary") created = service.events().insert(calendarId=calendar_id, body=event).execute() print("✅ 登録イベント情報:", created) return created.get("htmlLink") except Exception as error: print("❌ Googleカレンダー登録エラー:", error) raise error # 📆 任意の日数後の予定を取得(日本時間ベース) def getScheduleByOffset(day_offset: int): credentials = getCredentials() service = build("calendar", "v3", credentials=credentials) jst = pytz.timezone("Asia/Tokyo") target_date = datetime.now(jst) + timedelta(days=day_offset) start = datetime(target_date.year, target_date.month, target_date.day, 0, 0, 0, tzinfo=jst).isoformat() end = datetime(target_date.year, target_date.month, target_date.day, 23, 59, 59, tzinfo=jst).isoformat() events_result = service.events().list( calendarId=os.getenv("GOOGLE_CALENDAR_ID", "primary"), timeMin=start, timeMax=end, singleEvents=True, orderBy="startTime" ).execute() events = events_result.get("items", []) if not events: label = {0: "今日", 1: "明日", 2: "明後日"}.get(day_offset, f"{day_offset}日後") return f"{label}の予定はありません。" label = {0: "今日", 1: "明日", 2: "明後日"}.get(day_offset, f"{day_offset}日後") result = f"{label}の予定はこちらです:\n" for event in events: start_time = event["start"].get("dateTime", event["start"].get("date")) result += f"・{start_time}:{event['summary']}\n" return result |
.env(環境変数の例)
APIトークンやGoogle認証情報などの機密設定は`.env`ファイルに分離することで、コードの安全性と可搬性を保ちます。
以下の内容を `.env` ファイルに保存することで、アプリ全体で使用するAPIキーや認証パスを安全に管理します。 ソースコードには直接値を記述せず、この `.env` を通して値を読み込む構成にしています。
# LINE Messaging API トークン(v3対応)
LINE_CHANNEL_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
LINE_CHANNEL_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# OpenAI APIキー(ChatGPT応答用)
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Googleカレンダーの認証用JSONファイルのパス
GOOGLE_SERVICE_ACCOUNT_JSON=credentials.json
# 登録対象のGoogleカレンダーID(通常は "primary")
GOOGLE_CALENDAR_ID=primary
なお、Googleの認証JSONファイル(例: credentials.json)は、Google Cloud Console で 「サービスアカウントの鍵を発行」し、予定を登録したいカレンダーと共有設定を行ったうえで作成します。
実行結果の確認(LINE→登録完了メッセージ→Googleカレンダー反映)
すべてのコードを正しく貼り付け `.env` の設定も完了したら、実際にLINEからメッセージを送って動作確認を行います。 ここでは、予定を含んだ自然文をLINEで送信し、ボットがGoogleカレンダーに正しく予定を登録できるかを検証します。
LINEでの送信内容
明日15時に歯医者の予約を入れて
LINEからの返信メッセージ(Bot側)
予定『明日15時に歯医者の予約を入れて』を登録しました。
Googleカレンダー側に反映された予定(確認画面)
下記のように、Googleカレンダーに予定が1件登録されていれば成功です。
開始時刻 | 予定名 |
---|---|
2025-04-21 15:00 | 明日15時に歯医者の予約を入れて |
もし登録されていない、またはLINEからの返信が「登録に失敗しました」などになっている場合は、 以下の確認ポイントを見直してください。
- LINE Developers でWebhook URLが正しく設定されているか
- .env 内の GOOGLE_SERVICE_ACCOUNT_JSON や CALENDAR_ID が正しいか
- credentials.json のサービスアカウントがカレンダーに「予定の編集権限」を持っているか
- 自然文の中に「日付や時間を示す表現」が含まれているか(例:「明日」「13時」など)
問題がなければ、これであなたのAI執事は「予定を作る」能力を正式に手に入れました。 次回はこの仕組みを土台に、さらに人間らしい対話や調整機能へと進化させていきます。
ソースコードの補足と注意点
このセクションでは、記事で紹介したソースコードにおける注意点と、読者が実装・運用時に理解しておくべきポイントを記載します。
ChatGPTのエラー処理について
ChatGPTとの連携中に発生しうるエラー(特にAPI制限など)に対しては、 try-exceptブロックによるエラーハンドリングが実装されています。
たとえば、以下のようなエラーはChatGPT APIの利用制限(クォータ超過)によって発生します。
❌ ChatGPT応答エラー:Error code: 429 - {'message': 'You exceeded your current quota...'}
このような状況下でも、本システムは「現在AI応答が制限されています。」といった案内文を返し、処理全体が停止することを回避する設計になっています。
【補足】Quota(利用制限)が発生する原因と解消条件
OpenAIのAPIには「Usage Tier(利用階層)」という仕組みが存在し、Tier 1の段階では以下の制限があります。
- 月間利用量が一定に達すると一時的に制限(エラーコード 429)が発生
- たとえ残高があっても制限される(段階的リスク管理のため)
これを解除して、より柔軟な利用が可能な「Tier 2」に昇格するには、次の2条件を満たす必要があります。
条件 | 説明 |
---|---|
① $50以上の課金実績 | 初回から累計で$50を超える必要があります。 |
② 初回課金から7日経過 | 課金してすぐには昇格できません。待機期間が必要です。 |
つまり、$10や$20などの少額課金では、残高があってもTier 1の制限内でしか利用できません。頻繁に開発・テストを行う場合は、最初から$50を超える課金を行っておくことが推奨されます。
詳細については OpenAI公式のUsageページ を確認してください。
.envに記述すべき環境変数一覧
本システムでは、以下の環境変数を .env ファイルに記述して管理します。環境ごとの値を正しく設定しておくことが必須です。
これらが正しく設定されていない場合、システムは正常に動作しません。
変数名 | 用途 |
---|---|
OPENAI_API_KEY | ChatGPTとの連携に使用するAPIキー |
GOOGLE_CALENDAR_ID | 予定の登録・更新・削除を行う対象のGoogleカレンダーID |
GOOGLE_SERVICE_ACCOUNT_JSON | Google APIへの認証に必要なサービスアカウント情報のJSONファイルパス |
LINE_CHANNEL_ACCESS_TOKEN | LINE BOTからの送受信処理に必要なトークン(Messaging API用) |
LINE_CHANNEL_SECRET | Webhook署名検証用のシークレットキー |
各処理の仕組みと工夫
本セクションでは、AI執事ボットが「自然文を受け取って予定登録を行う」までの処理フローと、そこに込められた設計上の工夫を解説します。 ユーザーの負担を最小限に抑えつつ、確実にカレンダー登録まで行うためのロジック構成がテーマです。
自然文をどう処理して日時+予定名を抜き出しているか
LINE経由で送られてくるユーザーの入力は、次のような自然文です:
明日の15時に歯医者の予約を入れて
このメッセージをそのまま ChatGPT に渡すと、ChatGPT は文脈を理解し、 予定登録に必要な2つの要素 ――「予定名」と「日時」―― を返してきます。
予定名: 歯医者の予約
日時: 2025-04-21 15:00:00
このような形式で返ってくることを前提に、アプリ側では次のような処理で値を取り出します:
「予定名: 歯医者の予約」 →
title = "歯医者の予約"
「日時: 2025-04-21 15:00:00」 →
start_time = "2025-04-21T15:00:00" (ISO形式に変換)
このように、ChatGPTが「予定名」と「日時」を構造的に返す設計にしておくことで、多少ユーザーの文言に揺れがあっても、アプリ側では安定して情報を抽出・登録することができます。
また、この処理では、日付や時刻の抽出を外部ライブラリに依存せず、ChatGPTの構文解析力だけで成立させています。これにより、「明後日の昼」「金曜日の夜8時」「来週の会議」といった、多様な表現にも柔軟に対応できる設計となっています。
たとえば以下のようなカッコ付きの予定が入力された場合:
予定名: 面談(人事部)
日時: 2025-05-01 14:00:00
人間にとっては「面談(人事部)」のような予定名も自然に読めますが、AIにとっては「()」が意味の区切りと誤解される可能性があります。そのため、こうした表現でも正しく予定名を抽出できるよう、返答形式を明確に設計しています。
予定名に「(人事部)」のような補足情報がカッコ付きで含まれている場合でも、ChatGPTからこのように構造的な返答を得ることで、アプリ側では確実に title と start_time を抽出できます。
このように、予定名が単語だけでなく少し複雑な表現であっても問題なく対応できるよう、あらかじめ ChatGPT の返答形式を明確に設計しておくことが、システムの安定性につながります。
よくあるエラーと切り分けのコツ
このボットは、LINE・ChatGPT・Google Calendar・自然文解析など複数の要素が連携して動作しているため、 どれか1つが正しく設定されていないだけでも「動かない」「反応がない」という現象が発生します。 ここでは、読者がよく直面するエラーと、その原因の切り分けポイントを整理します。
Googleカレンダーへの書き込み権限を必ず付与してください
サービスアカウントにはデフォルトで読み取り権限しかありません。 予定を追加するには、Googleカレンダー側で明示的に共有設定を行い、 予定の変更権限または 共有の管理権限を与える必要があります。
共有の手順は以下のとおりです:
- Googleカレンダーを開く
- 左の「マイカレンダー」一覧から対象カレンダーの「︙」→「設定と共有」
- 「特定のユーザーと共有」→ サービスアカウントのメールアドレスを追加
- 権限を「予定の変更権限」に設定

※ サービスアカウントのメールアドレスは、認証用JSON内の "client_email" に記載されています。
LINE Webhookが反応しない場合
LINEにメッセージを送っても、Botからの返信が全く返ってこない場合、最も可能性が高いのがWebhook周りの設定ミスです。 以下の項目を確認してください。
- Webhook URLがLINE Developersに正しく登録されているか
- Webhook URLがHTTPSで始まっており、SSL証明書が有効か
- LINEアカウントがBotとの「1:1トーク」で有効になっているか
- LINE Developersのチャネル設定で「応答メッセージ」が無効化されているか(Botとの二重応答を避けるため)
- VPSやサーバーでFlaskが正しく起動しているか
Google APIで登録に失敗する場合
LINEから「予定を入れて」と送っても「登録に失敗しました」や応答がない場合、Google API連携が正しく設定されていない可能性があります。 主なチェックポイントは以下のとおりです。
- credentials.json がサーバー上に存在しているか
- そのファイルパスが .env の GOOGLE_SERVICE_ACCOUNT_JSON に正しく設定されているか
- サービスアカウントにカレンダーの「編集権限(共有)」が与えられているか
- .env の GOOGLE_CALENDAR_ID がカレンダーのIDと一致しているか(通常は "primary")
また、日付の書式が崩れていると Google API 側でエラーが発生することがあります。
ログにエラーメッセージが出力されていれば、スタックトレースも含めて確認してください。
まとめ:AI執事は“実用フェーズ”に入った
ここまでで、AI執事ボットは単なる「ChatGPTとLINEを繋げて喋らせるだけのツール」から脱却し、 自分の予定を管理し、記録し、整える“生活の一部”として機能するフェーズに突入しました。 コードは確かに複雑になりましたが、そのぶん得られる体験はまったく別物です。
自分の手で作った仕組みが日常に機能する喜び
予定が思い浮かんだ瞬間に「LINEで送るだけ」で登録できる。 この体験は、もはや“プログラムを動かす”という感覚ではなく、“自分の暮らしを作る”という実感に近づいてきます。
そしてその仕組みは、あなた自身の手で作られています。 これは「誰かが作ったアプリを使う」のではなく、「自分で作った仕組みが、自分の生活に馴染んでいる」という唯一無二の喜びです。
次回の予告(予定削除・確認応答などの双方向性)
今回の構成は、意図分類→分岐→処理実行という柔軟な土台をすでに備えています。 この構造を活かせば、次は「予定を削除したい」「日付を変更したい」「○○って何時だったっけ?」といった “対話型のスケジューラー”への進化がすぐにでも可能です。
ただし、「予定の変更」については技術的な制約があります。 特に、指定された変更先の時間帯にすでに別の予定が入っている場合、現在の構成では“そのまま上書きする”ことは避け、以下のようなメッセージを返して変更を中止する設計としています。
指定された時間にはすでに『会議』があります。
先に衝突する予定を削除してください。
今後は、LINEのテンプレートメッセージや状態管理を取り入れることで、 「上書きしてもよいかをBotが確認し、ユーザーが応答で判断する」ような双方向の処理も検討可能です。 まずは安全で破綻しない構成をベースに、段階的に進化を続けていきます。
- ✅ 「〇日の歯医者をキャンセルして」→ 自動で予定を削除
- ✅ 「今日の予定まとめて教えて」→ ChatGPTで整形されたカレンダー一覧
“便利”を超えて、“頼れる”。 AI執事は、ついにそこまで来ました。