オンラインスクールの面談の文字起こしから AI で議事録を作り、取りこぼしなく Notion に残す仕組みを構築

業務システム教育AI 活用

概要

オンラインスクールを運営する企業様の、講師と受講生の面談の議事録を自動で作る仕組みです。講師がオンライン会議(Google Meet)で面談を録画すると、会議の文字起こしが Google ドライブに保存されます。その文字起こしを見つけて AI(大規模言語モデル。以下 LLM)で議事録にまとめ、社内の情報共有ツール(Notion)に保存し、社内チャット(Slack)にお知らせします。講師が議事録のために追加で操作することはありません。

画面は持たず、裏側で動く処理だけで構成しています。地味ですが、「新しい文字起こしを 1 件も取りこぼさない」「同じ面談の議事録を 2 回作らない」「止まったらすぐ気づいて直せる」の 3 点に一番手をかけました。

課題

  • 面談の内容を残すには、文字起こしを読み返してまとめ直す手間がかかる。まとめ方も人によってばらつく

  • 文字起こしは講師一人ひとりの Google ドライブに保存されるため、全講師のドライブを見張る必要がある

  • 議事録が必要なのは一部のコースの面談だけで、それ以外は対象から外したい

  • Google ドライブの「変更があったら知らせる」仕組み(Watch)には、次のような癖がある

    • 通知の受け付けは最長 7 日で切れ、切れると何も届かなくなる
    • 同じ変更について通知が何度も届くことがある
    • 古い通知が後から届くことがある

作ったもの

議事録の自動作成

  1. 講師のドライブに新しい文字起こしが保存されると、Google から通知が届きます
  2. 通知が本物で、まだ処理していないものかを確かめます(後述)
  3. 文字起こしを読み出し、議事録の対象のコースかどうかを判定します
  4. LLM で決まった章立ての議事録を作ります
  5. Notion の議事録の一覧に、講師名・受講生名・日付・面談の開始時刻を付けて 1 ページ追加します
  6. Slack に完了をお知らせします

議事録は次の 7 章にそろえています。

章 中身
1. 基本情報 日時・講師・受講生
2. 受講生の現状 学習の進み具合、受講生の発言や気持ち、直近の活動
3. 課題・悩み 学習上の課題、案件に挑戦する不安、その他の悩み
4. 講師からのフィードバック 学習面の助言、案件獲得に向けた提案、心構えについて
5. 次回までのアクション 受講生のタスク・目標・予定
6. その他 補足
7. 面談サマリー 100〜200 文字の要約

自動で動く見回り

時刻(毎日) 処理 内容
13:00 通知の受け付けの更新 期限まで 24 時間を切った講師の分を、張り直します
13:30 健全性チェック 通知の受け付けの状態と直近 7 日の通知を調べ、レポートを作って Slack にリンクを送ります

どちらも GitHub Actions(プログラムを決まった時刻に動かすサービス)で動かし、急ぐときは画面から手動でも実行できます。

工夫したところ

LLM の出力を「決まった形」に固定する

  • LLM には自由に文章を書かせず、7 章それぞれの項目名と型を決めた形(JSON スキーマ)を渡し、その形以外では返せない設定(OpenAI の Structured Outputs の strict モード)で呼び出しています。返ってきた項目をそのまま Notion の見出しと段落に当てはめるので、章が抜けたり順番が入れ替わったりしません

  • 指示文では「文字起こしに無いことを補って書かない」「Markdown の記号(## や ** など)を使わない」を明記しました。Notion にはそのまま文字として入るため、記号が残ると読みにくくなるからです。あわせて、フィードバックの章は 3 つの小項目に分けて書く、次回までのアクションは 4 項目以上にする、など章ごとの書き方も決めています

  • 日時は LLM に任せず、文字起こしのファイル名に含まれる開始時刻と処理日から決めて上書きしています。文字起こしの中に日時が出てこなくても、確実な値が入ります

  • LLM の出力で受講生名が空のときは、ファイル名(カレンダーの予定名から作られる)の括弧の中から受講生名を取り出します。講師名も取れなければ「不明」と入れ、空欄のページができないようにしました

  • 議事録の対象かどうかは、ファイル名に「AI コース」の表記があるかで判定しています。予定名の付け方の決まりを資料にまとめ、判定は 1 か所だけに置きました。のちに新しい AI コースが増えたときも、表記の決まりを確かめたうえで判定を変えずに対応できました

  • 当初はノーコードの LLM ワークフローツール(Dify)で議事録を作っていましたが、途中でクラウドのサーバー側の関数(Supabase Edge Functions)に作り直しました。LLM の呼び出し・Notion への保存・Slack への通知をプログラムとして 1 か所にまとめ、手元から 1 件だけ実データで試せるスクリプトも用意しています

取りこぼしと二重処理を防ぐ

通知の受け取りから議事録の作成までに、確認を 3 段重ねています。

段 確認すること 防げること
1 通知が、その講師の今有効な受け付けから来たものか 古い受け付けから届いた通知を捨てる
2 通知の通し番号が、前に処理した番号より大きいか 重複して届いた通知、遅れて届いた古い通知を捨てる
3 そのファイルを先に処理し始めた処理がないか 同時に届いた通知で、同じ議事録が 2 つできるのを防ぐ
  • 3 段目は、LLM を呼ぶ前にデータベースの「処理済みファイル」の表へ 1 行書き込むことで「このファイルは自分が処理する」と先に押さえる方式です。ファイルと講師の組み合わせを主キー(同じ値を 2 回入れられない列)にしているので、同時に 2 つの処理が来ても、データベースが後の 1 つを必ず弾きます。実際に同時の通知で議事録が二重にできたことがあり、この方式に改めました

  • 途中で失敗したときは、この押さえを外します。次の通知や再投入で、もう一度処理できるようにするためです

  • 「どこまで読んだか」の目印(Google ドライブの変更履歴の続きを読むための値)は講師ごとにデータベースに保存し、次の通知では前回の続きから読みます

通知の受け付けを毎日張り直す

  • Google 側の状態を当てにせず、受け付けの期限はデータベースに記録した値を正としています。毎日の更新では、期限まで 24 時間を切った講師の分だけを張り直します

  • 受け付けの識別子(チャネル ID)は講師のメールアドレスから毎回同じものを作るようにしました。張り直しても識別子が変わらず、届いた通知がどの講師のものかも識別子から確実に分かります

  • 更新の処理が 2 つ同時に走らないよう、データベースで鍵を取ってから始めます。鍵は 30 分で自動的に外れるので、途中で止まっても次回の実行は妨げません

  • 張り直すときは、古い受け付けを止め(失敗したら 1 回やり直す)、通知の通し番号の記録を消してから新しく登録します。張り直すと Google 側の通し番号が小さい値から始まることがあり、古い記録が残っていると新しい通知を「古い」と判断して捨ててしまう不具合が起きたため、この手順を加えました

  • 更新の結果は「張り直し」「新規」「対象外」「失敗」の件数で出し、1 件でも失敗があればジョブ全体を失敗扱いにして気づけるようにしています

健全性チェックと知らせ方

  • 毎日、次の 4 つを機械的に数えています: 同じ講師に受け付けが複数ある/期限が切れている/識別子が決まりと違う/有効でない受け付けから通知が来ている

  • 数えた結果を LLM(OpenAI の gpt-4o-mini)に渡して、状況の要約と取るべき対応を日本語の文章にさせます。判定そのものはプログラムが行い、LLM には説明だけを任せています。元のデータはレポートの末尾にそのまま付けます

  • レポートは非公開の保存場所に日付ごとに保存し、パスワード付きのページで読めるようにしたうえで、Slack には件数とリンクだけを送ります。チェック自体が失敗したときも Slack にお知らせします

  • 議事録の作成で失敗したときは、「LLM の生成」「Notion への保存」「データベースの更新」「文字起こしの読み出し」など、どの段階で失敗したかを付けて Slack にお知らせします

復旧の手順を用意する

止まったときにすぐ戻せるよう、運用のためのスクリプトと手順書を用意しました。

困りごと 手順
議事録ができない 受け付けを全講師分まとめて張り直す(手動の強制更新)
文字起こしがあるのに議事録がない 指定した日時以降の新しいファイルを全講師分一覧し、未処理の文字起こしだけを再投入する
処理済みの印が残って再処理できない 1 件分の印を外してから、1 件だけ再実行する
  • 再投入も、通常の処理と同じ「先に押さえる → 作る → 記録する」の流れを通るので、再投入した分と通常の処理がぶつかっても二重にはなりません

  • 講師名の欄にメールアドレスが入った議事録が 6 件できたときは、該当の Notion ページを片付け、処理済みの印を外して作り直す一回限りのスクリプトで復旧しました。あわせて、講師名・受講生名が取れないときの扱いを決め直しています

テストと自動チェック

  • 通知のヘッダーの読み取り、識別子から講師を割り出す処理、文字起こしかどうかの判定、設定値の確認などを、自動テスト(Vitest)で確かめています。Google の API に実際につないで確かめるテストも用意しました

  • 変更を取り込むたびに、テストとビルドを GitHub Actions で自動で実行しています

← プロジェクト一覧へ戻る