第2回:JSONフィールド順序で翻訳表示が2倍速に
![]()
前回の記事で、「Intent-First Translation」という音声翻訳のアプローチをご紹介しました。話し始めてから約500ミリ秒で意図を表示し、会話のテンポを維持するというものです。
今回は、この速度を実現するために必要だった技術的な設計について、少し詳しくお伝えします。エンジニアの方にはもちろん、技術に関心のあるビジネスの方にも、「なぜ速いのか」のエッセンスが伝わればと思います。
3つのレイヤーで「段階的に」情報を届ける
Intent-First Translationは、単一の翻訳処理ではありません。速度の異なる3つのレイヤーを重ねて、段階的に情報を提供する構造になっています。
Layer 1: キーワード予測 → 約0ms (LLM不要)
Layer 2: 意図ラベル(Intent)→ 約500ms (LLMストリーミング)
Layer 3: 完全翻訳 → 約500-800ms(LLMストリーミング続き)
Layer 1:キーワード予測(遅延ほぼゼロ)
音声認識から届く断片的なテキストに対して、LLMを使わず辞書ベースで即座にトピックを予測します。
“meeting” → 「会議」、“budget” → 「予算」、“Tuesday” → 「火曜日」
これだけでも、聞き手は「会議と予算と火曜日の話だな」と瞬時に把握できます。LLMの応答を待つ必要がないため、ほぼゼロ遅延で表示されます。
Layer 2 & 3:LLMによる意図推定と翻訳
音声認識(Deepgram)のPartial結果——話している途中のまだ確定していないテキスト——が更新されるたびに、LLMへJSON形式で意図推定と翻訳を依頼します。
ポイントはストリーミング出力です。LLMがJSONを上から1文字ずつ生成するのを、リアルタイムでパースして、生成され次第フロントエンドへ送信しています。
JSONのフィールド順序が、速度を決めていました
ここが今回最もお伝えしたい点です。
LLMのストリーミング出力は、JSONを上から順番に生成します。つまり、最初に定義されたフィールドが最初に生成され、最後のフィールドは最後に生成されます。
これは、フィールドの配置順序によって、各情報がユーザーに届くタイミングが変わることを意味します。
意図と翻訳を先に配置した場合
{
"dialogue_act": "PROPOSAL",
"intent_label": "日程調整の提案", ← すぐ表示される
"slots": {"when": "Tuesday"},
"full_translation": "火曜に...", ← 翻訳も早めに届く
"confidence": 0.85,
"is_meaning_stable": true
}
翻訳を最後に配置した場合
{
"dialogue_act": "PROPOSAL",
"slots": {"when": "Tuesday"},
"key_terms": ["meeting", "reschedule"],
"confidence": 0.85,
"is_meaning_stable": true,
"intent_label": "日程調整の提案",
"full_translation": "火曜に..." ← すべての生成を待つ必要がある
}
後者の場合、翻訳が表示されるまでに全フィールドの生成を待つことになり、体感で約2倍の遅延が発生しました。
JSONのフィールド順序を入れ替える——たったこれだけの変更で、翻訳の表示速度がほぼ2倍に向上しました。LLMの「上から順に生成する」という性質を理解したことで見つかった最適化です。
進行中と確定時で、プロンプトを切り替える
音声認識の結果には2種類あります。
- Partial(進行中): 話している最中の不完全なテキスト(例: “I think we should…”)
- Final(確定): 発話が完了した確定テキスト(例: “I think we should reschedule the meeting to Tuesday.”)
この2つに対して、同じプロンプトを使うのは非効率だと気づきました。
| 状態 | 目的 | プロンプトの方針 |
|---|---|---|
| Partial(進行中) | 速く意図を表示する | 速度重視:意図 → 翻訳 を先に生成 |
| Final(確定) | 正確な翻訳を提供する | 品質重視:文脈分析を先に行い、翻訳の精度を上げる |
話している最中はスピード優先で大まかな意図と翻訳を先行表示し、話し終わった時点で品質優先の正確な翻訳に差し替えます。
これは、人間の同時通訳者が行っていることと同じ構造です。まず大意を伝え、確定したら正確に訂正する。
6つのLLMモデルを実際に比較しました
このシステムでは、用途や環境に応じてLLMを切り替えられるようにしています。同じ入力で比較した結果は以下の通りです。
| モデル | 翻訳表示速度 | 品質 | 5時間あたりのコスト |
|---|---|---|---|
| Groq / Llama 4 Maverick | 413ms | ◎ | $3.43(約515円) |
| Groq / Llama 3.3 70B | 480ms | ◎ | — |
| Groq / Llama 3.1 8B | 377ms | ○ | — |
| Groq / GPT-OSS 120B | 662ms | ◎ | — |
| Gemini 2.5 Flash Lite | 954ms | ◎ | $1.17(約175円) |
| OpenAI GPT-4o-mini | 1,976ms | ◎ | $1.74(約261円) |
Groq + Llama 4 Maverickは推論速度が800+ tokens/secと非常に速く、リアルタイム翻訳に最も適していました。一方、コストを重視する場合はGemini 2.5 Flash Liteが5時間で約175円と経済的です。
フロントエンドからワンクリックでモデルを切り替えられるUIを用意し、用途に応じた使い分けができるようにしています。
システム全体のデータフロー
技術的な全体像を示します。
[ブラウザ] [バックエンド(FastAPI)] [外部API]
| | |
|-- 音声バイナリ --------->| |
| |-- 音声データ ------------>| Deepgram
| |<-- Partial/Final text ----|
| | |
| |-- テキスト -------------->| LLM (Gemini/Groq)
| |<-- ストリーミングJSON ----|
| | |
|<-- intent_partial -------| (意図ラベルを即送信) |
|<-- translation_partial --| (翻訳を即送信) |
|<-- intent (complete) ----| (完全な結果) |
重要なのは、LLMの出力を最後まで待ってからまとめて送るのではなく、JSONの各フィールドが生成されるたびに個別のWebSocketメッセージとして即座にフロントエンドへ送信している点です。
LLMの無駄遣いを防ぐための工夫
Deepgramは非常に高速にPartial結果を返すため、何も対策しないとLLMへのリクエストが毎秒数十回に達してしまいます。これを防ぐために、いくつかの最適化を施しています。
- デバウンス処理(300ms): テキスト更新後300ms待ち、その間に新しいテキストが来なければLLMを呼ぶ
- 短文スキップ(5単語未満): “I” や “I think” のような短すぎるPartialはスキップ
- 重複チェック: 同じテキストを二重に処理しない
- 非同期パイプライン: LLMの応答待ちの間も、音声認識のデータは受信し続ける
推測翻訳(Speculative Translation)の実験
実験的な機能として、「推測翻訳」も実装しました。
対話行為(例:提案)と意図ラベル(例:日程調整)の2つの情報だけから、文の内容を推測して翻訳を先行表示するものです。正確さは確定翻訳に劣りますが、「何について話しているか」が確定翻訳より約500ms〜1秒早く伝わります。
この開発で得られた知見
このシステムの開発を通じて最も実感したのは、LLMの出力を「結果」としてではなく「ストリーム」として扱うことの重要性です。
- JSONのフィールド順序を最適化し、重要な情報を先に生成させる
- 生成途中のJSONを部分的にパースして即座に送信する
- Partial/Finalの状態に応じてプロンプトを動的に切り替える
いずれも、LLMの「上から順に生成する」という基本的な性質を理解し、それを活かした設計です。
次回は、ローカルLLMへの挑戦の記録と、このシステムの今後の展望についてお伝えします。