v2.1.5 · 不带会话标识的客户端恢复账号粘性
修复:不带会话标识的客户端也能保持账号粘性
问题来自线上实测。同一个调用密钥下,Codex 那路的请求每轮都命中同一个账号、上游提示缓存稳定(hit 稳定在几十万),而 narrafork 那路(/v1/chat/completions)每一轮都换号,hit 恒为 0。
抓包定位到根因:narrafork 的请求体顶层只有 model、messages、stream、stream_options、max_tokens、tools、tool_choice、reasoning_effort,既没有 conversation_id,也没有 metadata、client_metadata、prompt_cache_key。网关的会话键提取依次读这些字段,全都读不到就返回空串,会话粘性于是完全不生效——每一轮都走普通轮换重新选号,同一对话的上下文缓存整段作废。
修复方式是在没有显式会话标识时,从请求正文派生一个对话级回退键:取第一条 user 消息的文本做哈希。对话开头在整个对话内不变,所以同一对话的后续轮次(末尾不断追加消息)保持同一个账号,不同对话各自绑定。用抓到的真实流量验证过这个前提:14 条请求正好聚成 2 个对话,一个首条 user 125 字符、8 条请求,另一个 32 字符、6 条请求,而消息数从 255 一路涨到 670,首条 user 文本始终不变。
几处边界是有意这样定的:
- 显式会话标识仍然优先。Codex 之类会发
prompt_cache_key的客户端行为完全不变。 - 回退键只用于账号绑定,不参与上游关联头的聚合粒度。那部分在没有显式标识时仍按「对话轮」聚合,把回退键混进去会把粒度从轮级改成对话级,是另一件事。
- 关掉粘性(
session_sticky.enabled=false)时依然不建立任何绑定。 - 首条 user 消息没有文本(纯图片等)时不伪造键,保持原有的无粘性行为。
- 客户端裁剪历史把首条 user 删掉时键会变化、绑定重新分配,只影响缓存亲和;两条对话恰好以同样文本开头时会共用绑定,粒度仍细于原有的
metadata.user_id回退。
部署
网关镜像随本 tag 发布,标签 v2.1.5 与 latest 都已推送。线上用热更新完成切换(新进程先接管监听、旧进程把在途请求跑完再退出),当前对话未中断。
cd /opt/workbuddy2api
sudo docker compose pull wb2api
sudo docker compose up -d wb2api已知边界
- 回退键依赖客户端在后续轮次里保留首条 user 消息。这是主流 agent 客户端的常态,但不是协议保证。
- 本次只改会话粘性的键来源,选号、冷却、用量记账与协议转换均未改动。