跳转至

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)。用户裁定保留拦截,但在评审拦截流程时提出三个缺口:

  1. 拦一次之后随手重发就放行(空闲计时已被刷新),等于没给用户留选择的机会;
  2. 若 Trae 客户端对 500 静默重试,用户会在不知情下付掉全款;
  3. 拦之前的探测本身要花一次 5 万 token 的 miss 费,而工具定义变化这类分叉本地就能判定,不必花这笔钱。

2. 目标

  1. 拦截后进入冷却期(默认 120 s):期内该会话的请求继续拒,堵住「重发即放行」与静默重试;
  2. 冷却期内若检测到上下文被压缩,立即放行,不必干等到期;
  3. 工具定义指纹与上次快照不同时,跳过探测直接判失效(省掉探测费,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 同步,请勿在站点侧修改)