# Snowflake CoWorkをTreasure AI CDPに接続する

Snowflake CoWorkを使用すると、ビジネスユーザーはSnowflake内で自然言語による質問を行えます。PythonストアドプロシージャとExternal Access Integration（外部アクセス統合）を介してSnowflake CortexエージェントをTreasure AI CDP APIに接続することで、CoWorkのビジネスユーザーはSnowflakeを離れたりTreasure AI APIキーを露出させたりすることなく、Snowsightから直接、親セグメント、子セグメント、およびジャーニーを確認できます。

この連携パターンでは、AIの推論とトークン消費はすべてSnowflake内で実行され、**Snowflake AI Credits**として課金されます。Treasure AI側はHTTPS経由の通常のREST API呼び出しを処理するだけであるため、Treasure AI Creditsは消費されません。

## 連携の仕組み

ビジネスユーザーがSnowflake CoWorkで質問すると、`TREASURE_CDP_AGENT` Cortexエージェントが`td_cdp_lookup`カスタムツールを呼び出します。このツールはSnowflakeウェアハウス上で`TREASURE_AI.COWORK.TD_CDP_LOOKUP` Pythonストアドプロシージャを実行し、Snowflake内のSecretに保存されたキーを使ってTreasure AI CDP APIへ読み取り専用の`GET`リクエストを送信し、CoWorkが要約するための整形済みJSONを返します。

| Snowflakeオブジェクト  | 名前  | 用途  |
|  --- | --- | --- |
| **ウェアハウス** | `<YOUR_WAREHOUSE>`（例：`TREASURE_COWORK_WH`） | 質問ごとに数秒間ストアドプロシージャを実行します |
| **データベースとスキーマ** | `TREASURE_AI.COWORK` | ネットワークルール、シークレット、ストアドプロシージャ、Cortexエージェントを格納します |
| **ネットワークルール** | `TD_CDP_API_RULE` | ポート`443`でのアウトバウンド通信をご利用リージョンのTreasure AI CDP APIホストのみに制限します |
| **シークレット** | `TD_API_KEY` | ビジネスユーザーから見えないようにTreasure AI Master APIキーを保存します |
| **External Access Integration** | `TD_CDP_API_EAI` | ストアドプロシージャがCDP APIを呼び出せるようネットワークルールとシークレットを紐付けます |
| **ストアドプロシージャ** | `TD_CDP_LOOKUP` | CDP API（`list_parent_segments`、`list_segments`、`list_journeys`）を呼び出し、整形済みJSONを返します |
| **ロール** | `COWORK_USERS` | ビジネスユーザーにCoWork内でエージェントを呼び出すための読み取り専用アクセス権を付与します |
| **Cortexエージェント** | `TREASURE_CDP_AGENT`（`Treasure AI CDP`） | ビジネスユーザーに公開されるCoWorkエージェントです |


## 前提条件

- Snowflakeアカウントで**Snowflake CoWorkが有効**になっていること。
- **Snowflakeのロールとウェアハウス**: `SYSADMIN`および`ACCOUNTADMIN`へのアクセス権、およびウェアハウス（`TREASURE_COWORK_WH`など）。各ルックアップは数秒しか実行されないため、60秒の自動サスペンドを設定した専用のExtra Small（`X-SMALL`）ウェアハウスを推奨します。
- **専用のTreasure AIユーザー**: CoWorkユーザーに閲覧を許可する親セグメントのみに権限を絞った専用のTreasure AIユーザーを作成してください。すべてのCoWorkユーザーはこの単一のアイデンティティを通じてTreasure AIにクエリを実行します。
- **リージョン別のCDP APIホスト**: ご利用のTreasure AIリージョンに対応するCDP APIホスト名を確認します。


| リージョン  | ネットワークルール `VALUE_LIST` ホスト  | Python `CDP_HOST` URL  |
|  --- | --- | --- |
| **US (AWS us-east-1)** | `api-cdp.treasuredata.com:443` | `https://api-cdp.treasuredata.com` |
| **EU (eu01)** | `api-cdp.eu01.treasuredata.com:443` | `https://api-cdp.eu01.treasuredata.com` |
| **日本 (ap01)** | `api-cdp.treasuredata.co.jp:443` | `https://api-cdp.treasuredata.co.jp` |
| **アジア太平洋 (ap02)** | `api-cdp.ap02.treasuredata.com:443` | `https://api-cdp.ap02.treasuredata.com` |


## フェーズ1：SnowflakeでTreasure AI APIアクセスを設定する

このフェーズでは、Treasure AI APIキーを発行してSnowflakeのSecretに保存し、`TD_CDP_LOOKUP`ストアドプロシージャを作成します。

### ステップ1：Treasure AI Master APIキーを発行する

1. CoWork用に作成した専用の読み取り専用ユーザーとして[Treasure AI Console](/tools/td-console/log-in-to-td-console)にサインインします。
2. **My Settings → API Keys**へ移動し、**Add API Key**を選択します（[APIキーの取得](/products/my-settings/getting-your-api-keys)を参照）。
3. キーの種類として**Master Key**を選択し、保存して値（`<account_id>/<hex_secret>`）をコピーします。


専用ユーザーの権限を絞り、定期的にキーをローテーションしてください
Treasure AI CDP APIでオーディエンス、セグメント、ジャーニーを読み取るには、Write-OnlyキーではなくMaster Keyが必要です。このキーは必ずCoWorkが参照すべき特定の親セグメントの読み取り権限のみを持つ専用のTreasure AIユーザーで発行し、Snowflakeの`SECRET`内にのみ保存し、定期的にローテーションしてください。

### ステップ2：データベース、ネットワークルール、シークレットを作成する

SnowsightでSQLワークシートを開き、ロールを`SYSADMIN`、ウェアハウスを`<YOUR_WAREHOUSE>`（例：`TREASURE_COWORK_WH`）に設定して、スキーマ、ネットワークルール、シークレットを作成します。

```sql
USE ROLE SYSADMIN;
USE WAREHOUSE <YOUR_WAREHOUSE>;

CREATE DATABASE IF NOT EXISTS TREASURE_AI;
CREATE SCHEMA IF NOT EXISTS TREASURE_AI.COWORK;
USE SCHEMA TREASURE_AI.COWORK;

-- ご利用のTreasure AIアカウントがEU、日本、またはAP02の場合はホストを置き換えてください。
CREATE OR REPLACE NETWORK RULE TD_CDP_API_RULE
  MODE = EGRESS
  TYPE = HOST_PORT
  VALUE_LIST = ('api-cdp.treasuredata.com:443');

CREATE OR REPLACE SECRET TD_API_KEY
  TYPE = GENERIC_STRING
  SECRET_STRING = '<TREASURE_MASTER_API_KEY>';
```

両方の箇所でリージョン別ホストを一致させてください
EU、日本、またはAP02向けに`TD_CDP_API_RULE`のホストを変更した場合は、ステップ4の`CDP_HOST`も同じリージョンのHTTPSホスト名に変更する必要があります。

### ステップ3：External Access Integrationを作成する

`ACCOUNTADMIN`に切り替えてExternal Access Integrationを作成し、`SYSADMIN`に`USAGE`権限を付与します。

```sql
USE ROLE ACCOUNTADMIN;

CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION TD_CDP_API_EAI
  ALLOWED_NETWORK_RULES = (TREASURE_AI.COWORK.TD_CDP_API_RULE)
  ALLOWED_AUTHENTICATION_SECRETS = (TREASURE_AI.COWORK.TD_API_KEY)
  ENABLED = TRUE;

GRANT USAGE ON INTEGRATION TD_CDP_API_EAI TO ROLE SYSADMIN;

USE ROLE SYSADMIN;
USE SCHEMA TREASURE_AI.COWORK;
```

### ステップ4：`TD_CDP_LOOKUP`ストアドプロシージャを作成する

このストアドプロシージャは、Treasure AI CDP APIに対する3つの読み取り専用アクションを公開します。

| アクション  | 入力  | 呼び出されるTreasure AI CDP APIエンドポイント  |
|  --- | --- | --- |
| `list_parent_segments` | なし | `GET /audiences` |
| `list_segments` | `parent_segment_id` | `GET /audiences/{id}/segments` |
| `list_journeys` | `parent_segment_id` | `GET /audiences/{id}/folders`、その後フォルダごとに`GET /entities/journeys?folder_id=…`（ジャーニーIDで重複排除） |


`SYSADMIN`として次のSQLを実行し、ストアドプロシージャを作成します。

```sql
CREATE OR REPLACE PROCEDURE TREASURE_AI.COWORK.TD_CDP_LOOKUP(
    ACTION VARCHAR,
    PARENT_SEGMENT_ID VARCHAR DEFAULT ''
)
RETURNS STRING
LANGUAGE PYTHON
RUNTIME_VERSION = '3.11'
PACKAGES = ('snowflake-snowpark-python', 'requests')
HANDLER = 'main'
EXTERNAL_ACCESS_INTEGRATIONS = (TD_CDP_API_EAI)
SECRETS = ('td_key' = TD_API_KEY)
COMMENT = 'Read-only lookups against the Treasure AI CDP API for Snowflake CoWork'
EXECUTE AS OWNER
AS
$$
import json
import re

import _snowflake
import requests

# TD_CDP_API_RULE のリージョン別ホストと一致させる必要があります。
CDP_HOST = "https://api-cdp.treasuredata.com"
TIMEOUT_SECONDS = 30
MAX_ITEMS = 200


def _limit(rows):
    return rows[:MAX_ITEMS], len(rows) > MAX_ITEMS


def _get(path):
    key = _snowflake.get_generic_secret_string("td_key")
    resp = requests.get(
        CDP_HOST + path,
        headers={"Authorization": "TD1 " + key, "Accept": "application/json"},
        timeout=TIMEOUT_SECONDS,
    )
    resp.raise_for_status()
    return resp.json()


def _list_parent_segments(_parent_segment_id):
    rows = _get("/audiences")
    results = [
        {
            "parent_segment_id": str(r.get("id")),
            "name": r.get("name"),
            "description": r.get("description"),
            "population": r.get("population"),
            "updated_at": r.get("updatedAt"),
        }
        for r in rows
    ]
    return _limit(results)


def _list_segments(parent_segment_id):
    if not re.fullmatch(r"\d+", parent_segment_id):
        raise ValueError(
            "parent_segment_id must be a number. Call list_parent_segments first."
        )
    rows = _get("/audiences/" + parent_segment_id + "/segments")
    results = [
        {
            "segment_id": str(r.get("id")),
            "name": r.get("name"),
            "description": r.get("description"),
            "population": r.get("population"),
            "kind": r.get("kind"),
            "updated_at": r.get("updatedAt"),
        }
        for r in rows
    ]
    return _limit(results)


def _list_journeys(parent_segment_id):
    if not re.fullmatch(r"\d+", parent_segment_id):
        raise ValueError(
            "parent_segment_id must be a number. Call list_parent_segments first."
        )
    folders = _get("/audiences/" + parent_segment_id + "/folders")
    results = {}
    for folder in folders:
        body = _get("/entities/journeys?folder_id=" + str(folder.get("id")))
        for item in body.get("data") or []:
            a = item.get("attributes") or {}
            results[item.get("id")] = {
                "journey_id": item.get("id"),
                "name": a.get("name"),
                "state": a.get("state"),
                "paused": a.get("paused"),
                "parent_segment_id": a.get("audienceId"),
                "launched_at": a.get("launchedAt"),
                "updated_at": a.get("updatedAt"),
            }
    return _limit(list(results.values()))


ACTIONS = {
    "list_parent_segments": _list_parent_segments,
    "list_segments": _list_segments,
    "list_journeys": _list_journeys,
}

HTTP_HINTS = {
    401: "API key rejected (wrong key, wrong region, or a write-only key)",
    403: "the Treasure user behind the API key lacks permission",
    404: "not found (check the parent segment id)",
}


def main(session, action, parent_segment_id=""):
    action = (action or "").strip().lower()
    if action not in ACTIONS:
        return json.dumps({"error": "Unknown action. Use one of: " + ", ".join(ACTIONS)})
    try:
        results, truncated = ACTIONS[action]((parent_segment_id or "").strip())
        return json.dumps({
            "action": action,
            "count": len(results),
            "truncated": truncated,
            "results": results,
        })
    except requests.HTTPError as e:
        code = e.response.status_code if e.response is not None else None
        return json.dumps({"error": f"HTTP {code}: {HTTP_HINTS.get(code, 'Treasure API error')}"})
    except Exception as e:
        return json.dumps({"error": str(e)})
$$;
```

### ステップ5：ストアドプロシージャをテストする

ワークシート上で3つすべてのアクションを直接テストします（最初の呼び出しで返された`parent_segment_id`を2番目と3番目の呼び出しに渡します）。

```sql
CALL TREASURE_AI.COWORK.TD_CDP_LOOKUP('list_parent_segments');
CALL TREASURE_AI.COWORK.TD_CDP_LOOKUP('list_segments', '<PARENT_SEGMENT_ID>');
CALL TREASURE_AI.COWORK.TD_CDP_LOOKUP('list_journeys', '<PARENT_SEGMENT_ID>');
```

## フェーズ2：Cortexエージェントを作成・設定する

ストアドプロシージャがTreasure AIからJSONを返すことを確認したら、Agent StudioでCortexエージェントを作成し、`TD_CDP_LOOKUP`をツールとして紐付けます。

### ステップ1：`COWORK_USERS`ロールを作成して権限を付与する

`ACCOUNTADMIN`としてビジネスユーザー用のロールを作成し、ウェアハウス、データベース、スキーマ、およびストアドプロシージャに対する`USAGE`権限を付与します。

```sql
USE ROLE ACCOUNTADMIN;

CREATE ROLE IF NOT EXISTS COWORK_USERS;
GRANT ROLE COWORK_USERS TO USER <USER_1>;
GRANT ROLE COWORK_USERS TO USER <USER_2>;

GRANT USAGE ON WAREHOUSE <YOUR_WAREHOUSE> TO ROLE COWORK_USERS;
GRANT USAGE ON DATABASE TREASURE_AI TO ROLE COWORK_USERS;
GRANT USAGE ON SCHEMA TREASURE_AI.COWORK TO ROLE COWORK_USERS;
GRANT USAGE ON PROCEDURE TREASURE_AI.COWORK.TD_CDP_LOOKUP(VARCHAR, VARCHAR) TO ROLE COWORK_USERS;
```

### ステップ2：Snowsight Agent Studioでエージェントを作成する

1. Snowsightのナビゲーションメニューで、**AI → Agent Studio**を選択します。
2. **Agents**タブで、**Create agent**を選択します。
3. エージェントの詳細を入力します。
  - **Database and schema**: `TREASURE_AI.COWORK`
  - **Agent object name**: `TREASURE_CDP_AGENT`（エージェントのAPI URLはこの名前から生成されます）
  - **Display name**: `Treasure AI CDP`
4. **Create agent**を選択します。


### ステップ3：SQLでエージェント仕様を設定する

SQLワークシートで次の`ALTER AGENT`ステートメントを実行し、オーケストレーション指示、回答フォーマットルール、`td_cdp_lookup`ツール仕様を一括設定した上で、`COWORK_USERS`にエージェントの`USAGE`権限を付与します。

```sql
USE ROLE SYSADMIN;
USE SCHEMA TREASURE_AI.COWORK;

ALTER AGENT TREASURE_AI.COWORK.TREASURE_CDP_AGENT MODIFY LIVE VERSION SET SPECIFICATION = $$
models:
  orchestration: auto
instructions:
  orchestration: |
    Use the td_cdp_lookup tool for any question about Treasure AI parent segments, segments, audiences or journeys.
    - To list parent segments, call it with action = list_parent_segments.
    - To list the segments inside a parent segment, first get its parent_segment_id from list_parent_segments, then call action = list_segments with that id.
    - To list journeys, first get the parent_segment_id from list_parent_segments, then call action = list_journeys with that id.
    Never guess ids or numbers. If the tool returns an error, tell the user what it says.
  response: |
    Answer briefly, using a table when there is more than one item. "population" is the number of customer profiles; show it with thousands separators. If a population is empty, say the size hasn't been calculated yet. If "truncated" is true, say that only the first 200 items are shown.
tools:
  - tool_spec:
      type: code_execution
      name: code_execution
  - tool_spec:
      type: generic
      name: td_cdp_lookup
      description: "Read-only lookups against the Treasure AI CDP API: list parent segments, segments of a parent segment, and journeys of a parent segment. Returns JSON."
      input_schema:
        type: object
        properties:
          action:
            type: string
            description: "One of: list_parent_segments, list_segments, list_journeys"
          parent_segment_id:
            type: string
            description: "Numeric parent segment id from list_parent_segments. Required for list_segments and list_journeys; leave empty for list_parent_segments."
        required:
          - action
tool_resources:
  code_execution: {}
  td_cdp_lookup:
    type: procedure
    identifier: TREASURE_AI.COWORK.TD_CDP_LOOKUP
    execution_environment:
      type: warehouse
      warehouse: <YOUR_WAREHOUSE>
$$;

GRANT USAGE ON AGENT TREASURE_AI.COWORK.TREASURE_CDP_AGENT TO ROLE COWORK_USERS;
```

Agent Studioのエージェントの**Configuration → Tools**タブで、**Code Execution tool**が有効になっており、**Custom tools**の下に`TD_CDP_LOOKUP`が表示されていることを確認します。

### ステップ4：PreviewまたはSQLでエージェントをテストする

SQLワークシートまたはAgent Studioのエージェントの**Preview**タブからエージェントをテストします。

```sql
SELECT SNOWFLAKE.CORTEX.DATA_AGENT_RUN(
  'TREASURE_AI.COWORK.TREASURE_CDP_AGENT',
  $${"messages":[{"role":"user","content":[{"type":"text","text":"List my parent segments"}]}]}$$
) AS r;
```

**Preview**タブで*"List a couple of parent segments"*と質問し、親セグメントのIDと桁区切りされた母集団（Population）がテーブル形式で表示されることを確認します。

## フェーズ3：エージェントをSnowflake CoWorkに公開する

### ステップ1：エージェントのバージョンを公開する

エージェントへの変更はドラフトとして保存されます。エージェントのヘッダーに**Draft**または**Unpublished changes**と表示されている場合は、次の手順で公開します。

1. エージェントページ右上の**Publish**を選択します。
2. 本番トラフィックが新しいバージョンへルーティングされるよう**Use this version**にチェックを入れたままにし、任意でバージョンメモを入力して**Publish**を選択します。


### ステップ2：Snowflake Intelligence（CoWork）にエージェントを登録する

`ACCOUNTADMIN`として、アカウントのSnowflake Intelligenceオブジェクトに`TREASURE_CDP_AGENT`を追加し、`COWORK_USERS`に`USAGE`権限を付与します（すでにSnowflake Intelligenceオブジェクトがあるかどうかは`SHOW SNOWFLAKE INTELLIGENCES;`で確認できます）。

```sql
USE ROLE ACCOUNTADMIN;

CREATE SNOWFLAKE INTELLIGENCE IF NOT EXISTS SNOWFLAKE_INTELLIGENCE_OBJECT_DEFAULT;

ALTER SNOWFLAKE INTELLIGENCE SNOWFLAKE_INTELLIGENCE_OBJECT_DEFAULT
  ADD AGENT TREASURE_AI.COWORK.TREASURE_CDP_AGENT;

GRANT USAGE ON SNOWFLAKE INTELLIGENCE SNOWFLAKE_INTELLIGENCE_OBJECT_DEFAULT TO ROLE COWORK_USERS;
```

登録が完了すると、エージェントのヘッダーに**Added to Snowflake CoWork**と表示されます。

### ステップ3：Snowflake CoWorkからTreasure AIにクエリを実行する

ビジネスユーザーは[ai.snowflake.com](https://ai.snowflake.com)を開く（またはSnowsightで**AI → Snowflake CoWork**を選択する）ことで、アクティブなロールが`COWORK_USERS`であることを確認し、チャット入力欄で**Treasure AI CDP**を選択して、オーディエンス、セグメント、ジャーニーについて質問できるようになります。

## セキュリティ、コスト、および制限事項

- **課金とクレジット消費**: CoWorkでの質問は**Snowflake AI Credits**を消費し、`TD_CDP_LOOKUP`プロシージャは`<YOUR_WAREHOUSE>`上で数秒間実行されます。Treasure AI側は通常のREST API呼び出しを処理するだけであるため、Treasure AI Creditsは使用されません。SnowflakeでのCoWork利用状況は次のクエリで監査できます。

```sql
SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.SNOWFLAKE_COWORK_USAGE_HISTORY ORDER BY START_TIME DESC;
```
- **認証情報の分離**: Treasure AI Master APIキーは`TREASURE_AI.COWORK.TD_API_KEY`内に保存されます。プロシージャは`EXECUTE AS OWNER`として実行されるため、`COWORK_USERS`ロールを持つビジネスユーザーがシークレットを読み取ることはできません。キーをローテーションするには次を実行します。

```sql
ALTER SECRET TREASURE_AI.COWORK.TD_API_KEY SET SECRET_STRING = '<NEW_KEY>';
```
- **読み取り専用のメタデータ範囲**: ストアドプロシージャは固定のCDPエンドポイント（`/audiences`、`/audiences/{id}/segments`、`/audiences/{id}/folders`、および`/entities/journeys`）に対して`GET`リクエストのみを送信します。取得するのはエンティティのメタデータ（ID、名前、母集団、ステータス、タイムスタンプ）のみであり、個々の顧客のPIIの読み取り、セグメントルールの変更、ジャーニーの起動は行えません。
- **200件の上限セーフガード**: Cortexエージェントのコンテキストウィンドウ使用量とレイテンシを予測可能に保つため、`MAX_ITEMS = 200`によって各リストの返却件数が最大200件に制限されています。200件を超える項目が存在する場合、プロシージャは`truncated: true`を設定し、エージェントは最初の200件のみが表示されている旨をユーザーに通知します。


## トラブルシューティング

| 症状  | 主な原因  | 対処方法  |
|  --- | --- | --- |
| `HTTP 401: API key rejected` | 無効なAPIキー、リージョンホストの不一致、またはWrite-Only APIキーが使用された | [Treasure AI Console](/tools/td-console/log-in-to-td-console)で**Master Key**を発行し（[APIキーの取得](/products/my-settings/getting-your-api-keys)を参照）、`TD_CDP_API_RULE`と`CDP_HOST`の両方がご利用リージョンのCDP APIホストを使用していることを確認します |
| `HTTP 403: the Treasure user behind the API key lacks permission` | 専用のTreasure AIユーザーに親セグメントの閲覧権限がない | 専用のTreasure AIユーザーに必要な親セグメントへの閲覧権限を付与します |
| `TD_CDP_LOOKUP`呼び出し時の接続またはネットワークエラー | `CDP_HOST`のホスト名が`TD_CDP_API_RULE`に含まれていない | `TD_CDP_API_RULE`と`CDP_HOST`のホスト名を一致させ、フェーズ1のステップ2〜4を再実行します |
| AnacondaまたはPythonパッケージ規約に関するエラー | アカウントでSnowflakeのサードパーティPythonパッケージが有効化されていない | `ORGADMIN`がSnowsightの**Admin → Billing & Terms**でAnaconda規約に同意します |
| CoWorkでユーザーに`insufficient privileges`が表示される、またはエージェントが見つからない | ロールへの権限付与が不足している、またはエージェントがSnowflake Intelligenceオブジェクトに追加されていない | ユーザーのアクティブなロールが`COWORK_USERS`であることを確認し、フェーズ2のステップ1、フェーズ2のステップ3、およびフェーズ3のステップ2の`GRANT USAGE`ステートメントを再実行します |
| 最近の指示やツールの変更がCoWorkに反映されない | エージェントに未公開のドラフト変更がある | Agent Studioでエージェントを開き、**Publish**を選択します（フェーズ3のステップ1） |


## 次のステップ

- [Snowflake Cortex Code](/getting-started/ai/snowflake-cortex-code) — Snowflake Cortex Code CLIを`tdx mcp`またはTreasure AI Studio MCPエンドポイントに接続する
- [Databricks Genie Code](/getting-started/ai/databricks-genie-code) — Databricks Genie Code CLIをTreasure AIに接続する
- [Snowflake上のComposable Audience Studio](/products/customer-data-platform/composable-cdp/audience-studio/snowflake) — Zero-CopyでSnowflakeテーブル上に直接セグメントを構築する
- [Snowflake向けOpen Catalog Access](/products/customer-data-platform/open-catalog-access/snowflake-integration) — SnowflakeからApache Iceberg形式のCDPテーブルをクエリする