Snowflake Intelligence UI管理はもう卒業? dbtで Semantic ViewとAgentを効率的に構築・管理する方法

snowflake intelligence dbt

Snowflake IntelligenceのキモであるSemantic ViewとAgent。UIポチポチに疲れた方へ、dbtで一気通貫管理してAIデータ分析を爆速化する実装を紹介します!

こんにちは。エクスチュアの松村です。

Snowflake Intelligence 使ってますか

Intelligenceを活用するにあたって、SemanticViewとAgentをどう充実して作成していくかが、データ活用におけるキーファクターになることは、言うまでもありません
いかに内容を充足させるか次第でアウトプットが変わるのがIntelligenceです

SnowflakeのSemanticViewを作成するUI、UIとしてはシンプルで使いやすいと思うのですが、管理して充実していこうとすると不満が多く、やはりコードベースで管理したいと考えてしましまいます

本記事では、SnowflakeのSemanticViewとAgentをdbt Coreで作成しSnowflakeIntelligenceで使うまでの方法を紹介します

※dbt CoreをSnowflakeに接続して使えるようにするまでは各所で公開されていますので省略します

1.SemanticViewの作り方

 Semantic View は、データベース上にビジネスロジックを定義する仕組みです。
テーブル間の関係性(RELATIONSHIPS)、分析軸(DIMENSIONS)、集計定義(FACTSMETRICS)を「意味付けしたレイヤー」として Snowflake 上に構築します

dbtでSemanticViewを扱うには、Snowflake-Labsより提供されているdbt_semantic_viewを使います

package.yml の設定

dbtのpackage.ymlでdbt_semantic_viewを定義します

packages: 
 - package: Snowflake-Labs/dbt_semantic_view

dbtの依存関係を取得します

dbt deps

semantic viewクエリの作成

クエリ自体は、semantic view用のクエリで定義します。こちらがドキュメント上のフォーマットです

{{ config(materialized='semantic_view') }}
TABLES(
  {{ source('<source_name>', '<table_name>') }},
  {{ ref('<another_model>') }}
)
[ RELATIONSHIPS ( relationshipDef [ , ... ] ) ]
[ FACTS ( semanticExpression [ , ... ] ) ]
[ DIMENSIONS ( semanticExpression [ , ... ] ) ]
[ METRICS ( semanticExpression [ , ... ] ) ]
[ COMMENT = '<comment>' ]
[ COPY GRANTS ]

実際の記述は、このようになります(実SQLより一部抜粋)

{{ config(
    materialized='semantic_view',
    database=[database],
    schema=[schema],
    enabled=is_enabled_for_site_types(['btob_lead'])
) }}

TABLES (
    leads AS {{ ref('fact_leads') }}
        PRIMARY KEY (lead_id)
        COMMENT = 'Lead creation and lifecycle events',

    lead_scoring AS {{ ref('mart_lead_scoring') }}
        PRIMARY KEY (unified_user_id)
        COMMENT = 'Lead scoring with behavioral and demographic scores',
...(省略 リレーションするテーブルのリスト)
)
RELATIONSHIPS (
    lead_to_company AS
        leads(company_id) REFERENCES companies(company_id),
    lead_to_user AS
        leads(unified_user_id) REFERENCES user_dim(unified_user_id),
...(省略 リレーション情報)
)
DIMENSIONS (
    -- Time Dimensions
    date_dim.date_day AS date_day
        COMMENT = 'Calendar date',
    date_dim.year AS year
        COMMENT = 'Calendar year',
    pageviews.engagement_level AS engagement_level
        COMMENT = 'Page engagement level based on time spent (quick_view, brief_view, engaged, deep_engaged, very_deep_engaged)',
...(省略 ディメンション COMMENTにAIに認識させる内容を記述ことが重要)
)
METRICS (
    leads.total_leads AS COUNT(DISTINCT leads.lead_id)
        COMMENT = 'Total lead count',
    leads.new_leads AS COUNT(DISTINCT CASE WHEN leads.lead_status = 'new' THEN leads.lead_id END)
        COMMENT = 'New leads count',
    leads.qualified_leads AS COUNT(DISTINCT CASE WHEN leads.lead_status = 'qualified' THEN leads.lead_id END)
        COMMENT = 'Qualified leads count',
    -- MQL/SQL Metrics
    leads.mqls AS COUNT(DISTINCT CASE WHEN leads.is_mql = true THEN leads.lead_id END)
        COMMENT = 'Marketing Qualified Leads count',
    leads.sqls AS COUNT(DISTINCT CASE WHEN leads.is_sql = true THEN leads.lead_id END)
        COMMENT = 'Sales Qualified Leads count',
    lead_scoring.avg_lead_score AS AVG(lead_scoring.total_lead_score)
        COMMENT = 'Average lead score',
    -- Quality Metrics
    lead_scoring.a_grade_leads AS COUNT(DISTINCT CASE WHEN lead_scoring.lead_grade = 'A' THEN lead_scoring.unified_user_id END)
        COMMENT = 'A-grade lead count',
...(省略 メトリクス ここの分析指標を実施したい分析内容にあわせて充実させることが重要)
)

このようなクエリでsemantic viewを構築します

packageの設定により、configで materialized=’semantic_view’ が定義できるようになっています
それ以外の記述方法はdbtそのもです

SemanticViewのポイントの1つは、メトリクス(指標値)をいかにしっかり定義しておくかです

2.Agentの作り方

Agentはdbtのpackageではそのまま使えません
そこで、dbtのmacroを構築しました

このmacroを定義することで、Agentを扱えるようにします
(prod、staging、dev で名前を変えられるようにprefix名を付与してます)

macros/cortex_agent.sql

{% macro create_cortex_agent(agent_name, agent_spec) %}
    {%- set target_database = target.database -%}
    {%- set target_schema = target.schema | replace('staging', 'marts') | replace('STAGING', 'MARTS') -%}

    {#- Add environment prefix for non-production environments -#}
    {%- if target.name == 'dev' -%}
        {%- set prefixed_agent_name = 'DEV_' ~ agent_name -%}
    {%- elif target.name == 'staging' -%}
        {%- set prefixed_agent_name = 'STAGING_' ~ agent_name -%}
    {%- else -%}
        {%- set prefixed_agent_name = agent_name -%}
    {%- endif -%}

    {%- set full_agent_name = target_database ~ '.' ~ target_schema ~ '.' ~ prefixed_agent_name -%}

    {% set sql %}
        CREATE OR REPLACE AGENT {{ full_agent_name }}
        {{ agent_spec }}
    {% endset %}

    {% do log("Creating Cortex Agent: " ~ full_agent_name, info=True) %}
    {% do run_query(sql) %}
    {% do log("Cortex Agent created successfully: " ~ full_agent_name, info=True) %}

    -- Return empty result for dbt
    {{ return('SELECT 1 as agent_created') }}
{% endmacro %}
{% macro get_cortex_agent_tool_resources(semantic_view_refs) %}
    {%- set resources = {} -%}
    {%- for tool_name, sv_name in semantic_view_refs.items() -%}
        {%- set sv_ref = ref(sv_name) -%}
        {%- set _ = resources.update({tool_name: {"semantic_view": sv_ref.database ~ '.' ~ sv_ref.schema ~ '.' ~ sv_ref.identifier}}) -%}
    {%- endfor -%}
    {{ return(resources) }}
{% endmacro %}

{% materialization cortex_agent, adapter='snowflake' %}
    {%- set target_database = target.database -%}
    {%- set target_schema = target.schema | replace('staging', 'marts') | replace('STAGING', 'MARTS') -%}
    {%- set base_agent_name = model['alias'] -%}

    {#- Add environment prefix for non-production environments -#}
    {%- if target.name == 'dev' -%}
        {%- set agent_name = 'DEV_' ~ base_agent_name -%}
    {%- elif target.name == 'staging' -%}
        {%- set agent_name = 'STAGING_' ~ base_agent_name -%}
    {%- else -%}
        {%- set agent_name = base_agent_name -%}
    {%- endif -%}

    {%- set full_agent_name = target_database ~ '.' ~ target_schema ~ '.' ~ agent_name -%}

    -- Get the agent specification from the model's SQL
    {%- set agent_spec_sql -%}
        {{ sql }}
    {%- endset -%}

    -- Execute the CREATE AGENT statement
    {% set create_agent_sql %}
        CREATE OR REPLACE AGENT {{ full_agent_name }}
        {{ agent_spec_sql }}
    {% endset %}

    {% do log("=" * 80, info=True) %}
    {% do log("Deploying Cortex Agent: " ~ full_agent_name, info=True) %}
    {% do log("=" * 80, info=True) %}

    {% call statement('main', fetch_result=False) %}
        {{ create_agent_sql }}
    {% endcall %}

    {% do log("✓ Cortex Agent deployed successfully: " ~ full_agent_name, info=True) %}

    -- Return relation info for dbt (use 'view' type as dbt doesn't recognize 'agent')
    {%- set target_relation = api.Relation.create(
        database=target_database,
        schema=target_schema,
        identifier=agent_name,
        type='view'
    ) -%}
    {{ return({'relations': [target_relation]}) }}
{% endmaterialization %}

このマクロを用いたdbt Agentのクエリを書きます
AGENTのクエリはこちらの構文です

{{
  config(
    materialized='cortex_agent',
    database='[DB名]',
    schema='schema名',
    alias='alias名'
  )
}}
  [ COMMENT = '<comment>' ]
  [ PROFILE = '<profile_object>' ]
  FROM SPECIFICATION
  $$
  <specification_object>
  $$;

実際の記述は、このような形になります(一部大幅省略)

{{
  config(
    materialized='cortex_agent',
    database='[DB名]',
    schema='[スキーマ名]',
    alias='[エイリアス名]'
  )
}}


{#- Environment prefix for display name -#}
{%- if target.name == 'dev' -%}
  {%- set env_prefix = '[DEV] ' -%}
{%- elif target.name == 'staging' -%}
  {%- set env_prefix = '[STAGING] ' -%}
{%- else -%}
  {%- set env_prefix = '' -%}
{%- endif -%}

COMMENT = 'BtoBリード獲得を分析するエージェント。リードスコアリング、パイプライン分析、MQL/SQL分析、広告コスト対効果(CPL, Cost per MQL)、キーワード×キャンペーンパフォーマンス分析、ユーザー×クリックIDアトリビューション分析を提供します。'
PROFILE = '{
  "display_name": "{{ env_prefix }}分析アナリスト",
  "avatar": "briefcase",
  "color": "orange"
}'
FROM SPECIFICATION
$$
models:
  orchestration: claude-sonnet-4-5
※-- 省略 -- ※
instructions:
  system: |
    あなたはBtoBマーケティングとリード獲得のデータ分析エキスパートです。
    以下の分析領域を専門的に分析し、ビジネスインサイトを提供してください:
※-- 省略 ここはしっかり書きましょう-- ※
  response: |
    - 簡潔で分かりやすい日本語で回答してください
※-- 省略 ここはしっかり書きましょう-- ※

  sample_questions:
    - question: "今月のリード数を教えてください"
      answer: "LeadAnalystを使用して今月のリード数を集計し、ソース別・フォームタイプ別の内訳を表示します。"
※-- 省略 SnowSightからだとMAX15ですが、クエリで記述すると16以上入ります。が、SnowSightから保存できなくなるので注意-- ※

tools:
  - tool_spec:
      type: "cortex_analyst_text_to_sql"
      name: "LeadAnalyst"
      description: |
        【セマンティックビュー名】sv_btob_lead_analytics

tool_resources:
  LeadAnalyst:
    semantic_view: "[セマンティックビュー名]"
$$

CREATE AGENTクエリについてはこちらのドキュメントを参照してください

これでdbtでSemanticViewとAgentを管理できるようになります

まとめ

データモデルからセマンティックレイヤー、エージェントまでを一括で管理することにより、AI(自分はClaude派)に補完してもらうことが容易になります

Intelligenceを使ってうまく結果を出せない時、モデリング修正→セマンティック修正→Agent修正→デプロイ を全て一気通貫で実施することが可能になり、とてもスムーズに拡張を実施することができています
(MCPを使うことでstaging環境でAgentのテストも実施できないかは、ただいま検証中)

データモデリングからIntelligenceの活用までのフローで悩んでいる方、ぜひご活用ください

最後に

エクスチュアでは、データをもっと容易に活用してビジネスに成果を出すことを目的に、多くのご支援を実施させていただいております
Snowflake Intelligenceを始めデータの活用方法にお悩みのかた、お気軽にお問い合わせください!

類似投稿