生成AIと音声認識技術を使って会議を要約するアプリを作ってみた

## はじめに
昨今、大規模言語モデル(LLM)を使った業務効率化が各所で行われています。

今回はTeamsやzoomの録画で出力される`mp4`ファイルから会議を要約するアプリケーションを爆速で作ってみようと思います。

:::note 
末尾にサンプルコードが載っているので、簡単に試すことができます。
:::

👇 完成系はこちら

![スクリーンショット 2024-06-10 22.20.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/180832d9-68c0-8022-b7c4-d6dbe6af9b83.png)



## ステップ1: 動画から文字起こしを行う
まずは会議動画の文字起こしを行います。文字起こしでは今回Reason Speechを使っていきます。

👇 Reason Speechはこちら

https://research.reazon.jp/projects/ReazonSpeech/index.html

まず以下のように環境構築を行います。

```bash 
% sudo apt install ffmpeg
```

```bash 
(venv)% pip install Cython moviepy
(venv)% git clone https://github.com/reazon-research/ReazonSpeech
(venv)% pip install ReazonSpeech/pkg/nemo-asr
```

まずは動画形式(mp4)のデータを音声データ形式に変換し、次に音声データを文字起こしします。

上記のサンプルプログラムは以下です。

```python 
from reazonspeech.nemo.asr import transcribe, audio_from_path, load_model
from moviepy.editor import VideoFileClip
import tempfile

def transcribe_video(input_video):
    """
    input_video:mp4形式の動画データ
    """
    with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_video_file:
        temp_video_file.write(input_video.read())
        temp_video_file.seek(0)
        video_clip = VideoFileClip(temp_video_file.name)
        with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_audio_file:
            video_clip.audio.write_audiofile(temp_audio_file.name, codec='mp3')
            audio = audio_from_path(temp_audio_file.name)
            ret = transcribe(model, audio) #ret.textに文字起こしのテキストが格納される
    return ret.text
```

文字起こしの性能を確認してみたい方は公式のクイックスタートを確認してください。

https://research.reazon.jp/projects/ReazonSpeech/quickstart.html

## ステップ2: 文字起こしテキストから議事録作成
文字起こし済みのテキストから会議の要約を作成するにあたって、今回はClaudeを使用します。

まずAPIキーを以下のように環境変数に設定した後、`anthropic`ライブラリをインストールします。
```bash 
% export ANTHROPIC_API_KEY={APIキー情報}
```

```bash 
　(venv)% pip install anthropic
```

今回はプロンプトにアジェンダや参加者などの事前情報は入力せずに、会議の要約を作ってもらいます。

サンプルコードは以下です。システムプロンプトは公式を参考に作成します。

https://docs.anthropic.com/en/prompt-library/meeting-scribe

```python 
import anthropic
import os 

def generate_mtg_docs(meeting_log):
    client = anthropic.Anthropic(
        api_key=os.environ.get("ANTHROPIC_API_KEY"),
    )
    
    message = client.messages.create(
        model="claude-3-opus-20240229", #安く使いたい人向け"claude-3-haiku-20240307"
        max_tokens=4000,
        temperature=0.5,
        system="あなたの仕事は、提供された会議データを確認し、ミーティング中に特定の個人または部門に割り当てられたTODOに焦点を当てながら、重要な情報をとらえた簡潔な要約を作成することです。明確で専門的な言葉を使用し、見出し、小見出し、箇条書きなどの適切な書式を使用して、要約を論理的に整理します。要約が理解しやすく、会議の内容の包括的かつ簡潔な概要を提供するようにし、特に各アクション項目の責任者を明確に示すことに重点を置く。",
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": meeting_log
                    }
                ]
            }
        ]
    )
    return message.content[0].text
```

:::note warn
文字起こしテキストのトークン数>コンテキスト長となると、入力が途切れてしまうので分割する必要があります。

例えば文章が途中で切れないように`.`で`split`してからチャンク分けすると良いでしょう。
:::

## 3.アプリケーション化
今回は簡単のために`streamlit`でUIを作ります。

```bash 
(venv)% pip install streamlit
```

`streamlit`のデフォルトでは200MBまでしか動画ファイルをアップデートできません。
そのため`.streamlit/config.toml`ファイルを作成し、制限を1GBまでとしておきます。

```config.toml
[server]
maxUploadSize = 1000
```

あとはお好みですが、今回は以下3つの要件を満たすアプリを作ってみます。

- 動画ファイルをアップロードできるようにする
- 進捗確認のためにプログレスバーを設定&都度出力する
- 会議の要約をダウンロードできるようにする

サンプルコードは以下です。

```app.py 
import streamlit as st
from reazonspeech.nemo.asr import transcribe, audio_from_path, load_model
from moviepy.editor import VideoFileClip
import tempfile
import anthropic
import os 


def draw_progress_bar(status_text, progress_bar, percent):
    status_text.text(f'Progress: {percent}%')
    progress_bar.progress(percent)

def transcribe_video_generate_mtg_docs(input_video):
    st.write("---")
    st.write("アップロードされた動画ファイル名:", input_video.name)
    st.write("---")

    status_text = st.empty()
    status_text.text('Progress: 0%')
    progress_bar = st.progress(0)
    st.success("会議の要約生成中...")

    model = load_model()

    st.success("1.音声認識モデルのロード完了", icon="✅")
    draw_progress_bar(status_text, progress_bar, 25)

    with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_video_file:
        temp_video_file.write(input_video.read())
        temp_video_file.seek(0)
        
        video_clip = VideoFileClip(temp_video_file.name)
        with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_audio_file:
            video_clip.audio.write_audiofile(temp_audio_file.name, codec='mp3')
            audio = audio_from_path(temp_audio_file.name)

            st.success("2.動画データから音声データへ変換完了", icon="✅")
            draw_progress_bar(status_text, progress_bar, 50)
            ret = transcribe(model, audio)
            st.success("3.音声データから文字起こし完了", icon="✅")
            draw_progress_bar(status_text, progress_bar, 75)

    client = anthropic.Anthropic(
        api_key=os.environ.get("ANTHROPIC_API_KEY"),
    )
    message = client.messages.create(
        model="claude-3-opus-20240229",
        max_tokens=4000,
        temperature=0.5,
        system="あなたの仕事は、提供された会議データを確認し、ミーティング中に特定の個人または部門に割り当てられたTODOに焦点を当てながら、重要な情報をとらえた簡潔な要約を作成することです。明確で専門的な言葉を使用し、見出し、小見出し、箇条書きなどの適切な書式を使用して、要約を論理的に整理します。要約が理解しやすく、会議の内容の包括的かつ簡潔な概要を提供するようにし、特に各アクション項目の責任者を明確に示すことに重点を置く。",
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": ret.text
                    }
                ]
            }
        ]
    )

    draw_progress_bar(status_text, progress_bar, 100)
    st.success("4.会議の要約作成完了", icon="✅")

    st.download_button(
        label="会議の要約ダウンロード",
        data=message.content[0].text,
        file_name='mtg_docs.txt',
        mime='text/plain'
    )

st.title("会議要約アプリ(仮)")

input_video = st.file_uploader("会議の動画をアップロードしてください", type=["mp4", "wav"])

if st.button("会議の要約開始"):
    if input_video:
        transcribe_video_generate_mtg_docs(input_video)
    else:
        st.error("会議の動画をアップロードしてください")
```

実行方法は以下です。
```python 
(venv)% strealit run app.py
```

サーバーが立ち上がったら完成です！
動画をアップロードして会議の要約を試してみてください。

実務では追加の要件があるかと思いますが、割といい感じです。

:::note 
ちなみに文字起こし済みのテキストが800文字程度の場合、`claude-3-opus-20240229"`を使っても1回あたり$0.05=約8円ほどのコスト感です。

会議1回あたり3,000文字、週に2回実施として概算しても毎月約240円ほどのコストです。Opusではなく、HaikuやSonnetを使えば、それぞれ約4円、約50円と格段にコストが抑えられるので低コストで運用可能です。
:::

## おわりに
いかがだったでしょうか。

割と簡単に会議の要約アプリを作成できるので、プロンプトやフローを改良してより良い議事録作成アプリを作ってみてください。