Skip to content

上下文、知识与租户隔离 ​

上一节把模型输出限制为业务可接受的结果。本节把同样的问题转到输入:哪些文档可以送给模型,何时可以复用上一次答案?前置为上一节与 Python 列表操作;检索算法详见RAG 系统,这里聚焦数据责任。

三种数据不能混成聊天记录 ​

会话历史用于连贯对话,任务状态记录程序进展,知识证据支撑事实。历史中的“我已经付款”是用户陈述;账本中的付款记录才是业务事实。模型上下文是这些数据的有限视图,不是数据库,也不是审计日志。长期记忆要有来源、更新时间和删除策略,不应把模型生成的猜测永久存为用户事实。

上下文窗口要求输入和输出预算共同受限。先保留任务规则与当前问题,再按相关性选择授权证据;摘要会损失细节,因此原文仍留在有访问控制的数据层。不同模型的 tokenizer 不同,字符计数只能粗估,正式请求用对应平台的计量与上限。

权限必须早于截取 ​

设租户 A 有一条分数为 8 的文档,B 有两条分数为 10、9 的文档。若先全局取前两条再过滤 A,得到空结果;若先限定 A 再排序,则得到正确证据。更危险的是先送模型再遮盖答案,此时数据已经离开边界。

python
rows = [
    {'id': 'b1', 'tenant': 'B', 'score': 10, 'version': 1},
    {'id': 'b2', 'tenant': 'B', 'score': 9, 'version': 1},
    {'id': 'a1', 'tenant': 'A', 'score': 8, 'version': 3},
]
def retrieve(tenant, k):
    authorized = [r for r in rows if r['tenant'] == tenant]
    return sorted(authorized, key=lambda r: (-r['score'], r['id']))[:k]

def cache_key(tenant, acl_revision, document_version, question):
    return tenant, acl_revision, document_version, question

assert [r['id'] for r in retrieve('A', 2)] == ['a1']
assert cache_key('A', 1, 3, '政策') != cache_key('B', 1, 3, '政策')
assert cache_key('A', 1, 3, '政策') != cache_key('A', 2, 3, '政策')
print('isolation ok')

保存为 context.py 运行,输出 isolation ok。这验证的是授权过滤顺序和缓存身份,不是向量召回质量。acl_revision 表示权限策略版本;当授权变化,旧缓存不再命中。真实系统还需包含用户/角色可见集合的稳定身份、提示版本、模型版本和检索索引版本。租户内用户权限不同,仅含租户仍不够。

更新与删除是一条传播链 ​

原文更新后,切片、向量、摘要和结果缓存都有可能继续携带旧内容。用 document_id + revision 关联派生物,构建新版本后再原子切换活动索引。删除请求先建立不可查询的墓碑,再异步清理派生物;否则清理期间旧索引仍能返回数据。备份保留与清理期限要单独记录,不能声称删除主库等于所有副本即时消失。

如果原文已经撤销,缓存命中也必须重新核验可见性。对每次引用保存来源 ID、版本及片段范围;只有 URL 无法证明引用与生成时看到的文档相同。

练习 ​

在 rows 中给 a1 增加 deleted: True,给 B 文档增加 deleted: False。修改 retrieve:缺少 deleted 的旧记录仍按未删除处理;墓碑记录必须在排序前排除。验证 A 结果为空、B 前两条顺序保持。说明清除墓碑后旧向量尚未删除会有什么风险。

本章答案 · 下一章:运行状态

用心记录每一步成长