mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-21 17:38:00 +00:00
feat: opencode zen adapter + first-run config generation, fix stats/stream bugs
- adapters/opencode.lua: opencode.ai zen free pool adapter — sends the opencode client User-Agent (zen fingerprints clients by UA; non-official clients hit FreeUsageLimitError); pairs with api_key: public - config: no config file ships in the repo; first run generates a default config at the -config path with a random admin key, loopback listen and a keyless zen source (config.EnsureDefault); remove config.example.yaml - lua: seed bundled adapters from the embedded FS instead of a hardcoded name list - ui: widen model kind select (chat was clipped to 'cha') - phase 5 bugfixes: stats ms/s bucket mixing, cleanScopes nil, ctx.Err guards, direct-path ModelAvailable, empty stream body failure, bestImageModel rewrite, transform failure recording, Core.mu, timer, effective model for tool-calls
This commit is contained in:
36
plan.md
36
plan.md
@ -246,4 +246,38 @@ p==nil → 400/404;!TryAcquire → 429 busy(快速失败)
|
||||
- [x] **P4-7(2026-08-11)AUTO 链 tier 序 + 审计导出修复 + UI 密钥视图(今日会话)**:① `BuildChain` 排序由降序改升序(tier1=最高优先级先试,修 P10-5 中的序颠倒),单测同步;② 统计/CSV 全量(limit 20000、`Stats.AuditRecords` 读磁盘含 `*.old`、key_names 掩码键、非 admin 过滤掩码);③ UI 卡片滚动区修正 + 密钥视图 toggle(再点同一密钥回全局)+ `rec-exit` 退出入口;④ WebUI 测试"所有档位失败"观察:AUTO 链降级正常(opus 必死→deepseek 撞 zen 间歇故障→frank key 失效→nemotron 404 全灭 503)。
|
||||
- [x] **P4-8(2026-08-11)调度延滞排查结论**:homeagent AUTO 请求"看起来停在 opus"实为调度正常降级——完整 ChainErr 列出 tier1→2→3→4 全部尝试,审计只记 `ce.Tiers[0]`(首档)故 UI 显 opus;zen 源 `opencode.ai/zen/v1` 直连即 403 `[server_error] Upstream response was not valid JSON`(间歇)/ nemotron 404,确认为上游源自身问题,非网关透传或 homeagent 适配器。
|
||||
- [x] **P4-9(2026-08-11)WebUI 统计可视化**(多轮迭代定稿):① 模型用量%改为占总请求的比例(`r.reqs/total`,替代原"相对最大模型"的误导性 100%/97%);② 顶部五宫格统一为同构卡片——**标题 + 大数值 + 副标题 + 86px 图表区**:活跃请求=折线 sparkline(实时)、总请求=按天柱状图、tokens=各模型占比堆叠条(大数值示总用量)、平均延迟=折线 sparkline(实时)、状态码=按码值堆叠条 + chip 副标题;全部 canvas 2D 手绘(无第三方库),去掉灰色空壳占位与独立全宽状态卡/冗余副标题。
|
||||
- [x] **P4-10(2026-08-11)claude "Third-party apps now draw from your extra usage" 400 结论**:该报错为 **Anthropic 官方配额提示**(账号第三方 app 用量额度耗尽,提示到 Anthropic usage 设置充值),**非流量特征被识破**;伪装流量/换 adaptive 适配器解决不了(请求已被 Anthropic 接受并按账号用量计费)。且 qijiar+openai 适配器下 claude 模型本就能 200 成功(审计 16 次,含 claude-opus-4-8/claude-sonnet-5),AUTO 链/直连的 claude 失败多为上游 `model_not_found`(无 distributor 渠道)、`Concurrency limit exceeded`、`extra usage` 配额——均为上游账号/渠道级问题,非网关。若确需走 Anthropic 原生 `/v1/messages`,可为其源切换内置 `anthropic` 适配器(仅改变量 wire 格式,不影响配额)。
|
||||
- [x] **P4-10(2026-08-11)claude "Third-party apps now draw from your extra usage" 400 结论**:该报错为 **Anthropic 官方配额提示**(账号第三方 app 用量额度耗尽,提示到 Anthropic usage 设置充值),**非流量特征被识破**;伪装流量/换 adaptive 适配器解决不了(请求已被 Anthropic 接受并按账号用量计费)。且 qijiar+openai 适配器下 claude 模型本就能 200 成功(审计 16 次,含 claude-opus-4-8/claude-sonnet-5),AUTO 链/直连的 claude 失败多为上游 `model_not_found`(无 distributor 渠道)、`Concurrency limit exceeded`、`extra usage` 配额——均为上游账号/渠道级问题,非网关。若确需走 Anthropic 原生 `/v1/messages`,可为其源切换内置 `anthropic` 适配器(仅改变量 wire 格式,不影响配额)。
|
||||
|
||||
### Phase 5 — 2026-08-13 全量复查核实 & 修复(本轮会话)
|
||||
|
||||
> 三路并行审查(provider/registry/scheduler、gateway、config/store/core)+ 逐项对照源码核实,全部条目经本人亲自读取代码确认。
|
||||
|
||||
#### 5.1 已核实的未修正 bug(均属实)
|
||||
|
||||
| 编号 | 级别 | 问题 | 证据 |
|
||||
|---|---|---|---|
|
||||
| H1 | 高 | **stats 窗口单位错乱**:`Req.Time` 为毫秒(chat.go:572 `UnixMilli`),`stats.go:220` `h := r.Time/hourSec`(hourSec=3600 秒)→ 桶宽实为 3.6s;`WindowTokens`(stats.go:352)`h*hourSec >= cut`(cut 为秒)毫秒恒 ≥ 秒 → **任何 sec>0 窗口返回全部记录**;剪枝 960 桶 ≈ 仅保留 57.6min(stats.go:227-233)。槽配额(chat.go:326)/key 配额(scopeTokens)/UI 周月曲线全部失真;LoadAudit 回放同路径;`stats_test.go:125` 断言 372=全计,固化错误行为 | 逐行确认 |
|
||||
| H2 | 高 | **空 scope 语义分裂**:`cleanScopes`(core.go:276-288)返回非 nil 空切片 → CreateKey/UpdateKey 后 `allowedModels` 非 nil → 空 scope 无匹配 → **403 锁死**;config 序列化 `models,omitempty`(config.go:275)→ 重启后 nil → **全部放行**。另 `UpdateKey`(core.go:245)对未传 models 的 PUT **静默清空已有 scope** | 逐行确认 |
|
||||
| H3 | 高 | **客户端取消/断连被记失败**:Chat(provider.go:611-613)/Stream 首块前(:666-669)/Image(:751-753)对 `err != nil` 无条件 `RecordFailure`,无 `ctx.Err()==nil` 守卫 → SDK 超时/关页把健康源反复误冷却、退避翻倍至 30min。流式中段(:724)已有守卫 → "断开≠成败"只实现一半 | 逐行确认 |
|
||||
| H4 | 高 | **直连路径无视冷却(P3 残留)**:AUTO 走 `chainDrive` 查 `ModelAvailable`(scheduler.go:242),直连 Chat/ChatStream/Image(scheduler.go:334-398)不查 → 401 打满 30min 后直连照打,`RecordFailure(auth)`(provider.go:79-101)每次重写冷却截止 → **无限续期**,"无永久黑名单"被持续直连流量打破 | 逐行确认 |
|
||||
| M1 | 中 | **AUTO 槽模型名大小写不一致**:`buildAutoChain` 保留规则原文(core.go:415,Image 过滤用精确匹配 :406;`ProviderForSlot` 用 EqualFold)→ `runTier:194` 原样下发 → `ModelFor` 精确匹配失败回退 `bestChatModel`(provider.go:214-222)**静默发错模型**;`ModelAvailable` 未知模型恒 true(:303-310)→ 冷却失效;配额键(chat.go:326 vs 实际记账模型)错位 → 配额永不生效 | 逐行确认 |
|
||||
| M2 | 中 | **200-空流记为成功**:ChatStream 无 chunk 计数(provider.go:678-727),`:724` 对"0 chunk 干净 EOF"照记 `RecordSuccess`→ 空成功流不降级 | 逐行确认 |
|
||||
| M3 | 中 | **AUTO 生图错模型(仅混合源触发)**:`Image`(provider.go:738)`ModelFor`→`bestChatModel`(跳过 image 模型)→ 混合源(chat+image)把 chat 模型发往 /images/generations;`effectiveImageModel`(chat.go:447-459)只用于配额/scope 检查不修正请求体 | 逐行确认 |
|
||||
| M4 | 中 | **transform/unmarshal 失败不上账**:Chat(provider.go:619-626)/Image(:761-771)适配器产出坏数据直接 return → **适配器故障源永不退避**,每请求全额重打 | 逐行确认 |
|
||||
| M5 | 中 | **Core cfg 并发写无锁(潜伏)**:`Core` 仅 `autoChain` 原子(core.go:24-31);`c.cfg.Keys/Sources/Auto` 裸写(CreateKey:228/UpdateKey:237-251/SaveAutoRules:295/AddSource:516-525)与请求路径 `FindKey:201-208`/`mergedSources` 并发 → race;core 包零单测,`go test -race` 全绿属"无证据"非"安全" | 确认+race 全绿 |
|
||||
| L1 | 低 | busyWait 每轮 `time.After` 不 Stop(scheduler.go:273) | 确认 |
|
||||
| L6 | 低 | pinned(`src:model`)tool-call 请求:`resolveCands` 工具分支(chat.go:95-107)用原始带前缀串 `ModelFor`(不解前缀)→ 回退 bestChatModel | 确认 |
|
||||
|
||||
#### 5.2 修复计划(按序推进,每项完成即勾选)
|
||||
|
||||
- [x] **P5-1(H1)stats 窗口单位修正**:`aggregateLocked`(stats.go:220)毫秒转秒后再分桶(`h := r.Time/1000/hourSec`);窗口语义(0=全量;>0 滑动秒窗,整桶计引入截断误差≤1h,注释说明);剪枝阈值 960 小时桶 ≈ 40 天保留(注释注明);重写 `TestLoadAuditFullReplay`(相对时间戳 + 全量/3h/24h=372、1h ∈ [2,372)、m2=10 边界断言,旧断言 372=全计恰固化错误行为);新增 `TestStatsByStatus` 不受影响。
|
||||
- [x] **P5-2(H2)空 scope 归一化**:`cleanScopes` 空输入返回 nil(进程内=重启后语义一致);`UpdateKey` 仅当 `models != nil` 才覆盖 scope(未传保留、显式 `[]` 清空);单测 `TestCleanScopesNilForEmpty`/`TestCreateKeyWithoutScopeUnrestricted`/`TestUpdateKeyPreservesScopeWhenOmitted`。
|
||||
- [x] **P5-3(H3)断开不记失败**:Chat/ChatStream 首块前/Image 三处 `err != nil` 分支改为 `err != nil && ctx.Err() == nil` 才 `RecordFailure`;新增 `TestChatClientCancelNotRecorded`(注意:测试 handler 不能依赖服务端 ctx 取消传播——HTTP/2 下客户端取消不会触发服务端 ctx.Done,用显式 release 通道)。
|
||||
- [x] **P5-4(H4)直连冷却检查**:`Scheduler.Chat/ChatStream/Image` 调用前查 `ModelAvailable`,冷却中记 "cooling down" 错误顺延下一候选;全部冷却即失败返回——与 AUTO 一致"冷却=唯一硬跳过";auth 401 冷却不再被直连流量续期;新增 `TestDirectSkipsCooledCandidate`(冷却候选零命中 + 全冷报错)。已知取舍:AUTO 生图直连的可用性检查用 `ModelFor`(chat 模型)近似,精确 image 模型冷却检查待后续。
|
||||
- [x] **P5-5(M1)槽模型名规范化**:`Provider.ModelIDFold`(大小写无关匹配)+ `buildAutoChain` 用其回填真实模型 ID 到 `r.Model`,image-kind 过滤也基于规范化后的精确 ID;冷却/配额/上游模型三者键一致;单测 `TestSaveAutoRulesNormalizesModelCase`(GPT-4O → gpt-4o)。
|
||||
- [x] **P5-6(M2)空流不记成功**:ChatStream 统计有效 chunk 与 `[DONE]`;干净 EOF 且 0 chunk 且无 `[DONE]` → `RecordFailure`,有 `[DONE]` 或 ≥1 chunk → `RecordSuccess`;新增 `TestChatStreamEmptyBodyNotSuccess`。
|
||||
- [x] **P5-7(M3)AUTO 生图选 image 模型**:`bestImageModel()`(Kind==image 最高 priority,兜底 Models[0]);`Image` 对 AUTO/未知模型**改写请求体**(克隆 req 回填 image 模型 ID,非仅记账);新增 `TestImageAutoUsesImageModel`(混合源断言上游收到 img 模型而非 chat 模型——初版只改记账导致 body 仍为 AUTO,测试当场抓出)。
|
||||
- [x] **P5-8(M4)transform 失败记账**:Chat/Image 的 transform_response/unmarshal 失败分支补 `RecordFailure(model,0)`(组合 ctx.Err 守卫)。
|
||||
- [x] **P5-9(M5)Core 加锁**:`Core.mu sync.Mutex` 包裹全部 cfg 读写公开方法(ListKeys/FindKey/CreateKey/UpdateKey/DeleteKey/AutoRules/SaveAutoRules/Sources/AddSource/RemoveSource/Reload),私有内部函数不持锁防重入;`Config()` 仅测试读 Path 无需锁;新增 `core_test.go`(含 `TestConcurrentKeyMutation` 16 并发增删查)——`-race` 全绿。
|
||||
- [x] **P5-10(L1/L6)**:busyWait 改 `time.NewTimer`+`Reset`/defer Stop(修 20+ 计时器泄漏);`resolveCands` 工具分支改用剥前缀后的 `effective` 调 `ModelFor`(pinned tool-call 不再回退 bestChatModel)。
|
||||
- [x] **P5-11**:`go build ./...`、`go vet ./...`、`go test ./...`(含 e2e)全绿;`go test -race`(core/scheduler/gateway)全绿。待推送 + 生产机 `-tags luajit` 回归部署(P3-7 流程)。
|
||||
Reference in New Issue
Block a user