dbt のリネージを BigQuery Graph で調べられるようにしてみる

こんにちは、グループ情報部の@mashiikeです。
2026年4月9日に、BigQuery の新機能「BigQuery Graph」が Preview になりました。

cloud.google.com

BigQuery Graph は、BigQuery 上のテーブルデータをグラフとしてモデリングし、ISO GQL 標準に準拠した Graph Query Language で分析できる機能です。
ノードとエッジのテーブルさえ用意すれば CREATE PROPERTY GRAPH でグラフが定義でき、GQL で関係性を辿れるようになります。

身近なグラフデータと言えば、dbt のリネージグラフだなと思い、PoC としてパッケージを書いてみました。

github.com

dbt のリネージ情報について

dbt をお使いの方はご存知だと思いますが、dbt は compile 時にプロジェクト全体の DAG(有向非巡回グラフ)の情報を持っています。
Jinja のコンテキスト変数 graph.nodesgraph.sources を通じて、どのモデルがどのソースやモデルに依存しているかが全てわかります。

最近は MCP で BigQuery へアクセスできるので、リネージの情報が BigQuery 上にあれば、わざわざ Claude 等にリポジトリを渡さなくても依存関係を調べられて便利そうですよね。

仕組み

dbt-bigquery-lineage-sync は、2つのコンポーネントで構成されています。

nodes と edges のモデル

github.com

github.com

nodes と edges というモデルを dbt build すると、dbt の manifest にあるリネージ情報をマクロで参照して BigQuery に incremental model として保存します。
profile_name ごとに分けて保存しているのは、dbt で複数環境に分けて開発する場合は profile_name で環境を分けることが多いためです。

create_or_replace_lineage_graph マクロ

github.com

nodes と edges を保存するだけでも有用ですが、create_or_replace_lineage_graph マクロを使うことで Property Graph を定義でき、今回の Preview 機能である GQL を使ってリネージを調べられるようになります。

使い方

セットアップ

packages.yml に追加して dbt deps するだけです。

packages:
  - git: "https://github.com/mashiike/dbt-bigquery-lineage-sync"
    revision: v0.0.0

テーブルの作成とグラフの定義

# 1. ノードとエッジのテーブルを作成
dbt build --select dbt_bigquery_lineage_sync

# 2. Property Graph を作成
dbt run-operation create_or_replace_lineage_graph

これだけです。2段階なのは、Property Graph の DDL が dbt の標準的な materialization ではないため run-operation で実行する必要があるからです。

GQL で調べてみる

グラフができたら、GQL でリネージを調べてみましょう。

モデルの一覧を出す:

GRAPH `infra-dev-281205.dbt_lineage`.`graph`
  MATCH (n:DbtNode)
  WHERE n.resource_type = "model"
  RETURN n.name, n.materialized
  ORDER BY n.name

特定モデルの直接の上流を調べる:

GRAPH `infra-dev-281205.dbt_lineage`.`graph`
  MATCH (upstream:DbtNode)-[e:DependsOn]->(downstream:DbtNode)
  WHERE downstream.name = "orders"
  RETURN upstream.name, upstream.resource_type

3ホップ先まで辿る:

GRAPH `infra-dev-281205.dbt_lineage`.`graph`
  MATCH (src:DbtNode)-[:DependsOn]->{1,3}(dst:DbtNode)
  WHERE dst.name = "customers" AND dst.resource_type = "model"
  RETURN src.name, src.resource_type

この -[:DependsOn]->{1,3} という書き方で「1〜3ホップの範囲で DependsOn エッジを辿る」という意味になります。

「このソースを変更したら影響範囲どこまで?」みたいな問いに、SQL の延長でサクッと答えが出せるのはなかなか便利です。

ノートブックで可視化する:

BigQuery Studio のノートブックでは %%bigquery --graph マジックコマンドを使うと、GQL の結果をグラフとして可視化できます。

%%bigquery --graph
GRAPH `infra-dev-281205.dbt_lineage`.`graph`
  MATCH
    p = ((src:DbtNode)-[:DependsOn]->(dst:DbtNode))
  WHERE dst.name = "customers"
  RETURN TO_JSON(p) AS path

docs.cloud.google.com

この GRAPH 構文を人間が覚える必要があるのか?

GQL のクエリ例をいくつか紹介しましたが、正直なところこの構文を人間が毎回書くのかというと微妙なところです。
冒頭で「MCP で BigQuery にアクセスできるなら便利そう」と書きましたが、MCP Toolbox for Databases を使えば Claude Code から BigQuery に直接アクセスできます。

docs.cloud.google.com

.mcp.json に以下のように設定するだけです。

{
  "mcpServers": {
    "bigquery": {
      "command": ".bin/toolbox",
      "args": [
        "--prebuilt",
        "bigquery",
        "--stdio"
      ],
      "env": {
        "BIGQUERY_PROJECT": "${BIGQUERY_PROJECT}"
      }
    }
  }
}

さらに、リポジトリにはサンプルの Claude Code スキルも用意しています。

github.com

このスキルを入れておけば、「customers モデルのリネージを可視化してほしい」と聞くだけで GQL を組み立てて実行してくれます。

人間が GRAPH 構文を覚える必要はありません。

まとめ

BigQuery Graph が Preview になったので、dbt のリネージ情報を Property Graph として載せて GQL で調べられるようにする PoC パッケージを作ってみました。
やってみた感想としては、BigQuery Graph と dbt のリネージは相性が良いです。
「このソースを変えたら影響範囲は?」「このモデルの上流3段を辿りたい」みたいなクエリが、BigQuery の中だけで SQL の延長として書けるのは楽です。
グラフデータベースを別途用意する必要がないのも嬉しいですね。

まだ PoC なので粗い部分はありますが、興味のある方はリポジトリを試してみてください。
BigQuery Graph がGAになるのが楽しみです。

カヤックでは、データパイプラインを調べるのが好きなエンジニアも募集しています!

Generated with Claude Code

AI副村長「ねっぷちゃん」のロール設計と地域データ運用 — AI副村長をどう形づくったか

こんにちは、AI推進室の村井です。 先日、同じプロジェクトメンバーの大脇が、AI副村長「ねっぷちゃん」のアーキテクチャと技術選定について書きました。

techblog.kayac.com

ねっぷちゃんは、 GitHub の README にも書いた通り、AIを単なる道具として置くのではなく、その地域の文脈を持った存在として受け入れてもらえるのか。そんな仮説を実験しているプロジェクトです。 コンセプトとしては「存在するソフトウェア」── ただ機能するだけの道具ではなく、村の一員のようにそこにいると感じられる存在になれるかを試しています。

github.com

また、「AI副村長」という名前には、やがて村のムードや、今この村で起きていることを教えてくれる、村長のパートナーのような存在になっていけたら、という思いも込めています。

ねっぷちゃんが目指しているのは、ひと言でいえば「めっちゃ地元のAI」です。

では、そのねっぷちゃんの雰囲気は、どうやって生まれてきたのか。 なぜ「村のことに詳しいAI」と感じられるようになっていったのか。今回はその話を書いてみます。

実運用を通じて強く感じたのは、地域特化AIではシステムプロンプトだけでなく、参照するデータの質・鮮度・構造が、AIの印象や信頼感をかなり大きく左右する、ということでした。この記事では、ねっぷちゃんのロール設計を、プロンプト単体の話ではなく、データ設計や運用も含めた実践として振り返ります。

AIを理解するには作るしかないと思ったら、ホームコースが必要だとわかった話

AI推進室を立ち上げたのは、仮説というより、ほとんど直感からでした。
AIを理解するには、読むだけではなく、作るしかない。そう思ったからです。

HN、arXiv、X を読んでいるだけでも、もちろん面白いことはわかります。驚くべきことが起きているし、だから驚くし、同時に懐疑的にもなる。 でも少なくとも私は、読んでいるだけでは自分の手触りとしては掴みきれませんでした。

仲間たちと、いろいろなフィールドで生成AIを片っ端から環境をつくって試していく中で、だんだん考えが収束していき、ひとつの考えが生まれます。

AIはいずれ、あらゆるデジタル制作をかなりのところまで自動化してしまうだろう。でも、AIそのものを本質的に理解するには、触って、作って、失敗して、直すことを繰り返せる場所が必要だ。そう思うようになりました。

毎日試せる場所。
すぐに試して、すぐ壊して、また直せる場所。
それが必要だとわかってきて、自分の中ではそれを「ホームコース」と呼ぶようになりました。

では、そのホームコースをどこに置くのか。
ここで出てきたのが、「AIが全然関係なさそうな場所に置いた方が、かえって面白い研究や使い方が生まれるんじゃないか」という考えでした。田舎の村や畑、山の中。そういう場所です。

そこからつながったのが、北海道中川郡音威子府村(おといねっぷむら)でした。

なぜ600人の村なのか

音威子府村の人口は約600人です。

「小さすぎないか」とよく言われます。
でも、私たちにとってはこのサイズが重要でした。

いきなり大規模なものを作るのではなく、コンパクトに、クイックに試していける。住民ひとりひとりの声が直接届く距離感で、手応えも速く返ってくる。最小単位で動かして確かめる、という進め方に向いていたのです。

そしてもうひとつ、この村にはチャレンジを受け入れてくださる土壌がありました。村長の「やってみましょう」という返事の軽やかさと、受け入れてくださる村の方々の懐の深さが、このプロジェクトを動かした最大の要因のひとつでした。ソフトウェア開発の感覚で言えば、非常に貴重な実証環境でした。ご協力にはいつも感謝しています。

さらに、この村には全国でもかなり特徴的な構造があります。

600人の村で、10代が約100人。人口の中で10代後半が大きな比率を占めています。これは北海道おといねっぷ美術工芸高校があるためで、役場でヒアリングした際にも「関係者の半分が学校関係者」と言われるほど、この高校が村の構造に影響を与えています。

実は、この事実が、ねっぷちゃんのロール設計にかなり直結していきます。

「17歳女性」という設定は、最初のロール設計から始まった

ねっぷちゃんはメディア上で「17歳の女性AI」と紹介されました。ここはどうしてもキャラクター設定として受け取られやすい部分なんですが、実際には、まずユーザーペルソナを起点にしたロール設計として導入しています。

プレスリリース後、各メディアから最も多く受けた質問のひとつが、「17歳女性という設定はなぜそうなったのですか?」でした。考えたのは企画している私ですが、これには明確な理由があります。

それは、誰が一番自然に話しかけてくれそうか、ということを起点に考えたからです。

なぜ17歳か。
音威子府村では、高校生がデジタルなインターフェースに最も自然に触れてくれる層のひとつだと考えたからです。最初に気軽に話しかけてくれる可能性が高い相手に合わせて、会話のトーンや距離感を設計した。その初期条件のひとつが「17歳女性」というロールでした。

ここで意識したのは、柔らかい話し方で、軽やかなキャラクター、明るく前向き、という方向性です。役場の案内をする以上、情報は公式であるべきですが、接し方まで役所的に硬くする必要はない。むしろ、地域の人が最初の一言を投げやすいことの方が重要でした。

実装上は、"あなたは北海道音威子府(おといねっぷ)村に住む17歳の女の子「ねっぷちゃん」"というロール指示をシステムプロンプトに含めています。ただ、年齢や属性はあくまで対話のトーンを整えるためのものであって、「村のことに詳しい」と感じられる理由の中心はそこではありません。

むしろ実際には、参照する地域データが整備され、それが適切に取り出されることによって、今の「軽やかで明るいが、地域文脈を持ったAI」という印象が形作られていきました。世界観を先に厚く決めたというより、実際の利用シーンと地域データの整備からロールが定着していった、という順番に近いです。

なぜ最初に"普通のRAG"を選んだのか

当時、選択肢はいくつもありました。素直なベクトル検索でいくのか、BM25 を含むハイブリッド検索に寄せるのか、GraphRAG のように関係性をたどる取り出し方を試すのか。Mastra 側でもグラフベースで探索するRAGの方向性が出てきていて、そこも気になっていました。

でも、どれが一番勝ち筋なのかは、正直さっぱりわかりませんでした。

その中でRAGを選んだ理由は、まず前のプロジェクトでの経験です。RAG を使った知識抽出やセマンティック検索を実装した経験があり、その礎がありました。そこに Mastra.ai の当時のアーキテクチャを合わせて、まず立ち上げるのが現実的でした。

実装としては、シンプルなベクトル検索を軸にしつつ、取得した候補に対して軽いリランキングをかける構成です。最初にベクトル検索で関連する文書チャンクを取得し、その後にクエリとの意味的な一致度などをもとに再スコアリングして、最終的に上位のコンテキストをLLMに渡しています。いわゆる大掛かりなグラフ構造や複雑な推論はまだ入れていませんが、検索→再評価という二段階の構成で retrieval の安定性を上げる形です。

もうひとつは、優先順位の問題です。 村の情報を一通り投入したときに、どの程度の性能が出るのか。embedding にどれくらいのコストがかかり、1回の会話あたりどれくらいのランニングコストになるのか。どこまでデータ整備が必要で、どこまでセマンティック検索だけでいけるのか。そこがまだ見えていませんでした。

つまり当時は、最適解を選ぶより、最初のイテレーションを最速で回せる構成を選ぶ方が重要だったのです。

これは賛否がある判断だと思います。評価設計をもっと先に詰めるべきだった、という意見も理解できます。ただ、自分の優先順位は「実ユーザーが触る状態を作ること」でした。数値を先に揃えるより、まず使われる状況を作って、そこから問題を把握したかった。

その後の数ヶ月でも、AI技術はどんどん更新されていきました。Graph RAG の精度が上がった、新しいモデルが出た、新しい手法が気になり始めた。目移りはずっとあります。今もあります。

ただ、乗り換えるかどうかは、村で実際に使われながら判断する、というスタンスを保っています。Open R&D の醍醐味はそこにあると思っています。必要なら来週、新しい手法を実装して試すこともできます。でも、それをやる理由は「流行っているから」ではなく、「今ある現場の失敗に効くかどうか」です。

地域データの質が、AIの印象と信頼感を大きく左右した

「ねっぷちゃんは村のことに詳しい」と感じられる状態は、システムプロンプトだけでは作れませんでした。少なくとも今回の実装では、参照される地域データの質と、その取り出し方の設計が大きく効いています。

最初期のシステムプロンプトには、演技指導のような細かいキャラクター指示がたくさん入っていました。MVP以前の手元デモの段階では、設定的な文言も入れていた記憶があります。

でも、実際に運用を始めると問題が出ました。回答精度に悪影響が出たり、軽すぎる返答になったり、文脈と関係のないことを言い出したりする。書きたくはなりますが、詳しいと書いたから詳しくなるわけがないのです。むしろ、厚い演出がノイズになる場面がありました。

ハーネス実装を担当している大脇が、そうした要素をどんどん削っていき、私も「ハルシネーションの原因になるなら削ろう」と了承し続けた結果、今のねっぷちゃんには、かなり薄いキャラクターレイヤーだけが残っています。

これをシステムプロンプトの限界と呼ぶこともできるかもしれません。
でも私には、プロンティングとコンテキストがそれぞれどこで効いているのか、その役割の違いが前よりずっと見えやすくなった過程に思えました。

少なくとも今回のプロジェクトでは、キャラクターを厚く見せることをプロンプトだけで担うよりも、地域の公式情報をどう集め、どう整え、どう取り出せるようにするかの方が、「このAIは村のことをわかっている」という感触に直結していました。

村役場の Web サイトをスクレイピングし、PDF を Markdown に変換し、リンク切れや情報の古さを確認し、チャンキングして embedding し、Vector Store に格納する。この一連のデータ処理は、インフラやフレームワークの選定と同じくらい、設計の仕事です。

実際にはこの部分も単純ではなく、自治体のPDFには表や段組みが含まれていることが多いため、単純なパーサーだけではなく変換用にエージェンティックに整形してMarkdown化しています。その後、Markdownの見出し構造をもとにチャンクを分割し、短すぎるチャンクは除外しベクトル化するようにしています。

どのデータを取り込むか。
どう構造化するか。
鮮度をどう保つか。

この判断ひとつひとつが、RAG の retrieval 精度だけでなく、最終的な回答の質や信頼感を決めていきました。

何を見ながら改善していたのか

では、何を見ながら改善していたのか。
現時点では少なくとも、次の3点を重視しています。

1つ目は、事実として正しいか
役場の案内や収集日など、地域の生活に関わる情報では、自然な言い回しよりもまず正確であることが重要です。

2つ目は、情報が最新か
自治体の情報は、同じようなタイトルの PDF でも年度が違うことがあり、検索結果の上位に古い情報が出てくることがあります。地域AIでは、正しそうに見える古い情報を答えてしまうことが特に危険です。

3つ目は、地域文脈に接続できているか
たとえば曖昧な質問に対して、一般論ではなく「この村の話として」返せているかどうかです。

similarity、context precision、context relevance、hallucination といった一般的な評価値も可視化して確認しています。もちろん、こうした指標は改善の方向を見るうえで重要です。

ただ、少なくとも今の実装では、最終的な回答品質により大きく効いているのは、評価値の微調整そのものより、どのデータを取り込み、どう整え、どう新しい状態に保つかというキュレーションの方だと感じています。

失敗した問い合わせは、できるだけそのままテストケースとして残し、データ更新や変換処理の修正後に再確認しています。まだ十分に自動化された評価基盤とは言えませんが、少なくとも「どこで間違えたか」「何を直したか」を追える状態にしていくことは意識しています。

具体的には、失敗した問い合わせを固定のテストケースとして蓄積し、同じ質問を複数回実行して pass@k(1回でも正解すればOK) と passk(すべて正解した場合のみOK) の両方で安定性を見るようにしています。 各ケースには「正しく答えるべき質問」だけでなく、「情報が不足しているため推測せずに"わからない"と答えるべき質問」も含めています。ゴミ収集日のように誤答が生活に影響するものは後者に分類し、回答内容のキーワード判定や類似度の閾値を使った簡易的なグレーディングで確認しています。

まだ研究論文のような評価とは言えませんが、少なくとも 同じ失敗をもう一度起こさないためのテストセット としては機能し始めています。

皆さんどうやってるんでしょう。
地道な調整が効く世界にも見えていますし、イテレーションループの暴力で改善できる世界にも見えています。私もまだ答えはありません。ただ、この領域では仮説を語るより、失敗を評価セットに戻し続けることが今のところ一番効いています。

ゴミの日を間違えた失敗を改善する

実証実験を始めて数日で、住民の方から「ゴミの日が違う」という指摘をいただきました。

確認すると、村のデータにはゴミ収集カレンダーの PDFリンク があったのですが、表形式でパース精度が低いのか、ツールの不発か、うまくいかず、さらにブラウザツールが検索すると上位に出てくるのが一昨年のカレンダーだったため、まったく違う日付を答えていました。

この失敗は象徴的でした。
地域AIでは、「それっぽく答える」ことよりも、「古い情報を拾わない」ことの方がずっと大事です。

じゃあ最新のカレンダーをちゃんと扱えるようにしないとね、ということでデータを見直し、Markdown 変換後の扱い方も修正し、評価用のテストセットにその失敗ケースを加えて再確認しました。

派手な改善ではありません。
でも実際には、こういう地道な修正の積み重ねが効いています。

この手仕事をどこまで自動化できるか、どこまで精度高く維持できるかは今後の課題です。2026年1月時点では、少なくとも「どのデータを取り込むべきか」の初期判断は、まだ人手でやる方が品質が高いと感じていました。ただ、3月の今はもう違い始めている気もしていて、この領域の変化の速さを感じています。

gemini-embedding-2-preview のような新しい選択肢が出てくると、ゲームチェンジの匂いもします。とはいえ、新しい手法に飛びつく前に、まず目の前の失敗をどう潰すかの方が、今の現場では重要です。


住民の質問が、地域AIを育てていく

テクノロジーに詳しい必要のない、普通の人たちが、日常の中でどうねっぷちゃんに話しかけるのか。実証実験を通じて、その答えが少しずつ見えてきています。

「新しい道ができるらしいね?」
「今日は何のゴミの日?」
「テンが出た、どうしよう?」
「村長さんはどんな人?」

難しいことは聞いていません。
全部、生活の延長線上の質問です。

でも、相手はLLMです。その質問だけではコンテキストが足りない。
どの道の話なのか。
どの自治体での話なのか。
どの地域の文脈なのか。

その不足している前提を、「この村のAIである」という条件が補ってくれる。ここに地域AIの面白さがあります。

たとえば「テンが出た、どうしよう?」という質問。
私は最初、テンという単語を聞いても、この辺りの動物なんだろうな、くらいしか思いませんでした。

この質問が来たとき、ねっぷちゃんは村の2020年の広報誌まで遡って回答しました。村の公式な鳥獣被害防止計画の主要対象はヒグマ・エゾシカ・アライグマで、テンは中心対象ではない。ただし捕獲記録はある。テンは雑食で柔らかい野菜を狙うことがある。早めの収穫が有効——という内容です。

これは FAQ 的に最初から想定していた質問ではありません。
でも、村のナレッジが広報誌レベルまで入っていたことで、地域文脈を持った返答ができた。

RAG の retrieval として見ても、こういうクエリは面白いです。
事前に全部の質問を設計しきるのではなく、住民の自然な問いが、システムの弱いところと強いところをあぶり出してくれる。そこに、ホームコースとしての価値があります。

住民の方々が、ねっぷちゃんを介して村のことをAIに語りかけていく。その蓄積が、いつか村全体のムードの可視化や、村長さんが「今、村の人たちは元気か」を知る助けになるかもしれない。

人とAIが仲良くなること自体がゴールではありません。
AIを介して、人と人が仲良くなること。今はそこを目指して、AI副村長という形をアップデートしていっています。

なぜオープンR&Dなのか

ソースコードを公開している理由を一言で言えば、仲間探しです。

ChatGPT のような高性能な汎用AIが誰でも月数千円で使える時代になりました。でも、それを「その地域専用のAI」として育てていくには、まだ技術的な壁があります。だからこそ、地元のAIは、地元の人の手が入っていた方がいい。地元のエンジニアがやっていたらなおさらいい。

同じような動きが、全国の村や町で起きてほしいと思っています。

オープンにしているのは、完成品を見せたいからというより、「こういう課題がある」「ここはまだ弱い」「こうすると良くなるかもしれない」という知見を、もっと往復可能なものにしたいからです。面白いアイデアがあれば、コードを書いて PR を送ってもらえたらうれしいですし、逆に他地域での知見をこちらも学びたいと思っています。

これから

役場で作業をしていたとき、ガラケーを持ったおじいちゃんが、役場の人に質問しに来ていました。 その光景を見たとき、ねっぷちゃんが答えられることを、そのおじいちゃんは電話で聞けるようになるべきだと思いました。

役場に行けば物理的にねっぷちゃんに会える。
そこまで作れたら、ひとつのゴールだと思っています。

インターフェースの拡張として、村の公式LINEアカウントや電話番号を持たせて音声通話で会話できるようにしたり、フィジカルなボディを与えて村役場に置いたり、複数のチャネルにまたがるインタラクションの形を計画・検討しています。高齢者が多い地域では、Webチャットだけでは情報が届かないケースは十分にあります。すべての人がアクセスできるようにするには、物理側もマルチモーダルな展開が必要です。

データについては、住民のみなさんのニーズに寄り添いながら、随時ブラッシュアップしていますし、情報の精査は、今も最終的には人の目で確認しています。自動化ももちろん検討していますが、ここはねっぷちゃんのコアと言っていい部分なので、慎重に進めるつもりです。

おといねっぷ美術工芸高校の生徒さんたちとのコラボレーションも、いつかやってみたいと思っています。ねっぷちゃんが作品の一部になったり、逆にそこから新しい表現のエッセンスをもらったり。ロールもデータも、育っていく余地はまだたくさんあります。

さいごに

本プロジェクトはオープンソースとして公開しています。
開発への貢献やアイデアの提案なども歓迎しておりますので、お気軽にご相談ください。 github.com

技術的なアーキテクチャや実装の詳細は、大脇の記事もあわせてご覧ください。 techblog.kayac.com

このプロジェクトを通じて見えてきたのは、地域AIのふるまいは、プロンプトだけで決まるものではないということでした。少なくとも実運用では、どんな地域データを持ち、どう更新し、どう取り出せるようにするかが、回答の精度だけでなく、AIへの信頼感や「この地域のことをわかっている」という印象そのものに強く影響します。

その意味で、地域AIの設計は、モデル選定やUI設計だけではなく、データ整備と運用設計を含めて初めて成立するのだと思っています。

カヤックでは、AIエージェントを使って世の中を便利にしていくことに興味があるエンジニアを募集しています!