Lookerとセマンティックレイヤーで作る会話分析の運用と評価

はじめに

こんにちは。ファインディでデータエンジニアをしている開(@hiracky16)です。

今回はLookerに搭載されている会話分析機能を使って、ユーザーがより自律的にデータ抽出や分析ができるようにした話をします。セマンティックレイヤー(Explore)を会話分析に使用する設計や開発・テスト・評価までの運用のノウハウを紹介します。

会話分析(Conversational Analytics)は、Lookerに搭載されている機能で、自然言語で質問を投げるとエージェントが裏でクエリを組み立てて実行し、表やグラフを添えて答えてくれます。たとえば「1月から5月までのXXX数をUUベースで集計できますか?」と聞くと、次のように月別の集計結果とグラフが返ってきます。

Lookerの会話分析に自然言語で質問し、月別の集計結果とグラフが返ってきている画面

会話分析にセマンティックレイヤーを使う理由

以前公開したブログでは、Lookerを導入した背景や、登録ユーザーの7割がダッシュボードの作成・閲覧のために日々訪れていることを紹介しました。

tech.findy.co.jp

tech.findy.co.jp

これらの記事で触れたとおり、重要な指標や共通のディメンションは、セマンティックレイヤーへの集約を進めてきました。

集約した指標やディメンションを使ってダッシュボードを作り社内に展開していますが、「別の切り口で見たい」「集計ロジックを変えたい」といった要望に都度対応する運用となり、その工数が無視できなくなってきました。

一方で、会話ベースでデータ抽出・集計・分析ができるエージェントが世の中に広がってきました。私たちもGoogle製のADKを使って自作してみましたが、こちらもメンテナンスコストがかかります。さらに、全テーブルを対象にするため、SSoT(Single Source of Truth)が整っていない領域や、コンテキストが薄い領域では精度が出ず実用には届きませんでした。

ここで着目したのが、すでに集約を進めてきたセマンティックレイヤー(LookerのExplore)です。これを会話分析エージェントの入口に据えることで、ダッシュボード運用の工数と、自作エージェントのコンテキスト不足という両方の課題を解決できると考えました。全体像は次のとおりです。

flowchart LR
    subgraph DWH["データウェアハウス"]
        direction TB
        NOTE1[/"会社マスタが複数あり<br/>テーブル単位ではSSoTが曖昧"/]
        T1[fct_scouts<br/>送信済みスカウト]
        T2[fct_scout_replies<br/>スカウトへの返信]
        T3[fct_matches<br/>成立したマッチング]
        T4[dim_users<br/>ユーザー情報]
        C1[dim_companies_raw<br/>会社情報(raw)]
        C2[dim_company_master<br/>会社情報(master)]
    end

    subgraph SL["セマンティックレイヤー"]
        direction TB
        NOTE2[/"業務単位で集約し<br/>使う会社マスタもExploreで判断"/]
        E1["スカウトExplore<br/>送信数・返信率を定義"]
        E2["マッチングExplore<br/>成立件数・成立率を定義"]
    end

    T1 & T2 & T4 --> E1
    T3 & T4 --> E2
    C1 -->|スカウトはrawを使う| E1
    C2 -->|マッチングはmasterを使う| E2

    E1 --> A1[スカウト分析エージェント]
    E2 --> A2[マッチング分析エージェント]
    A1 & A2 --> U([利用者の質問])

左側のデータウェアハウスには大量のテーブルが並び、どれを使えばよいか、指標をどう計算すればよいかはテーブル単位では自明ではありません。たとえば会社マスタがdim_companies_rawdim_company_masterのように複数存在し、用途によってどちらを使うべきかが曖昧、といったこともあります。これをそのままエージェントに渡しても、うまく答えられないのは当然です。

そこで、Explore側で「スカウト」「マッチング」といった業務の単位にテーブルを集約し、送信数や成立率といった指標の定義まで含めて整えておきます。会社マスタのように複数の候補があるものも、Explore側でどちらを参照すべきかを判断します。たとえばスカウトExploreではdim_companies_rawを、マッチングExploreではdim_company_masterを使う、といった使い分けをExploreの定義として固定しておくわけです。エージェントは生のテーブルではなくこのExploreだけを見るので、何をどう集計すべきかが定まった状態で質問に答えられます。

セマンティックレイヤーを会話分析の入口にするメリット

セマンティックレイヤーを入口に据えると、大きく2つのメリットがあります。

参照先のデータを意味ある単位に限定できる

1つのエージェントは1〜5個のExploreに紐づきます。エージェントが触れられるデータがExploreの範囲に限定されるためエージェントの役割がはっきりします。

また、SSoTが担保できていないテーブルであっても、Lookerから参照する形にすればExplore上でSSoTを表現できます。生のテーブルをそのまま渡すのではなく、Lookerというフィルタを通すことで、エージェントが扱うデータの意味を整えられます。ダッシュボードと会話分析で使用するExploreは同じなので、両者で数値が食い違うという事態も避けられます。

コンテキストの肥大化を避けられる

全テーブルをエージェントに渡そうとすると、コンテキストが膨れ上がり、かえって精度が落ちます。Exploreを入口にすれば、LookMLのdescriptionや指標定義がそのまま「正しい意味」のソースになり、必要な範囲のコンテキストだけを渡せます。

LookerはKnowledge Catalog(旧Dataplex)との連携も進んでおり、メタデータをカタログ側で一元管理できるようになってきています。

docs.cloud.google.com

今はLookMLのdescriptionにコンテキストを持たせていますが、今後はKnowledge Catalog側にも用語や定義を蓄積し、それをエージェントのコンテキストとして活かせるようになることを期待しています。

デメリット

一方で、当然ながら弱点もあります。複数のExploreをまたぐ横断分析やファネル分析は苦手です。たとえば、ファインディが主催するイベントへの参加やメディア閲覧といったイベントを起点に、実際に企業とのマッチングに至るまでの流れを追う、といった分析は守備範囲の外でした。それぞれのイベントが別々のExploreに分かれていると、エージェントはどちらか一方しか見られず、つながりを追えません。

そこで、ユーザーという意味のある単位でフェーズをまたいだデータを1行にまとめた累積ファクトを作り、Exploreとして用意することで対処しました。次のように、1行=1ユーザーで各フェーズの日付を横並びに持たせておきます。

user_key イベント参加日 メディア閲覧日 マッチング日
u_001 2026-04-01 2026-04-03 2026-04-20
u_002 2026-04-05 (なし) (なし)
u_003 2026-04-10 2026-04-12 (なし)

こうしておけば、1つのExploreの中で「イベント参加からマッチングまで到達した割合は?」といったファネルをたどれます。横断的な問いに答える必要があるものは、あらかじめこの形に整形しておき、エージェントからも参照できるようにしています。

運用

LookMLの開発方法は前述のブログで紹介しているので割愛し、今回はLooker会話分析に使うエージェントの開発・運用について説明します。

エージェント定義をGitで管理する

会話分析のエージェントは、Lookerの画面上からでも作成・編集できます。ただ、画面で直接編集すると、誰がいつ何を変えたのかが追えず、システムプロンプト(instructions)の変更履歴も残りません。

そこで、エージェントに必要な項目をYAMLファイルとして定義し、Gitで管理することにしました。1ファイルにエージェントのID、名前、参照するExplore(sources)、システムプロンプト(instructions)、そして後述する評価ケース(cases)をまとめています。

id: hogefugapiyo   # 既存エージェントの更新用。新規作成時は省略
name: マッチング分析エージェント
description: 企業と求職者のマッチング状況を分析するエージェントです。
code_interpreter: false

# このエージェントが参照するExplore
sources:
  - model: sample_recruiting
    explore: fct_matchings
  - model: sample_recruiting
    explore: fct_daily_snapshot_matchings

# システムプロンプト
instructions: |
  あなたはマッチング分析の専門アシスタントです。
  LookerのExploreを使って、マッチングの成立件数・成立率・
  セグメント別トレンドに関する質問に回答します。

  ## 用語集
  - マッチング: 企業と求職者の間で選考が始まった状態
  - 成立率: マッチング成立件数 ÷ いいね件数
  ...

# 評価ケース(評価の章で後述)
cases:
  - prompt: "今月のセグメント別のマッチング成立率は?"
    reference: "セグメントでグループ化し、成立件数をいいね件数で割った値を回答する"

ExploreやYAMLが編集されると、GitHub ActionsでLookerのエージェントに反映されます。

回答品質の評価

エージェントを運用に乗せると、「ちゃんと正しく答えられているか」が気になります。システムプロンプトやExploreを変更するたびに、回答の質が上がったのか下がったのかを判断したくなります。

そこで、エージェント定義に評価ケースを書いておき、CIで自動評価する仕組みを用意しました。

評価ケースを定義する

評価ケースは、前述したエージェント定義のYAMLに質問(prompt)と期待する答え方(reference)の組として書いておきます。たとえば次のように、想定される質問のバリエーションを並べておきます。

cases:
  - prompt: "今月のマッチング成立件数は?"
    reference: "当月のマッチング成立件数の合計を回答する"

  - prompt: "セグメント別のマッチング成立率を高い順に教えて"
    reference: "セグメントでグループ化し、成立件数をいいね件数で割った成立率を降順で回答する"

  - prompt: "先月と今月でマッチング成立件数はどう変わった?"
    reference: "先月と当月の成立件数を比較し、増減と差分を回答する"

referenceには「完全一致の正解テキスト」ではなく、答えの作り方の説明を書いています。会話分析の回答は表現に幅があるため、文章の一致ではなく方向性で評価したいからです。

この評価ケースを使って、次の2軸で品質を測ります。

軸1: 回答テキストの品質をLLMで採点する

1つ目は、エージェントが返した回答テキストそのものの質です。これは Agent Platform(Vertex AI)の評価サービス を使い、LLM-as-a-Judge、つまり別のLLMに採点役をやってもらう形で測っています。

採点は4つの観点で行われます。

  • 質問の指示どおりに答えているか(Instruction following)
  • 与えられたコンテキストに基づいているか(Groundedness)
  • 十分な情報量で答えているか(Completeness)
  • 読みやすく整理されているか(Fluent)

これらをまとめたスコアを段階評価で受け取り、品質が落ちていないかを確認します。

軸2: 発行されたクエリを決定論的に検証する

LLMによる採点は揺れるため、それだけに頼るのは不安が残ります。そこでエージェントが実際に発行したクエリの中身を機械的に検証しています。

会話分析エージェントは、質問を受けると裏でLookerのクエリを組み立てて実行します。このクエリが「どのExploreを使い、どのフィールドを選び、どんなフィルタをかけたか」を期待値と照合して判定します。回答テキストの良し悪しではなく、構造として正しいクエリを組めているかを、ぶれのない形で担保します。

期待値は、評価ケースにassertionsとして書き足しておきます。

cases:
  - prompt: "今月のセグメント別のマッチング成立率は?"
    reference: "セグメントでグループ化し、成立件数をいいね件数で割った成立率を回答する"
    assertions:
      explore: fct_matchings          # このExploreを使っているか
      fields_include:                 # これらのフィールドが含まれているか
        - segment
        - matching_rate
      filters_include:                # このフィルタがかかっているか
        - field: matched_month
          value: this month

これらの評価はGitHub Actionsで自動実行され、結果はPRにコメントされます。QA Qualityの平均スコアやケースごとの内訳、アサーションの判定がひと目で分かるので、変更が品質に与えた影響をレビュー時に確認できます。

GitHub ActionsがPRにコメントしたLooker CA回答品質の評価結果。QA Quality平均スコアとアサーションの判定、ケースごとの内訳が表示されている

フィードバックサイクルを回す

評価ケースは社内メンバーから寄せられるフィードバックをもとに育てています。「この聞き方だと答えてくれない」「この数字が期待とずれている」といった、実際に使う人がぶつかった声ほど価値のあるテストケースになります。

寄せられた声は評価ケースに加えるだけでなく、エージェントに足りないコンテキストの手がかりとして、内容に応じてシステムプロンプト・LookML・BigQueryテーブルのどこに手を入れるかを判断します。こうしてフィードバックを起点にしたサイクルが回っています。

flowchart LR
    FB([社内メンバーのFB]) --> C[評価ケースに追加]
    C --> FIX{どこを直す?}
    FIX -->|答え方| P[システムプロンプト調整]
    FIX -->|定義不足| D[LookML description補強]
    FIX -->|データ構造の問題| M[dbtデータモデリング見直し]
    P & D & M --> EVAL[評価を実行]
    EVAL --> SCORE[スコアを比較]
    SCORE -.改善を確認.-> FB

成果

前回のブログで、Lookerの登録ユーザーの7割が週次でログインしていることをお伝えしました。いまではそのうちの半分が、会話分析を通じて自律的にデータ抽出や分析を行っています。

データチームへの影響も大きく、月に十数件あったデータ抽出の依頼はほぼなくなりました。代わりに届くのは、会話分析だけでは解けないより高度な分析を要する相談が中心です。単純な抽出作業から解放されて本来注力すべき仕事に時間を割けるようになりました。

利用者からのフィードバックも増えてきました。「便利になった」という好印象の声もあれば、期待した結果が得られなかったという指摘もあります。後者はチームにとって学びの多い情報で、どこにコンテキストが足りていないのかが明確になります。

このフィードバックサイクルが回り始めたことで、エージェントのinstructionsやLookMLの修正、データモデリングの見直しがこれまで以上に進むようになりました。前述したSSoTについても、Exploreで吸収するだけでなく、テーブルレベルで担保できるよう改善を進めています。

まとめ

本記事では、セマンティックレイヤー(LookerのExplore)を会話分析の入口に据える設計と、Git管理・2軸評価・改善サイクルまでの運用を紹介しました。この設計が効くのは、すでにLookMLで指標やディメンションの定義が進んでいる組織です。土台があるからこそすぐに始められ、成果につなげることができたと思います。

運用で意識しているのは、クイックウィンの積み重ねです。Lookerや生成AIは安価ではないため、小さな成功体験を積み上げ、利用者に定着してもらうことが重要です。そのためにはフィードバックを集める仕組みが重要で、社内勉強会で使い方を共有したり、要望を気軽に投げてもらえるチャンネルを用意したりしています。会話分析を実現させるためには技術的な仕組みと、声が集まる仕組みの両輪が必要だと感じています。

ファインディでは、データ基盤やデータ活用の仕組みづくりに一緒に取り組んでくれる仲間を募集しています。少しでも興味を持っていただけたら、まずはカジュアルにお話ししましょう。

herp.careers