Zenn
📹

【プロンプトも公開】Zoom 会議が自動で議事録に!文字起こし × 生成 AI で業務大幅削減

2025/02/12に公開
7

こんにちは、株式会社キカガク CTO 室の高橋です。
AI 活用推進をはじめとした、技術による課題解決や業務効率化に取り組んでいます。

Zoom には、録画を行うと文字起こしテキストを出力する機能があります。
その存在は知ってはいたのですが、ぱっと見た感じ「あぁー」とか「うん」といったフィラーが多く入っており、そのまま読むには難しいものでした。

一方で、会議内容を他者に共有するために議事録を記録するのが大変だったり、録画を共有しても視聴するのが大変であるという課題がありました。

そういった課題を解決するべく、Zoom の文字起こし作成をフックに、生成 AI を活用して要約を作成するワークフローを社内運用しているので、その仕組みについて紹介いたします!

出力イメージ

Zoom で録画し、文字起こしテキストが自動的に以下画像のように指定したフォーマットで Notion に出力されるようにしています。

出力イメージ

フローのイメージ

議事録まわりの手間が大幅削減

このワークフローの導入により、以下の効果が得られました。

  1. 議事録作成工数の削減
    自動化により、従来必要だった手動での記録作業が削減され、業務効率が大幅に向上しました。

  2. 情報の正確性と共有の円滑化
    タイムスタンプ付きの詳細な記録と生成 AI による要約で、会議内容が漏れなく整理され、社内での情報共有がスムーズになりました。

  3. 将来的な活用
    自動生成された議事録は RAG (Retrieval Augmented Generation) としても利用でき、ナレッジベースの拡充や AI によるさらなる分析にも応用可能です。

技術的解決策:AI と自動化で業務効率化を実現

今回のアプローチでは、以下の技術要素を組み合わせています。

Zoom Webhook 連携

Zoom ミーティング終了時に生成される文字起こしデータを Webhook で受け取り、処理を開始します。

Event Types は Recording Transcript files have completed です。

Zoom Webhook

テキストの前処理と変換

取得した VTT 形式の文字起こしファイルをタイムスタンプ付きのテキストへ変換し、発言ごとに正確な話者情報と時間情報を付与します。

生成 AI による分類と要約

会議内容の全体像を判断し、「smallTalk」などの対象外カテゴリーであれば処理を中止。それ以外の場合は、生成 AI を活用してマークダウン形式の議事録を自動生成します。

Notion への自動出力

作成した議事録を Notion API 経由で社内共有のデータベースに出力し、情報の一元管理を実現します。

このワークフローにより、議事録作成の手間が大幅に削減され、より正確かつ迅速な情報共有が可能となりました。

実装詳細

ここでは、各処理の実装例を解説します。今回のワークフローは Hono で実装しており、Cloud Run にデプロイしています。

Zoom文字起こしデータの取得と前処理

Zoom の Webhook を利用して、文字起こしファイルを取得する部分です。

// Webhook で受信したリクエストから payload とダウンロード用トークンを取得
const body = await req.json();
const payload = body?.payload;
const downloadToken = body?.download_token;

// 文字起こしファイルを探してダウンロード
const recordingFiles = payload.object?.recording_files;
const transcriptFile = recordingFiles.find(
  (file) => file.file_type === "TRANSCRIPT"
);

const transcriptResponse = await axios.get(transcriptFile.download_url, {
  headers: {
    Authorization: `Bearer ${downloadToken}`,
  },
  responseType: "arraybuffer",
});

さらに、取得した VTT 形式のデータをタイムスタンプ付きテキストに変換する関数は以下の通りです。

const convertVttToTextWithTimestamps = (vttData: Buffer): string => {
  const vttString = vttData.toString("utf-8");
  const lines = vttString.split("\n");

  let result: string[] = [];
  let currentTimestamp: string | null = null;

  for (const line of lines) {
    if (
      line.match(
        /^[0-9]+:[0-9]+:[0-9]+\.[0-9]+ --> [0-9]+:[0-9]+:[0-9]+\.[0-9]+/
      )
    ) {
      currentTimestamp = line;
    } else if (line.trim() !== "" && currentTimestamp) {
      result.push(`[${currentTimestamp}] ${line.trim()}`);
      currentTimestamp = null;
    }
  }

  return result.join("\n");
};

議事録の分類

会議の内容を解析し、議事録作成対象かどうかを判断するため、まずカテゴリ分類を実施します。
例えば、会話が単なる雑談(smallTalk)であれば、要約処理を中止する仕組みです。(この記事では Gemini を使用した実装例で紹介しています)

export const generateClassifyMeetingPrompt = (text: string) => {
  const systemPrompt = `あなたは会議の文字起こしデータを高精度で分類する専門AIです。
以下の分類基準に従って、入力される会議テキストを分析してください。

# 分類カテゴリーと判断基準

## smallTalk
- 業務に直接関係のない雑談
- キーワード例: 週末の予定、天気、食事

## internalMeeting
- プロジェクト進捗や課題の共有
- キーワード例: タスク、進捗、スケジュール

## private
- 個人的な相談や評価
- キーワード例: 相談、キャリア、評価

## other
- 上記に分類できない場合

# 出力形式
{
  "category": "選択したカテゴリー",
  "reason": "選択理由"
}

文字起こしは以下です:
${text}
`;
  return systemPrompt;
};

const classifyMeetingText = async (plainText: string) => {
  const systemPrompt = generateClassifyMeetingPrompt(plainText);
  const result = await model.generateContent(systemPrompt);
  const responseText = await result.response?.text();
  const { category, reason } = JSON.parse(responseText);
  return { category, reason };
};

const { category, reason } = await classifyMeetingText(plainText);

const shouldProcess = !["smallTalk", "private", "other"].includes(category);

if (!shouldProcess) {
  console.log("処理を中止します。理由:", reason);
  return { category, reason };
}

生成 AI による要約

処理を継続する場合、生成 AI でマークダウン形式の議事録を生成します。

const generateGlobalSummaryPrompt = (fullText: string, date: string): string => {
  return `
以下は会議の不完全な文字起こしです。
あなたの仕事は、この会議の議事録を明確なMarkdown形式でまとめることです。

## 全体の概要
- ミーティングの目的: [議論の主題、もしくは「不明」]
- 参加者: [参加者名または「不明」]
- 日時: ${date}
- 場所: [オンラインまたは「不明」]

## サマリ
要約のガイドライン:
- 議論の流れを論理的に整理して記載
- 重要な決定事項や懸念事項を優先的に含める
- 技術的な詳細は正確に記録
- 数値や具体的な指標は必ず含める
- 各項目は2-3文程度で具体的に記述

## 決まったこと
記載基準:
- 明確な合意が得られた事項
- 承認された計画や方針
- リソース配分に関する決定
- スケジュールの確定事項
- 技術的な選択や方向性
※ 暫定的な決定は「暫定的な決定事項:」と明記

## ネクストアクション
記載要件:
- タスクの具体的な内容
- 担当者(名前または役割)
- 期限または目標時期
- 優先順位(高/中/低)
- 依存関係がある場合はその旨を明記
※ 重要度に応じて並び替えて記載
※ タスクがない場合はセクションを省略

# 解析の注意点
- 略語や専門用語は可能な限り正式名称に展開
- 不明確な表現は文脈から補完
- 個人的な雑談は除外
- 重要な数値や指標は明確に記録
- 議論の背景や意図も可能な限り補足

以下が会議の文字起こしです:
${fullText}

以上の形式で議事録を作成してください。
`;
};

const summarizeWholeTranscript = async (plainText: string, date: string) => {
  const prompt = generateGlobalSummaryPrompt(plainText, date);
  const result = await model.generateContent(prompt);
  const responseText = await result.response?.text();
  return responseText || "";
};

const globalSummaryMarkdown = await summarizeWholeTranscript(plainText, payload.object?.start_time);

発言タイムラインの出力

文字起こしテキストを一定の長さで区切り、発言タイムラインを出力します。

const CHUNK_SIZE = 4000;
const splitTextIntoChunks = (text: string): string[] => {
  const chunks: string[] = [];
  const lines = text.split("\n");
  let currentChunk = "";

  for (const line of lines) {
    if ((currentChunk + line).length > CHUNK_SIZE) {
      if (currentChunk) {
        chunks.push(currentChunk.trim());
        currentChunk = "";
      }
      if (line.length > CHUNK_SIZE) {
        const words = line.split(" ");
        let tempChunk = "";
        for (const word of words) {
          if ((tempChunk + " " + word).length > CHUNK_SIZE) {
            chunks.push(tempChunk.trim());
            tempChunk = word;
          } else {
            tempChunk += (tempChunk ? " " : "") + word;
          }
        }
        if (tempChunk) {
          currentChunk = tempChunk;
        }
      } else {
        currentChunk = line;
      }
    } else {
      currentChunk += (currentChunk ? "\n" : "") + line;
    }
  }
  if (currentChunk) {
    chunks.push(currentChunk.trim());
  }
  return chunks;
};

チャンクごとの発言タイムライン抽出のためのプロンプトを用意しています。

export const generateTimelinePrompt = (chunkText: string): string => {
  return `
以下は会議文字起こしの一部です。
このチャンクから判別できるトピックと、その発言タイムラインをMarkdown形式で出力してください。

## トピックごとの発言タイムライン
### [トピック名]:開始時刻-終了時刻
- [HH:MM] [発話者]: [発言内容の要約]

以下がチャンク文字起こしです:
${chunkText}

上記の形式で出力してください。
`;
};

この段階で、要約テキストと発言タイムラインがマークダウン形式で作成されているので、それらを Notion に送信します。

工夫したポイント

  • システムプロンプト調整
    要約作成や発言タイムラインのシステムプロンプトを試行錯誤し、アウトプットが期待通りになるよう工夫しました。

  • 発言タイムラインのチャンク処理
    最初は文字起こしテキスト全体から一度に出力していましたが、安定した出力のためにチャンク処理を実施しました。
    (文脈を保つため、前の複数のチャンクも入力しています。)

  • モデルの選定
    利用する生成 AI のモデルごとに出力が変わるため、各モデルの特徴を理解する良い機会となりました。

今後の展望

さらなる精度向上

分類要約のプロンプト改善により、より精度の高い議事録生成を目指します。
また、カテゴリに合わせた要約フォーマットの変更も検討しています。

多様な連携先の拡大

Notion 以外の社内ツールや外部サービスとの連携を検討し、情報の一元管理活用の幅を広げます。

情報のアクセス性向上

過去の議事録にも容易に検索アクセスできる仕組みの構築を検討しています。

まとめ

Zoom の文字起こしを起点とし、生成 AI による自動要約と Notion への自動出力で、議事録作成の負担を劇的に軽減できました。

株式会社キカガクでは、社内外の AI 活用を推進するべく日々取り組んでいます。
この記事が参考になれば嬉しいです!

キカガク CTO 室とは?

株式会社キカガクの CTO 室は、生成 AI の活用と業務効率化の推進を担う中核部門です。
社内外の課題解決に向けた技術戦略を実行するため、以下の 2 つのチームで構成されています。

AI/LLM チーム

高度な自然言語処理や生成 AI 技術を駆使し、実用的なソリューションの開発・運用を担当しています。

Corporate ITチーム

社内 IT インフラの整備と、業務システムの統合・最適化を推進し、全社的なデジタルトランスフォーメーションを実現します。

エンジニア募集中!

株式会社キカガクは、教育を軸に人材領域で企業の DX を支援することで、未来の可能性を切り拓いています。
最先端の技術を駆使しながら、社会にインパクトを与えるプロジェクトに挑戦できる情熱あるエンジニアを募集しています!

求める人物像

  • 技術に情熱がある方
    最新の AI/LLM 技術、クラウド技術、Web 開発などに興味を持ち、常に学び続ける姿勢のある方
  • チャレンジ精神旺盛な方
    新しいアイデアや技術に積極的に取り組み、実装から改善まで自ら動ける方
  • チームプレイヤー
    多様なメンバーと協力し、課題解決に努められる方

ここが魅力!

  • 最新技術に触れられる環境
    生成 AI、自然言語処理、クラウドプラットフォームなど、業界最先端の技術に日常的に関われます。
  • 自律性と成長を促す文化
    自らのアイデアを形にできる自由な環境で、キャリアアップを支援します。
  • 社会にインパクトを与えるプロジェクト
    教育と人材の領域で企業の DX を推進するプロジェクトに参加し、社会に貢献できます。

応募方法

ご興味のある方は、ぜひ 採用情報ページ をご覧ください。
話を聞いてみたいという方も、カジュアル面談の応募をお待ちしております!

7
株式会社キカガク

Discussion

ログインするとコメントできます