工程坑:召回污染还原与 sanitize 清洗


文档摘要

工程坑:召回污染还原与 sanitize 清洗 本节摘要:这是第 9 章最实用的一节——SDK 接入最容易踩的三个工程坑。第一,召回内容若直接拼进用户消息会「污染」用户原意,需要还原;第二,回写时若不注意写入位置,会重复写入相同内容,需要切片控制;第三,对话里可能含敏感字段,回写前要 sanitize 清洗。代理层在内部默默处理了这些,SDK 用户要自己做。读完本节你会避开 SDK 接入最高频的三个坑。

工程坑:召回污染还原与 sanitize 清洗

本节摘要:这是第 9 章最实用的一节——SDK 接入最容易踩的三个工程坑。第一,召回内容若直接拼进用户消息会「污染」用户原意,需要还原;第二,回写时若不注意写入位置,会重复写入相同内容,需要切片控制;第三,对话里可能含敏感字段,回写前要 sanitize 清洗。代理层在内部默默处理了这些,SDK 用户要自己做。读完本节你会避开 SDK 接入最高频的三个坑。

一、坑一:召回污染用户消息

第一个坑:召回的记忆若直接拼进用户消息,会让模型「分不清」哪些是用户说的、哪些是记忆:

污染的问题 用户原消息:「帮我写个查询接口」 召回记忆:「技术栈 Go + PostgreSQL,禁止 ORM」 错误做法(污染): 把记忆拼进用户消息: 「帮我写个查询接口 [记忆:技术栈Go+PG,禁止ORM]」 → 模型可能以为「禁止ORM」是用户这次说的 → 误解 正确做法(还原/隔离): 记忆放独立区块(prepend/append),与用户消息隔离: [记忆区块] [用户消息:帮我写个查询接口] → 模型清楚区分「记忆」与「本次请求」
做法 后果
拼进用户消息(污染) 模型混淆记忆与用户意图
独立区块隔离(还原) 模型清晰区分

关键概念:这就是第 2 节强调「prepend/append 两区块」的原因之一——不只是 cache 优化,更是「隔离记忆与用户消息」避免污染。召回内容要有自己的「区块」,不能与用户消息混作一团。

二、污染的还原:如何处理已被污染的消息

如果已经发生了污染(比如某些框架会自动把记忆拼进消息),需要「还原」——把记忆从用户消息里分离出来:

还原的处理(概念) 被污染的消息:「帮我写接口 [记忆:...]」 ↓ 还原: ├─ 识别记忆部分(通常有标记或位置规律) ├─ 抽离到独立区块 └─ 用户消息恢复原样:「帮我写接口」 ↓ 重新组装:[记忆区块] + [原用户消息]

还原的关键是「识别记忆与原消息的边界」。实践中,SDK 接入时会在注入时打标记(如特殊分隔符),还原时按标记拆分。如果你的 Agent 框架会自动改写消息,要特别注意这一步——否则记忆会「渗」进用户意图。

⚠️ 注意:某些 Agent 框架(尤其是会做「消息增强」的)会在你不知情时把附加内容拼进消息。接入 SDK 时要测试「召回后,用户消息是否被改动了」——若是,要做还原。这是 SDK 接入比代理层接入多出的工作量之一:代理层在内部处理了,SDK 你要自己挡。

三、坑二:写入位置切片避免重复

第二个坑:回写对话时,如果不注意写入位置,会重复写入相同内容:

重复写入的问题 对话历史:[msg1, msg2, msg3, msg4, msg5] 错误做法(每次回写全部): 第1轮回写:[msg1, msg2, msg3, msg4, msg5] → 全写 第2轮回写:[msg1, msg2, msg3, msg4, msg5, msg6] → 又全写(msg1-5重复!) → L0 里 msg1-5 被存了两遍 → 冗余 + 抽取重复 正确做法(切片,只写新增): 第1轮回写:[msg1...msg5] → 写这5条 第2轮回写:[msg6] → 只写新增的 msg6 → 不重复
做法 后果
每次全量回写 重复存储、抽取重复
切片只写新增 无冗余

切片的实现要点是「记住上次写到哪了」——通常用消息偏移量或会话游标,每次回写从游标位置开始,写到末尾,然后更新游标。这保证每条消息只被回写一次。

💡 技巧:SDK 通常提供「增量回写」的 API(传游标或增量消息),优先用它而非「全量回写」。如果你的 SDK 版本只有全量回写,自己维护游标做切片——这能避免大量重复存储和重复抽取,对长期运行的 Agent 尤其重要。

四、坑三:sanitize 清洗敏感字段

第三个坑:对话里可能含敏感字段(密钥、个人信息),回写前要 sanitize(清洗):

sanitize 的必要性 对话内容:「我的 API key 是 sk-xxx,帮我...」 不清洗直接回写: → sk-xxx 进 L0 → 进记忆 → 可能被召回显示给别人 → 泄密! 清洗后回写: → 「我的 API key 是 [REDACTED],帮我...」 → 敏感字段被替换 → 安全

sanitize 的实现方式:

清洗对象 处理
API key / 密钥 替换为 [REDACTED]
个人信息(邮箱/手机) 脱敏或哈希
凭证 token 移除
# 概念性:sanitize 清洗 import re def sanitize(message): # 清洗常见敏感模式(概念性,真实规则更全) message = re.sub(r'sk-[A-Za-z0-9]+', '[REDACTED_KEY]', message) message = re.sub(r'\b\d{11}\b', '[REDACTED_PHONE]', message) # 手机号 return message # 回写前清洗 await client.capture(conversation=sanitize(conv), ...)

⚠️ 注意:sanitize 是 SDK 接入的「安全必修课」。代理层在内部做了清洗(第 8 章 forward 后的加工),SDK 用户要自己做。如果跳过这一步,敏感信息会进长期记忆,有泄密风险——尤其在 team 可见性的资产里,团队成员都可能召回看到。

五、三坑的共性:代理层帮你挡了

这三个坑有个共性——它们在代理层接入时都不存在(代理层内部处理了),只在 SDK 接入时需要自己面对:

代理层 SDK
召回污染 代理隔离注入 自己用区块隔离/还原
重复写入 代理管理游标 自己切片
sanitize 代理内部清洗 自己清洗

💡 技巧:这就是第 8 章说的「SDK 收益是完全控制,代价是自己处理细节」。这三个坑就是「细节」的典型——代理层替你挡了,SDK 要你自己挡。理解这一点,你会更清楚两种接入方式的取舍:省心(代理)vs 可控(SDK,但要自己挡坑)。

本节要点回顾

  1. 污染坑:召回内容拼进用户消息会混淆意图,用 prepend/append 区块隔离,必要时还原。
  2. 重复写入坑:每次全量回写导致冗余,用游标切片只写新增。
  3. sanitize 坑:对话含敏感字段,回写前清洗(替换/脱敏),否则进记忆有泄密风险。
  4. 三坑共性:代理层内部处理了,SDK 要自己做——这是 SDK「可控」的代价。
  5. 安全必修:sanitize 尤其重要,team 可见资产会被多人召回,敏感信息一旦入库风险大。

下一节看四步法的兜底——错误降级三原则与性能建议。


发布者: 作者: 灏天文库 转发
评论区 (0)
U