5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BigQuery Agent Analytics Plugin を使って ADK エージェントの回答を分析してみた

5
Last updated at Posted at 2026-06-29

こんにちは!
KDDIアイレットの取り組みとして6月22日〜7月3日の期間で開催中の「Google Cloud Next '26 / Google I/O やってみた系ブログリレー」、本日は9日目の投稿です。
今回は「BigQuery Agent Analytics Plugin」を対象に、実際に検証してみた様子をお届けします!

前回の記事はこちらです。


BigQuery Agent Analytics Plugin とは

一般提供が開始されたBigQuery Agent Analytics Plugin は、ADKのPluginアーキテクチャと BigQuery Storage Write API を使って、エージェントのイベントをBigQuery のテーブルに直接ログとして書き込むプラグインです。

捕捉できる主なイベントは以下です(一部抜粋)。

イベントタイプ いつ記録されるか 主なペイロード
USER_MESSAGE_RECEIVED ユーザー入力が来たとき テキスト
LLM_REQUEST モデルにリクエストを送ったとき プロンプト, モデル名, ツール
LLM_RESPONSE モデルから応答が返ったとき 応答テキスト, トークン数, レイテンシ
TOOL_STARTING / TOOL_COMPLETED ツール実行の開始/完了 ツール名, 引数, 結果
TOOL_ERROR / LLM_ERROR ツール/モデル呼び出しの失敗 エラーメッセージ
AGENT_RESPONSE 最終的なエージェントの応答 応答内容

書き込みは 非同期なので、エージェントの応答速度を邪魔しません。
さらに 1.27.0以降 では、イベントタイプごとにビュー(v_llm_responsev_tool_completed など)が自動生成されます。本記事でもこのビューを活用します。

BigQuery Agent Analytics Plugin でログを溜め、BigQueryのAI関数(LLM-as-a-Judge) で採点する、という流れを実際にやってみます。


1. エージェントを作る

まずは評価対象になるテスト用のエージェントを作ります。今回は「ユーザーの質問に答えるFAQボット」を題材にします。あえて評価で差が出るようにしておきます。

faq_agent/agent.py:

import os

from google.adk.agents import Agent
from google.adk.apps import App
from google.adk.models.google_llm import Gemini
from google.adk.plugins.bigquery_agent_analytics_plugin import (
    BigQueryAgentAnalyticsPlugin,
    BigQueryLoggerConfig,
)

# --- 設定 ---
PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
DATASET_ID = os.environ.get("BQ_DATASET_ID")
BQ_LOCATION = os.environ.get("BQ_LOCATION")

os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "True"


# --- ツール ---
def get_shipping_policy(region: str) -> dict:
    """指定された地域の配送ポリシーを返す。

    Args:
        region: 地域名(例: "japan", "us""""
    policies = {
        "japan": "通常配送は2〜3営業日、送料は5,000円以上で無料です。",
        "us": "Standard shipping takes 5-7 business days. Free over $50.",
    }
    return {
        "region": region,
        "policy": policies.get(region.lower(), "該当地域のポリシーは未登録です。"),
    }


# --- Plugin ---
bq_config = BigQueryLoggerConfig(
    enabled=True,
    create_views=True,
    custom_tags={"env": "eval", "agent_version": "v1"},
)

bq_plugin = BigQueryAgentAnalyticsPlugin(
    project_id=PROJECT_ID,
    dataset_id=DATASET_ID,
    table_id="agent_events",
    config=bq_config,
    location=BQ_LOCATION,
)


# --- エージェント ---
root_agent = Agent(
    model="gemini-2.5-flash",
    name="faq_agent",
    instruction=(
        "あなたはECサイトのFAQボットです。"
        "配送ポリシーに関する質問には get_shipping_policy ツールを使って正確に答えてください。"
        "ツールで得た情報以外は推測せず、わからない場合は正直にわからないと答えてください。"
    ),
    tools=[get_shipping_policy],
)


# --- App ---
app = App(
    name="faq_agent",
    root_agent=root_agent,
    plugins=[bq_plugin],
)

custom_tagsenvagent_version を入れておくと、後でSQLで「v1のevalランだけ採点する」のような絞り込みができて便利です。


2. エージェントを動かしてログを溜める

評価のためには、まず色々な質問を投げてログを作る必要があります。テスト用の質問リストを回すスクリプトを書きます。

run_eval_session.py:

import asyncio
import uuid

from google.adk.runners import InMemoryRunner
from google.genai import types

from faq_agent.agent import app, bq_plugin

# 評価用の質問セット
TEST_PROMPTS = [
    "日本への配送って何日くらいかかりますか?",
    "送料無料になる条件を教えて",
    "How long does shipping to the US take?",
    "海外配送はやってますか?",
    "返品はできますか?",
]


async def main():
    runner = InMemoryRunner(app=app)
    user_id = "eval_user"

    for prompt in TEST_PROMPTS:
        session = await runner.session_service.create_session(
            app_name=app.name, user_id=user_id, session_id=str(uuid.uuid4())
        )
        content = types.Content(role="user", parts=[types.Part(text=prompt)])

        print(f"\n=== Q: {prompt} ===")
        async for event in runner.run_async(
            user_id=user_id, session_id=session.id, new_message=content
        ):
            if event.is_final_response() and event.content:
                print("A:", event.content.parts[0].text)

    await bq_plugin.flush()


if __name__ == "__main__":
    asyncio.run(main())

実行します。

python run_eval_session.py

これで agent_events テーブルに、5つの質問それぞれの ユーザー入力 → LLMリクエスト → ツール呼び出し → 最終応答 という一連のイベントが流れ込みました。


3. 溜まったログを確認する

BigQuery スタジオで中身を見てみます。

SELECT timestamp, event_type, agent, content
FROM `<PROJECT_ID>.adk_agent_logs.agent_events`
ORDER BY timestamp DESC
LIMIT 20;

contentevent_type ごとに構造が変わる JSON型 です。生でほじくると大変なので、自動生成された ビュー を使います。

例えば「ユーザー入力」と「最終応答」をJOINして、(質問, 回答) のペアを作ってみます。

WITH questions AS (
  SELECT
    invocation_id,
    session_id,
    JSON_VALUE(content, '$.text_summary') AS user_question
  FROM `<PROJECT_ID>.adk_agent_logs.agent_events`
  WHERE event_type = 'USER_MESSAGE_RECEIVED'
),
answers AS (
  SELECT
    invocation_id,
    response_text AS agent_answer
  FROM `<PROJECT_ID>.adk_agent_logs.v_agent_response`
)
SELECT
  q.user_question,
  a.agent_answer
FROM questions q
JOIN answers a USING (invocation_id);

image.png

ここまでで「評価したい (質問, 回答) のペア」が SQL で取り出せる状態になりました。


4. LLM-as-a-Judge

BigQueryには Gemini をSQLから直接呼べるAI関数 が揃っています。代表的なものは以下。

関数 返り値 用途
AI.GENERATE STRING または STRUCT 自由記述・構造化出力(スコア+理由など)
AI.GENERATE_BOOL BOOL 「正しい/正しくない」の二値判定
AI.GENERATE_INT / AI.GENERATE_DOUBLE 数値 スコアを数値で出す
AI.SCORE FLOAT64 プロンプトに基づき行を採点してランキング

AI.GENERATE 系は endpoint でモデルを自分で選べる汎用関数、AI.SCORE はモデルやパラメータを BigQuery 側がよしなに選んでくれるマネージド関数、という違いがあります。順番に試していきます。

4-1. 二値判定

AI.GENERATE_BOOL で「回答が質問に対して妥当か」をTrue/Falseで判定します。

WITH qa AS (
  SELECT
    q.invocation_id,
    JSON_VALUE(q.content, '$.text_summary') AS user_question,
    a.response_text AS agent_answer
  FROM `<PROJECT_ID>.adk_agent_logs.agent_events` q
  JOIN `<PROJECT_ID>.adk_agent_logs.v_agent_response` a
    USING (invocation_id)
  WHERE q.event_type = 'USER_MESSAGE_RECEIVED'
)
SELECT
  user_question,
  agent_answer,
  AI.GENERATE_BOOL(
    CONCAT(
      'あなたはFAQボットの品質評価者です。',
      '次の回答が、質問に対して事実として妥当で、わからないことを誠実に伝えているなら true、',
      'ハルシネーションや的外れがあるなら false と判定してください。\n',
      '質問: ', user_question, '\n',
      '回答: ', agent_answer
    ),
    endpoint => 'gemini-2.5-flash'
  ).result AS is_acceptable
FROM qa;

image.png

「わからないことを正直に答えた」ケースも true 判定されています。
評価基準(誠実さも加点する)がプロンプト通りに効いていることがわかります。

4-2. スコア+理由を同時に出す

二値だと荒すぎるので、「1〜5点のスコア」と「その理由」を取ってみます。AI.GENERATEoutput_schema を使います。

WITH qa AS (
  SELECT
    q.invocation_id,
    JSON_VALUE(q.content, '$.text_summary') AS user_question,
    a.response_text AS agent_answer
  FROM `<PROJECT_ID>.adk_agent_logs.agent_events` q
  JOIN `<PROJECT_ID>.adk_agent_logs.v_agent_response` a
    USING (invocation_id)
  WHERE q.event_type = 'USER_MESSAGE_RECEIVED'
),
judged AS (
  SELECT
    user_question,
    agent_answer,
    AI.GENERATE(
      CONCAT(
        'あなたは厳格なFAQボット評価者です。以下の回答を評価してください。\n',
        '評価軸: (1)事実の正確さ (2)質問への的確さ (3)不明点を誠実に扱っているか\n',
        'score は 1(悪い)〜5(完璧) の整数。reasoning は日本語で簡潔に。\n',
        '質問: ', user_question, '\n',
        '回答: ', agent_answer
      ),
      endpoint => 'gemini-2.5-flash',
      output_schema => 'score INT64, reasoning STRING'
    ) AS judge
  FROM qa
)
SELECT
  user_question,
  agent_answer,
  judge.score,
  judge.reasoning
FROM judged
ORDER BY judge.score ASC;

output_schema => 'score INT64, reasoning STRING' と書くだけで、返り値が judge.score(整数)と judge.reasoning(文字列)のSTRUCTになります。

image.png

4-3. ランキング

「どの回答が一番マシ/ダメか順位付けしたい」だけなら、専用の AI.SCORE が手軽です。プロンプトに沿って行をスコアリングしてくれます。

WITH qa AS (
  SELECT
    JSON_VALUE(q.content, '$.text_summary') AS user_question,
    a.response_text AS agent_answer
  FROM `<PROJECT_ID>.adk_agent_logs.agent_events` q
  JOIN `<PROJECT_ID>.adk_agent_logs.v_agent_response` a
    USING (invocation_id)
  WHERE q.event_type = 'USER_MESSAGE_RECEIVED'
)
SELECT
  user_question,
  agent_answer,
  AI.SCORE(
    STRUCT(
      user_question,
      agent_answer,
      'この回答が質問に対してどれだけ役立つかを1-5で評価してください'
    )
  ) AS helpfulness
FROM qa
ORDER BY helpfulness DESC;

image.png


5. 評価結果を集計してダッシュボード化

採点結果をSQLで集計します。例えば「エージェントのバージョンごとの平均スコア」を出してみます。
ここでcustom_tags に入れた agent_version が効いてきます。

WITH qa AS (
  SELECT
    q.invocation_id,
    -- custom_tags は attributes に入る
    JSON_VALUE(q.attributes, '$.custom_tags.agent_version') AS agent_version,
    JSON_VALUE(q.content, '$.text_summary') AS user_question,
    a.response_text AS agent_answer
  FROM `your-project.adk_agent_logs.agent_events` q
  JOIN `your-project.adk_agent_logs.v_agent_response` a
    USING (invocation_id)
  WHERE q.event_type = 'USER_MESSAGE_RECEIVED'
),
judged AS (
  SELECT
    agent_version,
    AI.GENERATE(
      CONCAT(
        'FAQ回答を1〜5で採点。score INT64のみ返す。\n',
        '質問: ', user_question, '\n回答: ', agent_answer
      ),
      endpoint => 'gemini-2.5-flash',
      output_schema => 'score INT64'
    ).score AS score
  FROM qa
)
SELECT
  agent_version,
  COUNT(*)            AS n,
  ROUND(AVG(score), 2) AS avg_score,
  MIN(score)          AS worst,
  COUNTIF(score <= 2) AS num_bad
FROM judged
GROUP BY agent_version
ORDER BY avg_score DESC;

この結果テーブルをそのまま Looker Studio(データポータル) に繋げば、プロンプトを変えた v1v2 でスコアがどう動いたかを観測できます。

Tips:採点コストを抑える
AI.GENERATE 系は内部で Gemini を呼び出すので、行数が多いとそれなりに課金されます。

  • gemini-2.5-flash のような軽量モデルを使う
  • model_paramsthinking_budget を絞る
AI.GENERATE_BOOL(
  prompt,
  endpoint => 'gemini-2.5-flash',
  model_params => JSON '{"generation_config":{"thinking_config":{"thinking_budget":0}}}'
).result

まとめ

  • ADKの BigQuery Agent Analytics Plugin を使えば、plugins=[plugin] の一行でエージェントの全イベントを BigQuery に流せる
  • 溜まったログには イベント別ビュー が自動で用意されるので、(質問, 回答) のペアがSQLで簡単に取り出せる
  • そのペアに対して AI.GENERATE / AI.GENERATE_BOOL / AI.SCORE を呼ぶだけで、LLMを評価できる
5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?