应用工程

多租户 Agent 的数据隔离

给单个客户做一个 Agent demo 很简单,把它变成能同时服务几十上百个客户的 SaaS 产品,最容易被低估的一环是数据隔离。和传统多租户 Web 应用不同,Agent 系统里数据不只存在数据库表里——它还散落在向量存储、对话记忆、工具调用日志、prompt 拼接过程中,任何一个环节没做隔离,都可能造成 A 客户的数据出现在 B 客户的回答里。这类事故往往不是黑客攻击导致的,而是架构设计时漏掉了某个环节。

一、多租户 Agent 特有的隔离难点

传统 SaaS 应用的隔离经验(行级隔离、schema 隔离、独立数据库)大部分可以复用,但 Agent 系统多了几个新维度:

  • 向量检索会跨租户”泄漏”语义相似内容:如果向量库没有按租户过滤,语义检索可能召回其他租户的相似文档片段并拼进 prompt
  • 对话记忆/ 长期记忆机制可能串号:Agent 的记忆系统如果用了共享的 key 空间(比如按 session_id 而非 tenant_id + session_id 组合键),存在 ID 碰撞或越权读取的风险
  • 工具调用的副作用会突破数据边界:Agent 调用工具(发邮件、查订单、改库存)如果没有把租户上下文传递到工具执行层,可能对错误的租户产生实际操作后果,这比”看到不该看的数据”更严重
  • Prompt 拼接环节容易”手滑”混入跨租户内容:系统提示词里如果硬编码了共享的 few-shot 示例、或者把多个租户的上下文放进同一次调用做批处理优化,边界很容易在拼接逻辑里被破坏

二、向量存储层的隔离方案

向量数据库的隔离通常有三种实现路径,各有取舍:

方案隔离强度成本适用场景
物理隔离(每租户独立 collection/index)最强租户数多时索引管理开销大,冷门租户也占固定资源客户数量少、对隔离要求极高(金融、医疗)的场景
逻辑隔离(共享 collection + 租户 ID 过滤字段)依赖过滤是否被正确执行成本最低,资源利用率高中长尾客户量大、数据敏感度中等的场景
混合方案(核心大客户物理隔离,长尾客户逻辑隔离)按客户分级兼顾成本和大客户的合规要求To B SaaS 常见的分层客户结构

选逻辑隔离时最大的风险点是”过滤条件被漏掉的某次查询绕过”——比如某个新加的检索接口忘了加 tenant_id 过滤条件。缓解方法是把租户过滤逻辑收敛到统一的检索封装层,而不是让每个调用方各自拼查询条件,同时在检索结果返回前加一道”结果内容确实属于该租户”的兜底校验。

三、对话记忆与长期记忆的隔离

Agent 的记忆系统(短期对话历史、长期用户画像、跨会话摘要)如果设计不当,是最容易被忽视的泄漏点:

  • Key 设计必须包含租户维度:记忆存储的 key 至少应该是 tenant_id:user_id:session_id 的组合,而不是仅靠 session_id——单纯的 session_id 冲突概率虽低但不为零,而且一旦出错就是直接的越权读取
  • 记忆检索也要走和向量检索同样的隔离校验:很多团队给”业务数据检索”做了严格隔离,却漏了”Agent 自己的记忆库”这条通道,因为它看起来是内部实现细节而非业务数据
  • 跨租户的共享知识要显式区分:如果产品设计上确实需要一些全租户共享的公共知识(比如产品通用 FAQ),要在数据模型层面明确标记为”共享”,而不是靠”没设置租户 ID 就默认共享”这种隐式规则——隐式规则是事故温床

四、工具调用层的租户上下文传递

这是最容易出实际业务事故的环节,因为它涉及的不只是”读到了不该读的数据”,而是”对错误的对象执行了操作”。设计上的关键原则:

  • 租户上下文要作为强制参数贯穿整个调用链,从用户请求进入系统开始,到 LLM 决定调用哪个工具,再到工具执行层,租户 ID 应该由系统在每一层显式传递和校验,而不是依赖 LLM 在 prompt 里”记得”带上正确的租户信息——LLM 的输出不可完全信任,租户边界这种安全关键信息不能让模型自己来维护
  • 工具执行前做租户归属校验:比如”查询订单”工具,即便 LLM 传入的 order_id 是合法的,执行层也要再校验这个 order_id 是否真的属于当前请求的租户,双重校验能兜住 prompt injection 或者模型幻觉导致的越权
  • 审计日志按租户拆分留痕:每次工具调用都记录租户 ID、操作内容、结果,出问题时能快速定位是哪个环节的隔离失效

这部分和权限设计紧密相关,可以结合 AI Agent 工具权限粒度设计 一起考虑,把”租户边界”作为权限模型里的一个基础维度而不是事后补丁。

五、Prompt 拼接环节的边界

为了省成本,一些团队会尝试把多个租户的请求做批处理合并调用(比如把几个客户的相似查询拼进一次 prompt 批量处理),这种优化在 Agent 场景下风险很高——一旦拼接逻辑有 bug,或者模型输出本身把不同部分的上下文”串”到了一起,就会造成内容泄漏。建议:

  • 每次 LLM 调用只服务单一租户,不做跨租户的请求合并,省下的调用成本远不足以覆盖数据泄漏的风险和信任成本
  • 系统提示词中的共享模板要做审查,确保模板本身不包含任何特定租户的示例数据
  • 输出前做租户归属检查,尤其是 Agent 生成的内容涉及引用具体数据时,可以加一道轻量校验,确认引用的数据确实来自当前租户的检索结果

六、验证隔离是否真正生效

设计完隔离方案后,需要主动验证而不是假设它生效了:

  1. 构造跨租户探测用例:模拟租户 A 的用户尝试通过正常对话或者简单的 prompt injection 手法套取租户 B 的信息,纳入常规安全测试
  2. 把隔离校验纳入自动化测试:每次涉及检索层、记忆层、工具调用层的代码改动,跑一遍多租户隔离的回归测试,防止重构时不小心破坏边界
  3. 生产环境做抽样审计:定期抽查真实调用日志,确认每次工具调用的租户上下文和请求来源一致

七、常见误区

  • 只隔离了业务数据库,漏了向量库和记忆系统:这是最常见的疏漏,因为这两块经常被当作”Agent 内部实现细节”而不是需要按业务数据标准对待的敏感存储。
  • 依赖 LLM 在 prompt 里正确携带租户信息:租户边界是安全关键约束,必须由系统代码强制保证,不能指望模型”应该会遵守指令”。
  • 为了省成本做跨租户批处理:这类优化的风险收益比通常很差,尤其是在 Agent 会执行有副作用操作的场景下。
  • 隔离方案设计完就不再验证:代码会演进,新功能、新工具接入时很容易在无意间打破已有的隔离边界,需要持续测试而非一次性验收。

八、相关阅读