SOP:代理侧答复形状检查(reply_check)¶
- 主题目录:
reply-nudge-20261012/ - 执行时间:2026-10-12
- 改动对象:
proxy.py(5 处)、config.yaml(1 处) - 触发来源:用户裁定「按方案 A 实施代理侧轮末检查」
1. 背景¶
规则《完成汇报》是硬门禁:含结论的答复必须有「四列表 + 要你拍板:…」收尾,缺一即不合格。 但这条只能靠模型自觉——Trae 只有 SessionStart / PreToolUse / PostToolUse 三类事件,没有「最终答复」事件, Hook 看不到答复正文(规则文本自己也写了这句)。实测结果是反复漏、每次靠人提醒。
现有唯一机器兜底是 merge-guard 判据 W(同一会话累计 15 步未见 wrapup.py 就提醒一次),
它只管「收尾脚本跑没跑」,管不了「答复形状对不对」。
用户从三个方案中选定 A:代理侧轮末检查(另两个是收紧判据 W、维持现状)。
2. 目标¶
改后应达到的可验证状态:
- 新一轮首请求到达时,若上一轮 assistant 答复缺「四列表」或「要你拍板:」行,
提醒被追加到本条用户消息末尾,且不改
system、不改消息条数; - 上一轮答复合规,或已声明「这是讨论 / 无待决项」,则不注入;
- 轮内后续步进(末条不是用户输入)不检查;
- 开关
reply_check.enabled: false可关闭,重启即生效。
3. 前置条件¶
venv/(proxy.py依赖yaml等);- systemd 用户服务
token-proxy在用(systemctl --user,linger 已开); - 假上游验证脚本:
reply-nudge-20261012/verify_reply_nudge.py(自起临时端口,不碰 8788); - 改前备份(见第 5 章步骤 1)。
4. 影响范围¶
- 主机 / 组件:单机
192.168.31.250,仅代理进程proxy.py; - 是否停服:需重启服务,秒级(
systemctl --user restart token-proxy),不涉及 Slurm / LSF 节点,无在跑作业; - 窗口期:无特殊窗口;重启后状态下落(阶段 / 档位 / 快照)由既有机制恢复;
- 影响面:所有走本代理的会话。仅在缺项时每轮追加一条约 150 字的提醒,位于用户消息末尾, 不进入 system prompt 前部,前缀缓存命中不受影响(增量只算新增的那段)。
5. 分步操作¶
步骤 1:备份(高危:覆盖前必做)¶
cd ~/Documents/services/token_proxy
TS=$(date +%Y%m%d-%H%M%S)
mkdir -p reply-nudge-20261012/backup
cp -p proxy.py "reply-nudge-20261012/backup/proxy.py.bak.$TS"
cp -p config.yaml "reply-nudge-20261012/backup/config.yaml.bak.$TS"
预期:两条 cp 无输出、退出码 0。失败判断:权限或磁盘问题导致非 0,先修再重试,不得跳过备份。
步骤 2:改 proxy.py(5 处)¶
坑 1:注入不能改 system 消息。system 位于前缀最前,动它会让整段历史前缀重建(实测 93% miss)。
故注入位置选在本条用户消息末尾,前缀命中不受影响。
坑 2:判定「是不是新一轮首请求」不能用 role == "user" 一条,因为轮内工具结果也可能是 user 角色。
判据取「末条消息含 <user_input> 标签」——轮内步进不带该标签。
改动点:
# ① apply_prompt_hint() 之后新增:REPLY_TABLE_RE / REPLY_VERDICT_RE / REPLY_EXEMPT_MARKS / REPLY_NUDGE
# 与 _last_assistant_text() / build_reply_nudge() / append_hint_to_last_user() / parse_reply_check_config()
# ② ProxyHandler 类属性新增 reply_check_cfg(默认 {})
# ③ _prepare() 里 normalize_tools_order() 之后调用:
# if self.reply_check_cfg.get("enabled"):
# nudge = build_reply_nudge(obj)
# if nudge: meta["reply_nudge_applied"] = append_hint_to_last_user(obj, nudge)
# 位置在 build_shape_fingerprint 之后 → 指纹与 req_shape 记录不受注入影响
# ④ main() 里 ProxyHandler.reply_check_cfg = parse_reply_check_config(config, logger)
# ⑤ 启动日志增列「答复形状检查=开启/关闭」
判据:python3 -m py_compile proxy.py 通过。
步骤 3:改 config.yaml¶
在 cost: 段之前插入 reply_check: 段(带注释说明触发面与重启要求),enabled: true。
步骤 4:验证(第 6 章),通过后重启¶
systemctl --user restart token-proxy
systemctl --user is-active token-proxy # 预期 active
python3 -c "import urllib.request;print(urllib.request.urlopen('http://127.0.0.1:8788/healthz',timeout=3).read().decode())"
grep -a -o "答复形状检查=[^|]*" logs/proxy.log | tail -n 1 # 预期 答复形状检查=开启
6. 验证¶
cd ~/Documents/services/token_proxy
./venv/bin/python reply-nudge-20261012/verify_reply_nudge.py # 预期:结论:全部通过(24 项)
./venv/bin/python reply-nudge-20261012/verify_reply_nudge.py # 第二遍须同结果
三组用例:
- A 纯函数(15 项):两项皆缺 / 只缺一项 / 两项齐全 / 声明讨论 / 末条非用户输入 / 轮内步进 / 无上一轮答复 / 追加幂等 / 不改 system / 不改条数 / 配置解析四种入参。
- B 假上游端到端(8 项):自起临时端口 + 假上游,两次请求分别带「不合规 / 合规」的上一轮答复,
断言上游收到的是否带提醒,并核对
system与消息条数未变、运行日志无异常栈。 - C 真实 config(1 项):
config.yaml的reply_check.enabled为 true。
实测结果(2026-10-12):两遍均「结论:全部通过」;服务重启后 active、健康检查正常、启动日志显示「答复形状检查=开启」。
7. 回滚¶
备份位置:reply-nudge-20261012/backup/,逐条登记见 backup/README.md。
轻量回滚(推荐,只关功能不动代码):
cd ~/Documents/services/token_proxy
cp -p reply-nudge-20261012/backup/config.yaml.bak.<时间戳> config.yaml # 或只把 reply_check.enabled 改为 false
systemctl --user restart token-proxy
完整回滚:
cp -p reply-nudge-20261012/backup/proxy.py.bak.<时间戳> proxy.py
cp -p reply-nudge-20261012/backup/config.yaml.bak.<时间戳> config.yaml
systemctl --user restart token-proxy
回滚后验证:grep -a "代理启动" logs/proxy.log | tail -n 1 应不含「答复形状检查」;
回复内容不再出现「【答复形状检查】」字样。
回滚触发条件(任一即回滚):
- 注入内容出现在不该出现的位置(如 system 消息、或答复正文被改写);
- 同一轮重复注入多条,或把正常合规答复判为缺项导致每轮刷提醒;
- 代理出现非 0 退出、健康检查失败、或单请求耗时明显上升。
8. 风险说明¶
| 风险 | 说明 | 缓解 |
|---|---|---|
| 误提醒 | 纯事实问答 / 单点查询本可不带表 | 申明「这是讨论 / 无待决项」即豁免;命中只提醒不改写 |
| 提醒被重放 | 注入在用户消息末尾,轮内每步都会带上 | 文本约 150 字;模型补齐后自动停止(下一轮不再注入) |
| 判定依赖文本特征 | 模型用别的措辞写拍板行(如「请你定」)会被判缺项 | 判据只认「要你拍板:」;若实际出现别的惯用措辞,改 REPLY_VERDICT_RE 一处即可 |
| 前缀缓存 | 注入改变了末条用户消息内容 | 位置在末尾,前面的历史前缀照常命中;只有新增那段算未命中 |
| 重启代价 | 重启丢失阶段 / 档位的内存态 | 既有机制已在下落盘、启动恢复;本变更未改动该逻辑 |
9. 后续观察(未做项)¶
- 看
logs/usage.jsonl里reply_nudge_applied的出现频次:若长期为 0,说明模型已自觉或判据过于宽松; 若每轮都命中,说明提醒无效,需考虑改强(如把提醒插到 system,或改成拦截)。 docs/HANDOFF.md已接近偏高水位(约 9.2 KB),本次只定点加一行,未做落点搬移。
来源:
/home/rain/Documents/services/token_proxy/reply-nudge-20261012/SOP.md| 最后更新:2026-10-12 13:46(由build/sync.py同步,请勿在站点侧修改)