# 污染:塑形标记如何让模型"倒打一耙" > 2026-10-13。本文记录一个**本插件自己制造**的副作用,以及根治办法。 > 起因:维护者观察到界面里思考过程显示 `[reasoning extract: XX chars omitted] 加很短的句子`。 ## 0. 一句话 **本插件的初衷是去掉 "Hmm" 这类自我强化的填充噪音,却因为给塑形结果加了记账前缀, 自己造了一个更规整、更难摆脱的同类噪音。** ## 1. 污染是怎么发生的 ``` ① extract 把历史推理改写成 "[reasoning extract: N chars omitted] <决策句>" ② 这份文本随 assistant 消息进入**之后每一次请求**的上下文 (harness 依据:packages/core/session/src/surface.ts:deriveEventMessage() 对 assistant/message **原样返回**,历史推理会重复出网) ③ 模型读到"自己以前是这么想的" ⇒ 把记账标记当成**自己思维链的一部分** ④ 模型继续往下写时,**接着生成这个格式** ⇒ 自我强化 ``` ## 2. wire 级取证(决定性证据) 只看会话日志会得出错误结论(会话里的 `reasoning-chunks` 是**分片**记录,拼接会造出 "正文里含前缀"的假阳性)。真正的判据在**出网/回流**两侧: ### 2.1 出网侧:历史推理确实是塑形形态 用**出网抓包/统计插件**(监听 `llm/stream`,记录请求 messages 与响应 chunk)读其日志尾部 (**该类日志可达 20+ GB,务必用 offset 尾部读,绝不整读**): ``` provider=deepseek-official messages=631 inputChars=855989 出网 reasoning 块:已塑形 179 / 原文 12 ← 历史推理 94% 是塑形形态 ``` ### 2.2 回流侧:模型在**逐字生成**这个格式 ★ 判据:抓包插件记的是**细粒度流**(单次调用的 reasoning 分片数 65~3391, 说明它位于过滤器**外层** ⇒ 看到的是真实模型输出)。取输出以 `[reasoning extract:` 开头的调用,其分片为: ``` [ 0] "[" [ 5] " " [ 1] "reason" [ 6] "242" [ 2] "ing" [ 7] " chars" [ 3] " extract" [ 8] " omitted" [ 4] ":" [ 9] "]" [10] " Both" [11] " fixes" ... ``` ⇒ **模型是逐字"写"出 `[reasoning extract: 242 chars omitted]` 的**, 不是被动携带。它把塑形器的记账标记当成了自己的思路内容。 ## 3. 为什么这比 "Hmm" 更糟 | | "Hmm" 这类自然填充 | 本插件的前缀 | |---|---|---| | 来源 | 模型自己的习惯 | **外部注入的格式** | | 形态 | 模糊、多变 | **规整、可精确复制** | | 自我强化 | 会(注意力涣散) | **更强**(格式明确 ⇒ 模型精确模仿并延续) | 本插件本应减少前者,结果引入了后者。 ## 4. 根治:默认不加任何前缀 `OPS.extract` 现在**默认返回纯抽取结果**,不带任何标记: ```js if (op.marker !== true) return picked // 默认:干净 ``` - 需要人类审计"省略了多少"的极端场景,可显式写 `{ op:'extract', marker:true }`, 并**自行承担污染风险**; - 正常回溯请用**侧路原文寄存表**(§5)。 ### 副作用:还更省了 实测 4,872 个真实推理块(`extract`, `maxChars=240`): | 形态 | 塑形后字符 | 压缩率 | |---|---|---| | 带前缀(旧默认) | 832,855 | 92.11% | | **不带前缀(新默认)** | **663,521** | **93.72%** | ⇒ **去掉前缀额外省 169,334 字符(压缩率 +1.60 个百分点)**, 两个方向完全一致,**没有取舍**。 ### 顺带修掉的一个隐患 1.0.4 的"短块护栏"(不比原文短就原样返回)让塑形**幂等**: `863 → 62 → 62 → 62`。在 1.0.3 及以前,已带前缀的文本再进 `extract` 会 **再加一层前缀、每次都涨**。也就是说:**短块护栏顺带堵住了"回流文本雪崩"**。 ## 5. 原文怎么回溯:侧路寄存表 原文在会话里**永久消失**(`extract` 只留决策句)。但**绝不能**把原文塞回 payload (那等于取消塑形)。所以走侧路: ``` ~/.dsh-entry-shaper.originals.jsonl { k: sha256(shaped), v: 原文, t: 时间 } ``` ### 5.1 为什么用内容哈希做键 塑形是**单射**——实测 4,872 个真实块 + 400 条极端构造样本(含"决策句完全相同、 只有正文不同"): ``` 样本 400 条 → 400 个不同的塑形结果 撞键 0,最大一对多扇出 1 ``` ⇒ shaped 文本本身就能唯一定位原文,**不需要额外键**。 这也是被迫的:塑形发生在 `llm/stream` 中间件里,那一刻**还没有事件 seq** (seq 在 `session.append` 时才分配)⇒ 没有稳定的顺序键可用。 ### 5.2 每轮清空(这不是优化,是**必需**) 寄存表若不清空会随会话线性增长 ⇒ **硬盘会爆**。清空后文件大小只覆盖**当前轮**: | 会话 | 单轮推理原文均值 | 最大单轮 | p90 单轮 | |---|---|---|---| | acde5be7 | 23,200 字符 | 182,684 | 55,087 | | d5dce281 | 37,204 字符 | 272,654 | 93,687 | ⇒ 峰值 **≈0.3 MB**,恒定不增长。 清理点在 `onEvent` 里监听 `turn/start`(**不放在 `handleEvent` 里**:换轮事件在 `cfg.enabled=false` 或熔断期间照样到来,寄存表也必须跟着清,否则"关掉插件"反而留下 一个不再清空的堆积文件;清空纯粹是磁盘卫生,与塑形无关)。 ### 5.3 回溯工具 ```powershell node tools/original.mjs --stats # 统计当前轮 node tools/original.mjs --list # 列出条目 node tools/original.mjs "" # 查回原文 ``` ## 6. 正确的判据(给后来者) 排查"塑形有没有生效/有没有副作用"时: | ❌ 不要 | ✅ 要 | |---|---| | 只看会话里的 `assistant/message` | 看**出网** messages(抓包记录的 `kind=request`) | | 把 `reasoning-chunks` 的 `texts` 直接拼接判断"正文含关键词" | 那是**分片**记录,拼接会造**假阳性** | | 认为"抓包看到的输出 = 模型原始输出" | 先确认中间件顺序:**分片数多**(几十~几千)才说明抓包插件在外层 | | 整读抓包日志 | 该文件可达 **20+ GB**,必须 offset 尾部读 | **本轮我在这个坑里栽了三次**,都是"字段/顺序假设错误 ⇒ 得出反向结论"。