この記事では、音声認識API「AmiVoice API」と生成AIを組み合わせて、会議や商談の録音を「あとから聞き返せる議事録」に変える設計を整理します。
単に音声をテキスト化するだけなら、いまは珍しくありません。問題はその先です。
- どの発言が、どの決定につながったのか
- ToDo は誰に割り当てられたのか
- 要約が本当に元発言に基づいているのか
- 後から質問したとき、根拠の時刻まで戻れるのか
ここまで扱って、ようやく「音声体験」と呼べます。
先に結論を書くと、AmiVoice API は入口で、生成AIは出口です。体験の品質を決めるのは、その間にある「認識結果の正規化」「根拠付き要約」「確認UI」「再処理できるジョブ設計」です。
想定するユースケース
例えば、営業会議や社内定例の録音をアップロードすると、次のような画面が返ってくるアプリを考えます。
- 会議全体の要約
- 決定事項
- ToDo と担当者
- 未決事項
- 重要発言へのタイムスタンプリンク
- 「予算の話はどこ?」のような自然文検索
ここで大事なのは、生成AIの回���をそのまま信じさせないことです。議事録は業務の根拠になります。回答には、元の発言へ戻れるリンクが必要です。
全体アーキテクチャ
最小構成は次のように分けます。
| 層 | 役割 | 注意点 |
|---|---|---|
| フロントエンド | 音声アップロード、進捗表示、要約確認、質問UI | 長時間処理を同期画面で待たせない |
| API 層 | 認証、ファイル受付、ジョブ作成 | API Gateway + Lambda などで入口を薄く保つ |
| ストレージ | 音声ファイル、認識結果、生成結果の保存 | S3 などに原本と派生物を分けて保存する |
| 音声認識 | AmiVoice API による文字起こし | API仕様や話者分離の扱いは公式仕様を確認する |
| 正規化 | 発話単位、時刻、話者、句読点の整形 | LLMに投げる前の品質が回答品質を決める |
| 生成AI | 要約、ToDo抽出、質問応答 | 根拠のない断定を避ける |
| 検索 | チャンク検索、全文検索、ベクトル検索 | タイムスタンプを失わない |
構成を図にすると、かなり地味です。
Browser / Mobile
-> API Gateway
-> Lambda: create transcription job
-> S3: raw audio
-> Worker: call AmiVoice API
-> DB: transcript segments
-> LLM worker: summary / action items / QA index
-> Search index: timestamped chunks
-> UI: summary + source links
地味ですが、この分割にしておくと失敗時の再実行が楽です。音声認識だけ再実行したいのか、要約だけ作り直したいのか、検索インデックスだけ更新したいのかを分けられます。
認識結果をそのままLLMに渡さない
音声認識APIの結果を、そのまま長い文字列として生成AIに渡すのはおすすめしません。
理由は単純で、後から根拠に戻れなくなるからです。
最低でも、次のような単位に整形してから扱います。
{
"meeting_id": "mtg_20260522_001",
"segment_id": "seg_00042",
"speaker": "speaker_2",
"start_ms": 184230,
"end_ms": 191840,
"text": "次回までに料金プランの比較表を作ります。",
"confidence": 0.91
}
この形式にしておくと、生成AIが「料金プランの比較表を作る」というToDoを抽出したときに、元発言の segment_id と時刻を一緒に返せます。
生成AIに任せる処理を分ける
ひとつの巨大プロンプトで、要約、ToDo、リスク、質問応答を全部やらせると、保守しづらくなります。
私は次のよう���分けるのが扱いやすいと考えています。
- 会議全体の短い要約
- 決定事項の抽出
- ToDo の抽出
- 未決事項・確認事項の抽出
- ユーザー質問への回答
それぞれ出力形式を固定します。
あなたは会議ログを整理するアシスタントです。
以下の transcript segments だけを根拠に、ToDoを抽出してください。
制約:
- 推測で担当者を補わない
- 不明な場合は assignee: null とする
- 必ず根拠 segment_id を含める
- 出力は JSON 配列のみ
ここで重要なのは、「不明なら不明」と出させることです。議事録で一番困るのは、もっともらしい嘘です。
RAGにするなら、チャンクに時刻を持たせる
会議ログに対して「来月の予算の話はどうなった?」と聞けるようにするなら、RAG構成にしたくなります。
ただし、チャンクから時刻情報を落とすと一気に使いづらくなります。
検索対象のチャンクには、本文だけでなく次の情報を持たせます。
- meeting_id
- segment_id の範囲
- start_ms / end_ms
- speaker
- original_text
- normalized_text
- embedding text
回答時には、生成AIに「引用IDなしの回答は禁止」と指示します。
{
"answer": "予算は次回会議までに再見���もりする方針です。",
"citations": [
{ "segment_id": "seg_00042", "start_ms": 184230 },
{ "segment_id": "seg_00051", "start_ms": 231900 }
]
}
UI側では citation をクリックすると、その時刻から音声を再生できるようにします。これで「AIがそう言っている」ではなく「元発言に戻れる」体験になります。
長時間音声はジョブとして扱う
音声ファイルは、テキスト入力より失敗パターンが多いです。
- ファイルが大きい
- 無音区間が長い
- 複数人が被って話す
- ノイズが多い
- 処理時間が長い
- ユーザーが画面を閉じる
そのため、同期APIで全部返す設計にしない方が安全です。
POST /meetings
-> meeting_id を返す
POST /meetings/{id}/audio
-> upload_url を返す
POST /meetings/{id}/transcription-jobs
-> job_id を返す
GET /meetings/{id}/jobs/{job_id}
-> queued / running / failed / completed
失敗時には、どの段階で落ちたかを残します。
- upload_failed
- transcription_failed
- normalization_failed
- llm_summary_failed
- indexing_failed
これを残さないと、運用時に「なんか議事録ができません」しか分かりません。地獄です。
セ���ュリティとプライバシーを後回しにしない
音声には、メールより生々しい情報が入ります。
顧客名、価格、障害、評価、採用、個人情報。会議音声はだいたい危険物です。
最低限、次は設計に入れます。
- アップロード時の同意表示
- 保存期間の明示
- 原音声と生成結果の削除機能
- プロジェクト単位のアクセス制御
- 監査ログ
- LLMに渡す前のマスキング
- 外部APIに送るデータ範囲の説明
特に、生成AIの要約だけ消しても原音声が残っていたら意味がありません。削除要求は、原本、認識結果、要約、検索インデックスまで一貫して処理する必要があります。
体験として一番効くのは「確認できる」こと
音声認識と生成AIを組み合わせると、つい自動化を前面に出したくなります。
でも、業務で本当に使われるには、自動化より確認性が大事です。
良いUIは、たとえば次のようになります。
- 要約文の横に根拠発言を表示する
- ToDo に「担当者不明」の状態を許す
- ユーザーが要約を修正できる
- 修正前のAI出力と修正後の確定版を分ける
- 重要な決定事項には「確認済み」フラ���を付ける
AIが書いた議事録を、そのまま正式記録にする必要はありません。AIは下書きを作り、人間が確定する。この分担の方が現実的です。
まとめ
AmiVoice APIのような音声認識APIと生成AIを組み合わせると、文字起こしを超えた音声体験を作れます。
ただし、品質を決めるのはモデルの派手さではありません。
- 認識結果を発話単位で保存する
- タイムスタンプを最後まで失わない
- LLM処理を用途別に分ける
- 回答には根拠を必ず付ける
- 長時間音声をジョブとして扱う
- 削除、権限、監査を最初から設計する
ここまで作って初めて、「文字起こしツール」ではなく「聞き返せる議事録体験」になります。
音声認識APIは入口です。生成AIは出口です。その間の設計を雑にすると、便利そうに見えるだけの危ない議事録ができます。そこを丁寧に作るのが、音声体験の本体です。