成功构建隐私感知 AI 软件需要考虑并分类您计划预先存储的数据。
译自 Building Privacy-Aware AI Software With Vector Databases,作者 Zachary Proser。
GenAI 通过将专有数据与各个用户知识相结合,创建个性化网络体验。我们如何确保按照安全合规标准安全地处理此知识?
我们如何向用户保证删除其个人身份信息 (PII)?
让我们研究可用于确保应用程序符合安全和隐私标准的工具和模式。
检索增强生成,一种使用私有数据丰富 GenAI 响应的架构,通常用于解决大型语言模型的缺陷,包括幻觉和短上下文窗口。
但 RAG 还可以帮助我们构建隐私感知 AI 系统,可按需忘记有关个人的特定信息。
为了遵守安全标准,我们需要确保用户数据:
特性 | 描述 |
---|---|
分离 | 仅对所有者可见,对其他用户不可见。 |
私有 | 未通过训练或微调提供给 LLM,仅在推理或生成时提供。 |
可按需删除 | 用户应在希望时被遗忘。 |
命名空间可分离用户数据,并适合作为安全基元。
使用 RAG 时,仅在生成时将数据作为上下文提供给 LLM,但数据无需用于训练或微调 AI 模型。
这意味着用户数据不会作为知识存储在模型本身中,而只是在请求生成内容时显示给 GenAI 模型。
RAG 能够实现个性化,同时严格控制用于生成特定于用户的响应的任何 PII。
专有数据或 PII 会根据每个请求与 LLM 共享,并且可以从系统中快速删除,从而使信息在未来请求中不可用。
当用户希望被遗忘时,从向量数据库索引中删除其数据将导致 RAG 系统不再了解他们。
数据删除后,LLM 将无法回答有关给定用户或主题的问题。检索阶段将不再在生成时向 LLM 提供任何此类信息。
与训练或微调相比,RAG 在管理特定于用户的数据方面提供了更大的灵活性,因为你可以从生产系统中快速删除一个或多个实体的数据,而不会影响其他用户的系统性能。
设计软件以实现隐私感知需要了解与存储的每种类型客户数据相关的风险。
首先,对需要存储在 向量数据库 中的数据类型进行分类。具体来说,识别哪些数据是公开的、私有的以及哪些包含 PII。
假设我们正在构建一个电子商务应用程序,该应用程序将存储公开、机密和 PII 数据的组合:
公开:公司名称、个人资料图片和职位。 私有:API 密钥、组织 ID、购买历史记录。 PII:全名、出生日期、帐户 ID。
接下来,确定哪些数据将仅存储为向量,哪些数据必须存储在元数据中以支持筛选。
我们的目标是在尽可能少地存储 PII 和提供丰富的应用程序体验之间取得平衡。
使用元数据进行筛选非常强大,但其最简单的形式需要以纯文本形式存储私有数据或 PII,因此我们希望注意公开哪些字段。
有了这种理解,我们可以考虑每种数据类型,并应用以下技术来安全地处理它。
对不同目的使用单独的索引。如果应用程序管理地理位置的自然语言描述和一些个人身份用户数据,请创建两个单独的索引,例如位置和用户。
根据索引包含的内容为其命名。将索引视为存储的数据类型的顶级存储桶。
正如我们之前关于 构建多租户系统 所写,命名空间是用于在单个索引中分离组织或用户的便捷且安全的基元。
将命名空间视为索引中的特定于实体的分区。如果索引是用户,则每个命名空间都可以映射到每个用户的名称。每个命名空间仅存储与其用户相关的数据。
使用命名空间还可以通过减少在返回相关结果时需要搜索的总空间来帮助提高查询性能。
Pinecone 支持 ID 前缀,这是一种在 upsert 时将额外数据附加到向量的 ID 字段的技术,以便您稍后可以引用内容的“片段”,例如第 1 页、第 23 块中的所有文档,或部门 Z 的用户 A 的所有向量。
ID 前缀非常适合将一组向量与特定用户关联,以便您可以在他们要求您时有效地删除该用户的数据。
例如,想象一个处理餐馆订单的应用程序,以便用户可以使用自然语言找到他们的购买记录:
index = pc.Index("serverless-index")
index.upsert(
vectors=[
{"id": "order1#chunk1", "values": [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]},
{"id": "order1#chunk2", "values": [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]},
{"id": "order1#chunk3", "values": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]},
{"id": "order1#chunk4", "values": [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4]}
],
namespace="orders"
)
ID 字段可以提供任何组合的分层标签,这些组合在您的应用程序中是有意义的。
这样,您可以更轻松地执行批量删除和列出操作:
# Iterate over all chunks from order #1
for ids in index.list(prefix='order1#', namespace='orders'):
print(ids)
使用 ID 前缀需要在设计应用程序时进行一些前期规划,但它提供了一种方便的方法来引用与特定实体相关的所有向量和元数据。
检索增强生成将专有、私有或快速更改的数据添加到 LLM 响应中,以将其建立在真实性和特定上下文中。
但这也是为您的最终用户提供有关其被遗忘权的保证的理想方式。让我们考虑一个电子商务场景,我们的用户可以使用自然语言与商店互动、检索旧订单、购买新产品等。
在以下 RAG 工作流中,用户的自然语言查询首先转换为查询向量,然后发送到向量数据库以检索与用户参数匹配的订单。
在推理时获取用户的个人上下文(他们的订单历史记录)和一些个人身份信息,并将其提供给生成模型以满足他们的请求。
当您使用 ID 前缀方案发出批量删除时会发生什么?
for ids in index.list(prefix='order1#', namespace='orders'):
print(ids) # ['order1#chunk1', 'order1#chunk2', 'order1#chunk3']
index.delete(ids=ids, namespace=namespace)
您已删除所有系统特定于用户的内容,以便后续检索查询不会返回任何结果——我们已有效地从 LLM 中删除了对我们用户的了解。
ID 前缀允许我们隔离、标记并稍后列出或删除特定于实体的数据。这使我们能够将 RAG 扩展到一个架构中,该架构提供了有关数据删除的保证。
您通常可以完全避免在向量数据库中存储个人身份信息。相反,您可以通过存储对其他系统的引用或外键来保护您的用户安全,例如您在其中存储完整用户记录的私有数据库中的行 ID。
您可以在本地或由云服务提供商托管的加密和安全存储系统中维护完整的用户记录。这减少了看到您用户数据的系统总数。
此过程有时称为令牌化,类似于模型将我们发送到提示中的单词转换为给定词汇表中单词 ID 的方式。您可以使用 此处 的交互式令牌化演示来探索此概念。
假设您的应用程序可以提供查找表或可逆令牌化过程。在这种情况下,您可以将外键写入在 upsert 期间与向量关联的元数据,而不是使用户数据可见的明文值。
外键可以是任何对您的应用程序有意义的内容:PostgreSQL 行 ID、您保留用户记录的关系数据库中的 ID、URL 或可用于查找其他数据的 S3 存储桶名称。
在 upsert 向量时,您可以附加您希望的任何元数据:
index.upsert(
vectors=[
{
"id": "order1#chunk1",
"values": [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
"metadata": {"user_id": 3046, "url": "https://example.com"}
},
{
"id": "order2#chunk2",
"values": [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2],
"metadata": {"user_id": 201}
}
]
)
您可以在将用户数据写入元数据之前使用哈希对其进行混淆。
隐藏不是加密。混淆用户数据并不能提供与加密相同级别的保护,但它可以使 PII 不被意外泄露。
您的应用程序提供在将用户 PII 作为元数据附加到其关联向量之前对其进行哈希的逻辑:
有许多类型的哈希操作,但从高层次上讲,它们将输入数据转换为一系列本身没有意义但可能被攻击者逆转或破解的字符。
您的应用程序可以在将值写入元数据之前以多种方式混淆用户数据,包括不安全的邮件哈希或 base64 编码:
在对用户数据进行哈希并将其存储为元数据后,您的应用程序通过相同的哈希逻辑运行查询以导出元数据筛选器值。
向量数据库返回与您的查询最相关的结果,就像以前一样。
您的应用程序在对用户数据进行操作或将其返回给最终用户之前会对其进行脱混淆:
这种方法提供了额外的纵深防御。即使攻击者可以访问您的向量存储,他们仍然需要逆转您的应用程序级哈希才能获取明文值。
混淆和哈希用户数据比以纯文本存储它们更好,但不足以抵御技术娴熟且有动机的攻击者。
在每次更新之前加密元数据、重新加密查询参数以执行查询以及解密每个请求的最终输出可能会给您的系统带来很大的开销,但这是确保您的用户数据安全且您的向量存储对它所服务的查询的敏感数据一无所知的最佳方式。
工程中的所有事情都是权衡取舍,您需要仔细权衡持续加密和解密的性能损失,以及安全维护和轮换私钥的开销,以及泄露敏感客户数据的风险。
如果您遵循通过维护单独的命名空间来实现多租户的建议惯例,则可以通过单个操作方便地删除存储在该命名空间中的所有内容。
要从命名空间中删除所有记录,请为您的客户端指定适当的 deleteAll
参数,并提供一个 namespace
参数,如下所示:
index.delete(delete_all=True, namespace='example-namespace')
成功构建隐私感知 AI 软件需要考虑和分类您计划预先存储的数据。
它要求您为自己留下对内容片段的周到处理,正如我们在 ID 前缀和元数据过滤中看到的那样,您可以使用它来有效地从您的系统中删除整个用户或组织的知识。
通过在您的堆栈中使用 Pinecone 向量数据库并进行一些周密的规划,您可以构建生成式 AI 系统,这些系统同样响应用户的需求并尊重他们的隐私。