こんにちは、one_inaです❄️
これまでSnowflake Cortex AIの機能について、以下の記事で紹介してきました。
- Snowflake Cortex Analyst で「自然言語でデータに質問する」体験を作ってみた - Tech Waves
-
検索の常識が変わる?Snowflake Cortex Search を使って、3ステップで「意味で探す」検索エンジンを作ってみる - Tech Waves
今回は「AI Functions」の活用について紹介したいと思います。
AI活用が急速に進む中で、データのセキュリティやプライバシー保護は
これまで以上に重要視されています。
特に、顧客との会話ログや音声認識テキスト、スキャンされた手書きのメモなどは 活用できれば大きなチャンスとなりますが、氏名、電話番号、住所といった個人情報が含まれることが多く、これらを安全に処理しなくては、セキュリティ上大きな問題となります。そのため、個人データのマスキングはデータ活用の第一歩となります。
今回は、SnowflakeのフルマネージドAIサービスであるSnowflake Cortex AI の「AI Functions」の利用して、ステージ上にある非構造化データ(会話履歴ファイル)から個人情報を自動で検出し、マスキングする仕組みをサンプルで試してみました。
目次
- Snowflake Cortex AI とは?
- Cortex AI Functions とは?
- 今回のユースケース:顧客対応ログの匿名化
- 使用する関数の解説
- 実装ステップ
- 検証:マスキングの精度を確認
- 導入のメリットと今後の展望
1. Snowflake Cortex AI とは?
まず、今回使用する Snowflake Cortex AI について簡単におさらいしましょう。
Snowflake Cortex AI は、Snowflakeのプラットフォーム内で直接利用できるフルマネージドなAIサービスです。
ユーザーはインフラの管理を一切気にすることなく、 SQLやPythonを通じて最新の大規模言語モデル(LLM)や機械学習モデルを呼び出すことができます。
最大の特徴は、「データがSnowflakeの外に出ない」 という点です。外部のAPIを個別に契約したり、 機密データをインターネット経由で送信したりする必要がないため、 非常に高いセキュリティレベルを維持したままAIの恩恵を受けることができます。
2. Cortex AI Functions とは?
Cortex AI Functions は、Snowflake Cortex が提供するAI機能を SQL関数(およびSnowpark Python)として利用できる 形にしたものです。
生成(LLM)、分類、抽出、要約、感情分析、埋め込み生成などを、データが存在するSnowflake内で実行できます。
公式ドキュメント(日本語): Snowflake Cortex AI 関数(LLM 関数を含む)
代表的な関数(例)
| カテゴリ | 関数(例) | 用途の例 |
|---|---|---|
| 生成(LLM) | AI_COMPLETE | 要約、文章生成、FAQ回答、SQL/コード生成など(プロンプト補完) |
| 分類 | AI_CLASSIFY | 問い合わせ分類、文書カテゴリ付与、タグ付け |
| 抽出 | AI_EXTRACT | 文章から必要項目の抜き出し(氏名・製品名・要点など) |
| 感情分析 | AI_SENTIMENT | 口コミ/コールログのポジネガ判定 |
| フィルタ | AI_FILTER | 条件に合う行だけ抽出(WHERE/JOINの条件にも利用可能) |
| 集計(複数行の洞察) | AI_AGG / AI_SUMMARIZE_AGG | 大量ログを行単位ではなく「まとめて洞察/要約」 |
| 埋め込み | AI_EMBED / AI_SIMILARITY | 類似検索、クラスタリング、RAG/検索の前処理 |
| 非構造データ | AI_PARSE_DOCUMENT | PDF/画像/文書からのテキスト抽出、 |
| マスキング | AI_REDACT | テキストから個人を特定できる情報(PII)を編集します。 |
※関数の対応範囲(入力の種類、モデル指定、権限など)は更新されるため、最新情報は上記の公式ドキュメントを参照してください。
3. 今回のユースケース:顧客対応ログの匿名化

今回想定するサンプルシナリオは、コールセンターやチャットサポートの現場で蓄積された「会話ログ」の活用です。
多くの企業では、以下のような課題を抱えています。
- 蓄積された会話データを分析して顧客体験(CX)を向上させたい。
- しかし、ログには顧客の氏名や連絡先が生データとして含まれている。
- AIに活用すると学習に利用されてしまう懸念がある。
- 分析チームに渡す前に、コンプライアンスの観点から個人情報を隠す必要がある。
- 従来の手法(正規表現など)では、表記ゆれや複雑な文脈に対応しきれず、漏れが発生してしまう。
この課題を、Snowflake Cortex AI の「非構造化データ処理」と「LLMによる意味理解」を組み合わせて解決します。
4. 使用する関数の解説
本記事では、2つの強力な AI Functions を組み合わせて使用します。
AI_PARSE_DOCUMENT
この関数は、PDFや画像、テキストファイルなどの非構造化データからテキスト情報を抽出する関数です。単なるOCR(文字認識)だけでなく、レイアウトを理解してテキストを抽出する能力を持っています。 これにより、ステージ上にバラバラに保存されたログファイルを構造化データとして扱えるようになります。
AI_REDACT
今回の中核となる関数です。テキスト内に含まれる氏名、住所、電話番号、メールアドレスなどの機密情報(PII)を自動で検出し、それらを [REDACTED] という文字列に置換(マスキング)します。LLMをベースにしているため、「これは人の名前だ」「これは住所の一部だ」という文脈を理解した上での処理が可能です。
5. 実装ステップ
それでは、実際にSnowflake上で実装を進めていきましょう。
4-1. ステージの準備とファイルの配置
まずは、会話履歴(今回はテキストファイルやPDFを想定)を格納するためのステージを作成します。
-- ステージの作成(ディレクトリテーブルを有効化)
CREATE OR REPLACE STAGE RAW.MY_CHAT_LOG_STAGE
DIRECTORY = (ENABLE = TRUE);
ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE');
4-2. 作成したステージにサンプルログファイルをUP
事前に用意しておいたテスト用サンプルの架空の会話履歴のログファイルを用意してUPします。

今回作成したサンプルのログファイルはこんなテキストデータのイメージです

4-3. ディレクトリテーブルのリフレッシュ
ステージにファイルを置いたら、最新のファイルリストをSnowflakeに認識させます。
ALTER STAGE RAW.MY_CHAT_LOG_STAGE REFRESH;
4-4. マスキング処理の一括実行
PARSE_DOCUMENT でテキストを抽出し、その結果を REDACT に渡すクエリを記述します。
CREATE OR REPLACE TABLE RAW.MASKED_CHAT_DATA AS
WITH raw_extracted_data AS (
SELECT
RELATIVE_PATH,
SNOWFLAKE.CORTEX.PARSE_DOCUMENT(
'@RAW.MY_CHAT_LOG_STAGE',
RELATIVE_PATH,
{'mode': 'OCR'}
):content::STRING AS extracted_text
FROM DIRECTORY(@RAW.MY_CHAT_LOG_STAGE)
),
chunked AS (
SELECT
RELATIVE_PATH,
extracted_text,
chunk.index AS chunk_index,
chunk.value::STRING AS chunk_text
FROM raw_extracted_data,
LATERAL FLATTEN(
input => SNOWFLAKE.CORTEX.SPLIT_TEXT_RECURSIVE_CHARACTER(
extracted_text, 'none', 800
)
) AS chunk
),
redacted AS (
SELECT
RELATIVE_PATH,
chunk_index,
chunk_text,
AI_REDACT(input => chunk_text) AS redacted_chunk
FROM chunked
)
SELECT
RELATIVE_PATH,
LISTAGG(chunk_text, '') WITHIN GROUP (ORDER BY chunk_index) AS "元のデータ",
LISTAGG(redacted_chunk, '') WITHIN GROUP (ORDER BY chunk_index) AS "マスキング後データ"
FROM redacted
GROUP BY RELATIVE_PATH;
SQLの解説
このSQLの解説をしてみます。 概要としてはステージ上のドキュメント(チャットログ)を読み取り、テキストを抽出し、個人情報をマスキングしたテーブルを作成するものです。
全体構造
WITH 句で3段階の処理(CTE)を経て、最終的に CREATE OR REPLACE TABLE でテーブルを作成します。
textドキュメント読み取り → テキスト分割 → マスキング → 結合して保存
1. SNOWFLAKE.CORTEX.PARSE_DOCUMENT
sqlSNOWFLAKE.CORTEX.PARSE_DOCUMENT(
'@RAW.MY_CHAT_LOG_STAGE',
RELATIVE_PATH,
{'mode':'OCR'}
)
役割: ステージ上のファイル(PDF、画像など)からテキストを読み取る関数です。
- 第1引数: ファイルが置いてあるステージの場所
- 第2引数: ステージ内でのファイルパス
- 第3引数:
'OCR'モードを指定 → 画像内の文字も光学文字認識で読み取る
戻り値はJSONオブジェクトなので、:content::STRING で本文テキスト部分だけを文字列として取り出しています。
2. DIRECTORY(@RAW.MY_CHAT_LOG_STAGE)
sqlFROM DIRECTORY(@RAW.MY_CHAT_LOG_STAGE)
役割: ステージ内のファイル一覧をテーブルのように取得する機能です。 各行が1ファイルに対応し、RELATIVE_PATH カラムでファイルパスを取得できます。
3. SNOWFLAKE.CORTEX.SPLIT_TEXT_RECURSIVE_CHARACTER
sqlSNOWFLAKE.CORTEX.SPLIT_TEXT_RECURSIVE_CHARACTER(
extracted_text,
'none',
800
)
役割: 長いテキストを小さなチャンク(断片)に分割する関数です。
- 第1引数: 分割したいテキスト
- 第2引数: 言語指定(
'none'は言語非依存) - 第3引数: 1チャンクあたりの最大文字数(800文字)
なぜ分割するかというと、次のステップの AI_REDACT に一度に渡せるテキスト量に制限があるためです。
4. LATERAL FLATTEN(...)
sqlLATERAL FLATTEN(
input=> SNOWFLAKE.CORTEX.SPLIT_TEXT_RECURSIVE_CHARACTER(...)
)AS chunk
役割: 配列を行に展開する処理です。
SPLIT_TEXT_RECURSIVE_CHARACTER は配列(チャンクのリスト)を返します。FLATTEN がその配列の各要素を1行ずつに展開します。LATERAL は「同じ行のデータを参照しながら展開する」という意味です。
chunk.index: 何番目のチャンクか(0, 1, 2...)chunk.value: チャンクのテキスト本体
5. AI_REDACT
sqlAI_REDACT(input=> chunk_text)AS redacted_chunk
役割: テキスト内の個人情報(名前、電話番号、メールアドレスなど)をAIが自動検出し、 マスキング(伏せ字化)する関数です。
例: 田中太郎の電話番号は090-1234-5678 → [PERSON]の電話番号は[PHONE]
6. LISTAGG(...) WITHIN GROUP (ORDER BY chunk_index)
sqlLISTAGG(chunk_text,'')WITHIN GROUP (ORDER BY chunk_index)
役割: 分割したチャンクを元の順番通りに結合して1つの文字列に戻す集約関数です。
- 第1引数: 結合する値
- 第2引数: 区切り文字(
''= 区切りなし、つまりそのまま連結) WITHIN GROUP (ORDER BY chunk_index): チャンク番号順に並べてから結合
これにより、ファイルごとに「元のテキスト全文」と「マスキング済みテキスト全文」が得られます。
処理の流れまとめ
| ステップ | 処理 | 結果 |
|---|---|---|
| 1 | ステージのファイル一覧取得 | ファイルパスの一覧 |
| 2 | 各ファイルをOCRでテキスト化 | 生テキスト |
| 3 | 800文字ずつに分割 | チャンクの配列 → 行に展開 |
| 4 | 各チャンクの個人情報をマスキング | マスキング済みチャンク |
| 5 | チャンクを順番に再結合 | ファイルごとに元テキストとマスキング済みテキストのペア |
最終テーブルは、ファイルごとに1行で「元のデータ」と「マスキング後データ」を比較できる構造になります。
6. 検証:マスキングの精度を確認
実際に処理した結果を比較してみましょう。
元のデータ(extracted_text):
オペレーター: テスト田中 慎吾
オペレーター: EC サポートセンターテスト田中 慎吾でございます。
顧客: テスト45440 です。メールはtesthogehoge363@test.com で登録してます。
オペレーター: 再入荷通知を [testhogehoge363@test.com](<mailto:testhogehoge363@test.com>) 宛にお送りいたします。
お電話番号 060-3782-6483 も控えさせていただきます。
実行後のデータ(REDACT適用後):
オペレーター: **[NAME]**
オペレーター: EC サポートセンター**[NAME]**でございます。
顧客: [NAME] です。メールは**[EMAIL]** で登録してます。
オペレーター: 再入荷通知を **[EMAIL]** 宛にお送りいたします。
お電話番号 **[PHONE_NUMBER]** も控えさせていただきます。
注目ポイント
- 文脈の理解: 「テスト田中 慎吾」と「顧客: テスト45440 」の両方をしっかり認識して隠せています。
- 多様な要素の検知: 電話番号、メールアドレスという異なる形式の個人情報を、一つの関数で一括処理できています。
- 精度の高さ: 正規表現では難しい「名前」なども、LLMが適切に判断してマスキングしています。
7. 導入のメリットと今後の展望
Snowflake Cortex AI を使ってみて感じた最大のメリットは、
「マスキング処理がSQLライクな関数だけで完結する」 という点です。
通常、ここまでの処理を行おうとすると、以下のような工程が必要になります。
- 顧客対応ログ(音声・テキスト・PDF・手書きメモなど)を回収し、外部ストレージやETL基盤に集約する
- OCR/音声認識/PDF解析などでテキスト化し、前処理(整形・不要文字の除去・フォーマット統一)を行う
- 正規表現や辞書、独自スクリプトで個人情報(氏名・住所・電話番号・メール等)を抽出・マスキングするロジックを実装・運用する
- 精度検証とチューニング(表記ゆれ・文脈依存・誤検知/検知漏れ)を繰り返し、監査対応のためのログやルール管理も整備する
- マスキング済みデータを分析用テーブルにロードし、分析/AI基盤に受け渡す(必要に応じてアクセス制御も追加)
しかし、Cortex AI であれば、これらすべてがSnowflakeの内部で完結します。 データエンジニアは、これまで通りSQLを書く程度の労力で、これらのことを実現できます。
今後は、このマスキング処理をストリームと組み合わせて、 「ステージにファイルが置かれた瞬間に自動で匿名化してテーブルに格納する」 といったリアルタイムなデータパイプラインへの応用も試してみたいと思います。
最後までお読みいただきありがとうございました!
