Cortex Analyst の Cortex Search 統合によるリテラル検索の改善を試してみた #SnowflakeDB
はじめに
Snowflake 上の構造化データに対して自然言語で問い合わせ、分析を行える機能である Cortex Analyst の Cortex Search との統合を試してみましたので、本記事で検証内容をまとめてみます。
機能の概要
前提として Cortex Analyst 機能については以下の記事で詳しく紹介されています。
この上で、本記事で扱う Cortex Analyst の Cortex Search との統合機能については以下に記載があります。
Cortex Analyst がユーザーの質問から正しい SQL クエリを生成するには、データベース上のテーブルでフィルタに使用する正確なリテラル値(実際のデータ値)を特定する必要があります。しかし、ユーザーの自由な質問表現からこれらの値を直接抽出できるとは限りません。
公式ドキュメントの例ですが、ユーザーが「What was my overall sales of iced tea in Q1?(第1四半期のアイスティーの売上は?)」と質問したとします。もし商品名カラムに単純な文字列検索(LIKE '%iced tea%'など)を適用してしまうと、本来取得したい「Ice Tea」が漏れてしまうだけでなく、「spiced tea」という偶然「iced tea」の文字列が含まれる別の商品がヒットし、誤検知が起こり得ます。
上記の例のように、ユーザーの質問に含まれる単語とデータベース内の値が完全に一致しない場合があるため、この対応方法として、Cortex Analyst では以下の2つをサポートしています。
- サンプル値に対するセマンティック検索(Sample values の追加)
- Cortex Analyst で使用するセマンティックモデルでは、対象のディメンションなどに対してサンプル値(Sample values)を直接追加できます。ドキュメントによると、特にカーディナリティが低い(明確な値が約1~10個程度)ディメンションでは、この方法が推奨されています
- サンプル値を追加しておくと、Cortex Analyst が SQL を生成する前に、ユーザーの質問とサンプル値の間でセマンティック類似性検索を行い、単純な文字列一致よりも関連性の高いリテラル値を見つけ出せる可能性があります
- この方法では、検索された固定サイズのサンプル値セットのみがリテラルとして LLM に渡されるため、LLM のコンテキストウィンドウ上限を超えるリスクや、追加のストレージも不要です
- Cortex Search Service を使ったセマンティック検索
- カーディナリティが高い(明確な値が10個以上)、あるいは値が頻繁に変更されるディメンションでは、こちらの方法が推奨されています
- Snowflake の Cortex Search は、意味的に類似したデータを検索する「ベクトル検索」と、語彙的に類似したデータを検索する「キーワード検索」を組み合わせたハイブリッド検索が可能です
- 注意点として、執筆時点では、ディメンションごとにサポートされる Cortex Search サービスは1つのみです。また、Cortex Search サービスの利用に伴う追加のストレージおよびコンピューティングコストが発生します
試してみる
サンプルデータ
ここでは、曖昧な検索で誤検知が起きやすいデータをサンプルとして用意し、AI が文字列の部分一致ではなく意味的な検索ができているかを確かめるためのデータを含めます。
PRODUCTSという商品テーブルを以下の内容で作成し、このテーブルに対する問い合わせを行います。
-- PRODUCTSテーブル
CREATE OR REPLACE TABLE PRODUCTS (
PRODUCT_ID NUMBER,
PRODUCT_NAME VARCHAR COMMENT '商品の名前。Cortex Searchの検索対象列',
CATEGORY VARCHAR,
PRICE NUMBER
);
INSERT INTO PRODUCTS (PRODUCT_ID, PRODUCT_NAME, CATEGORY, PRICE) VALUES
-- 【正解なのに漏れるデータ】
-- 文字列に「アイスティー」を含まないため、LIKE検索ではヒットしないが、意味的にはアイスティーなのでCortex Searchで拾ってくれることを期待するレコード
(1, '冷たい紅茶', 'ティー', 150),
(2, '夏期限定アイスダージリン', 'ティー', 170),
-- 【ノイズとして混じるデータ】
-- 文字列に「アイスティー」を含むため、LIKE検索ではヒットしてしまうが、飲み物ではないため除外されてほしいデータ
(3, 'アイスティー用グラス', '食器', 800),
(4, 'アイスティー専用ガムシロップ', '調味料', 300),
-- 【単純な正解データ】
(5, 'ピーチアイスティー', 'ティー', 150),
(6, '無糖アイスティー', 'ティー', 120),
-- 【その他のダミーデータ】
(7, 'ホットアップルティー', 'ティー', 160),
(8, 'スパイスティー', 'ティー', 140),
(9, 'ブレンドコーヒー', 'コーヒー', 110),
(10, 'コーヒーゼリー', 'デザート', 200),
(11, 'オレンジジュース', 'ジュース', 150);
セマンティックビューの作成
セマンティックビューは Snowsight 経由で作成しました。

検証済みクエリとして、以下を追加しておきました。
-- ブレンドコーヒーの商品情報を表示して
SELECT
PRODUCT_ID,
PRODUCT_NAME,
CATEGORY,
PRICE
FROM TEST_DB.PUBLIC.PRODUCTS
WHERE PRODUCT_NAME IN ('ブレンドコーヒー');

検証済みクエリを追加したので、対象のテーブル・カラムは自動的に選択されています。

始めの段階では、オプションのサンプル値の追加は無しでセマンティックビューを作成しました。

この時点のセマンティックビューは、以下のような内容となっていました。
create or replace semantic view TEST_DB.SEMANTIC_VIEWS.TEST_SV
tables (
TEST_DB.PUBLIC.PRODUCTS comment='The table contains records of products available for sale. Each record represents a single product and includes details about its categorization and pricing.'
)
dimensions (
PRODUCTS.CATEGORY as CATEGORY comment='Product category names written in Japanese.',
PRODUCTS.PRICE as PRICE comment='The price of the product.',
PRODUCTS.PRODUCT_ID as PRODUCT_ID comment='Unique identifier for each product in the table.',
PRODUCTS.PRODUCT_NAME as PRODUCT_NAME comment='商品の名前。Cortex Searchの検索対象列'
)
comment='Cortex Searchとの統合のテスト'
with extension (CA='{"tables":[{"name":"PRODUCTS","dimensions":[{"name":"CATEGORY"},{"name":"PRICE"},{"name":"PRODUCT_ID"},{"name":"PRODUCT_NAME"}]}],"verified_queries":[{"name":"0;1","sql":"SELECT PRODUCT_ID, PRODUCT_NAME, CATEGORY, PRICE FROM products WHERE PRODUCT_NAME IN (''ブレンドコーヒー'')","question":"ブレンドコーヒーの商品情報を教えて","verified_at":1771640685,"verified_by":"Semantic Model Generator"}]}');
エージェント経由で問い合わせ
Cortex Agent を作成し、ツールとして上記のセマンティックビューを指定し、エージェント経由で問い合わせを行ってみます。
まずは検証済みクエリとして追加したそのままの内容で動作を確認します。
ブレンドコーヒーの商品情報を表示して
この場合、下図の応答となり基本的な動作は問題なさそうです。

続けて、今回検証したいあいまいな検索を行います。単純な文字列一致では拾うことが難しい「アイスティー」関連の商品を、エージェントがどう処理するのか確認します。
アイスティーの商品情報を表示して
この時点では、結果は下図のようになり、SQL の生成もできませんでした。

サンプル値の追加
はじめにセマンティックビューで対象のディメンション(PRODUCT_NAME)を編集し、サンプル値を下図のように追加します。

この状態で再度同じ問い合わせ(「アイスティーの商品情報を表示して」)を行うと、以下の思考ステップとなり、「正解データ」は拾えましたが、単純なLIKE '%アイス%' AND product_name LIKE '%ティー%'による文字列検索のため「ノイズとして混じるデータ」が含まれ、意味的にはアイスティーなので含めてほしかった「冷たい紅茶」や「夏期限定アイスダージリン」は含まれませんでした。
Thinking steps
Planning the next steps
ユーザーは「アイスティーの商品情報を表示して」と質問しています。これは商品情報の取得を求める質問です。
システムリマインダーによると、ユーザーは "producr_detail" というツールを選択しています。このツールはコーヒーやその他の商品情報にアクセスできるCortex Analystツールのようです。
ツールの説明を見ると、検証済みクエリの例があります:
Question: ブレンドコーヒーの商品情報を教えて
SQL: SELECT PRODUCT_ID, PRODUCT_NAME, CATEGORY, PRICE FROM products WHERE PRODUCT_NAME IN ('ブレンドコーヒー')
これと同様に、「アイスティー」の商品情報を取得する必要があります。
PLAN:
producr_detail ツールを使用して、アイスティーの商品情報を取得
結果が返ってきたら、ユーザーに情報を表示
まずは producr_detail ツールを呼び出します。
has_time_column: 商品情報のテーブルなので、時系列データではなさそう → false
need_future_forecasting_data: 将来の予測は不要 → false
previous_related_tool_result_id: 初回なので空文字列
query: "アイスティーの商品情報"
original_query: "アイスティーの商品情報を表示して"
Executing SQL
Answer accuracy verified by agent owner
WITH __products AS (
SELECT
category,
price,
product_id,
product_name
FROM TEST_DB.PUBLIC.PRODUCTS
)
SELECT
product_id,
product_name,
category,
price
FROM __products
WHERE
product_name LIKE '%アイス%' AND product_name LIKE '%ティー%'
-- Generated by Cortex Analyst (request_id: 64c2ea60-4a58-4cae-9fe9-fa32a386ca9b)
;
Planning the next steps
SQLクエリが実行され、アイスティー関連の商品情報が取得できました。4つの商品が見つかりました:
アイスティー用グラス (食器) - 800円
アイスティー専用ガムシロップ (調味料) - 300円
ピーチアイスティー (ティー) - 150円
無糖アイスティー (ティー) - 120円
この結果は表形式で表示するのが適切です。ユーザーは「表示して」と言っているので、テーブルブロックを使って結果を表示します。
数値データがあり、比較可能なデータですが、商品情報の単純な一覧なので、チャートは必要ありません。テーブル形式が最適です。

サンプル値を削除し、Cortex Search サービスを追加する
Cortex Search サービスの統合を試してみます。事前に以下の内容で Cortex Search サービスを作成しました。
-- Cortex Search Serviceの作成
CREATE OR REPLACE CORTEX SEARCH SERVICE products_search_svc
ON product_name
ATTRIBUTES category
WAREHOUSE = compute_wh
TARGET_LAG = '1 day'
AS (
SELECT
product_name,
category
FROM PRODUCTS
);
作成したサービスに SQL で検索し動作を確認してみます。
SELECT
SNOWFLAKE.CORTEX.SEARCH_PREVIEW (
'products_search_svc',
'{
"query": "アイスティー",
"columns": ["product_name", "category"],
"limit": 3
}'
);
整形していますが、この場合、以下のような出力となりました。「アイスティー用グラス」がこの中では意味的に近いと評価されました。 商品名のような短い単語同士の比較では、文脈を読み取ることが難しいのかもしれません。
{
"results": [
{
"@scores": {
"text_match": 1.9612972e-7,
"cosine_similarity": 0.5061194,
"reranker_score": -4.25
},
"category": "食器",
"product_name": "アイスティー用グラス"
},
{
"@scores": {
"text_match": 1.9612972e-7,
"cosine_similarity": 0.49871403,
"reranker_score": -4.75
},
"category": "ティー",
"product_name": "無糖アイスティー"
},
{
"@scores": {
"text_match": 1.9612972e-7,
"cosine_similarity": 0.49757475,
"reranker_score": -5.78125
},
"category": "調味料",
"product_name": "アイスティー専用ガムシロップ"
}
],
"request_id": "ad831a93-1ba4-4302-8a5f-5670c3e610f2"
}
Cortex Search サービス用にモデルを指定できます。日本語など、多言語に対応しているモデル(voyage-multilingual-2)を使用できるので、こちらで作成し直してみます。
CREATE OR REPLACE CORTEX SEARCH SERVICE products_search_svc
ON product_name
ATTRIBUTES category
WAREHOUSE = compute_wh
TARGET_LAG = '1 day'
EMBEDDING_MODEL = 'voyage-multilingual-2' -- モデルを変更
AS (
SELECT
product_name,
category
FROM PRODUCTS
);
この場合、上位3件は以下の出力となり「無糖アイスティー」の類似度が上昇しています。ここでは、このモデルを使用することとしました。
{
"results": [
{
"@scores": {
"text_match": 1.9612972e-7,
"cosine_similarity": 0.697859,
"reranker_score": -4.75
},
"category": "ティー",
"product_name": "無糖アイスティー"
},
{
"@scores": {
"text_match": 1.9612972e-7,
"cosine_similarity": 0.6248987,
"reranker_score": -4.25
},
"category": "食器",
"product_name": "アイスティー用グラス"
},
{
"@scores": {
"text_match": 0.0,
"cosine_similarity": 0.61080647,
"reranker_score": -4.9375
},
"category": "ティー",
"product_name": "ピーチアイスティー"
}
],
"request_id": "687b0086-e1b6-48c0-94b0-c4c10630a869"
}
なお、今回は明示的に指定することはありませんが、Cortex Search サービス作成時に ATTRIBUTES category と指定しておくことで、検索時に特定の属性で絞り込み(フィルター)を行うことも可能です。
SELECT
SNOWFLAKE.CORTEX.SEARCH_PREVIEW (
'products_search_svc',
'{
"query": "アイスティー",
"columns": ["product_name", "category"],
"filter": {"@eq": {"category": "ティー"}},
"limit": 10
}'
);
出力
{
"results": [
{
"@scores": {
"text_match": 1.9612972e-7,
"cosine_similarity": 0.697227,
"reranker_score": -4.75
},
"category": "ティー",
"product_name": "無糖アイスティー"
},
{
"@scores": {
"text_match": 0.0,
"cosine_similarity": 0.61071473,
"reranker_score": -4.9375
},
"category": "ティー",
"product_name": "ピーチアイスティー"
},
{
"@scores": {
"text_match": 0.0,
"cosine_similarity": 0.6252878,
"reranker_score": -7.0625
},
"category": "ティー",
"product_name": "スパイスティー"
},
{
"@scores": {
"text_match": 0.0,
"cosine_similarity": 0.5680423,
"reranker_score": -7.34375
},
"category": "ティー",
"product_name": "ホットアップルティー"
},
{
"@scores": {
"text_match": 0.0,
"cosine_similarity": 0.5315695,
"reranker_score": -7.65625
},
"category": "ティー",
"product_name": "夏期限定アイスダージリン"
},
{
"@scores": {
"text_match": 0.0,
"cosine_similarity": 0.5822128,
"reranker_score": -9.4375
},
"category": "ティー",
"product_name": "冷たい紅茶"
}
],
"request_id": "04687dec-ddc8-42fa-91a6-af893a7861a7"
}
Cortex Analyst に Cortex Search サービスを統合する
Cortex Analyst で対象のディメンション(PRODUCT_NAME)に先の手順で作成した Cortex Search サービス追加します(サンプル値は削除しています)。

この時点でのセマンティックビューの定義を確認するとwith cortex search serviceとして、以下のように追加されていました。
create or replace semantic view TEST_DB.SEMANTIC_VIEWS.TEST_SV
tables (
TEST_DB.PUBLIC.PRODUCTS comment='The table contains records of products available for sale. Each record represents a single product and includes details about its categorization and pricing.'
)
dimensions (
PRODUCTS.CATEGORY as CATEGORY comment='Product category names written in Japanese.',
PRODUCTS.PRICE as PRICE comment='The price of the product.',
PRODUCTS.PRODUCT_ID as PRODUCT_ID comment='Unique identifier for each product in the table.',
PRODUCTS.PRODUCT_NAME as PRODUCT_NAME comment='商品の名前。Cortex Searchの検索対象列' with cortex search service PRODUCTS_SEARCH_SVC
)
comment='Cortex Searchとの統合のテスト'
with extension (CA='{"tables":[{"name":"PRODUCTS","dimensions":[{"name":"CATEGORY"},{"name":"PRICE"},{"name":"PRODUCT_ID"},{"name":"PRODUCT_NAME"}]}],"verified_queries":[{"name":"\\"0;1\\"","sql":"SELECT PRODUCT_ID, PRODUCT_NAME, CATEGORY, PRICE FROM products WHERE PRODUCT_NAME IN (''ブレンドコーヒー'')","question":"ブレンドコーヒーの商品情報を教えて","verified_at":1771640685,"verified_by":"Semantic Model Generator"}]}');
検索対象のディメンションに Cortex Search サービスを追加した状態で、エージェント経由で問い合わせを行います。なお、エージェントのモデル指定はAutoとしています。
アイスティーの商品情報を表示して
思考ステップは以下のようになり、「冷たい紅茶」と「夏期限定アイスダージリン」が IN 句に含まれていました。これらは文字列として「アイスティー」を含んでいないため、単純なLIKEによる検索では拾うことができていませんでしたが、Cortex Search サービスとの統合による、意味的な類似性の検索によって、これらもアイスティーの仲間と判断され、抽出されています。
Thinking steps
Planning the next steps
The user is asking "アイスティーの商品情報を表示して" which means "Display the product information for iced tea" in Japanese.
Let me break down this request:
This is a straightforward information retrieval question about a specific product (iced tea/アイスティー)
I need to find product information, which suggests I should use the producr_detail tool (text-to-SQL semantic model)
No clarification is needed - the intent is clear: retrieve product details for iced tea
Looking at the tool description for producr_detail, I can see there's a verified query example:
Question: ブレンドコーヒーの商品情報を教えて
SQL: SELECT PRODUCT_ID, PRODUCT_NAME, CATEGORY, PRICE FROM products WHERE PRODUCT_NAME IN ('ブレンドコーヒー')
This is very similar to my question - just a different product. So the semantic model knows how to handle product information queries.
PLAN:
Query the producr_detail tool to get product information for アイスティー (iced tea)
Present the results to the user
Let me call the producr_detail tool with the appropriate parameters.
Executing SQL
Answer accuracy verified by agent owner
WITH __products AS (
SELECT
category,
price,
product_id,
product_name
FROM TEST_DB.PUBLIC.PRODUCTS
)
SELECT
product_id,
product_name,
category,
price
FROM __products
WHERE
product_name IN ('無糖アイスティー', 'アイスティー専用ガムシロップ', 'アイスティー用グラス', 'ピーチアイスティー', '夏期限定アイスダージリン', '冷たい紅茶')
-- Generated by Cortex Analyst (request_id: d08aa8dc-eecf-4bca-9df6-bbf8b4a21e1a)
;
Planning the next steps
Great! I received the SQL results for iced tea products. The query returned 6 products related to iced tea (アイスティー):
冷たい紅茶 (Cold Tea) - ティー - 150円
夏期限定アイスダージリン (Summer Limited Ice Darjeeling) - ティー - 170円
アイスティー用グラス (Iced Tea Glass) - 食器 - 800円
アイスティー専用ガムシロップ (Iced Tea Syrup) - 調味料 - 300円
ピーチアイスティー (Peach Iced Tea) - ティー - 150円
無糖アイスティー (Unsweetened Iced Tea) - ティー - 120円
This is a straightforward list of products. Since the user asked to "display" the product information, a table would be the best format. The SQL results contain clear tabular data that would be perfect for presentation in a table format.
Let me present this information in a table format using the table block.

一方で「アイスティー専用ガムシロップ」や「アイスティー用グラス」といった飲み物以外の商品も除外されずに IN 句に含まれていました。 Cortex Search はベクトル検索だけでなくキーワード検索も行うため、先の SQL での検索時と同様に、「アイスティー」という文字列が完全に含まれるこれらの商品が高い関連度スコアを獲得し、リテラルの候補として LLM に渡されと考えられます。
ただし、最終的なチャットの出力画面では、エージェントの LLM の判断で飲料と関連商品を分けて回答してくれました。
スレッドを新しくし、何度か試してみましたが、ここでは同様の結果でした。


さいごに
Cortex Analyst の Cortex Search との統合を試してみました。サンプル値の設定とうまく使い分けることで効果を発揮できる場面がある機能と思います。
こちらの内容がどなたかの参考になれば幸いです。






