Snowflake Cortex SearchでPDFチャットボットを作る - チュートリアル全3回の実践記録

Snowflake Cortex SearchでPDFチャットボットを作る - チュートリアル全3回の実践記録

2026.04.21

はじめに

こんにちは、データ事業本部のまっきーです。

前回の記事「Snowflake入門 - AWSメインでやってきた自分から見たZero to Snowflake」では、Snowflakeの無料トライアルを使ってZero to Snowflakeチュートリアルに取り組みました。UNDROPやデータシェアリングといった機能に驚いたのが印象的でした。

今回はその続きとして、Cortex Searchチュートリアル(全3回) を実践しました。最終的にはPDFファイルに対して自然言語で質問できるRAGチャットボットを作るところまでやります。

そもそも Cortex Search って何?

チュートリアルに入る前に、Cortex Searchが何をするものなのかを整理しておきます。

通常のSQLでデータを検索するときは、WHERE name = '東京' のように条件を正確に指定して探します。一方、Cortex Searchを使うと「東京で人気の観光スポットは?」のように自然言語で意味を汲み取った検索ができます。

作り方もシンプルで、SQL一発でサービスが立ち上がります。

CREATE CORTEX SEARCH SERVICE my_search  -- 検索サービスの名前
  ON text_column                        -- どのカラムを検索対象にするか
  WAREHOUSE = my_wh                     -- 処理に使うコンピュートリソース
  AS (SELECT text_column FROM my_table);-- 検索対象のデータを返すクエリ

これだけで、裏側の検索エンジンの構築やインデックスの管理はSnowflakeが全部やってくれます。利用者が意識する必要はありません。

AWSでの経験がある身として驚いたのは、検索基盤を自分で構築しなくていいという点です。AWSで同じようなことをやろうとすると、検索サービスを立てて、LLM連携を設定して、Lambdaでつなぎこんで...とかなりの構築作業が必要になります。それがSQLの1行で済んでしまう。

そもそも RAG って何?

もう一つ、チュートリアルを進める上で理解しておきたい概念がRAG(Retrieval-Augmented Generation)です。

RAGを一言でいうと、「まず検索で関連情報を引っ張ってきて、それをLLMに渡して回答を生成させる」 パターンです。

ユーザーの質問

Cortex Search で関連テキストを検索(= Retrieval)

検索結果 + 質問を LLM に渡す

LLM が回答を生成(= Generation)

Snowflakeでは、検索パートをCortex Searchが、回答生成パートをCOMPLETE関数が担います。

COMPLETE関数は、SnowflakeのSQL内から直接LLMを呼び出せる関数です。

SELECT SNOWFLAKE.CORTEX.COMPLETE('snowflake-arctic', 'RAGって何?');

これだけでLLMが回答を返してくれます。APIキーの取得も外部接続の設定も不要で、Snowflakeの中で全部完結します。

そもそも Streamlit in Snowflake って何?

3つのチュートリアル全てでUI構築に使うので、こちらも軽く触れておきます。

Streamlit はPythonだけでWebアプリが作れるフレームワークで、それをSnowflake上でそのまま動かせるのが Streamlit in Snowflake(SiS)です。

  • Snowsight上でPythonコードを書くだけでアプリが起動する
  • ホスティングサーバーやフロントエンド基盤を用意する必要がない
  • アプリからSnowflakeのデータ・関数(Cortex含む)に直接アクセスできる

AWSでチャットボットUIを作るなら、AmplifyやS3 + CloudFrontでフロントをホスティングして、APIGateway + Lambdaでバックエンド連携して...と構築作業が結構発生します。それがPythonファイル1つで済んでしまうのは衝撃でした。

Tutorial 1: テキストデータで検索サービスを作る

概要

最初のチュートリアルでは、Airbnbのリスティングデータ(テキスト)をCortex Searchに取り込んで、自然言語で検索できるようにします。

やったこと

  1. データベースとウェアハウスを作成
  2. JSONデータ(Airbnbのリスティング情報)をSnowsightからロード
    スクリーンショット 2026-04-13 14.54.56
  3. テキストデータをチャンク分割(SPLIT_TEXT_RECURSIVE_CHARACTER
  4. Cortex Search Serviceを作成
  5. Streamlit in Snowflakeで検索UIを構築

ポイント

チャンク分割がここで初めて出てきます。チャンクとは「テキストを適度なサイズに切った断片」のことです。長いテキストをそのまま検索対象にすると、どこに欲しい情報があるか分からず精度が落ちるため、検索しやすいサイズに分割しておきます。

SNOWFLAKE.CORTEX.SPLIT_TEXT_RECURSIVE_CHARACTER(
    text_column,  -- 分割対象のテキスト
    1500,         -- 1チャンクの最大文字数
    300           -- チャンク間のオーバーラップ(重なり)
)

SNOWFLAKE.CORTEX. という接頭辞がついた関数は、Snowflake の AI 機能(Cortex)が提供する専用関数です。通常の SQL 関数と同じ感覚で使えます。

オーバーラップ(重なり)は、前のチャンクの末尾300文字を次のチャンクの先頭にも含める仕組みです。これにより、チャンクの境目で文脈が途切れるのを防ぎます。

Cortex Search Serviceの作成は本当にSQL一発です。

CREATE CORTEX SEARCH SERVICE my_search_service
    ON search_column            -- 検索対象のカラム
    WAREHOUSE = my_wh           -- 使用するウェアハウス
    TARGET_LAG = '1 hour'       -- インデックス更新の最大遅延
    AS (
    SELECT
        search_column,          -- 検索対象(必須)
        other_column            -- 検索結果に含めたい追加情報
    FROM my_table
    );

TARGET_LAG = '1 hour' は「元テーブルが更新されたら最大1時間以内にインデックスも更新する」という設定です。リアルタイム性と処理コストのバランスを取る仕組みで、値を小さくすれば鮮度は上がりますが、その分コンピュートリソースを消費します。

Tutorial 2: RAG チャットボットを作る

概要

Tutorial 1では「検索して結果を返す」だけでしたが、Tutorial 2では検索結果をLLMに渡して人間が読める回答を生成するところまでやります。これがRAGチャットボットの基本形です。

Tutorial 1との違い

Tutorial 1 Tutorial 2
入力 テキストデータ テキストデータ(同じ)
検索 Cortex Search Cortex Search(同じ)
出力 検索結果の一覧 LLMが生成した回答文
UI 検索ボックス チャットUI(会話履歴付き)

新しく登場するのがCOMPLETE関数です。前述の通り、SQL内からLLMを呼び出せる関数で、Cortex Searchで引っ張ってきた検索結果とユーザーの質問を合わせてプロンプトを組み立て、LLMに回答を生成させます。

ポイント

チャット履歴の考慮が地味に重要です。「2023年のGDP成長率は?」→「同じ四半期の失業率は?」と続けて聞いたとき、2問目の「同じ四半期」が何を指すかは会話の流れを見ないと分かりません。

Tutorial 2のコードでは、過去の会話履歴をLLMに渡して「この文脈で、ユーザーの質問を補完したクエリを作って」と指示する仕組みが入っています。RAGを実用的にするには、こういった工夫が必要になるんですね。

Tutorial 3: PDF からチャットボットを作る

概要

ここが一番の見せ場です。Tutorial 1・2はテキストデータが既にある状態からスタートしましたが、Tutorial 3はPDFファイルという非構造化データから始めます

使用するデータはFOMC(米国連邦公開市場委員会)の議事録PDF、12ファイル(各約10ページ)です。

全体の流れ

PDF をステージにアップロード
  ↓ 「生のPDF、まだ中身は読めない」
PARSE_DOCUMENT でテキスト抽出
  ↓ 「テキストになったが長すぎて検索に使えない」
SPLIT_TEXT_MARKDOWN_HEADER でチャンク分割
  ↓ 「検索しやすいサイズの断片になった」
Cortex Search Service 作成
  ↓ 「自然言語で検索できるようになった」
Streamlit でチャットボット UI
  ↓ 「質問 → 検索 → LLM回答 のRAGが完成」

Step 1-2: PDFのアップロード

ステージ(Stage)って何?
Snowflake内に用意されているファイル置き場のことです。CSV/JSON/PDFなどのファイルをここにアップロードしておき、SQLから参照してロードや解析を行います。AWSでいうS3バケットに近い位置付けですが、Snowflake内のロール/権限で直接アクセス制御できるのが特徴です。

Snowflake内に「ステージ」というファイルの置き場を作り、そこにPDFをアップロードします。

CREATE OR REPLACE STAGE fomc
    DIRECTORY = (ENABLE = TRUE)
    ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE');

DIRECTORY = (ENABLE = TRUE) を付けるとステージ内のファイル一覧をSQLで取得できるようになります。後でPDFを一括処理するのに必要な設定です。

SnowsightのUIからドラッグ&ドロップでPDFをアップロードできます。

スクリーンショット 2026-04-16 14.32.07

Step 3: PDFからテキスト抽出 → チャンク分割

ここがTutorial 3で新しく登場する部分です。

まずPARSE_DOCUMENTでPDFの中身をテキストに変換します。

CREATE OR REPLACE TABLE raw_text AS
SELECT
    RELATIVE_PATH,                          -- ステージ内のファイルパス(例: fomcminutes20230201.pdf)
    TO_VARCHAR(
        SNOWFLAKE.CORTEX.PARSE_DOCUMENT(    -- PDF → テキスト変換する Cortex 関数
            '@fomc',                        -- ステージ名(@をつけて参照)
            RELATIVE_PATH,                  -- 対象ファイル
            {'mode': 'LAYOUT'}              -- 見出し構造を保持するモード
        ):content                           -- 抽出結果の JSON から content 部分を取り出す
    ) AS EXTRACTED_LAYOUT
FROM DIRECTORY('@fomc')                     -- ステージ内のファイル一覧を取得する関数
WHERE RELATIVE_PATH LIKE '%.pdf';           -- PDF ファイルだけに絞る

いくつか Snowflake 特有の構文があるので補足します。

  • DIRECTORY('@fomc'): ステージ内のファイル一覧をテーブルのように返す関数です。RELATIVE_PATH というカラムでファイル名が取れます。
  • PARSE_DOCUMENT(...): Cortex が提供する PDF 解析関数です。'LAYOUT' モードを指定すると、PDFの見出し構造がMarkdown形式(### 等)で保持されます。
  • :content: Snowflake の半構造化データ(JSON)アクセス構文です。PARSE_DOCUMENT の戻り値は JSON なので、そこから content キーの値を取り出しています。前回の記事でやった v:temp と同じ書き方です。

次にSPLIT_TEXT_MARKDOWN_HEADERでチャンク分割します。

SNOWFLAKE.CORTEX.SPLIT_TEXT_MARKDOWN_HEADER(
    EXTRACTED_LAYOUT,                                      -- 分割対象のテキスト
    OBJECT_CONSTRUCT('#', 'header_1', '##', 'header_2'),   -- どのヘッダーレベルを区切りに使うか
    2000,                                                  -- チャンクの最大文字数
    300                                                    -- オーバーラップ
)

Tutorial 1ではSPLIT_TEXT_RECURSIVE_CHARACTER(文字数ベースの分割)を使いましたが、今回はMarkdownのヘッダーを区切りとして「意味のあるまとまり」で分割します。PDFからLAYOUTモードでテキスト抽出したのは、このヘッダー情報を使うためでした。

OBJECT_CONSTRUCT('#', 'header_1', '##', 'header_2') は Snowflake でキーと値のペアからJSONオブジェクトを作る関数です。ここでは「#header_1##header_2 として扱う」という定義を渡しています。分割された各チャンクに「どのセクションのテキストか」のヘッダー情報が付与されるため、後の検索精度が上がります。

12本のPDFから422個のチャンクが生成されました。中身を LIMIT 5 で5件だけプレビューしたのが以下です。各チャンクに Header 1: Minutes of the Federal Open Market Committee のようなヘッダー情報が付いているのが確認できます。

スクリーンショット 2026-04-16 15.05.17

Step 4-5: 検索サービスとチャットボット

ここからはTutorial 2とほぼ同じ流れです。Cortex Search Serviceを作成し、Streamlitでチャットボットを構築します。

Tutorial 3で追加された機能として、回答の下にReferences(参照元PDF)のリンクテーブルが表示されます。「この回答はどのPDFの情報に基づいているか」が一目で分かるようになっており、RAGの実用性として重要な要素です。

スクリーンショット 2026-04-16 15.43.38

日本語で質問しても、LLMが英語のPDFから情報を取得して日本語で回答してくれました。

ハマったところ

StreamlitアプリでCOMPLETE関数を呼ぶ際、Python SDKのComplete()関数(REST API経由)がアカウントの制限でブロックされました。

# ❌ これがブロックされた
from snowflake.cortex import Complete
Complete(model, prompt)

ワークシートのSQL関数(SNOWFLAKE.CORTEX.COMPLETE())は動いたので、Python SDK経由からSQL経由に切り替えて解決しました。

# ✅ SQL経由に変更
result = session.sql(
    f"SELECT SNOWFLAKE.CORTEX.COMPLETE('{model}', '{prompt}') AS response"
).collect()

やっていることは同じ(LLMにプロンプトを投げて回答を得る)ですが、裏の通信経路が違います。トライアルアカウントや特定のリージョンでこの制限に当たることがあるようです。同じエラーが出た方はこの方法を試してみてください。

3つのチュートリアルを通して、Snowflake上でRAGチャットボットを構築するまでの一連の流れを体験しました。

改めて驚いたのは、必要なものが全部Snowflakeの中に揃っているという点です。

やりたいこと AWSでの構成例 Snowflakeでは
テキストの意味検索 OpenSearch Serverless + 埋め込みモデル + インデックス管理 CREATE CORTEX SEARCH SERVICE
LLM呼び出し Bedrock + IAM + Lambda SNOWFLAKE.CORTEX.COMPLETE()
PDF解析 Textract + S3 + Lambda PARSE_DOCUMENT()
フロントエンドUI Amplify or S3 + CloudFront Streamlit in Snowflake
認証・権限管理 IAM + Cognito Snowflakeのロール

AWSだと複数のサービスを組み合わせて構築する必要があるものが、SnowflakeではSQLとPythonスクリプトだけで完結します。AWSでの構築・運用に慣れている身としては、「この部分を全部Snowflakeが吸収してくれるのか」という感覚でした。

もちろん、細かいカスタマイズ性や既存AWSリソースとの統合ではAWSに分がある場面もあると思います。ただ「とにかく早くRAGを試したい」「基盤の面倒を見たくない」という場面では、Snowflakeの統合的なアプローチは非常に魅力的でした。

次はCortex Analyst(自然言語からSQL生成)とSnowflake Intelligence(AIエージェント)の実践編を書く予定です。Cortex Searchで作った検索機能が、Intelligenceの中でどう「ツール」として呼び出されるのか、そのあたりも含めて深掘りしていきます。

この記事をシェアする

関連記事