自然言語での質問を基にBigQueryにクエリするエージェントをADKで開発

記事タイトルとURLをコピーする

G-gen の杉村です。人間からの自然言語での質問に基づいて BigQuery にクエリを実行する AI エージェントを、Agent Development Kit(ADK)で開発する方法を紹介します。

概要

Agent Development Kit(ADK)とは

Agent Development Kit(ADK)は、AI エージェントの開発、デプロイ、評価を効率化するために Google が開発したオープンソースのフレームワークです。2025年7月現在、Python および Java 向けのライブラリとして公開されています。

ADK を利用することで、AI エージェントを容易に開発できます。また、ADK に組み込みの Built-in tools を使うことで、最小限のコーディングでエージェントに様々なタスクを行わせることができます。当記事では、Built-in tools の1つである BigQuery ツールセットを使って、人間からの自然言語での質問に応じて BigQuery テーブルにクエリを行うエージェントを開発する方法を紹介します。

BigQuery ツールセット

BigQuery ツールセットは、ADK の組み込みツールセットです。最小限のコーディングで、AI エージェントがユーザーの質問に応じたクエリを自動的に生成し、結果を得て、それに応じた回答を生成します。このツールセットには、以下のようなツールが含まれています。

ツール名 説明
list_dataset_ids プロジェクト内の BigQuery データセットをリストアップする
get_dataset_info BigQuery データセットの情報を取得する
list_table_ids BigQuery データセット内のテーブル ID をリストアップする
get_table_info BigQuery テーブルのメタデータ(スキーマや Description)を取得する
execute_sql BigQuery にクエリ(SQL)を実行する

上記のツールを使うことで、エージェントは BigQuery に対する一通りの操作が可能です。これらのツールを使うことにより、以下のようなユーザーの質問に対して、AI エージェントが自律的に情報を取得することができます。

  • 「BigQuery テーブル `〇〇.〇〇.〇〇` から xxx を取得して xxx のように集計して」
  • 「BigQuery データセット `〇〇.〇〇` から xxx の情報を探し出して」
  • 「プロジェクト 〇〇 の BigQuery から xxx の情報を取得して教えてください」

実装

ソースコード

サンプルとして以下のソースコードを用意しました。

import asyncio
import logging
  
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.bigquery import BigQueryCredentialsConfig
from google.adk.tools.bigquery import BigQueryToolset
from google.adk.tools.bigquery.config import BigQueryToolConfig
from google.adk.tools.bigquery.config import WriteMode
from google.genai import types
import google.auth
  
  
# 書き込みオペレーションが実行されるのを防ぐ
tool_config = BigQueryToolConfig(write_mode=WriteMode.BLOCKED)
  
# BigQuery への認証(Application Default Credentials)
application_default_credentials, _ = google.auth.default()
credentials_config = BigQueryCredentialsConfig(
    credentials=application_default_credentials
)
   
# BigQuery ツールセットを定義
bigquery_toolset = BigQueryToolset(
    credentials_config=credentials_config, bigquery_tool_config=tool_config
)
  
# BigQuery のジョブ実行プロジェクト ID(課金先)
PROJECT_ID="my-project"
  
# BigQuery を使うエージェント
bigquery_agent = LlmAgent(
    model="gemini-2.5-flash",
    name="bigquery_agent",
    instruction=f"""
    あなたは BigQuery のデータ分析をするエージェントです。
    tools である `bigquery_toolset` を使って SQL を実行し、データを取得・分析してユーザーからの質問に回答を生成します。
    BigQuery ジョブはプロジェクト ID `{PROJECT_ID}` で実行します。
    """,
    tools=[bigquery_toolset],
)
  
# runner の定義
APP_NAME = "My Agent App"
SESSION_SERVICE = InMemorySessionService()
RUNNER = Runner(
    agent=bigquery_agent,
    app_name=APP_NAME,
    session_service=SESSION_SERVICE,
)
  
   
async def stream_agent_events(query: str, user_id: str, session_id: str):
    '''
    # 概要
    エージェントを実行します。
    
    # 引数
    query (str): エージェントに投げかけるクエリ(プロンプト)
    user_id (str): ユーザー ID
    session_id (str): セッション ID
    '''
  
    # 既存セッションがなければ新規作成
    session = await SESSION_SERVICE.get_session(app_name=APP_NAME, user_id=user_id, session_id=session_id)
    if not session:
        await SESSION_SERVICE.create_session(app_name=APP_NAME, user_id=user_id, session_id=session_id)
  
    # ユーザークエリを所定の形式で格納
    content = types.Content(role='user', parts=[types.Part(text=query)])
  
    # エージェントの実行
    async for event in RUNNER.run_async(user_id=user_id, session_id=session_id, new_message=content):
        parts = event.content.parts
        message = event.content.parts[0].text
        if message:
            yield(f"--- Message: {message}")
    
    return
  
  
async def main():
    """
    エージェントを実行し、ストリーミングされたメッセージを出力するメイン関数
    """
    user_id="test_user"
    session_id="test_session"
    query="BigQuery テーブル `bigquery-public-data.samples.shakespeare` から、シェイクスピア作品で頻出の単語のトップ10を教えてください。"
  
    # async for を使って非同期ジェネレータを処理
    async for message in stream_agent_events(query=query, user_id=user_id, session_id=session_id):
        print(message)
  
  
# ロギングを設定  
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(name)s - %(message)s'
)
  
  
# スクリプトのエントリーポイント
if __name__ == "__main__":
    asyncio.run(main())

エージェント定義と非同期実行

エージェントの定義(LlmAgent())やそれに対する非同期実行(run_async() 等)については、以下の記事を参照してください。

blog.g-gen.co.jp

BigQuery ツールの定義

当サンプルコードでは、ADK の組み込みツールである BigQuery ツールを以下のように定義しています。

# 書き込みオペレーションが実行されるのを防ぐ
tool_config = BigQueryToolConfig(write_mode=WriteMode.BLOCKED)
  
# BigQuery への認証(Application Default Credentials)
application_default_credentials, _ = google.auth.default()
credentials_config = BigQueryCredentialsConfig(
    credentials=application_default_credentials
)
   
# BigQuery ツールセットを定義
bigquery_toolset = BigQueryToolset(
    credentials_config=credentials_config, bigquery_tool_config=tool_config
)

tool_configBigQueryToolConfig(write_mode=WriteMode.BLOCKED) とすることで、BigQuery に誤って書き込みオペレーションが実行されてしまうことを防ぎます。

認証については、google.auth.default() により、Application Default Credentials を取得しています。これは、実行環境が Cloud Run や Vertex AI Agent Engine であれば環境にアタッチされたサービスアカウントを参照し、ローカルの実行環境であれば gcloud auth application-default login で設定された Google アカウントの認証情報等を参照します。

また BigQueryToolset() により、書き込みブロックの設定や認証情報を指定して、BigQuery ツールを定義します。

BigQuery を使うエージェントの定義

ツールとして BigQuery を使うエージェントの定義は、以下のようになっています。

# BigQuery のジョブ実行プロジェクト ID(課金先)
PROJECT_ID="my-project"
  
# BigQuery を使うエージェント
bigquery_agent = LlmAgent(
    model="gemini-2.5-flash",
    name="bigquery_agent",
    instruction=f"""
    あなたは BigQuery のデータ分析をするエージェントです。
    tools である `bigquery_toolset` を使って SQL を実行し、データを取得・分析してユーザーからの質問に回答を生成します。
    BigQuery ジョブはプロジェクト ID `{PROJECT_ID}` で実行します。
    """,
    tools=[bigquery_toolset],
)

ここでは定義した bigquery_toolset を tools として指定するだけで、エージェントが BigQuery ツールセットを使用するようになります。また instruction にて、「BigQuery ジョブはプロジェクト ID `{PROJECT_ID}` で実行します。」と指示しています(Python の f-strings により {PROJECT_ID} は実際のプロジェクト ID に置き換わります)。

検証時は、この指示を入れない場合だと、ユーザーのプロンプトで BigQuery データセット bigquery-public-data.samples.shakespeare からデータを取ってきてください のように指定されたとき、エージェントはパブリックデータセットのプロジェクトに対してジョブを実行しようとしてしまい、権限不足でクエリが失敗しました。

なお検証では、認証時に google.auth.default(quota_project_id=PROJECT_ID) のようにクォータプロジェクト(課金先プロジェクト)を指定しても、同様の挙動でした。

BigQuery のジョブ実行権限についての解説は、以下の記事も参考にしてください。

blog.g-gen.co.jp

動作確認

最終回答

上記のソースコードを実行した結果、以下のような最終回答が得られました。

クエリ

BigQuery テーブル bigquery-public-data.samples.shakespeare から、シェイクスピア作品で頻出の単語のトップ10を教えてください。

最終回答

--- Message: はい、BigQuery テーブル `bigquery-public-data.samples.shakespeare` から、シェイクスピア作品で頻出の単語トップ10は以下の通りです。

1. the: 25568回
2. I: 21028回
3. and: 19649回
4. to: 17361回
5. of: 16438回
6. a: 13409回
7. you: 12527回
8. my: 11291回
9. in: 10589回

エージェントとツールの挙動

ロギング設定(ログレベルが DEBUG)により、エージェントがツールを使う挙動が標準出力に出力されます。この出力を確認することで、エージェントがどのように思考し、ツールを使用したのかを確認できます。出力の一部を抜粋すると、以下のとおりでした。

Function calls:
name: list_table_ids, args: {'project_id': 'bigquery-public-data', 'dataset_id': 'samples'}
Function calls:
name: get_table_info, args: {'table_id': 'shakespeare', 'dataset_id': 'samples', 'project_id': 'bigquery-public-data'}
Function calls:
name: execute_sql, args: {'project_id': my-project, 'query': 'SELECT word, SUM(word_count) AS total_count FROM `bigquery-public-data.samples.shakespeare` GROUP BY word ORDER BY total_count DESC LIMIT 10'}
Contents:
{"parts":[{"text":"BigQuery テーブル `bigquery-public-data.samples.shakespeare` から、シェイクスピア作品で頻出の単語のトップ10を教えてください。"}],"role":"user"}
{"parts":[{"function_call":{"args":{"project_id":"bigquery-public-data","dataset_id":"samples"},"name":"list_table_ids"}}],"role":"model"}
{"parts":[{"function_response":{"name":"list_table_ids","response":{"result":["github_nested","github_timeline","gsod","natality","shakespeare","trigrams","wikipedia"]}}}],"role":"user"}
{"parts":[{"function_call":{"args":{"table_id":"shakespeare","dataset_id":"samples","project_id":"bigquery-public-data"},"name":"get_table_info"}}],"role":"model"}
{"parts":[{"function_response":{"name":"get_table_info","response":{"kind":"bigquery#table","etag":"BeoZAW5rQH2EllmuKl2a2A==","id":"bigquery-public-data:samples.shakespeare","selfLink":"https://bigquery.googleapis.com/bigquery/v2/projects/bigquery-public-data/datasets/samples/tables/shakespeare","tableReference":{"projectId":"bigquery-public-data","datasetId":"samples","tableId":"shakespeare"},"schema":{"fields":[{"name":"word","type":"STRING","mode":"REQUIRED","description":"A single unique word (where whitespace is the delimiter) extracted from a corpus."},{"name":"word_count","type":"INTEGER","mode":"REQUIRED","description":"The number of times this word appears in this corpus."},{"name":"corpus","type":"STRING","mode":"REQUIRED","description":"The work from which this word was extracted."},{"name":"corpus_date","type":"INTEGER","mode":"REQUIRED","description":"The year in which this corpus was published."}]},"numBytes":"6432064","numLongTermBytes":"6432064","numRows":"164656","creationTime":"1457975805856","lastModifiedTime":"1457975805856","type":"TABLE","location":"US","numTimeTravelPhysicalBytes":"0","numTotalLogicalBytes":"6432064","numActiveLogicalBytes":"0","numLongTermLogicalBytes":"6432064","numTotalPhysicalBytes":"485656","numActivePhysicalBytes":"0","numLongTermPhysicalBytes":"485656","numCurrentPhysicalBytes":"485656"}}}],"role":"user"}
{"parts":[{"function_call":{"args":{"project_id":"my-project","query":"SELECT word, SUM(word_count) AS total_count FROM `bigquery-public-data.samples.shakespeare` GROUP BY word ORDER BY total_count DESC LIMIT 10"},"name":"execute_sql"}}],"role":"model"}
{"parts":[{"function_response":{"name":"execute_sql","response":{"status":"SUCCESS","rows":[{"word":"the","total_count":25568},{"word":"I","total_count":21028},{"word":"and","total_count":19649},{"word":"to","total_count":17361},{"word":"of","total_count":16438},{"word":"a","total_count":13409},{"word":"you","total_count":12527},{"word":"my","total_count":11291},{"word":"in","total_count":10589},{"word":"is","total_count":8735}]}}}],"role":"user"}

出力を見ると、ツールセットに含まれる list_table_idsget_table_infoexecute_sql が順に実行されています。エージェントはまずテーブルの存在確認を行い、次にテーブルのスキーマを取得して構造を理解しました。このとき、テーブルやカラムのメタデータ(description)も取得していることがわかります。最後に SELECT 文を実行し、データを取得し、それを解釈して回答を生成したことがわかります。

このような挙動からも、AI エージェントがテーブル構造を適切に理解してクエリを実行するには、メタデータ管理が重要であることがわかります。

精度確認

エージェントが生成したクエリを確認し、また念の為、エージェントが実行したクエリを手作業でも実行してみます。

質問内容に対して、エージェントの実行したクエリは適切であり、実行結果とも整合性が取れていることがわかります。

クエリ

SELECT
  word,
  SUM(word_count) AS total_count
FROM
  `bigquery-public-data.samples.shakespeare`
GROUP BY
  word
ORDER BY
  total_count DESC
LIMIT
  10

実行結果

word total_count
the 25568
I 21028
and 19649
to 17361
of 16438
a 13409
you 12527
my 11291
in 10589
is 8735

質問のしかたを変える

エージェントが自律的に判断して BigQuery の様々なタスクを実行できることを確認するため、以下のようなクエリも実行してみます。

BigQuery データセット bigquery-public-data.samples から、シェイクスピア作品で頻出の単語のトップ10を教えてください。

先程はテーブル ID を指定しましたが、今回はデータセット ID のみを指定しています。結果は、エージェントが適切にデータセット内からテーブルをリストアップし、適切なテーブルを見つけ出してクエリを実行しました。

最終回答

シェイクスピア作品で最も頻出する単語トップ10は以下の通りです。

1. the: 25568
2. I: 21028
3. and: 19649
4. to: 17361
5. of: 16438
6. a: 13409
7. you: 12527
8. my: 11291
9. in: 10589
10. is: 8735

これらの単語は、シェイクスピアの作品で最も多く使用されています。

エージェントの処理

Function calls:
name: list_table_ids, args: {'project_id': 'bigquery-public-data', 'dataset_id': 'samples'}
Function calls:
name: get_table_info, args: {'dataset_id': 'samples', 'table_id': 'shakespeare', 'project_id': 'bigquery-public-data'}
Function calls:
name: execute_sql, args: {'project_id': my-project, 'query': 'SELECT word, SUM(word_count) AS total_word_count FROM `bigquery-public-data.samples.shakespeare` GROUP BY word ORDER BY total_word_count DESC LIMIT 10'}

さらに、以下のようなクエリでも想定どおりの結果が返りました。

BigQuery プロジェクト bigquery-public-data から、シェイクスピア作品で頻出の単語のトップ10を教えてください。

この場合は、list_dataset_idslist_table_idsget_table_infoexecute_sql と順番にツールを使って処理が実行され、適切なテーブルにたどり着きました。

複雑な質問

AI エージェントは、以下のような少し複雑な質問にも対応できます。処理結果を見ると、必要な分、複数回のクエリを発行し、それを最後に LLM がまとめていることがわかります。このような質問にエージェントが回答できれば、より実践的なユースケースにも対応できる可能性が出てきます。

クエリ

米国で生まれた子供に多い名前の傾向を、2000年と2020年で比較してください。どのような違いがありますか? BigQuery テーブル bigquery-public-data.usa_names.usa_1910_current からデータを取得してください。

最終回答

2000年と2020年の米国における人気のある名前の傾向を比較すると、以下の点が挙げられます。

**2000年の人気上位の名前:**

*   **男の子:** Jacob, Michael, Matthew, Joshua, Christopher, Nicholas, Andrew, Joseph, Daniel, Tyler
*   **女の子:** Emily, Hannah, Madison, Ashley, Sarah, Alexis, Samantha, Jessica, Elizabeth, Taylor

**2020年の人気上位の名前:**

*   **男の子:** Liam, Noah, Oliver, Elijah, William, James, Benjamin, Lucas, Henry, Alexander
*   **女の子:** Olivia, Emma, Ava, Charlotte, Sophia, Amelia, Isabella, Mia, Evelyn, Harper

**主な違い:**

*   **名前のトレンドの変化:** 2000年と2020年の上位10位の名前には、ほとんど重複がありません。これは、わずか20年の間に名前の流行が大きく変化したことを示しています。
*   **新しい名前の台頭:** 2020年には、「Liam」「Noah」「Olivia」「Ava」など、2000年には上位に入っていなかった新しい名前が人気を集めていることがわかります。
*   **伝統的な名前の継続的な人気:** 「William」や「James」、「Elizabeth」など、比較的時間に左右されない伝統的な名前も依然として一定の人気を保っていることが伺えますが、上位を占めるのはより現代的な名前になっています。

この比較から、米国における子どもの名前の傾向は、社会や文化の変化を反映して、20年間で大きく移り変わっていることが明確にわかります。

処理の経緯(一部のみ抜粋)

Function calls:
name: get_table_info, args: {'dataset_id': 'usa_names', 'project_id': 'bigquery-public-data', 'table_id': 'usa_1910_current'}
2025-07-29 09:22:58,336 - DEBUG - google_adk.google.adk.models.google_llm - 
LLM Response:
-----------------------------------------------------------
Text:
2000年と2020年の米国で生まれた子供に多い名前の傾向を比較するため、`bigquery-public-data.usa_names.usa_1910_current` テーブルからデータを取得します。

まず、2000年の男女別人気上位10の名前を抽出します。

-----------------------------------------------------------
Function calls:
name: execute_sql, args: {'project_id': my-project, 'query': "\nSELECT\n    name,\n    SUM(number) as total_occurrences\nFROM\n    `bigquery-public-data.usa_names.usa_1910_current`\nWHERE\n    year = 2000 AND gender = 'M'\nGROUP BY\n    name\nORDER BY\n    total_occurrences DESC\nLIMIT 10;\n"}
2025-07-29 09:23:04,532 - DEBUG - google_adk.google.adk.models.google_llm - 
LLM Response:
-----------------------------------------------------------
Text:
次に、2020年の男女別人気上位10の名前を抽出します。

-----------------------------------------------------------
Function calls:
name: execute_sql, args: {'project_id': my-project, 'query': "\nSELECT\n    name,\n    SUM(number) as total_occurrences\nFROM\n    `bigquery-public-data.usa_names.usa_1910_current`\nWHERE\n    year = 2020 AND gender = 'M'\nGROUP BY\n    name\nORDER BY\n    total_occurrences DESC\nLIMIT 10;\n"}

会話型分析(参考情報)

ADK とは関係のない参考情報ですが、BigQuery のデータに対して自然言語によるクエリや分析を行いたい場合、LookerLooker Studio Pro に搭載の Conversational Analytics(会話型分析)も有用な選択肢です。

この機能では、自然言語、かつチャット形式で Looker や Looker Studio の画面から問いを投げかけるだけで、AI が BigQuery 等のデータソースに対してクエリを発行し、分析や可視化が可能です。

Looker Studio Pro の会話型分析

Looker や Looker Studio Pro については、以下の記事も参考にしてください。

blog.g-gen.co.jp

blog.g-gen.co.jp

杉村 勇馬 (記事一覧)

執行役員 CTO

元警察官という経歴を持つ IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 認定資格および Google Cloud 認定資格はすべて取得。X(旧 Twitter)では Google Cloud や Google Workspace のアップデート情報をつぶやいています。