跳转至

SOP:强制合并优先 + 采集 tools 名字序列(2026-10-06)

主题目录:merge-toolsseq-20261006/|类型:C 类变更(改全局规则 + 改代理采集) 变更对象:~/.trae-cn/user_rules/rule-agent-efficiency.md、proxy.py 交付物:本 SOP、backup/README.md、backup/ 下备份文件

1. 背景

两个结论都来自 2026-10-06 的一轮讨论与取数:

  1. 合并碎步是效率与成本的综合优化项,但一直只是"建议"。 实测:碎步组(连续 ≥3 步、单步 out ≤1500)共 341 组 / 1814 步,其中纯只读工具组仅 13 组, 含 RunCommand 的组 252 组(74%),组长度 P50=4 / P90=9 / max=34。 即:真正该合并的是同一轮内的多次独立取数,而这类机会因为"合并取数"在规则里没有强制力, 连续 4 轮未下降。故把"合并优先"从建议升为强制。
  2. tools_hash 变化无法定性。实测:tools_hash 变化共 90 次,其中 0 次伴随"新工具被调用",40% 的变化前后工具个数相同(说明至少一部分只是顺序/定义变化)。 现有指纹只有哈希,区分不了"工具集增删"与"顺序抖动"——这才是此前把它误归因为 "新工具出现"的根因。故补采工具名序列。

2. 目标(可验证)

  • logs/req_shape.jsonl 每步 shape 新增 tool_defs:按请求原始顺序的工具名列表; 取不到名字时以 "?" 占位;不记描述与参数(保持"不落对话内容"边界)。
  • 据此可判定:tool_defs 排序后相同(sorted 一致)而 tools_hash 不同 → 仅顺序抖动; sorted 也不同 → 确有增删。
  • 全局规则「执行方式(必须)」表新增一行「同一轮内多个独立信息收集」,强制合并优先。
  • 现有链路不受影响:analyze_prefix_fail.py(读 head/tools_hash)行为不变、 shape-toolname-20261002/verify_shape_tools.py 全部用例仍通过。

3. 前置条件

  • 工程根 /home/rain/Documents/services/token_proxy,含 venv/。
  • 生产代理以 systemd 用户服务 token-proxy 运行(端口 8788);run.sh 与 systemd 互斥。
  • 离线验证用「假上游 + 临时端口」,不触碰 8788、不写生产 logs/。

4. 影响范围

项 说明
主机 仅 192.168.31.250(rain),单机
需停服 是:systemctl --user restart token-proxy 会打断在途流式回答(数秒)
窗口期 顺序执行、无外部依赖,数分钟内完成
数据 logs/req_shape.jsonl 追写新字段,旧记录保留原样(向后兼容);体积预计 8.2 MB → 约 13~19 MB
上游 请求体不被修改(build_shape_fingerprint 纯旁路),不影响 prompt 缓存
规则 改 user_rules 会使每轮前缀分叉(≈0.35 元/次),须开新会话

5. 分步操作

步骤 1:备份(高危前置)

两处被改文件各留一份可回滚副本,放本主题 backup/ 下、按原路径层次存放。

cd /home/rain/Documents/services/token_proxy
ts=$(date +%Y%m%d-%H%M%S)
mkdir -p merge-toolsseq-20261006/backup/trae-cn/user_rules
cp -p /home/rain/.trae-cn/user_rules/rule-agent-efficiency.md \
  "merge-toolsseq-20261006/backup/trae-cn/user_rules/rule-agent-efficiency.md.bak.$ts"
cp -p proxy.py "merge-toolsseq-20261006/backup/proxy.py.bak.$ts"
chmod 600 merge-toolsseq-20261006/backup/trae-cn/user_rules/*.bak.* merge-toolsseq-20261006/backup/*.bak.*

预期:两份备份权限 600,md5sum 与原件一致。 失败判断:md5 不一致 → 重新复制。台账见 backup/README.md。

步骤 2:采集端加字段(proxy.py)

新增 _tool_defs(tools):遍历 tools 数组,按原顺序取名——优先 function.name, 回退 name / type,都取不到记 "?"。在 build_shape_fingerprint 中: shape 初值加 "tool_defs": [];tools 分支内赋值 shape["tool_defs"] = _tool_defs(tools)。

坑:tool_defs 必须在 json.dumps(tools, ...) 之前或之后均可,但不得改写 tools 本身—— tools_hash 的语义(含顺序)必须保持不变,否则 analyze_prefix_fail.py 的既有结论会失真。 另外 _tool_names(记"调用了哪个工具")与 _tool_defs(记"可用工具有哪些")是两个不同的东西,不要混用。

cd /home/rain/Documents/services/token_proxy
./venv/bin/python -m py_compile proxy.py     # 预期无输出,退出码 0

实现体:proxy.py(_tool_defs / build_shape_fingerprint),实际执行以实现体为准。

步骤 3:全局规则加「强制合并优先」

在 ~/.trae-cn/user_rules/rule-agent-efficiency.md 的「执行方式(必须)」表内, 「需读取多个文件」行之后插入一行(内容以实现体为准):

grep -n "强制合并优先" /home/rain/.trae-cn/user_rules/rule-agent-efficiency.md   # 预期命中 1 行

步骤 4:离线验证(红线:不可只看代码推断)

三件事全做,缺一不可:

cd /home/rain/Documents/services/token_proxy
# ① 纯函数断言:顺序保持 / 集合可判 / 不落描述与参数
./venv/bin/python -c "
import json, proxy
a=[{'type':'function','function':{'name':'Read'}},{'type':'function','function':{'name':'Grep'}}]
b=list(reversed(a))
assert proxy._tool_defs(a)==['Read','Grep'] and proxy._tool_defs(b)==['Grep','Read']
assert proxy._tool_defs([])==[]
assert proxy._tool_defs([{'name':'X'},{'type':'web_search'},{'bad':1},'str'])==['X','web_search','?','?']
s1=proxy.build_shape_fingerprint({'messages':[{'role':'user','content':'hi'}],'tools':a})
s2=proxy.build_shape_fingerprint({'messages':[{'role':'user','content':'hi'}],'tools':b})
assert s1['tools_hash']!=s2['tools_hash'] and sorted(s1['tool_defs'])==sorted(s2['tool_defs'])
s3=proxy.build_shape_fingerprint({'messages':[],'tools':[{'type':'function','function':{'name':'Read','description':'SECRET-ARG'}}]})
assert 'SECRET' not in json.dumps(s3)
print('tool_defs 断言通过')
"
# ② 端到端:复用既有脚本(假上游 + 临时端口,4 条请求)
./venv/bin/python shape-toolname-20261002/verify_shape_tools.py

预期:① 打印通过;② A/B 两段全部 [通过]、退出码 0,末尾给出临时目录供取证。 失败判断:任一断言失败 → 第 7 章回滚 proxy.py 后重做步骤 2。

步骤 5:重启代理使其生效

systemctl --user restart token-proxy
systemctl --user is-active token-proxy        # 预期 active

高危:会打断在途流式回答(含当前会话),须选无人使用的时机执行。

步骤 6:验证生效

cd /home/rain/Documents/services/token_proxy
tail -n 1 logs/req_shape.jsonl | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['shape'].get('tool_defs'))"

预期:打印一个工具名列表(非空);旧记录无该字段属正常(向后兼容)。

6. 验证

项 命令 预期
语法与编译 ./venv/bin/python -m py_compile proxy.py 退出码 0
纯函数语义 步骤 4 ① 打印通过
端到端链路 步骤 4 ② 全部 [通过],usage.jsonl 记账正常
服务状态 systemctl --user is-active token-proxy active
字段落地 步骤 6 最新记录 tool_defs 非空
规则落地 grep -n "强制合并优先" ...rule-agent-efficiency.md 命中 1 行(新会话后生效)
规则效果(跨轮) python3 cost_audit/step_profile.py --top 300 看"疑似可并组/步"是否回落(当前基线 1343 组 / 8099 步)

7. 回滚

备份位置:merge-toolsseq-20261006/backup/,台账见 backup/README.md。

cd /home/rain/Documents/services/token_proxy
# 回滚 proxy.py
cp -p merge-toolsseq-20261006/backup/proxy.py.bak.<ts> proxy.py
./venv/bin/python -m py_compile proxy.py && systemctl --user restart token-proxy
# 回滚规则
cp -p merge-toolsseq-20261006/backup/trae-cn/user_rules/rule-agent-efficiency.md.bak.<ts> \
  /home/rain/.trae-cn/user_rules/rule-agent-efficiency.md

回滚后验证:systemctl --user is-active token-proxy 为 active; shape-toolname-20261002/verify_shape_tools.py 全部通过;规则文件 grep 强制合并优先 无命中。 tool_defs 字段消失不影响历史(旧记录本就无此字段)。

回滚触发条件(任一达标即回滚):重启后 is-active 非 active、端到端脚本有 [失败]、 或新记录 tool_defs 持续为空而 tools 非 0。

8. 风险说明

风险 缓解
重启打断在途流式回答 选无人使用时机;本变更全程可回滚
req_shape.jsonl 体积增长(预估 +5~11 MB) 只记名字不记描述;该文件不可再生、绝不可删,仅需留意磁盘余量
误把 tool_defs 当"调用了什么" 文档写明:tool_defs = 可用工具清单,tool_names = 本步实际调用
改规则后旧会话每轮多付约 0.35 元 收尾后开新会话(Hook 已提醒)
强制合并可能掩盖失败 规定用 ; 而非 &&,部分失败仍返回其余结果;依赖链必须拆步

来源:/home/rain/Documents/services/token_proxy/merge-toolsseq-20261006/SOP.md | 最后更新:2026-10-06 22:32(由 build/sync.py 同步,请勿在站点侧修改)