Cloudflare が Voice Agent のターンメトリクスを公開、音声が返らない原因を種類別に切り分けられるように
@cloudflare/voice v0.4.0 で、Voice Agent の各ターンが型付きの VoiceTurnMetrics を生成するようになった。turnId、終了理由(completed / no_output / output_limit / content_filtered / model_error / tts_error / aborted)、段階別の所要時間が取得できる。従来は成功して出力のあったターンしか見えず、失敗・中断・空のターンがどう終わったかは分からなかった。
ニュース原文を読む ↗要約
Cloudflare は 2026-09-11、@cloudflare/voice v0.4.0 を公開し、Voice Agent の各ターンが どこで時間を使い、どう終わったか を検査できるようにしました。
@cloudflare/voice は Cloudflare Agents 上でリアルタイム音声エージェントを構築するパッケージです。マイク音声を WebSocket で Agent へ流し、音声認識し、onTurn() でモデルを走らせ、応答を音声合成して返します。1ターンは次の段階を通ります。
ユーザー発話 → 音声認識(STT) → モデル → 音声合成(TTS) → 音声
従来の問題は、集計メトリクスが4つあるだけで、成功して出力があったターンしか見えなかったことです。 失敗したターン、中断されたターン、空のターン、テキストのターンが どう終わったのかは観測できませんでした。
v0.4.0 では、音声・テキストの各ターンが型付きの VoiceTurnMetrics を生成します。含まれるのは3つです。
- 同一ターンのイベントを突き合わせるための
turnId - 終了理由(terminal outcome):
completed/no_output/output_limit/content_filtered/model_error/tts_error/aborted - 主要段階の所要時間: 発話から最終トランスクリプトまで、モデルから最初のテキストまで、TTS から最初の音声まで、ターン全体
取得はイベントリスナーで行います。
client.addEventListener("turnmetrics", (turn) => {
console.log(turn.outcome, turn.turnTotalMs);
});
所要時間の扱いには注意が必要です。 公式に明記されているとおり、これらの時間は重なりうるもので、足し合わせても全体にはなりません。 また、到達しなかった段階の時間は省略されます。
最新のサマリーは VoiceClient、useVoiceAgent()、useVoiceInput() から取得できます(useVoiceInput() は計測可能な発話・文字起こし部分のみ)。
結果として得られるのは、切り分けの解像度です。 エージェントが音声を返さなかったとき、モデルが何も返さなかったのか、出力上限に達したのか、コンテンツフィルタに掛かったのか、TTS が失敗したのか、中断されたのか を区別できます。
あわせてローカルデバッグ用に、サーバー側のライフサイクルイベントをブラウザコンソールへ転送する設定が追加されました。
import { Agent } from "agents";
import { withVoice } from "@cloudflare/voice";
const VoiceAgent = withVoice(Agent, {
diagnostics: {
browserConsole: true,
},
});
何が変わったか
@cloudflare/voicev0.4.0 で、ターンごとに型付きのVoiceTurnMetricsを生成turnIdによる同一ターンのイベント突き合わせ- 終了理由7種:
completed/no_output/output_limit/content_filtered/model_error/tts_error/aborted - 段階別所要時間: 発話→最終トランスクリプト、モデル→最初のテキスト、TTS→最初の音声、ターン全体
- 所要時間は 重なりうるもので加算できない。未到達の段階は省略される
VoiceClient/useVoiceAgent()/useVoiceInput()から最新サマリーを取得diagnostics.browserConsoleでサーバーライフサイクルイベントをブラウザへ転送- 従来は集計4指標のみで、成功・非空のターンしか観測できなかった
業務インパクト(一般企業向け)
音声エージェントを本番で運用したことがある人には、この更新の意味がすぐ分かるはずです。
音声インターフェースの障害報告は、ほぼ例外なく 「反応しない」「返事が遅い」 という形で上がってきます。この2つは症状であって原因ではありません。返事が来ない理由は少なくとも5つあり、遅い理由は4段階のどこかにあります。 ログに残るのが「成功したターン」だけだと、失敗したターンは存在しなかったことになり、再現もできません。
今回の変更は、この空白を埋めます。 no_output と content_filtered と tts_error は、ユーザーから見ると同じ「無言」ですが、対処はまったく違います。 モデルのプロンプトを直すのか、フィルタ設定を見直すのか、TTS の設定・障害を疑うのか。切り分けが実測でできるかどうかで、対応時間が変わります。
段階別の所要時間も同じ構図です。 「音声アシスタントが遅い」という苦情に対して、STT が遅いのか、モデルの初回トークンが遅いのか、TTS の立ち上がりが遅いのかで、打ち手は別物です。当て推量でモデルを軽いものに替えて、実は STT がボトルネックだった、という無駄が減ります。
運用設計としては、turnId の存在が効きます。 同一ターンのイベントを突き合わせられるということは、アプリ側のログと紐づけられるということです。ユーザーからの問い合わせを特定のターンまで辿る導線を、設計に組み込めます。
注意点として、「所要時間は加算できない」という公式の但し書きは、ダッシュボード設計で必ず効きます。 段階別の時間を積み上げ棒グラフにすると、合計がターン全体と一致せず、見る人を混乱させます。重なりうる区間であることを前提にした可視化を最初から選ぶ必要があります。
副業・個人活用視点
音声エージェントの開発を受託している、あるいはこれから狙う人にとって、これは提案材料になります。
音声系の案件は、デモは動くが本番で安定しない、というパターンで揉めやすい領域です。「返事が来ないときの原因を種類別に記録し、ダッシュボードで見られるようにする」 は、機能開発とは別に値段が付く仕事です。今回 SDK 側が素材を出してきたので、実装コストが下がった状態で提案できます。
個人開発でも効用があります。 音声エージェントを趣味で作ると、動かないときの原因追跡で時間を溶かしがちです。終了理由が7種類に分かれて返ってくるだけで、試行錯誤の方向が絞れます。 diagnostics.browserConsole でサーバー側イベントを手元のコンソールに集められる点も、1人で開発しているときほど効きます。
教える立場では、これは「可観測性とは何か」の教材として質が高い素材です。 可観測性という言葉は抽象的で伝わりにくいのですが、「成功したターンしか見えない状態」と「失敗の種類が7つに分かれて見える状態」の対比なら、実感として理解できます。測れないものは直せないという原則を、具体例として示せます。
さらに、「終了理由を型で表現する」という設計判断は、AI以外の開発にも一般化できます。エラーを文字列で投げるのではなく、起こりうる終わり方を列挙して型にする。 これはドメイン設計の基本であり、音声という題材を通して教えると伝わりやすい部分です。