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 的一轮讨论与取数:
- 合并碎步是效率与成本的综合优化项,但一直只是"建议"。
实测:碎步组(连续 ≥3 步、单步 out ≤1500)共 341 组 / 1814 步,其中纯只读工具组仅 13 组,
含
RunCommand的组 252 组(74%),组长度 P50=4 / P90=9 / max=34。 即:真正该合并的是同一轮内的多次独立取数,而这类机会因为"合并取数"在规则里没有强制力, 连续 4 轮未下降。故把"合并优先"从建议升为强制。 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同步,请勿在站点侧修改)