Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

生成AIを使って、自動で議事録を生成した話

はじめに

こんにちは、データプラットフォームチームのマイン(id:manhnguyen1998)です。

近年、生成AIが急速に注目を集めています。皆さんの企業ではすでに活用されていますか? それとも、現在検討中でしょうか? あるいは、すでにプロダクトをリリースされた方もいるかもしれません。

Classiでもこの流れに乗り、数ヶ月前から生成AIを活用したプロダクト開発に取り組み、生産性向上を目的としたツールの開発を進めています。本記事では、データプラットフォームチームが開発した、AIを活用して会議の議事録を自動生成するツール「Turing」について紹介します。

tech.classi.jp

背景

Classiでは音声会議にGoogle Meetを利用しており、ほとんどの会議の議事録は手動でesaやConfluenceに記録されています。しかし、手動での記録には以下のような課題があります。

  • 100%の内容を記録するのは難しい
  • 記録ミスが発生する可能性がある
  • 記録しながら会議に参加するのが負担になる

これらの課題を解決するために、Turingを活用して自動的に情報を取得し、参加者がより会議に集中できる環境を作ることを目指しました。

AIを活用した議事録の自動生成

活用方法

Classiでは主にSlackを用いてコミュニケーションを行っています。そのため、Slackを中心とした運用が最も利便性が高いと考え、以下の2つの方法でTuringを活用できるようにしました。

  • Slack Workflowを利用する方法
    • ユースケース:
      • 適当に動画のリンクを入力して議事録を作成したい場合
      • まだ自動化設定できない場合
  • 自動化した方法
    • 会議が終わった後、自動で検知して許可があったら議事録を作成

Slack Workflowを利用する方法

SlackのWorkflow機能を活用し、簡単に議事録を作成できる仕組みを構築しました。 Workflowのイメージは以下です

Workflow開始
Slack Workflowのイメージ

利用の流れは以下の通りです。

  • Google Meetの録画機能を利用し、会議を録画
  • 記録した会議動画の編集権限をBot Userに付与Workflowを起動し、会議動画のGoogle Driveリンクを入力
  • モードを選択
    • 会議の種類に応じて異なるプロンプトをAIに適用
  • その他必要情報を入力して、「Submit」ボタンを押す
  • WorkflowがSlack Appにリクエストを送信
  • 議事録の生成が完了すると、リンクをSlackに通知

自動化した方法

Slack Workflowは便利ですが、毎回手動で情報を入力するのが手間となるため、一部のフローを自動化してもう一つの方法を開発しました。

会議後に自動送信されたメッセージの例

利用の流れは以下の通りです。

  • 会議開始前にBot Userを共同主催者として追加
  • 会議終了後、参加者宛にメンションされたSlack通知が届く
  • デフォルトの設定を確認し、ユーザーは「修正」または「そのまま作成」を選択
  • 議事録の生成が完了すると、リンクをSlackに通知

ここで「なぜユーザーのアクションが必要なのか? そのまま作成すればよいのでは?」という疑問があるかもしれません。意図としては、取得した情報と実際の内容が異なる可能性があるため、作成前に確認を行うことで精度を向上させることを目的としています。また、Bot Userを追加すると自動で議事録が作成されることを望まないユーザーもいるため、事前の確認を行うことで柔軟に対応できるようにしています。

システム構成

このシステムを実現するため、以下のアーキテクチャを構築しました。

アーキテクチャ

Slack Workflowを利用する場合

  1. WorkflowがSlack Appにメンションイベントを送信
  2. Cloud Runがリクエストを受信
  3. Google Driveの認証を行い、動画をダウンロード
  4. 動画を処理し、AI(Gemini)に入力
  5. AIの出力を基に議事録を作成し、esa/Confluence/Google Docsに保存
  6. 作成した議事録のリンクをSlackに通知

自動化を利用する場合

  1. Bot Userのメールボックスにメールが来たとき、自動でラベルをつける。
    • これは処理された/処理されてないメールを判定できるようにするためです
  2. 定期的にGoogle Apps Scriptで監視 (15分)
  3. 記録した動画のリンクを取得し、Webhook経由でSlackにメッセージを送る
  4. ユーザーが設定を確認し、リクエストを送信
  5. Cloud Runが処理を実行し、議事録を作成
  6. Slackにリンクを通知

技術的な工夫

Geminiには以下の制約があります。

  • 長時間の動画では精度が低下し、処理時間が長くなる
  • トークン数の制限があり、長い動画は処理できない

これらの問題を解決するために、以下の対策を講じました。

動画を10分ごとに分割し、並列処理を行うことで精度と処理時間を改善

長時間の動画では、AIが途中までしか理解できず、トークン制限に達すると生成が中断され、議事録のトランスクリプトが途中で途切れてしまう問題が発生していました。その結果、サマリーの生成もうまくいかないことが多くありました。しかし、動画を10分単位に分割し、それぞれを処理することで、AIが各部分をより深く理解できるようになり、結果として正確なサマリーを生成できるようになりました。

また、処理時間の改善にもつながりました。以前は1時間程度の動画を処理するのに約30分かかっており、非常に不便でした。しかし、動画を分割し、並列でAIに処理させることで、処理時間を約5分弱まで短縮することができました。

リクエストの多重送信を防ぐため、Backoffリトライを採用

動画を分割し並列でAIにリクエストを送ることで、一部のリクエストがRatelimitに引っかかり、時折500系エラーが発生する問題がありました。この問題を解決するために、BackoffリトライとSemaphoreを導入しました。

同時に最大4リクエストまで送信し、500系エラーが発生した場合は30秒間のBackoffリトライを行うように設定しました。その結果、Gemini AIのサーバー側のエラーは一度も発生せず、安定した処理が実現できました。

Semaphoreの例

MAX_CONCURRENT_REQUESTS = 4
semaphore = threading.Semaphore(MAX_CONCURRENT_REQUESTS)

def generate_transcript_with_limit(
    model: GenerativeModel, minutes_prompt: str, gcs_uri: str
):
    with semaphore:
        return generate_transcript_from_uri(model, minutes_prompt, gcs_uri)

Backoff リトライの例

def generate_transcript_from_uri(
    model: GenerativeModel,
    minutes_prompt: str,
    gcs_uri: str,
    max_retries: int = 5,
    base_delay: int = 30,
):
    attempt = 0
    while attempt < max_retries:
        try:
            video_part = Part.from_uri(gcs_uri, mime_type="video/mp4")
            response = model.generate_content([minutes_prompt, video_part])
            return response.text
        except Exception as e:
            attempt += 1
            print(f"Error processing {gcs_uri} (attempt {attempt}): {e}")
            if attempt >= max_retries:
                raise Exception(
                    f"Failed to generate transcript for {gcs_uri} after {max_retries} attempts: {e}"
                )

            # Exponential backoff starting from 30 seconds, with added jitter between 5 and 10 seconds
            backoff_time = base_delay * (1.5**attempt) + random.uniform(5, 10)
            print(f"Retrying in {backoff_time:.2f} seconds...")
            time.sleep(backoff_time)

結果

最終的な出力

生成された議事録は以下のような形式になります。

結果のイメージ

サマリーで全体の内容を把握可能

サマリーの例
サマリーの例

誰が何を話したかが記録され、情報の正確性を担保(白塗り部分には参加者の名前が入ります)

Transcriptの例

利用状況とユーザーの声

リリース以降、月平均13件の議事録が作成され、合計39件の議事録が生成されました。一見すると少なく感じるかもしれませんが、詳しく見ると状況は異なります。特に、自動化機能の改善をリリースしたのは1ヶ月前で、その後の1ヶ月間で合計30件の議事録が作成されました。これはほぼ毎日利用されている計算になり、利用頻度が大きく向上していることが分かります。

ユーザーの声

今後の展望

この記事を執筆している時点で、AIモデルは驚異的なスピードで進化を続けています。そのため、今後の議事録作成ツールの未来について、いくつかの展望を考えてみます。

まず、DeepSeekなどのリリースにより、AI市場の競争は本格化してきています。今後、さらに多くのAIモデルが登場し、精度の向上やコストの低減が進むことが予想されます。こうした流れの中で、議事録作成ツールもより高度な機能を求められるでしょう。

現在のTuringは、参加者リストをもとにプロンプトを生成していますが、情報の精度には限界があります。より正確な議事録を作成するためには、会議の詳細情報など、より多くの要素をプロンプトに組み込む必要があり、それによって精度向上が期待できます。

また、将来的にはGoogle Meetにも議事録生成機能が搭載される予定です。現時点では日本語には対応していませんが、今後リリースされた場合、Turingの役割や存続について再考する必要があるかもしれません。Turingが今後どのように進化していくのかは不透明ですが、少なくとも社内ツールとして有益に活用され、AIの認識を高めることができた点においては、一定の成果を達成できたと考えています。

まとめ

Turingは、Classiの社内において会議の効率化に貢献し、生成AIの有用性を示しました。今後も技術の進化に合わせて、より精度の高い議事録作成ツールへと発展させていきたいと考えています。

© 2020 Classi Corp.