AIでプレゼン内容をフィードバックするアプリ実装に挑戦

## はじめに
本記事では、生成AIを活用したプレゼン支援ツール「AIでプレゼン内容をフィードバックするアプリ」の実装についてご紹介します。このアプリは、作成された報告資料のデータを読み込み、プレゼンの音声と表示しているスライドを紐づけて記録・評価することで、資料との齟齬や分かりにくさ、フィラーの有無などをフィードバックすることを目的として開発したものを紹介しています。

## 2.背景 
仕事上、お客様に資料を使って説明する機会があるのですが、その際に自分の作った資料と話し方で果たしてうまく伝わるのか、不安に思うことがありました。特に緊張すると「ええと」「あのー」といった口癖（フィラーというらしいです）が多くなるのも気になっており。かといって誰かに聞いてもらうのも時間が取れず、、

そこで、「自分のプレゼン内容に対してアドバイスをくれるAIが欲しい」ということで、生成AIを活用したプレゼン支援アプリを作ってみることにしました！

### アプリケーションの機能
- **入力ファイル:**
  - **プレゼン資料（パワーポイント）:** AIには事前にプレゼン資料を読み込ませたうえでプレゼン内容のデータを評価してもらいます。これにより、発表内容と資料内容を紐づけた指摘を実現させます
  - **文字データ＋スライド表示時間（テキスト）:** 評価してもらうためにはプレゼン内容となる音声データが必要ですが、今回は音声データから文字データに変換したものをLLMに渡すようにします。また、上記文字データに加え、プレゼン時に「どのページをどのくらいの時間表示しているか」というのも検知してLLMに渡します
  - **プロンプト指示ファイル（テキスト）:** 評価してもらうにあたり、どのような役割で、どのような観点で評価してほしいのか、システムプロンプトとしてLLMに渡します
　
- **出力ファイル:**
  - **リアルタイム文字起こし（コンソール画面表示）:** 発表中は、現在話している音声内容と、投影中の資料の情報（スライド番号、表示時間）をコンソール画面上に表示させます
  - **評価ファイル（テキストファイル）:** 発表終了後、事前のシステムプロンプトに従った評価結果をtxt出力させます

  　
- **利用した外部サービス(API):**
  - **LLM:** Azure Open AI (モデル: gpt-4o)
  - **音声認識:** Google Cloud Speech-to-Text

上記だけだと分かりにくいので、簡単なイメージを作ってみました。（これでもまだ分かりにくいですが）

 **ざっくりアプリイメージ**
　 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4098395/ceba1dce-c06c-4a02-987c-aaa444466212.png)


## 3. 開発環境
本アプリの実装はPythonで開発を進めました。最終的には`pyinstaller`を使用してexeファイルとしてパッケージ化しています。

### フォルダ構成
exe実行時のフォルダ構成は以下の通りです。`exe`フォルダに実行ファイルとシステムプロンプトファイル、`confidential`フォルダに各クラウド（Azure OpenAIとGoogle Cloud）の認証情報が格納された`key.json`ファイルをそれぞれ配置します。

```
├ exe
│ ├ 実行ファイル.exe         ・・・実行ファイル。条件をそろえた状態で本ファイル実行
│ └ systemprompt.txt         ・・・システムプロンプト。AIの役割、評価時の条件を追記
└ confidential               ・・・各クラウドサービス利用時に必要な認証情報を格納
    ├ Azure
    │ └ key.json
    └ Google Cloud
        └ key.json
```

## 4. 実装手順
主要な処理を担うコードの一部を抜粋して説明します。
<font color="ffaa00">※本コードはCopilotなどAIアシスタントも使いながら実装しておりますが、「動くこと」を第一優先として実装しているため、一部可読性が悪かったり、コーディング規約に則っていない記載方法があることをご了承ください。</font>

### ステップ1: 設定値と定数定義
この部分は、アプリケーション全体で利用する各種設定値と、クラウドサービスへの認証情報をロードする処理を定義しています。`GCP_KEY_FILE_PATH`や`AZURE_KEY_FILE_PATH`で指定されたJSONファイルからAPIキーなどの認証情報を読み込み、Google Speech-to-TextとAzure OpenAIサービスを利用するための準備を行います。

```python
# --- 設定値と定数定義 ---
# クラウドサービスの認証情報JSONファイルのパス
GCP_KEY_FILE_PATH = '../confidentials/googleSpeechToTextAPI/key.json'
AZURE_KEY_FILE_PATH = '../confidentials/Azure/key.json'
# システムプロンプトファイル（AIへの指示内容）のパス
SYSTEM_PROMPT_FILE_PATH = 'systemprompt.txt'

# 音声認識とストリーミングに関する設定
STREAMING_LIMIT_MS = 600000   # 音声ストリーミングの最大制限時間（ミリ秒） - 10分
AUDIO_RATE_HZ = 16000         # 音声のサンプリングレート（Hz）
AUDIO_CHUNK_SIZE = int(AUDIO_RATE_HZ / 10) # 1回のオーディオバッファで処理するフレーム数（100ミリ秒分）

# 音声認識結果のリアルタイム表示に関する設定
TRANSCRIPT_DISPLAY_WIDTH = 50 # リアルタイム表示時に改行する文字数

# Azure OpenAIに関する設定
AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4o" # 使用するAzure OpenAIモデルのデプロイメント名
AZURE_OPENAI_MAX_TOKENS = 1600 # AI応答の最大トークン数
AZURE_OPENAI_TEMPERATURE = 0.7 # AI応答のランダム性 (0.0-1.0, 高いほど多様)
AZURE_OPENAI_TOP_P = 0.95     # AI応答の多様性 (0.0-1.0, 高いほど多様)

# --- 認証情報とシステムプロンプトのロード ---
def load_credentials_and_config():
    """
    JSONファイルからAzure OpenAIとGoogle Cloudの認証情報を読み込み、グローバル変数に設定します。
    """
    # ... (認証情報ロードの具体的な処理) ...

def load_system_prompt():
    """
    システムプロンプトファイルを読み込み、その内容を返します。
    """
    # ... (システムプロンプトロードの具体的な処理) ...
```

### ステップ2: PowerPointイベントハンドラの処理
`PowerPointEventHandler`クラスはスライドの変更をリアルタイムで検知します。`process_slide_change_in_edit_mode`メソッドでは、スライドが切り替わるたびにそのスライド番号と、スライド表示時間を記録します。これにより、「どのスライドをどのくらいの時間表示し、その間何を話したか」というのが判断できるようになります。

```python
class PowerPointEventHandler:
    """
    PowerPointアプリケーションからのイベント（スライドショー開始、終了、スライド変更）を捕捉し、
    プレゼンテーションの進行状況と発表内容（スライドと発言）を記録・管理します。
    """
    def __init__(self):
        self.current_slide_index = None
        self.slide_start_time = None
        self.recorded_presentation_content = ''
        self.powerpoint_file_path = ''
        # ... (その他の初期化) ...

    def process_slide_change_in_edit_mode(self, ppt_window):
        """
        PowerPoint編集モードでスライドが変更された際に情報を処理し、
        発表内容にスライド切り替え情報と前スライドの発言内容を追記します。
        """
        current_slide_index = ppt_window.View.Slide.SlideIndex
        if current_slide_index > 0 and current_slide_index != self.current_slide_index:
            # ... (スライド切り替え時の時間計算と記録処理) ...
            self.recorded_presentation_content += slide_change_info_message + '\n'
            # ... (文字起こし内容の記録処理) ...
            self.current_slide_index = current_slide_index
            self.slide_start_time = datetime.now()
```

### ステップ3: 音声認識とAIフィードバック生成ループ
`listen_and_print_loop`関数は、マイクからの音声をGoogle Cloud Speech-to-Textにストリーミングし、リアルタイムで文字起こしを行います。発表練習を終わらせたい場合は「終わります」というキーワードを話すと終了と判断し、これまでの発表内容とPowerPoint資料のテキスト情報を基に、Azure OpenAIに対してフィードバック生成のリクエストを送信します。この時、PowerPointから抽出したテキストコンテンツと、これまでの発言内容がプロンプトとして渡されます。

```python
def listen_and_print_loop(audio_stream, event_handler, quit_flag):
    """
    Google Cloud Speech-to-Textからのストリーミングレスポンスを処理し、
    リアルタイムで文字起こしを表示し、特定のキーワードで発表を終了させます。
    """
    # ... (音声認識結果のリアルタイム表示処理) ...

    if result.is_final: # 最終的な認識結果（発話の区切り）が確定した場合
        # ... (確定した文字起こし内容の処理) ...

    if re.search(r'(終わります)', event_handler.current_transcript, re.I):
        # ... (発表終了時の処理とAIフィードバックの生成) ...
        ai_client = AzureOpenAI(
            azure_endpoint=AZURE_OPENAI_ENDPOINT,
            api_key=AZURE_OPENAI_API_KEY,
            api_version="2024-05-01-preview",
        )
        slide_texts_list = extract_text_from_pptx(event_handler.powerpoint_file_path)
        # ... (AIへのプロンプト構築とフィードバック生成リクエスト) ...
        completion = ai_client.chat.completions.create(
            model=AZURE_OPENAI_DEPLOYMENT_NAME,
            messages=initial_chat_messages,
            # ... (その他のパラメータ) ...
        )
        initial_feedback_content = completion.choices[0].message.content
        # ... (フィードバックの再確認とファイル出力) ...
```

## 5. 挙動確認
早速このアプリを使っていきます！`systemprompt.txt`とパワーポイント資料は以下の通りです。パワーポイント資料はCopilotにそれらしいサンプルを作ってもらいました。

![プロジェクト計画書.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4098395/d21dff2c-e76a-4ca9-a0ea-520aa2bfc26e.png)

### systemprompt.txt
```
#役割
あなたは優秀なプレゼンターです。
#タスク
添付した資料（プロジェクト計画書）を説明する練習を行います。[発表内容：]を聞き、以下「＃条件」を実施して説明に対する指摘をしてください。
#条件
1.今回の発表に対する前提は以下の通りです。この発表では特に、聞き手の立場を想定して指摘をしてください。
・発表者：プロジェクトマネージャー
・聞き手：得意先
  A:社内の情報セキュリティ部門になります。明確な期日はありませんが、
    セキュリティにおいては明確な指標を満たさないと承認してくれません。
  B:社外の会社様になります。期日は確実に守る必要があります。
    担当者が多忙なため、定例の会議も多くは参加できません。
・この会議の目的：プロジェクト計画書の記載内容でプロジェクトを進めることを合意頂く
2.以下記載の通り、資料の内容を加味したレビューをしてください
・資料記載と齟齬のある発言はないか
・資料記載の説明漏れはないか（スライドページ単位で説明漏れがないかも含む）
・資料記載の重要度に合わせた説明粒度になっているか
・資料記載内自体に齟齬はないか
3.以下記載の通り、一般的な発表の内容に対するレビューをしてください
・用語の名称にばらつきがなく、統一した表現になっているか
・フィラー「ええと」「まぁ」の多用はないか
・時間内に説明できているか。説明の少ない（もしくは多い）スライドはないか。（全体の発表時間は1分です）
```

### 挙動確認
実際にこのアプリケーションを実行すると、コンソール画面が立ち上がり、PowerPointの監視が開始されたことを表示します。（左がパワーポイント画面、右が今回のアプリのコンソール画面）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4098395/fdd71876-30f0-4f96-b012-4803fcf0fbad.png)
```
Agent: パワーポイント資料のモニタリングを開始しました。
　　　　　対象資料: C:\架空のプロジェクト計画書.pptx
---
　　　　　練習を開始する場合：Enterキーを入力してください。
　　　　　終了する場合：'q'を入力してEnterを入力してください
:
```

ここでEnterキーを入力すると、プレゼン練習が開始され、音声認識がアクティブになります。

```
Agent: プレゼンテーション練習を開始します。
Agent: 発表を始めてください。「終わります」の発言で評価を始めます。
- START!! 
```

実際にパソコンのマイクに向かって話をしてみます。話した内容がリアルタイムでコンソール上に文字起こしされました。スライドを切り替えると、そのスライド番号と、前のスライドで話していた時間が表示されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4098395/95c63591-9f1f-47c4-b940-79bb2b9961b2.png)
```
スライド：1, 時間:0:00:13.083112 プロジェクト概要ですがこのプロジェクトは楽空のプロジェクトとして計画されています目的は 
 的はこのプロジェクト計画書の作成とその説明資料を作成することです
 ・・・
```

発表の最後に「終わります」と発言すると、アプリケーションは発表終了を検知し、AIによる評価が開始されます。

```
以上で発表を終わります

 スライド: 7, 時間:0:00:28.605165

 - FINISH!! - Agent: 発表終了と判断しました。評価いたしますので少々お待ちください...
```

評価完了すると、`発表評価.txt`というファイルが出力されました。
実際に出力されたテキストの中身はこのような感じでした。（一部抜粋）

```
【評価】
スライド 1:
- 指摘内容: 特に問題はありませんが、スライドの内容が簡潔すぎるため、もう少し詳しく説明しても良いかもしれません。（追記）特にプロジェクトの概要や背景についての説明を追加すると、聞き手の理解が深まるでしょう。
-(中略)-
スライド 5:
- 指摘内容: メンバー紹介の際に「スタバ 高橋」との発言がありますが、「テスター 高橋健一」と修正する必要があります。また、各メンバーの役割をもう少し具体的に説明すると聞き手にとって理解しやすくなります。（追記）メンバーの専門性や経験、役割がプロジェクトにどう寄与するのかを詳しく説明すると良いでしょう。
-(中略)-
全体のフィードバック:
- 発表全体の時間が1分以内で終わっており、各スライドの説明が不十分な印象があります。特に重要なスライド（目的、タイムライン、リスクと軽減策）はもう少し時間をかけて説明することをお勧めします。（追記）聞き手の理解を深めるために、各スライドに対する質問を想定し、それに対する準備を行うと良いでしょう。
- フィラー「ええと」「あの」の多用が見られ、聞き手に不安を与える可能性があります。フィラーを減らす努力が必要です。（追記）練習を重ねることでフィラーを減らし、より流暢に話すことができるようにすることをお勧めします。
```

全体的にフィラーの指摘や時間配分の指摘、資料との整合性を加味した指摘はある程度できている印象でした！

音声認識の誤り（テスター→スタバ）は、文字起こしサービスの精度向上や設定見直し、話す環境（マイクとの距離や環境音の抑制）を整えたり、
指摘内容が抽象的なものはシステムプロンプトで指示内容をもう少し具体的にすることで改善の余地はありそうです！

## 6. まとめ
今回の開発を通して、課題は残っているものの、生成AIを活用したプレゼン支援ツールの可能性を確かに感じることができました。

しかし、それ以上に驚きだったのは、クラウドサービス（Google Cloud Speech-to-TextやAzure OpenAIなど）やPythonのライブラリ、さらにはCopilotといったAIアシスタントを組み合わせることで、これほどのシステムがある程度一人でも実装可能であるという点です。ほんの数年前までは特別な環境や開発スキルがないとできなかったことが、今では実現できるようになり、技術の進化に喜びと驚きを感じています。


今後も様々なサービスが登場すると思われるので、引き続きアンテナを張り巡らせつつ、新しい技術を活用した様々なアプリを試作していきたいと思います！

## 7. 参考リンク

[Google Cloud Speech-to-Text公式ドキュント(ストリーミング入力の音声を文字に変換する)](https://cloud.google.com/speech-to-text/docs/transcribe-streaming-audio?hl=ja)
[Slide.Application プロパティ (PowerPoint)](https://learn.microsoft.com/ja-jp/office/vba/api/powerpoint.slide.application)