检测并隐去个人身份信息 (PII)

个人身份信息 (PII) 包括姓名、地址、电话号码、电子邮件地址、纳税识别号以及其他可用于(单独或与其他信息结合)识别个人身份的数据。大多数组织在处理 PII 数据时都需要满足相关的监管和合规要求。AI_REDACT 是一个完全托管的 Cortex AI 函数,它使用大型语言模型 (LLM) 来帮助您从非结构化文本数据中检测、定位并隐去 PII。

AI_REDACT 可帮助您为呼叫中心培训、情绪分析、保险与医疗分析以及机器学习 (ML) 模型训练等用例准备文本。

小技巧

使用 AI_PARSE_DOCUMENT 或 AI_TRANSCRIBE,在应用 AI_REDACT 之前将文档或语音数据转换为文本。

AI_REDACT

AI_REDACT 函数具有两种操作模式:detectredact。默认为 redact。在 detect 模式下使用 AI_REDACT 以识别 PII 的位置,然后以编程方式选择要隐去的 PII。在 redact 模式下使用 AI_REDACT,将输入文本中的 PII 替换为占位符值。

重要

AI_REDACT 使用 AI 模型对敏感信息进行尽力隐去处理。请务必查看输出,以确保符合组织的数据隐私策略。如果 AI_REDACT 无法检测或隐去您数据中的任何 PII,请 :doc:` 联系 Snowflake 支持部门 </user-guide/contacting-support>`。

区域可用性

请参阅 可用性

限制

  • AI_REDACT 使用 AI 模型,可能无法识别所有个人身份信息。请务必查看输出,以确保符合组织的数据隐私策略。如果 AI_REDACT 无法隐去某些 PII,请联系 Snowflake 支持部门

  • COUNT_TOKENS 和 AI_COUNT_TOKENS 函数尚不支持 AI_REDACT。

  • AI_REDACT 对处理结构良好的英文文本效果最佳。性能可能因其他语言或存在许多拼写、标点符号或语法错误的文本而异。

  • AI_REDACT 目前仅支持 US PII 以及部分 UK 和加拿大人的 PII,具体情况见 检测到 PII 类别 中的说明。

  • 目前,AI_REDACT 可以输入和输出的词元数量受到限制。输入和输出总共最多可以有 4,096 个词元。输出限制为 1,024 个词元。如果输入文本较长,请将其拆分为更小的块,并分别对每个块进行隐去处理,可借助 SPLIT_TEXT_RECURSIVE_CHARACTER 完成。有关隐去超过词元限制的文本示例,请参阅 分块示例

    备注

    词元是 AI 模型处理的最小数据单位。对于英文文本,行业准则认为一个词元约为 4 个字符,即 0.75 个单词。

检测到 PII 类别

AI_REDACT 支持检测并隐去以下类别的 PII。“Category”列中的值是可选 categories 实参中支持的字符串。

类别

备注

NAME

识别全名、名字、中间名和姓氏

EMAIL

PHONE_NUMBER

DATE_OF_BIRTH

GENDER

识别男性、女性和非二元性别

AGE

ADDRESS

识别:

  • 完整邮政地址(US、UK、CA)

  • 街道地址(US、UK、CA)

  • 邮政编码(US、UK、CA)

  • 城市(US、UK、CA)

  • 州 (US) 或省 (CA)

  • 县、区或镇 (US)

NATIONAL_ID

识别社会安全号码 (US)

PASSPORT

识别护照号码 (US、UK、CA)

TAX_IDENTIFIER

识别个人纳税人编号 (ITNs)

PAYMENT_CARD_DATA

识别完整的卡信息、卡号、到期日期和 CVV

DRIVERS_LICENSE

识别 US、UK 和 CA 驾照

IP_ADDRESS

备注

AI_REDACT 支持对某些 PII 类别的部分匹配。例如,仅使用名字即可通过 [NAME] 占位符触发信息隐去处理。

使用 detect 模式保留特定 PII

默认情况下,AI_REDACT 会将检测到的所有 PII 替换为占位符值。在某些情况下,您可能希望在隐去其余内容的同时保留某些 PII。例如,您可能希望在呼叫中心通话记录或客户评论中隐去所有姓名,但保留已知员工姓名。

使用 detect 模式构建选择性隐去工作流程:

  1. 调用 AI_REDACT,并将 mode 实参设置为 detect,以识别并定位输入文本中的 PII。

  2. 将检测到的文本范围与您希望保留的值的允许列表进行比较。

  3. 仅隐去不在允许列表中的 PII。

当您以 detect 模式调用 AI_REDACT 时,该函数会返回一个包含 spans 数组的 OBJECT。数组中的每个元素都是一个 OBJECT,包含以下字段:

字段

类型

描述

category

VARCHAR

PII 类别,例如 NAMEADDRESS。有关支持的类别,请参阅 检测到 PII 类别

start

NUMBER

输入文本中检测到的 PII 的起始索引。

end

NUMBER

输入文本中检测到的 PII 的结束索引。

text

VARCHAR

输入文本中匹配到的 PII 文本。

有关使用 detect 模式的示例,请参阅 检测和选择性脱敏示例

处理多行查询中的行级错误

重要

如果您的查询在每一行上都失败,原因可能是已知约束,而不是行级错误。有关词元限制、语言支持及其他限制的详细信息,请参阅 限制

如果 AI_REDACT 无法处理输入文本,则会引发错误。当查询需要对多行数据进行隐去处理时,错误将导致整个查询失败。要允许继续处理其他行,可以将会话参数 AI_SQL_ERROR_HANDLING_USE_FAIL_ON_ERROR 设置为 FALSE。此时发生错误将返回 NULL,而不会停止查询。

ALTER SESSION SET AI_SQL_ERROR_HANDLING_USE_FAIL_ON_ERROR=FALSE;
Copy

将此参数设为 FALSE 后,您还可将 TRUE 作为 AI_REDACT 的最后参数传递,此时返回值将是一个包含两个独立字段的 OBJECT:一个存储屏蔽后的文本,另一个存储错误信息。其中一个字段值为 NULL,取决于 AI_REDACT 调用是否成功。

以下示例演示在处理多行时如何使用错误处理:

  1. 创建一个包含未脱敏文本的表。

    CREATE OR REPLACE TABLE raw_table AS
      SELECT 'My previous manager, Washington, used to live in Kirkland. His first name was Mike.' AS my_column
      UNION ALL
      SELECT 'My name is William and I live in San Francisco. You can reach me at (415).450.0973';
    
    Copy
  2. 设置会话参数。

    ALTER SESSION SET AI_SQL_ERROR_HANDLING_USE_FAIL_ON_ERROR=FALSE;
    
    Copy
  3. 创建一个脱敏表,其中包含 valueerror 的列。

    CREATE OR REPLACE TABLE redaction_table (
      value VARCHAR,
      error VARCHAR
      );
    
    Copy
  4. raw_table 中脱敏 PII,并将行插入到 redaction_table,以存储脱敏后的文本和错误消息。

    INSERT INTO redaction_table
    SELECT
        result:value::STRING AS value,
        result:error::STRING AS error
      FROM (SELECT AI_REDACT(my_column, TRUE) AS result FROM raw_table);
    
    Copy

成本注意事项

AI_REDACT 的计费基于处理的输入和输出令牌数量,与其他 Cortex AI 函数相同。有关详细信息,请参阅 Snowflake 定价指南

脱敏示例

基本脱敏示例

以下示例从输入文本中隐去姓名和地址。

SELECT AI_REDACT(
  input => 'My name is John Smith and I live at twenty third street, San Francisco.'
  );
Copy

基本脱敏输出:

My name is [NAME] and I live at [ADDRESS]

以下示例仅从输入文本中隐去姓名和电子邮件地址。请注意,文本中仅包含名字,该名字已被识别并处理为 [NAME]。输入文本不包含电子邮件地址,因此输出中不会显示电子邮件占位符。

SELECT AI_REDACT(
  input => 'My name is John and I live at twenty third street, San Francisco.',
  categories => ['NAME', 'EMAIL']
  );
Copy

选择性脱敏输出:

My name is [NAME] and I live at twenty third street, San Francisco.

端到端示例

以下示例对一个表中的行进行处理,并将隐去敏感信息后的结果插入到另一个表中。您可以使用类似的方法将脱敏后的数据存储在现有表的某一列中。脱敏后,文本将传递给 AI_SENTIMENT 函数,以提取整体情绪信息。

  1. 创建一个包含未脱敏文本的表。

    CREATE OR REPLACE TABLE raw_table AS
      SELECT 'My previous manager, Washington, used to live in Kirkland. His first name was Mike.' AS my_column
      UNION ALL
      SELECT 'My name is William and I live in San Francisco. You can reach me at (415).450.0973';
    
    Copy
  2. 查看未脱敏的数据。

    SELECT * FROM raw_table;
    
    Copy
  3. 创建脱敏表。

    CREATE OR REPLACE TABLE redaction_table (value VARCHAR);
    
    Copy
  4. raw_table 中脱敏 PII,并将行插入到 redaction_table

    INSERT INTO redaction_table
      SELECT AI_REDACT(my_column) AS value FROM raw_table;
    
    Copy
  5. 查看脱敏结果。

    SELECT * FROM redaction_table;
    
    Copy
  6. 对脱敏后的文本运行 AI_SENTIMENT 函数。

    SELECT
        value AS redacted_text,
        AI_SENTIMENT(value) AS summary_sentiment
      FROM redaction_table;
    
    Copy

分块示例

本示例演示如何通过将长文本拆分为较小块、分别对每块进行隐去处理,再将经过隐去处理后的块重新组合为最终输出,从而实现对 PII 的隐去处理。此方法可规避 AI_REDACT 的词元限制。

  1. 创建一个包含患者数据的表。

    CREATE OR REPLACE TABLE patients (
      patient_id INT PRIMARY KEY,
      patient_notes TEXT
      );
    
    Copy
  2. 将文本拆分为多个块,对每个块应用 AI_REDACT,并将脱敏后的块连接起来。

    CREATE OR REPLACE TABLE final_temp_table AS
      WITH chunked_data AS (
        SELECT
            patient_id,
            chunk.value AS chunk_text,
            chunk.index AS chunk_index
          FROM
            patients,
            LATERAL FLATTEN(
                input => SNOWFLAKE.CORTEX.SPLIT_TEXT_RECURSIVE_CHARACTER(
                    patient_notes,
                    'none',
                    1000
                    )
                ) AS chunk
          WHERE
            patient_notes IS NOT NULL
            AND LENGTH(patient_notes) > 0
        ),
      redacted_chunks AS (
          SELECT
              patient_id,
              chunk_index,
              chunk_text,
              TO_VARIANT(results:value) AS redacted_chunk,
              TO_VARIANT(results:error) AS error_string
            FROM (
              SELECT
                  patient_id,
                  chunk_index,
                  chunk_text,
                  AI_REDACT(chunk_text,TRUE) AS results
                FROM
                  chunked_data
            )
      ),
      final AS (
          SELECT
              chunk_text AS original,
              IFF(error_string IS NOT NULL, chunk_text, redacted_chunk) AS redacted_text,
              patient_id,
              chunk_index
            FROM
              redacted_chunks
      )
      SELECT * FROM final;
    
    Copy
  3. 查询结果。

    SELECT
        patient_id,
        LISTAGG(redacted_text, '') WITHIN GROUP (ORDER BY chunk_index) AS full_output
      FROM final_temp_table
      GROUP BY patient_id;
    
    Copy

检测和选择性脱敏示例

基本检测示例

以下示例在不脱敏输入的情况下,识别并返回每个检测到的 PII 实例的类别、位置和文本。

SELECT AI_REDACT(
    input => 'My old manager, Washington, used to live in Washington. His first name was Mike.',
    return_error_details => FALSE,
    mode => 'detect'
    );
Copy

基本检测输出:

{
  "spans": [
    {
      "category": "NAME",
      "end": 26,
      "start": 16,
      "text": "Washington"
    },
    {
      "category": "ADDRESS",
      "end": 54,
      "start": 44,
      "text": "Washington"
    },
    {
      "category": "NAME",
      "end": 79,
      "start": 75,
      "text": "Mike"
    }
  ]
}

使用允许列表的端到端示例

以下示例演示了一个使用 detect 模式和允许列表的选择性脱敏工作流程。该示例从暂存文件加载需要保留的名称列表,在 detect 模式下使用 AI_REDACT 识别 PII 的位置,然后将结果传递给 Python UDF,仅对不在允许列表中的 PII 进行脱敏。

  1. 通过将列表从暂存区加载到临时表中来保留一个允许值列表。

    CREATE OR REPLACE TEMP TABLE string_list (value STRING);
    
    COPY INTO string_list
      FROM @mystage/allowlist.txt
      FILE_FORMAT = (
        TYPE = 'CSV'
        RECORD_DELIMITER = '\n'
        FIELD_DELIMITER = '\t'   -- any char NOT in file
        TRIM_SPACE = TRUE
        SKIP_HEADER = 0
        );
    
    Copy
  2. 查看允许列表表

    SELECT * FROM string_list;
    
    Copy

    允许列表表输出:

    VALUE
    Mike
    David
    
  3. 创建一个 Python UDF,根据允许列表对 PII 进行选择性脱敏。

    CREATE OR REPLACE FUNCTION redact_spans_with_allowlist(
      SPAN_DATA VARIANT,
      ALLOWLIST ARRAY,
      ORIGINAL_TEXT STRING
      )
      RETURNS STRING
      LANGUAGE PYTHON
      RUNTIME_VERSION = '3.8'
      HANDLER = 'redact_text'
      AS
      $$
      def redact_text(span_data, allowlist, original_text):
          spans = span_data.get('spans', [])
          # Sort descending to maintain index integrity
          sorted_spans = sorted(spans, key=lambda x: x['start'], reverse=True)
    
          result = original_text
    
          for span in sorted_spans:
              text_val = span.get('text')
              if text_val in allowlist:
                  continue
    
              start, end = span['start'], span['end']
              label = f"[{span['category']}]"
    
              # Splice the string
              result = result[:start] + label + result[end:]
    
          return result
      $$;
    
    Copy
  4. 测试 UDF。

    SELECT redact_spans_with_allowlist(
      PARSE_JSON('{"spans": [{"category": "NAME", "end": 26, "start": 16, "text": "Washington"}, {"category": "NAME", "end": 79, "start": 75, "text": "Mike"}]}'),
      ARRAY_CONSTRUCT('Washington'), -- This will NOT be redacted
      'Hello, my name is Washington and his is Mike.'
      );
    
    Copy
  5. detect 模式下运行 AI_REDACT。

    CREATE OR REPLACE TABLE raw (message TEXT);
    
    INSERT INTO raw (message) VALUES
      ('My old manager, Washington, used to live in Washington. His first name was Mike.');
    
    SELECT
        t.message AS message,
        AI_REDACT(input=>t.message, return_error_details=>FALSE, mode=>'detect') AS spans,
        redact_spans_with_allowlist(spans, l.str_list, message) AS result
      FROM raw t
        CROSS JOIN (
          SELECT ARRAY_AGG(value) AS str_list
            FROM string_list
          ) l;
    
    Copy

端到端允许列表示例输出:

MESSAGE

SPANS

RESULT

我的前任经理 Washington 曾经住在华盛顿。他的名字是 Mike。

{
  "spans": [
    {"category": "NAME",
    "end": 26,
    "start": 16,
    "text": "Washington"
    },
    {"category": "ADDRESS",
    "end": 54,
    "start": 44,
    "text": "Washington"
    },
    {"category": "NAME",
    "end": 79,
    "start": 75,
    "text": "Mike"
    }
  ]
}
Copy

我的前任经理 [NAME] 曾经住在 [ADDRESS]。他的名字是 Mike。