Repository navigation
multi2api v1.9.11
会话粘性修复(issue #2)
会话粘性自 v1.8.0 起从未生效过。这个版本修好了。
缺陷
ExtractKey 只从请求体里找三个字段:
metadata.conversation_id
conversation_id
metadata.user_id
而 OpenAI 兼容客户端(DSH / Cline / Continue 等绝大多数)从不发送这些字段:
sessionId只在baseUrl含api.openai.com时才写进prompt_cache_key(本地网关不满足)x-session-affinity/session_id头只对 OpenRouter 启用,网关侧也不读
⇒ sessKey 恒为空 ⇒ Resolve 从未被调用 ⇒ 粘性层退化为纯随机轮换。
实测后果
/status的sticky_sessions在 6927 个请求下恒为 0- 同会话相邻两轮的账号转移矩阵 ≈ 20/20/20/20/20(5 个号纯随机)
- 上游 prompt cache 按账号隔离 ⇒ 每次换号 = 整段对话重新预填充
- 长对话单笔换号损失实测 2.8 ~ 6.0 credit(正常请求 0.1~0.3)
- 受控对照:不带会话标识 19.8% 缓存命中 vs 带
conversation_id98.7%
修法:第 4 级兜底「对话前缀指纹」
三条显式键都落空时,用 model + messages 前两条(system 截 512 字符)的 FNV-1a 指纹作会话键。
为什么这是对的不变量:多轮对话的 messages 是前缀增长的 —— 第 1 轮 [system, user1],第 2 轮 [system, user1, asst1, user2],前面永远不变 ⇒ 只取前两条,指纹在整段对话里恒定。这正是"缓存亲和"需要的东西,且不需要客户端配合。
三个刻意的取舍:
| 决定 | 理由 |
|---|---|
| 只取前两条 message | 全量哈希会随每轮增长而变,指纹每轮都不同 = 等于没粘性 |
| system 截前 512 字符 | 有的客户端往 system 塞时间戳/请求号,全取会漂移 |
| 形态不认识时返回空 | 宁可退回轮换,也不要算出一个会漂移的键(后者会让表里堆满一次性条目,既占内存又让 GC 白跑) |
7 条守卫测试
StableAcrossTurns ★ 核心:多轮对话指纹必须不变(1/2/3 轮同键)
DiffersByFirstUser 不同首条 user 必须不同键
DiffersByModel 不同模型(模型进了指纹前缀)
ExplicitKeyWins 显式键优先,不被指纹覆盖
ContentBlocksFlattened 内容块数组折平后与纯字符串同键
UnknownShapeReturnsEmpty 形态不认识返回空
SystemTimestampTailDoesNotDrift system 前 512 相同、尾巴时间戳不同 ⇒ 同键
预期效果(issue 报告的实测数据)
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 缓存 miss 率 | 13.3% | 3.1% |
| 成本 / 百万 prompt token | 3.7 credit | 1.2 credit(−68%) |
sticky_sessions |
恒 0 | 14~16 |
| 同会话连续请求同号比例 | ~20% | 100% |
⚠ issue #2 里另外两个观察,本版未处理
① 粘性表纯内存,重启即丢
无 Redis(redis_mode: noop)时,重启后所有长会话被判 assign、重新分配 ⇒ 整段重算。实测两笔:msgs=1146 花 5.59 credit、msgs=753 花 1.76 credit,仅仅因为重启。
建议:无 Redis 时也提供本地文件兜底(data/sticky-bindings.json,原子写 + 启动恢复)。
② context_length_exceeded 仍会轮换全部账号
上游 400 fatal_client("prompt is too long: 100001 tokens > 100000 maximum")实测仍把账号池轮一遍,最后返回笼统的 503 budget_exhausted:global,客户端看不到上游原因。
建议:确定性错误立即失败并透传上游原文。
升级注意
前端 embed 在二进制里,必须重新 build:
git pull
go build -o bin\wb2api-server.exe ./cmd/server重启后验证粘性生效:/status 的 sticky_sessions 应不再是 0;同一会话连发多轮,request-log 里的 uid 应稳定在同一个账号。