# sanitizer · 文本清洗
**不注册任何工具**,只挂三个阶段钩子,在 Agent 全链路上洗掉两类污染:
1. **坏字节**:坏 UTF-8、`U+FFFD`(替换符)、ANSI 转义序列
2. **思维泄漏**:LLM 输出里残留的工具调用标记
## 为什么需要它
坏字节会**被 LLM 复读**。一次工具返回乱码(比如源码里带 ANSI 颜色码、或二进制片段被当文本读出来),
这些字节会进上下文,之后模型每次生成都可能把它抄一遍 —— 越滚越脏。
在每个入口洗掉,比事后清理便宜得多。
思维泄漏则是另一种:模型有时把 `...` 这类内部标记直接写进正文,
用户就看到一堆不该出现的 XML。
## 挂载的三个阶段
| 阶段 | 处理对象 | 作用 |
|---|---|---|
| `on_input` | `ctx.RawMessage` | 洗用户输入,脏字节不进后续链路 |
| `after_toolcall` | `ctx.ToolResults` | 洗工具结果,**坏字节不进 LLM 上下文** |
| `post_action` | `ctx.LLMText` | 洗模型输出:先清思维泄漏,再清乱码 |
每次有改动都打一行日志(`cleaned N bytes`),便于确认它真的在工作而不是静默失败。
## 识别哪些泄漏形态
按正则匹配多种标记写法,覆盖不同模型家族的习惯:
- `…`、`…`、`…`
- `…`
- 上述标记包在 ```xml / ```json 代码块里的形态
- 中文括号变体:`【tool_call】…【/tool_call】`
## 实现要点
- 依赖 **ABI v2 的 stage 写回能力**:插件对 `StageContext` 的修改会同步回内核。
在 v1 上改了不生效。
- 读写 `StageContext` 时按约定加 `ctx.Lock()`。
## 构建
```bash
go build -buildmode=plugin -o sanitizer.so .
```
或经 `hmapdev build` 打包为 `.hmap`。