音声 AI エージェント — 聞いて、調べて、話すパイプライン
何を測れば信頼できるか — 区間記録・品質指標・失敗率・ゲート
一言でいうと
音声アシスタントの1ターンは複数の段階を経ており、各段階は別々に遅くなり、別々に失敗します。そのため、ターンごとにトレース(trace)を1つ、段階ごとにスパン(span)を1つ残し、すべての指標(段階別の遅延分布、WER、応答品質、失敗率)を、その同じ元データから計算します。エラー率とユーザーが体験した失敗率は別の数字で、根拠検査を通過した誤答は、正解表でしか見えません。最後に、ベースライン版より悪化していたらデプロイを止めるゲートを置きます。
なぜ必要なのか
「平均応答1.2秒、良好」のような報告は、3つのことを隠します。どの段階が遅いか(分布と段階)、どのターンが失敗したか(失敗の種類)、そして答えが合っていたか(品質)です。前のモジュールで見たように、このパイプラインは根拠検査を通過した誤答を出します(モジュール5)。土曜日の質問に平日の時間を答えても、数字がドキュメントにあるので通過します。遅延とエラーだけを測るダッシュボードでは、この誤答が「成功」として集計されます。
どう動くのか
トレースとスパン。OpenTelemetryの形に従います。スパンごとにtrace_id・span_id・parent_id・名前・開始・終了・状態・属性があります。1ターンが、ルートスパン(turn)1つと、子スパン(asr・retrieve・llm・verify・tts)を持ちます。属性には、確定したテキスト、見つけたドキュメント、答え、出典を入れます。この元データ1つから、遅延分布(スパンの長さ)、WER(asrスパンのテキスト対正解)、品質(ルートスパンの答え対正解表)、失敗率(状態)がすべて出ます。指標ごとに別々にログを残すと、異なるターンを数えることになって、数字同士が合いません。
WERを見直す。合成した電話クエリ12個で、このパイプラインのASRのWERは20%近くでした(モジュール3のLibriSpeechでは1%余り)。同じモデルです。評価データがサービスと似ている必要がある理由が、数字で見えます。
3つの品質指標。(1)事実の正確さ: 答えのある質問で、答えに正解の事実(「9 a.m.」、「$80」…)のうち1つが含まれているか。(2)拒否の正確さ: 答えのない質問を拒否したか。(3)根拠一致率: 話した答えの数字が、引用ドキュメントにあるか。このパイプラインは、拒否の正確さ100%、根拠一致率100%なのに、事実の正確さは50%でした(クラスターのnuc1ノードで測った値)。同じモデル・同じシード・temperature 0なのに、Mac(arm64)では30%でした。CPUが違うと浮動小数点の計算順序が変わって、0.5Bモデルの答えが何個か変わります。そのため、自分の実行で測り直し、ベースライン版も同じ種類のノードで作ります。0.5Bモデルがドキュメントの別の数字を写したり、検査で落ちた答えの代わりの抽出文が質問を外れたりするからです。根拠の一致は、正確さではありません。
エラーと失敗は違います。3つの段階をわざと失敗させてみると、はっきりします。ASRが空のテキストを出すと、ユーザーは「もう一度お願いします」を聞きます(再質問、ユーザーの失敗)。LLMがタイムアウトしても、検索した段落の文を読み上げれば、ユーザーは答えを聞けます(1段階下げた応答、エラーだが失敗ではない)。TTSが失敗すると、ユーザーは沈黙を聞きます(失敗)。エラーが出たターン3つのうち、ユーザーの失敗は2つでした。ダッシュボードにエラー率だけを置くと、「静かにうまく切り抜けたエラー」と「ユーザーを見捨てたエラー」を区別できません。
SLOとゲート。SLO(サービスレベル目標)は、ユーザーが体験することで書きます。例: 最初の音のp95が2.5秒以下、事実の正確さ0.6以上、ユーザー失敗率5%以下。測定と比べて、合格・未達を判定します。ゲート(gate)は一歩進んで、ベースライン版より悪化していないかを見ます。指標ごとに方向と許容幅が違います。WERと失敗率は上がれば悪く(絶対幅)、事実の正確さは下がれば悪く、遅延はマシンごとに揺れるので、比率(例: 20%)で見ます。ゲートもコードなので、止めるべきものを入れてみて、赤信号が点くかをテストします。
現場での姿
評価データは、小さく始めて、事故のたびに増やします。このコースの正解表は、12個のクエリに手で付けた正解ドキュメントと事実だけです。それでも、事実の正確さが半分余りだということ、根拠一致率100%がそれを隠すことを示すには十分です。運用では、実際の通話から引き継がれたターン(handoff)と再質問ターンをサンプルとして取り出し、正解表に足していきます。LabHubリポジトリが、ゲートを新しく作るたびに「止めたいものを入れてみて、赤信号が点くかを確認する」と書き残している(AGENTS.md)のも、同じ原則です。
遅延指標はマシンに左右されるという点も、記録に残します。同じパイプラインが、ノードのCPUによって数十%違う時間がかかります。そのため、ゲートでは、遅延を同じマシンで測ったベースラインと比較するか、比率で見て、ベースライン版の指標に「どこで測ったか」も書き添えます。絶対的なしきい値1つで判定すると、遅いノードで動かした日ごとに、偽のリグレッションが出ます。逆に、品質指標(WER・事実の正確さ)はマシンに左右されないので、同じ入力・同じシードでもう一度動かせば、同じ数字が出る必要があります。違う数字が出たなら、それ自体が調査すべき出来事です。
次のラボですること
ベースラインのパイプラインにスパン記録を仕込み、12個のクエリをトレースとして残します。同じ元データから、段階別のp50/p95、WER、事実・拒否の正確さと根拠一致率を計算します。3つの段階を失敗させて、エラーターンとユーザーの失敗を分け、SLOを決めて判定します。ベースライン版に対するリグレッションを止めるgate.pyを作って、採点ツールの隠しケースの試験を受け、今回の版の指標でゲートを動かします。