いつもクリエイターズブログをご愛読いただいている皆様、初めまして。
業務管理部のkimと申します。
普段は デカメ―ル をはじめとした自社サービスのサポートや、総務・事務対応のほか
社内の情報セキュリティ管理も担当しております。
最近では、社内業務の効率化をDXで実現することにも取り組んでいます。
今回は、このDXの取り組みの一つとして、エンジニアではない私が生成AI技術を活用し
Web会議の議事録作成を自動化することができましたので、ご紹介させていただきます。
今回の目的
弊社では、毎朝Web会議(Google Meet)で朝礼を行い、議事録を作成しています。
しかし、議事録の作成と内容の要約作業に地味な手間がかかっており、課題となっていました。
そこで今回は、議事録の作成~要約~投稿まで全てAIにお任せしてしまおう!という目的になります。
必要な下準備や作業内容についても、ほぼ全てChatGPTに教えてもらいました。
使用ツール
1Google Meet:ふだんのWeb会議で使用。最近日本語対応したGeminiの「自動文字起こし」を活用します。
2Slack:ふだんの社内コミュニケーションで使用。議事録の投稿先となります。
3ChatGPT:GASスクリプトの作成、および議事録まとめ処理(API使用)を行います。
準備
Google Meet議事録データの仕様を確認
自動文字起こしをオンにした会議で、終了時に会議の主催者のGoogleドライブの「マイドライブ」に保存されます。
OpenAI APIでsecret keyを取得
取得した議事録データの内容を読み取り、ChatGPTにAPI経由で要約してもらいます。
※1回当たり 約 $0.05〜$0.10(7〜15円)程度のコストが発生します。
実行回数や文字数が多い場合は、料金に注意しましょう。
OpenAI Platformにログインし、 sk- から始まるAPI用のSecret Keyを作成して控えておきます。
※はじめて利用する場合は、クレジットカードなどの支払い方法の設定が必要です。
Slack botの作成とBot User OAuth Tokenの取得
Slack API画面にログインし、「Create New App」よりSlack botを新規作成します。
https://api.slack.com/appsbotが作成できたら、ワークスペースへのインストールを行います。
※「インストールするボットユーザーがありません」と表示される場合は以下のURLをご参照ください。
https://the-simple.jp/slack-nobotuser
「OAuth & Permissions」に移動し、「Bot Token Scopes」の「Add an OAuth Scope」に以下を設定します。
1channels:read
2chat:write.public
3chat:write
すると、OAuth Tokens に xoxb- から始まる「Bot User OAuth Token」が追加されるので、こちらを控えておきます。
Google Apps Scriptの実装
スクリプトファイルの作成
Googleドライブにログインし、右クリックメニューの「その他」から「Google Apps Script」を選択します。
使用するGoogle APIの設定
作成したGoogle Apps Scriptの編集画面の左横にある「サービス」の+ボタンから
Calendar、Drive、Slides のAPIをそれぞれ追加します。
スクリプトの内容(ChatGPT作成)
作成したGoogle Apps Scriptの「コード.gs」に以下を反映し、保存します。
ここで先ほど控えた OpenAI APIのsecret key(115行目)と、Slack botのBot User OAuth Token(179行目)を適用します。
1// === メイン関数(カレンダー連携+ChatGPT要約付き) ===
2function runDailyMorningReportByCalendar() {
3 try {
4 const event = getTodaysMorningMeetingEvent();
5 if (!event) throw new Error("今日の朝礼イベントが見つかりません");
6
7 const file = getMeetingDocFromDrive();
8 if (!file) throw new Error("マイドライブ内に本日作成された議事録ファイルが見つかりません");
9
10 const plainText = extractPlainText(file.getId());
11 const summary = generateSummaryByChatGPT(plainText); // ChatGPTで要約生成
12 postToSlackAsText(summary); // Slackに投稿
13
14 } catch (e) {
15 Logger.log("❌ 処理中にエラーが発生しました: " + e.message);
16 } finally {
17 createTriggerForNext(); // ← 成功/失敗に関わらず必ず実行される
18 }
19}
20
21// === 明日9:30にこの関数を再実行するトリガーを作成 ===
22function createTriggerForNext() {
23 // 既存の同名トリガーを削除
24 const triggers = ScriptApp.getProjectTriggers();
25 for (let trigger of triggers) {
26 if (trigger.getHandlerFunction() === "runDailyMorningReportByCalendar") {
27 ScriptApp.deleteTrigger(trigger);
28 Logger.log("🗑️ 古いトリガーを削除しました: " + trigger.getUniqueId());
29 }
30 }
31
32 // 新規トリガーを作成
33 const tomorrow = new Date();
34 tomorrow.setDate(tomorrow.getDate() + 1);
35 tomorrow.setHours(9, 30, 0, 0);
36
37 ScriptApp.newTrigger("runDailyMorningReportByCalendar")
38 .timeBased()
39 .at(tomorrow)
40 .create();
41 Logger.log("✅ 次回の9:30実行トリガーを作成しました");
42}
43
44// === 今日に開始する「全体朝礼」イベントを取得 ===
45function getTodaysMorningMeetingEvent() {
46 const calendarId = 'primary';
47 const today = new Date();
48 const start = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 9, 0, 0);
49 const end = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 9, 30, 0);
50
51 const events = Calendar.Events.list(calendarId, {
52 timeMin: start.toISOString(),
53 timeMax: end.toISOString(),
54 singleEvents: true,
55 maxResults: 5
56 }).items;
57
58 for (let event of events) {
59 if (event.summary.includes('全体朝礼')) {
60 return event;
61 }
62 }
63 return null;
64}
65
66// === マイドライブから本日作成された「Gemini」かつ「全体朝礼」かつファイル名に本日の日付を含むドキュメントを取得 ===
67function getMeetingDocFromDrive() {
68 const folderName = "Meet Recordings";
69 const folders = DriveApp.getFoldersByName(folderName);
70 if (!folders.hasNext()) throw new Error(`「${folderName}」という名前のフォルダが見つかりませんでした`);
71
72 const folder = folders.next();
73 const files = folder.getFilesByType(MimeType.GOOGLE_DOCS);
74 let latestFile = null;
75 const todayStrForDate = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy-MM-dd");
76 const todayStrForName = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd");
77
78 while (files.hasNext()) {
79 const file = files.next();
80 if (
81 file.getName().includes("Gemini") &&
82 file.getName().includes("全体朝礼") &&
83 file.getName().includes(todayStrForName)
84 ) {
85 const createdStr = Utilities.formatDate(file.getDateCreated(), "Asia/Tokyo", "yyyy-MM-dd");
86 if (createdStr === todayStrForDate) {
87 if (!latestFile || file.getLastUpdated().getTime() > latestFile.getLastUpdated().getTime()) {
88 latestFile = file;
89 }
90 }
91 }
92 }
93
94 if (!latestFile) throw new Error("本日作成された『Gemini』かつ『全体朝礼』が含まれる議事録が見つかりませんでした");
95
96 return latestFile;
97}
98
99// === 議事録本文を抽出 ===
100function extractPlainText(docId) {
101 const file = Drive.Files.get(docId, { fields: 'exportLinks' });
102 const exportUrl = file.exportLinks['text/plain'];
103
104 const response = UrlFetchApp.fetch(exportUrl, {
105 headers: {
106 Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
107 }
108 });
109
110 return response.getContentText();
111}
112
113// === ChatGPTで要約を生成(gpt-4-turbo使用) ===
114function generateSummaryByChatGPT(inputText) {
115 const apiKey = 'sk-***'; // ← OpenAI APIで作成したSecret Keyをここに設定
116 const url = "https://api.openai.com/v1/chat/completions";
117 const today = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd");
118
119 const prompt = `以下の文字起こしを元に、Slack投稿向けの「本日の朝礼要約(${today})」形式でカテゴリ別に要約してください。
120
121🔽 出力フォーマットは以下のように統一してください:
122・各カテゴリに「【カテゴリ名】」をタイトルとして明記すること
123・カテゴリは以下の順で並べてください:
124 1. 勤怠状況の確認
125 2. 全体共有
126 3. リリース報告
127 4. セキュリティニュースの共有
128 5. LT発表
129 6. その他
130
131🔽 注意点:
132・「Gemini」や「文字起こし」などの自動処理に関するメモや注意書きは含めないでください
133・実際の発表内容を基に、箇条書きでわかりやすく表現してください
134・「その他」の部分では、LTの感想を交えたユーモアある一言を添えてください
135・その他、任意の追加指示を記載
136
137--- 以下、文字起こし本文 ---
138
139${inputText}`;
140
141 const payload = {
142 model: "gpt-4-turbo",
143 messages: [
144 {
145 role: "system",
146 content: "あなたはSlack投稿形式の朝礼要約を作成するアシスタントです。"
147 },
148 {
149 role: "user",
150 content: prompt
151 }
152 ],
153 temperature: 0.3
154 };
155
156 const options = {
157 method: "post",
158 headers: {
159 Authorization: "Bearer " + apiKey,
160 "Content-Type": "application/json"
161 },
162 payload: JSON.stringify(payload),
163 muteHttpExceptions: true
164 };
165
166 const response = UrlFetchApp.fetch(url, options);
167 const result = JSON.parse(response.getContentText());
168
169 if (!result.choices || result.choices.length === 0) {
170 Logger.log(response.getContentText());
171 throw new Error("❌ ChatGPTから要約が得られませんでした");
172 }
173
174 return result.choices[0].message.content;
175}
176
177// === Slackにテキスト形式で投稿 ===
178function postToSlackAsText(summary) {
179 const slackToken = 'xoxb-***'; // Slack botの「Bot User OAuth Token」をここに設定
180 const channelId = 'C123456789'; // メッセージを投稿するSlackチャンネルのID
181
182 const today = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd");
183 const header = `📌 *本日の朝礼要約(${today})*`;
184
185 const payload = {
186 channel: channelId,
187 text: `${header}\n\n\`\`\`\n${summary}\n\`\`\``
188 };
189
190 const response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", {
191 method: "post",
192 headers: {
193 Authorization: "Bearer " + slackToken,
194 "Content-Type": "application/json"
195 },
196 payload: JSON.stringify(payload),
197 muteHttpExceptions: false
198 });
199
200 Logger.log("📡 Slack postMessage response: " + response.getContentText());
201}
202
※2025/5/13 追記:createTriggerForNext 関数において、古いトリガー設定を削除する処理が不足していたため追記しました。(23~30行目)
スクリプトの実行:初回操作
実際の運用を始める前に、Google Apps Scriptの編集画面で
「runDailyMorningReportByCalendar」関数を「▶ 実行」で手動実行します。
これで明日以降、同じ時間に議事録の作成が行われるようになります。
(このスクリプトでは 09:20 を指定しています)
なお、Google Workspaceの設定によっては下記のようなエラーが表示される場合があります。
この場合、「エラーの詳細」をクリックすると表示される内容のうち
~.apps.googleusercontent.com (画像の黄色下線部分)を
以下手順の「クライアントID」に 置き換えて設定することで、利用可能となります。
https://support.itmc.i.moneyforward.com/l/ja/article/x6sms6m4ja-google-oauth#2197951945結果
議事録をSlackに投稿できました!🎉
実際の会議の内容も適切に要約されており、数日使っていますが十分な実用性を感じています。
しかし、
今日のLTはまさに「誰得」の極みでしたね!
といった、失礼な言動もチラホラみられました。まだ調整の余地がありますね…。
ChatGPTへ指示する内容はGASスクリプトの const prompt 部分(108行目)で変更できるので、ご参考ください!
注意:Google Meetの文字起こしについて
議事録を残したい会議では、必ず文字起こしをオンにしましょう(1敗)。
会議が始まる前に、Meetの画面右上にある鉛筆マークからオンにしておきます。
なお、定期的な会議では、文字起こしボタンの押し忘れを防止するために、あらかじめGoogleカレンダーで文字起こしを設定しておくことも可能です。
しかし、この機能を使うとデフォルトで会議の使用言語が英語になってしまい、議事録データも英語になってしまうバグが起きるようです。※2025/4/22 現在
この場合も議事録データは残りますが、日本語を空耳で英語にしたような使い物にならない内容になってしまいます。
Googleのサポートの方に問い合わせたところ、この不具合は 2025/4/23 から2週間ほどかけて解決される見通しとのことです。
https://workspaceupdates.googleblog.com/2025/04/updates-for-configuring-your-preferred-language-take-notes-for-me-recorded-captions-meeting-transcripts-google-meet.html
これが完全自動化の最後のピースになるので、早めに解決してくれることを祈ります…。
※2025/5/8 追記:Googleカレンダーの設定画面で会議の使用言語が選択できるようになりました!
さいごに
いかがでしたでしょうか。
私自身はプログラミングの知識が豊富とは言えませんが、生成AIの力を借りることで議事録作成の自動化を実現できました。
最も苦労したのは、AIへの仕様や要望の伝え方、動作検証、そして修正のプロセスでした。
この経験を通じて、AIへ適切な指示を出す力 = プロンプト力 の重要性を実感しました。
同じような課題をお持ちの方のお役に立てれば幸いです!