跳转至

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. 目标

改后应达到的可验证状态:

  1. 新一轮首请求到达时,若上一轮 assistant 答复缺「四列表」或「要你拍板:」行, 提醒被追加到本条用户消息末尾,且不改 system、不改消息条数;
  2. 上一轮答复合规,或已声明「这是讨论 / 无待决项」,则不注入;
  3. 轮内后续步进(末条不是用户输入)不检查;
  4. 开关 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 同步,请勿在站点侧修改)