AI音声ロールプレイ研修を内製した話

ClassLab Engineering の Dev チームメンバーが執筆しました。

*この記事は、音声AI・リアルタイムAPI・Electronでの業務ツール開発に興味があるエンジニア向けです。*

目次

1. 背景

ClassLab のコールセンターでは、新人オペレーターが電話応対スキルを身につけるまでに一定の研修期間が必要です。従来はベテラン社員がロールプレイの相手役を務めていましたが、以下の問題がありました。

  • ベテラン社員が研修に拘束され、本来業務が圧迫される
  • 研修のスケジュール調整が難しく、新人が十分な練習量を確保できない
  • ロールプレイの評価基準が属人的で、フィードバックの質にばらつきがある
  • 「AIが顧客役を演じ、通話終了後にスコアリングと改善点を自動提示する」システムを内製することで、これらの課題を解消しました。プロジェクト名は Convers AI です。

    2. 課題

    | 課題 | 影響 | 定量データ |
    |——|——|———–|
    | ベテラン社員の拘束 | 研修1回あたり2名体制が必要 | 月間研修工数 約40時間 |
    | 練習量の不足 | 新人1人あたり週2-3回しか実施できない | 独り立ちまで平均8週間 |
    | 評価のばらつき | 担当者によりフィードバックの質が異なる | 評価基準が暗黙知 |
    | 日本語音声の処理 | 自動VADが日本語の「間」で誤検知 | 発話途中での誤切断が多発 |

    特に技術的に難しかったのは、日本語音声のリアルタイム処理です。OpenAI Realtime APIのサーバーサイドVAD(Voice Activity Detection)は英語に最適化されており、日本語の「間」や相槌で発話終了と誤判定される問題がありました。

    3. 設計

    システム構成

    graph TB
        subgraph "Salesforce"
            LWC[LWC ユーティリティバー<br/>研修項目・担当者選択]
        end
    
        subgraph "デスクトップアプリ Electron"
            FE[Vue.js 3 + Pinia<br/>UI・状態管理]
            BE[Express 5 + WebSocket<br/>音声処理・API連携]
            FFmpeg[FFmpeg<br/>ノイズ低減・フォーマット変換]
        end
    
        BE --> FFmpeg
    
        subgraph "外部サービス"
            RT[OpenAI Realtime API<br/>音声ロールプレイ]
            CHAT[OpenAI Chat API o4-mini<br/>スコアリング・分析]
            SLACK[Slack API<br/>録音アップロード]
        end
    
        LWC <-->|WebSocket| BE
        FE <-->|WebSocket| BE
        BE <-->|WebSocket| RT
        BE -->|REST| CHAT
        BE -->|REST| SLACK
        BE -->|REST OAuth2| LWC

    技術選定

    | 項目 | 選定技術 | 選定理由 |
    |——|———|———|
    | デスクトップ | Electron 35 | Salesforce LWCとのローカル通信(localhost WebSocket)が必要 |
    | フロントエンド | Vue.js 3 + Pinia | 軽量で学習コストが低い。状態管理がシンプル |
    | バックエンド | Express 5 + WebSocket (ws) | Electronメインプロセスで動作。双方向通信が容易 |
    | 音声AI | OpenAI Realtime API | リアルタイム音声対話。日本語対応。低遅延 |
    | 分析AI | o4-mini | 通話終了後のスコアリング。コストパフォーマンスが高い |
    | 音声処理 | FFmpeg (ffmpeg-static) | ノイズ低減・フォーマット変換。パッケージに同梱可能 |

    Webアプリではなく Electron デスクトップアプリ を選んだ理由は、Salesforce LWCとの連携方式にあります。Salesforce のユーティリティバー(LWC)から ws://localhost:3001 でローカルアプリと通信するため、ブラウザのセキュリティ制約を回避する必要がありました。

    Push-to-Talk(PTT)モードの設計判断

    本システム最大の設計判断は、OpenAIの自動VADを無効化し、Push-to-Talk方式を採用したことです。

    sequenceDiagram
        participant OP as オペレーター
        participant APP as Electron App
        participant AI as OpenAI Realtime API
    
        OP->>APP: PTTキー押下(Shift+Space)
        APP->>AI: response.cancel
        Note over APP: AI応答中なら中断
        APP->>AI: input_audio_buffer.clear
        Note over APP: 前回の残留バッファを破棄
    
        loop 発話中(PTT押下中)
            OP->>APP: 音声入力(MediaRecorder WebM)
            APP->>APP: FFmpeg WebM→PCM16変換+ノイズ低減
            APP->>AI: input_audio_buffer.append
        end
    
        OP->>APP: PTTキー解放
        APP->>AI: input_audio_buffer.commit
        APP->>AI: response.create
        AI->>APP: response.audio.delta(AI音声)
        APP->>OP: スピーカー再生

    自動VADを使わない理由:

    • 日本語の「間」(沈黙)を発話終了と誤判定し、オペレーターの発話途中でAIが割り込む
    • 相槌(「はい」「ええ」)を独立した発話と認識し、不要な応答が返る
    • コールセンター研修では発話タイミングの制御自体がスキルの一部であり、意図的に制御する方が訓練効果が高い
    • OpenAI API設定: turn_detection: null(自動VAD完全無効化)

      4. 実装

      音声パイプライン

      マイク入力からOpenAI APIへの送信までの音声処理パイプラインです。

      // FFmpegによる入力音声の前処理
      // MediaRecorderからWebM/Opus形式で受け取り、OpenAI要求のPCM16に変換
      const ffmpegArgs = [
        '-f', 'webm',           // 入力: MediaRecorder出力(WebM/Opus 60ms chunks)
        '-i', 'pipe:0',         // stdin入力
        '-af', [
          'highpass=f=60',       // 60Hz以下をカット(環境ノイズ除去)
          'lowpass=f=10000',     // 10kHz以上をカット
          'volume=2.5',          // 音量補正
        ].join(','),
        '-ar', '24000',          // OpenAI要求: 24kHz
        '-ac', '1',              // モノラル
        '-f', 's16le',           // PCM 16bit Little Endian
        '-fflags', '+nobuffer',  // 低遅延フラグ
        '-flags', 'low_delay',
        'pipe:1',                // stdout出力
      ];

      通話後の自動分析

      ロールプレイ終了後、録音音声をSlackにアップロードし、o4-miniでスコアリングを実行します。

      async function analyzeSession(
        transcript: string,
        trainingItem: TrainingItem
      ): Promise<AnalysisResult> {
        const response = await openai.chat.completions.create({
          model: 'o4-mini',
          messages: [
            {
              role: 'system',
              content: `あなたはコールセンターの品質管理担当者です。
      以下の研修項目に基づいてロールプレイを評価してください。
      研修項目: ${trainingItem.name}
      評価基準: ${trainingItem.criteria}`,
            },
            {
              role: 'user',
              content: `以下の通話内容を評価し、JSON形式で返してください。
      ${transcript}`,
            },
          ],
          response_format: { type: 'json_object' },
        });
      
        return JSON.parse(
          response.choices[0].message.content ?? '{}'
        );
      }

      Salesforce連携

      分析結果はSalesforce のカスタムオブジェクトに自動保存されます。LWCのユーティリティバーから研修を開始し、結果がSalesforceに戻る一気通貫のフローを実現しています。

      // Salesforceへの分析結果保存(OAuth2認証済みaxiosインスタンス)
      await sfClient.post('/services/data/v59.0/sobjects/TrainingResult__c', {
        trainee__c: traineeId,
        trainingItem__c: trainingItemId,
        score__c: analysis.overallScore,
        feedback__c: analysis.feedback,
        recordingUrl__c: slackPermalink,
        sessionDate__c: new Date().toISOString(),
      });

      5. 結果(数値)

      | 指標 | Before | After | 改善率 |
      |——|——–|——-|——–|
      | 研修1回あたりの人件費 | 2名体制(ベテラン+新人) | 新人1名のみ | -50% |
      | 月間研修可能回数 | 週2-3回(スケジュール制約) | 無制限(AIが常時対応) | 大幅増加 |
      | 独り立ちまでの期間(※) | 約8週間 | 約5週間(練習量増加により短縮) | -37% |
      | フィードバックの一貫性 | 担当者依存(ばらつき大) | AI基準で統一 | 標準化 |
      | 評価項目のカバー率 | 主要3-4項目 | 全評価項目を網羅 | 全項目 |

      ※ 独り立ち基準: SVの同席なしで1日の受電を完了できること。

      最大の効果はベテラン社員の時間解放です。月間約40時間の研修工数が実質ゼロになり、その時間を顧客対応や品質改善に充てられるようになりました。

      6. 展望

      次に取り組む課題

      • 評価パイプラインの高度化: 複数モデルでの評価比較や、過去の高評価ロールプレイとの類似度分析。次のAI/MLエンジニアと一緒に設計したい領域です
      • 研修シナリオの拡充: クレーム対応・解約抑止・アップセルなど、より高度なシチュエーション別のシナリオ設計
      • 研修データ分析基盤: 蓄積されたスコア推移・つまずきパターンから研修プログラム自体を改善するフィードバックループの構築
      • 関連記事

  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次