はじめに
この記事は Google ADK によるマルチエージェント社内ITヘルプデスク構築 をテーマにした全4回シリーズの第4回です。
| 回 | テーマ |
|---|---|
| 第1回 | Gemini CLI でエージェントを動かしてみる |
| 第2回 | ADK でマルチエージェントを構築し Google Cloud にデプロイする |
| 第3回 | ADK マルチエージェントを BigQuery と連携させる |
| 第4回(本記事) | Gen AI evaluation service で ADK マルチエージェントの応答品質を測る |
第3回までで、ルーター・FAQ検索・エスカレーションの3つのエージェントが BigQuery と連携して動き、inquiry_log に問い合わせ履歴が残る状態が完成しました。
手元で動かす限り「だいたいうまくいっている」ようには見えます。
しかし、その状態ではどの程度うまくいっているのか、どこから改善すべきなのかを説明できません。
本記事では Gen AI Evaluation Service を使い、マルチエージェントの応答品質を3つの軸で定量評価します。
最終的に「複数の指標で実測値を持つ → 改善の優先順位がつけられる」状態に持っていきます。
何を評価するか(3軸)
社内ITヘルプデスクのマルチエージェントで「品質」と言ったとき、実は1つの数字には収まりません。
最低限、次の3つの軸で別々に測る必要があります。
| 軸 | 測りたいこと | 評価方法 |
|---|---|---|
| ① ルーティング精度 | ルーターが正しいエージェントに振り分けたか | Exact Match(期待値 vs 実測値) |
| ② 回答品質 | FAQ検索エージェントの回答が質問に対して適切か | Gen AI Evaluation の coherence(首尾一貫性、標準メトリクス)+ faq_groundedness(FAQ に基づくか、カスタムメトリクス)+ expected_faq_id の Exact Match(自前チェック) |
| ③ フォールバック判定 | 解決不能な質問を正しくエスカレーションできたか | Exact Match(expected_resolved vs 実測値) |
3軸に分けるのは、それぞれスコアが落ちた時の改善ポイントが違う からです。
ルーティング精度が低ければ instruction やルーター側の判断ロジックを直す。回答品質が低ければ FAQ の中身か instruction を直す。
フォールバック判定が甘ければ「迷ったらエスカレ」のような instruction を強化する。一つの総合スコアにまとめてしまうと、どこを直せばいいかが見えなくなります。
評価データセットの設計
12 ケースの内訳
なお、今回の12ケースは評価設計を説明するための小規模な検証データセットです。
そのため、ここでの精度は本番全体の品質を統計的に保証するものではなく、改善ポイントを発見するためのベースラインとして扱います。
| 種別 | ケース数 | 備考 |
|---|---|---|
第3回の inquiry_log と同じケース |
4 | シリーズ間の連続性 |
| エッジケース(誤判定誘発・冗長/極短質問・新カテゴリ等) | 8 | エージェントが取りこぼしやすいケースを炙り出す |
なお、これ以降登場する NW-001 AC-001 などの FAQ ID は、第3回で作った faq_master テーブルの主キーです。カテゴリ略号(NW=ネットワーク/AC=アカウント/HW=ハードウェア/SW=ソフトウェア)+ 連番の形式で、例えば AC-001 は「パスワードを忘れた/アカウントがロックされた」という FAQ を指します。
エッジケースは意図的に「エージェントが取りこぼしそうな問い合わせ」を混ぜています。例えば:
- C05: 「新しくアカウントを作成してほしい」 — 「アカウント」というキーワードに引っかかって AC-001(パスワード忘れ)に誤マッチしないか
- C07: 「プリンターのトナーが切れたので注文したい」 — 「プリンター」キーワードで HW-001(印刷できない)に誤マッチしないか(実態は調達系)
- C08: 「社内 Wi-Fi が突然全員繋がらなくなった」 — Wi-Fi キーワードで NW-002 に行くか、それとも「全員」という影響範囲で全社障害扱いするか
- C10: 冗長な前置きで質問するケース — 長文の中から意図を拾えるか
- C11: 「Teams のチャンネル作成権限がほしい」 — Teams キーワードで SW-002 に行くか、それとも権限管理として別案件扱いするか
eval_dataset.jsonl のスキーマ
各ケースに ground truth(期待値) と context(FAQ 解決時の参照テキスト) を持たせます。
{
"case_id": "C01",
"question": "VPN に接続しようとすると認証エラーが出て接続できません",
"expected_category": "ネットワーク系",
"expected_resolved": true,
"expected_faq_id": "NW-001",
"expected_routed_to": "faq_searcher_agent",
"context": "以下の手順を試してください。\n1. Caps Lock がオフになっているか確認する\n..."
}
context は対応する FAQ の answer を入れます。後述するカスタム評価メトリクス(faq_groundedness)が「応答がこの context に基づいているか」を判定するための reference になります。エスカレーション期待ケース(expected_resolved=false)では context は空にしておきます。
評価メトリクスの選び方
標準メトリクスを試したら groundedness が全件 0 だった
最初は Gen AI Evaluation の標準メトリクスだけで済ませようと考えました。
from vertexai.evaluation import EvalTask, MetricPromptTemplateExamples metrics = [ MetricPromptTemplateExamples.Pointwise.COHERENCE, MetricPromptTemplateExamples.Pointwise.GROUNDEDNESS, ]
ところが実行してみると groundedness が全件 0.0、std も 0.0 という不自然な結果に。原因を調べると、標準 GROUNDEDNESS のプロンプトには次のように書かれていました。
groundedness, which measures the ability to provide or reference information included only in the user prompt.
本記事執筆時点で確認した標準 GROUNDEDNESS のテンプレートでは、user prompt に含まれる情報を参照できているかを評価する設計になっていました。
そのため、今回のように FAQ の answer を context として別途持たせる構成では、期待した評価になりませんでした。
ユーザーの質問は短く(「VPN に接続できません」)、応答は長い(FAQ の手順)なので、標準メトリクスでは 0 になったと考えられます。
カスタム pointwise メトリクスを書く
そこで PointwiseMetric でカスタムメトリクスを定義しました。
from vertexai.evaluation import PointwiseMetric, PointwiseMetricPromptTemplate
faq_groundedness = PointwiseMetric(
metric="faq_groundedness",
metric_prompt_template=PointwiseMetricPromptTemplate(
criteria={
"groundedness": (
"The response should be based only on the FAQ context provided. "
"It must not introduce facts, URLs, system names, or contact "
"information that are absent from the FAQ context."
),
},
rating_rubric={
"5": "Fully grounded in the FAQ context. No extra information.",
"3": "Mostly grounded but introduces minor reformulations or extras.",
"1": "Largely diverges from the FAQ context or fabricates information.",
},
input_variables=["question", "context", "response"],
),
)
input_variables で question / context / response の 3 つを指定すると、評価データセット側に同名の列を用意するだけで、自動的にプロンプトに差し込まれます。
最終的に使うメトリクスは coherence(標準)+ faq_groundedness(カスタム)+ expected_faq_id の Exact Match の 3 種類になりました。
評価実装のキモ
評価対象のエージェントは、再デプロイの時間とコストを節約するため、ADK の Runner を使って手元のプロセスから実行します。Runner は ADK の実行ループを担うコンポーネントで、run_async() によりエージェント実行中のイベントを逐次取得できます。
今回はこのイベントから、最終応答だけでなく function_call / function_response の情報も拾います。
評価に必要なメタデータを ADK イベントから取り出す
エージェントの応答テキストだけでは、ルーティング先・カテゴリ・FAQ ID といったメタデータが取れません。これらは ADK のイベントから直接拾います。
async for event in runner.run_async(...): for part in event.content.parts: # 1. log_inquiry の引数からカテゴリ・routed_to・resolved を取得 if part.function_call and part.function_call.name == "log_inquiry": log_args = dict(part.function_call.args) # 2. search_faq の戻り値から FAQ ID を抽出 if part.function_response and part.function_response.name == "search_faq": m = re.search(r"FAQ ID:\s*([A-Z]+-\d+)", str(part.function_response.response)) if m: faq_id = m.group(1) # 3. 応答テキストは重複排除しつつ蓄積 if part.text and part.text not in seen_texts: response_text_parts.append(part.text) seen_texts.add(part.text)
最後の重複排除がポイントです。ADK の親エージェントと子エージェントは同じ text を流すことがあるため、素直に蓄積すると応答が2回繰り返されたまま評価に流れてしまいます。
Gen AI Evaluation の呼び出し
eval_df = pd.DataFrame([
{
"prompt": r["question"], # COHERENCE 用
"question": r["question"], # カスタム faq_groundedness 用
"response": r["actual_response"],
"context": r["context"],
}
for r in faq_results
])
eval_task = EvalTask(
dataset=eval_df,
metrics=[
MetricPromptTemplateExamples.Pointwise.COHERENCE,
faq_groundedness,
],
)
result = eval_task.evaluate()
返ってくる result.summary_metrics に集約スコア、result.metrics_table に各ケースのスコアが入ります。12ケース分の評価は約 20 秒で完了しました。
実行結果
数字サマリ
========== 評価結果サマリ ========== 総ケース数: 12 ① ルーティング精度: 83.3%(10/12) ② FAQ ID 一致率: 80.0%(FAQ解決が期待される5ケース中 4件一致) ③ フォールバック判定精度: 83.3%(10/12) 【回答品質】 coherence/mean: 4.0 coherence/std: 1.41 faq_groundedness/mean: 2.6 faq_groundedness/std: 0.89
coherence(首尾一貫性)は 4.0 と高めである一方、faq_groundedness は 5段階評価の中では低めでした。
応答を読みやすく整えることは LLM の得意分野ですが、FAQ への忠実さには改善余地があることが分かります。
誤判定したケースの分析
ルーティングとフォールバックで誤判定が出たのは2件、どちらもエッジケースでした。
C08: 「社内 Wi-Fi が突然全員繋がらなくなりました」
| 期待 | 実測 | |
|---|---|---|
| ルーティング | エスカレーション(全社障害) | FAQ検索(NW-002 で回答) |
LLM は「Wi-Fi」というキーワードに反応して NW-002 をヒットさせ、そのまま回答してしまいました。「全員繋がらない=影響範囲が大きい=エスカレすべき」という文脈判断が、キーワードマッチに負けた形です。
第3回で複合語キーワードを採用してマッチング精度は上げたのですが、影響範囲のような業務文脈は別軸で見る必要があります。
C10: 「いつもお世話になっております。実は先週から…パスワードを忘れて…どうすればリセットできるのでしょうか」
| 期待 | 実測 | |
|---|---|---|
| ルーティング | FAQ検索(AC-001) | エスカレーション |
冗長な前置きで質問したケース。本来は AC-001(パスワードリセット)でヒットすべきところが、エスカレに流れました。「業務が滞ってしまっています」のような前置きがエスカレを誘発した可能性があります。
一見誤判定だが実は賢い動作(C07・C11)
逆に興味深かったのが C07(プリンタートナー注文)と C11(Teams のチャンネル作成権限)です。どちらも search_faq がキーワードで一旦 FAQ をヒットさせたものの、エージェントが内容を見て「これは印刷できない問題ではなく注文の話」「これは権限管理で別案件」と判断し、エスカレに振り直しました。
検索が弱マッチになっても、最終応答で LLM が正しく振り分ける挙動が確認できた良い例です。
coherence 4.0 / faq_groundedness 2.6 の差は何か
coherence は文の論理的な流れを見るので影響を受けにくい一方、faq_groundedness は「FAQ context にない表現が混じったか」を厳しく見ます。
応答に「以下の手順をお試しください:」のような丁寧語への言い換えが入る、【FAQ ID: NW-001】 のような ID 表記が応答の本文中に混入する、といった要因で減点される構造です。
2.6 というスコアは完全な逸脱(ハルシネーション)ではなく、自然な応答生成の結果として一定の言い換えが入った状態と読めます。
「FAQ を一字一句コピペさせる」のが目的なら instruction を厳格化、「読みやすさを優先して言い換えを許容する」なら現状維持、と方針判断の材料になります。
結果の解釈と改善の方向性
ここでの数値は、前述の12ケースに対するベースラインとして解釈します。
| 指標 | 実測値 | 解釈 |
|---|---|---|
| ルーティング精度 83.3% | 単純な誤マッチ(C08)と前置き混入(C10)が原因 | instruction で「影響範囲・前置き・本題の切り分け」を強化する余地 |
| FAQ ID 一致率 80% | FAQ解決期待ケースのうち C10 が誤ルーティング | ルーティング精度の改善とほぼ連動 |
| フォールバック判定 83.3% | C08 で「Wi-Fi 全員」を見逃した | 影響範囲(全員/全社)を判定するルールを instruction に追加する余地 |
| coherence 4.0 | 応答は読みやすい | 維持 |
| faq_groundedness 2.6 | 応答に FAQ context 外の情報が混入(FAQ ID 表記・補足表現など) | search_faq の戻り値から FAQ ID 表記を除く、または応答整形ルールを instruction で厳格化 |
特に「影響範囲(全員/全社)はキーワードマッチでは捉えられない」という気付きが、評価を通して数字付きで言えるようになりました。
まとめとシリーズ全体について
第4回で達成したのは、マルチエージェントの品質を数字で継続的に確認するための土台を作ったこと です。
- ルーティング・回答・フォールバックの3軸で別々に測ることで、改善の優先順位がつけられるようになった
- Gen AI Evaluation の標準メトリクス(
coherence)とカスタムメトリクス(faq_groundedness)を組み合わせれば、業務要件にぴったり寄せた評価ができる - 評価結果を見ることで、第3回までは見えていなかった弱点(影響範囲の判定・冗長な前置き)が炙り出された
シリーズ全体の総括
全4回を通して、社内ITヘルプデスクのマルチエージェントを段階的に作ってきました。
最初はファイル1枚から始め、次にコードベースのマルチエージェントへ広げ、その後 BigQuery と連携してデータ駆動の構成にし、最後に評価で品質を数値化するという流れです。
| 回 | できるようになったこと |
|---|---|
| 第1回 | Gemini CLI に GEMINI.md 1枚渡せばエージェントが動く |
| 第2回 | ADK でルーター・FAQ検索・エスカレの3エージェント構成を作って Google Cloud にデプロイ |
| 第3回 | FAQ をテーブル化し、問い合わせ履歴を inquiry_log に貯めるデータ基盤を整備 |
| 第4回 | 評価データセットと Gen AI Evaluation で品質を3軸で数値化 |
ここまで来ると、実運用に乗せるための準備が一通り揃った状態です。
次のステップは、評価結果に基づく instruction の改善・FAQ の拡充・実ユーザーからの満足度フィードバック(satisfaction_score カラム)の収集を回す改善サイクルになります。
LLM ベースのエージェントは「動かす」までは比較的すぐ行けますが、「品質を担保し続ける」フェーズに入った瞬間に評価データセットと評価指標が必須になります。
また、今回は12ケースの小規模な評価でしたが、実運用に近づけるには、実際のinquiry_logから失敗例・曖昧な問い合わせ・頻出カテゴリを定期的に抽出し、評価データセットへ追加していく必要があります。
評価データセット自体を育てることで、instruction 改善や FAQ 拡充の効果を継続的に比較できます。
本シリーズがそのスタート地点として参考になれば幸いです。