当 AI Agent 成为主流开发工具,每一次对话都携带完整的历史记录发送给 LLM 提供商。这对 DLP(数据泄露防护)系统提出了一个不容忽视的挑战:同样的内容被反复扫描,造成巨大的性能浪费。
问题:O(N²) 的扫描开销
LLM API 的对话格式要求每次请求都在 messages[] 数组中携带完整的对话历史。一个 50 轮的 Agent 对话意味着:
- 第 1 轮:扫描 1 条消息
- 第 2 轮:扫描 2 条消息
- 第 3 轮:扫描 3 条消息
- …
- 第 50 轮:扫描 50 条消息
总扫描量为 1 + 2 + 3 + … + 50 = 1,275 次消息扫描,其中只有 50 次是真正的新内容。剩下的 1,225 次都是对历史消息的重复扫描。
这不仅浪费计算资源,更关键的是会产生重复的告警和审计记录,让安全团队淹没在噪音中。
当前方案:消息级哈希缓存
Bastion 目前采用的方案是消息级哈希缓存。核心思路很简单:对每条消息的内容计算 SHA-256 哈希,用哈希值作为缓存键,将 DLP 扫描结果缓存起来。
工作流程如下:
- 收到 API 请求后,解析
messages[]数组 - 对每条消息计算内容哈希
- 命中缓存 → 直接返回缓存的扫描结果,标记为「已知发现」
- 未命中 → 执行实际扫描,将结果写入缓存,标记为「新发现」
- 只对「新发现」触发告警和审计记录
这样,一个 50 轮的对话实际只扫描了 50 条消息(每条只扫描一次),复杂度从 O(N²) 降到了 O(N)。在实际运行中,第 50 轮请求的缓存命中率高达 98%,只有最新的 1 条消息需要扫描。
方案的边界
消息级缓存解决了最突出的性能问题,但在实际使用中还存在一些边界情况。
缓存粒度问题。 当前缓存以整条消息为单位。如果 Agent 在回复中追加了少量内容(比如工具调用结果),整条消息的哈希就会改变,需要重新扫描。这在 tool use 密集的场景下会降低命中率。
跨会话隔离。 缓存是进程内的 LRU 缓存,重启后丢失。不同用户的会话共享同一个缓存实例——这在单用户网关场景下是合理的,但如果面向多租户则需要隔离。
流式响应的时机。 对于 streaming 响应,DLP 扫描发生在完整响应组装完成后。如果在流式传输过程中发现敏感内容,此时数据已经部分送达客户端。
后续的思考方向
分块哈希。 将消息内容按固定大小(如 512 字节)分块计算哈希,这样即使消息末尾追加了内容,前面的块仍然命中缓存。代价是实现复杂度增加,且需要处理跨块的模式匹配。
持久化缓存。 将缓存写入 SQLite,这样重启后不需要重新预热。对于长时间运行的 Agent 任务(可能跨越多次网关重启),这可以避免首次请求的全量扫描。
会话感知扫描。 在代理层维护会话状态,记录每个会话已扫描到的消息索引。新请求进来时,只扫描索引之后的新消息,完全跳过历史部分。这比哈希缓存更高效,但需要准确追踪会话边界。
增量 Redaction。 对于选择 redact(脱敏)模式的用户,可以在首次发现敏感内容时就将脱敏后的版本缓存起来。后续请求直接使用已脱敏的版本,避免重复的正则匹配和替换操作。
小结
Agent 模式下的 DLP 重复扫描本质上是一个「带历史的增量处理」问题。消息级哈希缓存是一个简单有效的第一步,将 O(N²) 降到了 O(N)。但随着 Agent 对话越来越长、工具调用越来越频繁,更细粒度的缓存策略和会话感知的扫描机制将成为必要的演进方向。
安全不应该成为 AI 开发体验的瓶颈。好的 DLP 系统应该像 Git 一样——你知道它在保护你,但几乎感觉不到它的存在。