はじめに
こんにちは。ベガコーポレーションの古川です。
前回の記事で、GoogleのAgent Development Kit(ADK)ベースの社内AIチャットボット「ベガちゃん」をリリースした話を書きました。
(前回記事でも紹介しましたが、ベガちゃんのご尊顔をもう一度どうぞ)
ベガちゃんはSlackをUIとして、社内のドキュメント(Confluence等)を検索して質問に答えることができます。おかげで「この手続きはどうすればいいっけ」系の問い合わせの多くに回答できるようになりました。
一方、ベガちゃんがまだ回答できなかったのがビジネスデータに関する質問です。
「先月の売上はいくら?」
「全社売上に占める新商品の売上割合は?」
こういった問いに答えるには、ドキュメントではなく数値データを探して集計・分析する必要があります。
弊社のデータ基盤はBigQuery (BQ) に集約され、LookerをBIとして社内ユーザーに提供されています。この基盤を使いながら、「ユーザーが聞いたビジネス上の問いに定量的に答えられるエージェント」を目指して、Lookerのデータ探索・調査を行う機能をベガちゃんに追加しました。
(ちなみにこの機能は2026年の年初には完成していましたが、記事を書くのに時間がかかってしまいました。)
先に実際の動作イメージ
ユーザーが「先月のxx店の売上は?」と聞いた場合の流れをざっくり示します。
1. get_dashboard_list()
→ 公式ダッシュボード一覧をBQから取得
→ タイトル・説明から「実店舗」「売上」に関連しそうな候補を選定
2. get_dashboard_definition(dashboard_id=...)
→ 選定したダッシュボードの全タイルリストを取得
→ 「店舗別売上」「実店舗売上推移」などの候補タイルを特定
3. get_dashboard_tile_context(url=..., tile_title="店舗別売上")
→ フィルタ適用後のSQLと実行結果を取得
→ SQLのカラム・データを確認
4a. 既存タイルで答えられる場合
→ 「先月の売り上げはXXXX円です。公式ダッシュボードの『店舗別売上』タイルに先月のデータがあります。URL: ...」
4b. フィルタや粒度が足りない場合
→ get_lookml_model_explore() でフィールド存在確認
→ create_and_run_look() でLookを新規作成
→ 「分析Lookを作成しました。URL: ...」
ダッシュボードという大きな粒度から探索して、そこからダッシュボードタイル、Explore、の順に掘り下げていくことで、信頼性と効率を両立させています。
設計思想:なぜBQではなくLookerに一本化したか
最初に悩んだのが「どこからデータを取らせるか」です。
各所で議論されていますが、選択肢は大きく2つあります。
- BigQueryに直接クエリさせる (Text-to-SQL)
- Looker をベースに回答させる
BQを直接渡すと起きること
AIエージェントにBigQueryを直接渡した場合、3つの問題が起きます。
① 意図しない高額クエリのリスク
全テーブルスキャンを走らせるクエリをエージェントが平気で書いてしまいます。プロダクションのDWHで気軽にフルスキャンされると、月の請求額に響きます。
② ビジネス定義のない生SQLによる結果の不一致
「売上」という言葉一つとっても、出荷ベースなのか受注ベースなのか、キャンセル控除後か否か、モールと実店舗をどう扱うかなど、社内でのビジネス定義があります。これをBQのテーブルからSQLで集計しようとすると、各自が独自解釈で書いたクエリが乱立し、「同じ質問なのに人によって数字が違う」状態になります。
③ ユーザーが結果の妥当性を評価できない
これが最も大きな理由です。
SQLベースで回答する場合、回答した数値の根拠はSQLになってしまいます。多くの社内ユーザーは、SQLや生のテーブルを触る機会がありません。そのため、出力されたSQLが正しいのか、元になっているテーブルが正しいのか、妥当性を評価する手段がありません。
AIの回答精度が十分高ければ、この妥当性評価のプロセスは不要になると思います。しかしながら、現在の体制・データ基盤・AIの能力で得られるSQLの結果では、十分な精度に達するのはまだ先になると考えました。
Lookerに一本化した理由
弊社は長年Lookerを愛用しており、これまでに事業部と会話して定義されたビジネスロジックがLookerに蓄積されています。LookML・Explore・ダッシュボードの中に、ビジネス定義がすでに織り込まれている状態です。これをエージェントにも参照させることで、クエリ結果の統一を図れます。
もう一つの理由が権限管理です。基本的に社内ユーザーがデータ基盤のデータに触れるインターフェースはLookerに集約されており、ユーザーごとの閲覧権限もLookerの機能で制御されています。AIエージェントが利用するツールもLookerに絞ることで、この権限設定をそのまま活かせます。
この考えのもと、エージェントとデータ基盤のインターフェースはLookerに一本化しました。
アーキテクチャ概要
Lookerに対して探索・調査を行うdata_insightsサブエージェントは以下の8つのツールを持ちます。
tools=[
get_dashboard_list, # ダッシュボード一覧取得(BQキャッシュ参照)
get_dashboard_definition, # ダッシュボード定義取得(BQキャッシュ参照)
get_dashboard_tile_context, # ダッシュボードのクエリ結果取得
get_lookml_model_explore, # Explore定義取得
get_explore_context, # ExploreのURL分析
get_look_context, # LookのURL分析
get_merge_query_context, # MergeQueryのURL分析
create_and_run_look, # Look作成・実行
]
大きく「URL解析系」と「データ探索系」に分けられます。
2つのデータ調査動線
想定されるユーザーの使い方は大きく2パターンあります。
動線①:URLを持っている場合
「このExplore見てたんだけど、この数字なんで?」「このダッシュボードのこのタイルがおかしい」という場合です。LookerのURL(Explore・Look・ダッシュボード・Merge Query)をそのまま貼り付けてもらうだけでOKです。
URLをパースして必要な情報を取得します。
# 概念コード
def get_explore_context(url):
# URLをパースしてモデル名・Explore名を抽出
parts = urllib.parse.urlparse(url)
query_params = urllib.parse.parse_qs(parts.query)
# qidパラメータがあれば、フィルタ適用済みのクエリ状態を復元してSQLと結果を取得
slug = query_params["qid"][0]
query_def = looker_sdk.query_for_slug(slug=slug)
sql_query = looker_sdk.run_query(query_id=query_def.id, result_format="sql")
result = looker_sdk.run_query(query_id=query_def.id, result_format="md")
# LookML定義も取得
look_ml = get_lookml_model_explore(
lookml_model_name=model_name,
explore_name=explore_name,
)
return {
"model": model_name,
"explore": explore_name,
"sql": sql_query,
"result": result,
"lookml": look_ml,
}
qidパラメータ(フィルタや選択フィールドを含むクエリスラグ)から、ユーザーが実際に見ていた状態を復元できるのがポイントです。SQL・実行結果・LookML定義をまとめて取得するので、「なぜその数字か」「なぜ意図通りの数字になっていないか」を答えやすくなります。
動線②:新しいデータを探したい場合
「〇〇の月次推移が見たい」「△△のデータって取れる?」という場合です。
ここで探索優先度の設計が重要になります。
ダッシュボード → Explore の順で探す理由
Exploreを先に調べる方が効率よさそうに見えますが、実際はダッシュボードを先に探す方が良い結果につながります。
ダッシュボードは多くの人が閲覧して共通認識が醸成されているため、より強固なビジネスロジックが定義されているという強みがあります。どのフィールドを使えば「売上」が取れるか、どのフィルタが必要か、といった会社のナレッジがタイルの中に凝縮されています。
Exploreは定義の網羅性は高いですが、「どのフィールドが実際に使われているか」「どのフィルタが暗黙の前提か」がわかりにくいです。ダッシュボードのタイルを見れば、そのあたりがほぼ自動的に解決されます。
ダッシュボードの信頼性を段階化した
ただし、Lookerでは誰でも好きなダッシュボードを作れます。「野良ダッシュボード」も存在します。そのまま全部探索するとノイズになるため、対象を絞りました。
| 優先度 | 種別 | 認定フロー |
|---|---|---|
| 高 | Looker運用チームが管理する「公式ダッシュボード」 | 全社審査で承認 |
| 中 | 特定部署で認められた「部門ダッシュボード」 | 部門長が承認 |
| 対象外 | 誰かが個人的に保存した野良ダッシュボード | それ以外 |
弊社では数値の定義や表現を社内で統一するため、広く使われる公式・部門ダッシュボードの作成は承認制としています。エージェントには公式・部門ダッシュボードのみを渡すことで、探索範囲とビジネス的な信頼性を担保しました。
ダッシュボード単体でユーザーの質問に完璧に回答できない場合は、
- ダッシュボードのフィルタを調整
- ダッシュボードのタイルを参考にExploreでLookを新規作成
の順に自由度を増やしていくことで、公式性の高いダッシュボードを足掛かりにして詳細にデータを探索する動線を設計しています。
ちなみに、Looker APIからダッシュボード一覧を取得しようとすると非常に時間がかかるので、日次のバッチで取得してきたダッシュボード一覧/詳細をあらかじめBQのテーブルに保持しておき、AIがダッシュボードを探すときはBQに保持したデータを渡すようにしています。
ユーザーの閲覧権限管理
ここは今回の失敗談枠です。
要件:ユーザーごとに見せていいデータを制御する必要がある
機密性の高いデータは閲覧制限を設け、担当部署のメンバーしか見られないようにする必要があります。
Lookerでは、ユーザーやグループごとに閲覧できるデータ(モデル・Explore・行レベルアクセス)をきめ細かく設定でき、弊社ではこの機能で権限制御を行っています。このガバナンスはそのままAIエージェントにも適用する必要がありました。
最初の実装:LookerのOAuthアプリを作ってOAuth認可フローを実装
「ユーザーの権限でLooker APIを呼び出す」には、そのユーザーのアクセストークンが必要です。
最初は愚直にOAuth 2.0認可コードフローを実装しました。
- LookerのOAuthアプリを作成
- Slackから「Lookerと連携する」ボタンを押させて認可コードを取得
- コードをトークンに交換して保存
- APIを叩くときはそのトークンを使う
結構な実装量になりました。そして複雑なので保守するのが大変そうです。
また、ユーザー側でもSlack上でOAuthの認可を許可する一手間が発生します。
後になって気づいたこと:Looker APIにsudo機能があった
実装が終わり運用を始めた頃、Looker APIにLogin userというメソッドがあることに気づきました。
これは「任意のユーザーIDの権限でAPIを呼び出す」機能です。いわゆるsudoそのものです。
これを使ってユーザー権限を動的に切り替えることで、呼び出し元ユーザーの権限でLooker APIを呼び出せます。Looker側のログにはsudo元とsudo先の両方のIDが記録されるため、ログとしても申し分ありません。
Slack上でのシームレスなUXを実現するため、sudo方式を採用し、OAuthの処理は削除しました。
ちなみに、権限切り替え(昇格)を適用するのはSlack DMからの利用に限定しています。チャンネルから呼び出された場合は最低限の権限(全社員が閲覧可能なデータのみの閲覧権限)でLookerにアクセスする設計にしました。これは、権限の強いユーザーが機密性の高い質問をパブリックチャンネルで聞いてしまったとき、他のユーザーに結果が見えてしまうリスクを避けるためです。
リリースの効果
ビジネスデータに関する問いに、ベガちゃんが自律的に回答できるようになりました。ユーザーはLookerのURLやExploreの存在を知らなくても、チャットで「先月の売上は?」と聞くだけでデータにたどり着けます。
「1分でこれ返してくれるなら自分で探すより絶対早い」
というユーザーの声も届いており、Lookerへの習熟度によらずデータを活用できるようになってきました。
また副次的な効果として、Looker運用チームへの問い合わせslackに対しても自動回答が可能になりました。
問い合わせの多くは、以下の内容と原因に分類できます。
- Lookerで欲しいデータが見つからない
- A. そもそもデータが存在しない(LookML未定義)
- B. データはあるが、どのダッシュボード・Exploreで見ればいいかわからない
- データの値がおかしい
- A. LookML定義や元データに問題がある
- B. ユーザーの操作が間違っている(フィルタやDimensionの設定ミス)
当初、エージェントが自律的に対応できるのはユーザー起因のB系のパターンだけだと思っていました。A系のパターンは結局人が作業する必要があるため、エージェントが回答しても意味がないと思っていました。
しかし実際には、原因がAであったとしても、ベガちゃんが原因調査までは自動で対応してくれるため、Looker運用チームの初期調査の工数が大幅に減りました。
今後の展望
現在のベガちゃんはSlackでの質問に回答することに主眼を置いて開発したため、データの「探索」までしか対応していない状態です。
一方、メルカリさんのソクラテスはデータ分析用途に特化しており、仮説提案・検証からレポート作成まで一気通貫で対応できるとのことです。
今後は、ベガちゃんも単に1つのデータを探すだけでなく、複数のデータを組み合わせて仮説を検証するような、より高度な分析用途にも対応できるようにしていきたいと考えています。
