SOP:探针拦截策略增强(冷却期 + 压缩即放行 + 工具指纹变化免探测)¶
- 主题目录:
probe-cooldown-20261010/|实施日期:2026-10-10|实施人:Agent(用户授权) - 实现体:
proxy.py(parse_cache_probe_config()新增两项、CacheProbe新增 4 个方法、check()与_reject_cache_expired()改造)、config.yaml(cache_probe段新增两项) - 关联:
probe-prefix-fix-20261010/SOP.md(探测体取本次前缀)、tools-hash-20261010/(跨日变化根因)、cache-probe-20261008/SOP.md(探针本体)
1. 背景¶
2026-10-10 查明:WebSearch 工具的描述内嵌当天日期,跨日必变,导致「空闲 ≥1 h + 跨日」后的首次请求前缀分叉、几乎整段重算(实测 350,719 token)。用户裁定保留拦截,但在评审拦截流程时提出三个缺口:
- 拦一次之后随手重发就放行(空闲计时已被刷新),等于没给用户留选择的机会;
- 若 Trae 客户端对 500 静默重试,用户会在不知情下付掉全款;
- 拦之前的探测本身要花一次 5 万 token 的 miss 费,而工具定义变化这类分叉本地就能判定,不必花这笔钱。
2. 目标¶
- 拦截后进入冷却期(默认 120 s):期内该会话的请求继续拒,堵住「重发即放行」与静默重试;
- 冷却期内若检测到上下文被压缩,立即放行,不必干等到期;
- 工具定义指纹与上次快照不同时,跳过探测直接判失效(省掉探测费,500 立刻返回)。
3. 前置条件¶
- 已备份
proxy.py/config.yaml/cache-probe-20261008/offline_verify.py(见 §7); ./venv/bin/python -m py_compile proxy.py cache-probe-20261008/offline_verify.py通过;- 离线验证 19/19 通过(
cache-probe-20261008/offline_verify.py); - 重启代理会打断在途流式回答:选无在跑任务时执行。
4. 影响范围¶
| 项 | 内容 |
|---|---|
| 主机 | 本机(dell-R620) |
| 组件 | proxy.py(探针判定)、config.yaml(cache_probe 段) |
| 服务 | systemd 用户服务 token-proxy.service(8788);需重启才生效 |
| 是否停服 | 是(重启约 1~2 s),会打断在途流式回答 |
| 窗口期 | 无在跑任务时 |
| 影响面 | 全局;但只在「该会话空闲 ≥1 h 后的请求」上动作,冷却期只影响被拦的那个会话 |
| 成本 | 工具定义变化场景省掉一次探测(约 0.03~0.05 元);冷却期不改成本,只改「什么时候能继续」 |
5. 分步操作¶
步骤 1:配置新增两项(config.yaml)¶
cd /home/rain/Documents/services/token_proxy
grep -n -A9 '^cache_probe:' config.yaml
预期:可见 cooldown_seconds: 120 与 cooldown_shrink: 0.9 两项。
坑:cooldown_seconds: 0 表示不启用冷却(回到旧行为);cooldown_shrink 必须落在 (0, 1),越界会被回退为默认并记 warning。
步骤 2:探针判定改造(proxy.py)¶
实现体:CacheProbe。check() 的判定顺序变为「冷却期 → 空闲判定 → 工具指纹 → 探测」,新增 _tools_fp() / _tools_changed() / _enter_cooldown() / _cooldown_verdict() 四个方法。
grep -n '_cooldown_verdict\|_tools_changed\|_enter_cooldown\|_tools_fp' proxy.py
预期:check() 内 2 处调用(冷却判定、工具变化)+ 4 处方法定义。
坑 1:工具指纹必须在「排序后」的 tools 上算。normalize_tools_order() 在前(第 1686 行附近),meta["_probe_tools"] 落在其后,快照里存的也是排序后的,故纯顺序抖动不会误判为「已变」。
坑 2:冷却记录只在内存(_cools),重启即清空。这是有意的:重启通常意味着用户已在处理问题,不必继续拦。
步骤 3:冷却期内的「已压缩」判定¶
_cooldown_verdict() 比较本次与拦下时的前缀字符数:本次 ≤ 拦下时 × cooldown_shrink 即判为已压缩。压缩必然保留首条 system(conv_key 由它决定,改了就不是同一会话),故比对的是 system 之后的部分。
坑:判定用「字符数」而不是消息条数。Trae 报错后用户补一句「继续」会让条数变多、字符数略增,不应误判为压缩;只有真正缩短上下文才会低于阈值。
步骤 4:重启代理¶
systemctl --user restart token-proxy
sleep 2
curl -s http://127.0.0.1:8788/healthz
grep -o '缓存探测=开启[^|]*' /home/rain/Documents/services/token_proxy/logs/proxy.log | tail -n 1
预期:启动行含 缓存探测=开启(空闲>=3600s, 探前 50000 tok, 命中<90% 拦截, 冷却 120s, 超时 15s)。
注意:本步会打断当前正在进行的流式回答。
6. 验证¶
| 项 | 命令 | 第 1 遍 | 第 2 遍(幂等复验) |
|---|---|---|---|
| 语法 | ./venv/bin/python -m py_compile proxy.py cache-probe-20261008/offline_verify.py |
PYCOMPILE-OK | 同 |
| 离线用例 | ./venv/bin/python cache-probe-20261008/offline_verify.py |
19 项 0 失败 | 19 项 0 失败 |
| 服务 | curl -s http://127.0.0.1:8788/healthz |
{"status":"ok",...} |
同 |
本次新增/调整的用例(关键断言):
| 用例 | 断言 | 实测输出 |
|---|---|---|
| U13 冷却期内重发仍拒 | 首拦 500 且上游收到 1(探测);重发 500 且上游收到 0 | 通过 |
| U14 冷却期内已压缩则放行 | 200,上游收到 1 且非探测 | 通过 |
| U15 工具定义已变:免探测直接拦 | 500,上游收到 0(即未探测) | 通过 |
| U15r 反例:仅顺序不同不误拦 | 200,上游收到 2(探测 + 业务) | 通过 |
| U12 探测体用本次前缀(消息已变) | 探测体含本次标记、不含旧标记 | 通过 |
| U12r 反例:旧口径必失败 | 旧口径探测体只含旧标记 | 通过 |
线上生效验证(需真实场景,不主动制造):
grep -E '缓存探测跳过|冷却期内上下文已压缩|缓存拦截冷却期结束' /home/rain/Documents/services/token_proxy/logs/proxy.log | tail -n 5
预期:平日无输出;跨日后的首次请求会出现 缓存探测跳过:工具定义指纹已变,随后几十秒内的重发出现冷却拦截,压缩后出现「冷却期内上下文已压缩」。
7. 回滚¶
- 备份位置:
probe-cooldown-20261010/backup/,逐条登记见backup/README.md。
只改配置即可等效回退冷却期(推荐先试):
cd /home/rain/Documents/services/token_proxy
# 把 cooldown_seconds 改为 0(停用冷却;工具指纹变化直接拦仍保留)
sed -i 's/^ cooldown_seconds: .*/ cooldown_seconds: 0/' config.yaml
systemctl --user restart token-proxy
curl -s http://127.0.0.1:8788/healthz
整体回退:
cd /home/rain/Documents/services/token_proxy
cp -p probe-cooldown-20261010/backup/proxy.py.bak.20261010-011715 proxy.py
cp -p probe-cooldown-20261010/backup/config.yaml.bak.20261010-011715 config.yaml
systemctl --user restart token-proxy
curl -s http://127.0.0.1:8788/healthz
回滚后验证:/healthz 返回 200;logs/proxy.log 不再出现「冷却」相关行。
8. 风险说明¶
| 风险 | 缓解 |
|---|---|
| 冷却期挡住客户端的自动重试,UI 可能连续报错 | 这是刻意行为(用户不希望静默付全款);cooldown_seconds: 0 可一键停用 |
| 「已压缩」用字符数阈值(0.9)判断,可能误判 | 阈值可配;误判方向是「多放行」而非「多拦」,不会卡死用户 |
| 工具指纹变化直接拦依赖「tools 位置靠前」这一实测结论 | 依据:本次命中 21760 token 恰为 system 段长度;若哪天上游拼接顺序改变,表现为误拦,回滚 cooldown_seconds: 0 不解决,需整体回退 |
| 冷却记录只在内存,重启即失效 | 有意为之,见步骤 2 坑 2 |
附录:与上一轮的关系¶
上一轮(probe-prefix-fix-20261010) |
本轮 |
|---|---|
| 探测体从「上次快照前缀」改为「本次请求前缀」 | 在正确判定之上补拦截策略 |
| 解决「假阳性放行」 | 解决「拦了也白拦(随手重发即过)」与「探测费白花」 |
来源:
/home/rain/Documents/services/token_proxy/probe-cooldown-20261010/SOP.md| 最后更新:2026-10-10 01:21(由build/sync.py同步,请勿在站点侧修改)