mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-12 20:27:23 +00:00
feat(channels): 补两个参考明确"不能省"的基础设施(已失效模型 + 目录闸门)
穷尽对照 143 个参考模块后,挑出两个**有实测理由**的补上。 ## ① 已失效模型记录(照参考 dead-model-store.ts,708 行) 修的是真实报障:用户选中某平台的兜底模型,每轮 404 —— 该模型**已被上游下架**, 但列表里仍显示、还标着「免费」。根因:兜底表是**编译期快照**,上游下架不会 自己变。 ★ 关键纪律(参考文件头有实测教训):**不能用「远端目录里没有 ⇒ 已下架」反推**。 那个判据只在远端目录完整权威的平台上成立;buddy 那类平台的远端目录**已知残缺** (CLI token 只拿得到 13 个内部别名、IDE 有 20 个),按目录比对会把「你有权用、 只是 token 看不到」的模型删掉;qoder 更是没有可比对的目录。 ⇒ 唯一跨平台安全的判据是**阳性证据**:真的 404 过才记。 判据逐条对齐参考(17 个用例,全是它踩过的误判): - `model_not_found` 只在**独占一句话成分**时算(后面只允许结束/句末标点/收尾 括号引号,**不含逗号**)—— 它在遥测里常作字段名出现: `error_class=model_not_found, retry allowed` 曾被误判下架 - rate limit / quota / insufficient / unauthor / forbidden / invalid key 一律**不是** - `expired` 必须带主语(token/session/key/…),否则会误杀真实下架文案 - 只认 found/exist,**不认** available/supported(那是"换套餐就能恢复") - 判定顺序:NOT_GONE 先于 GONE(与参考 `isModelGoneError` 逐行一致) TTL 30 天(过期自动复活,照参考 DSH_DEAD_MODEL_TTL_DAYS 语义)。 `model_clear_dead` 语义**修正**:原实现清的是"被禁用且不在目录里"(判据用错了、 拿目录反推),现改为清**失效记录**。 ## ② 远端目录加载闸门(照参考 remote-catalog-gate.ts) 参考点名「**不能省**」:宿主对每个 provider 拉目录、再对每个模型解析一次, 于是「目录没拿到」被放大成**一次请求 × 每个模型**,而超时是 30–60s 量级 —— 未登录/离线时表现就是「首屏巨长」。三道语义:命中缓存 / 并发去重 / 失败冷却。 Lua 无协程做不了「共享同一个 Promise」,用**时间戳闸门**等价实现 (in_flight_at 防同轮重复 + retry_at 失败冷却)。 含参考的**墙钟回拨防护**:`retry_at - now > cooldown` 一律按已到期处理 —— 否则系统时间回拨一天会把冷却变成事实上的永久卡死。 ## ③ 顺手修:状态落盘不建目录 `persistState` 直接 `io.open(path..".tmp")`,目录不存在就失败。而失败只打 stderr 一行「状态落盘失败」—— 表现是「状态永不落盘、重启即丢」,很容易被忽略。 现先 `mkdir -p`。这是实测发现的(authorize 测试的临时目录尚未创建时报的)。 ## 判据 `channels_dead_model_test.go`:判据 17 例(含 6 个"看着像但不是下架"的反例)、 记录-剔除-恢复闭环、闸门冷却。 `channels_pool_test.go`:`model_clear_dead` 断言按新语义改写,并加"清失效记录 **不得**动 disabled 黑名单"的隔离断言。 回归:9 个包全绿。
This commit is contained in:
@ -265,17 +265,33 @@ func TestChannelsExtendedRPCBehavior(t *testing.T) {
|
||||
invoke(t, ps, "model_set_disabled_many", `{"platform":"minimax","model_ids":["MiniMax-M3","MiniMax-M2.7-highspeed"],"disabled":false}`)
|
||||
})
|
||||
|
||||
t.Run("model_clear_dead_只清黑名单里已下线的", func(t *testing.T) {
|
||||
invoke(t, ps, "model_set_disabled", `{"platform":"minimax","model_id":"MiniMax-M3","disabled":true}`)
|
||||
// model_clear_dead 的语义已按参考修正:它清的是**有阳性 404 证据**的
|
||||
// "已失效模型"记录,**不是**"被禁用且不在目录里"的条目。
|
||||
//
|
||||
// 旧断言测的是后者(拿远端目录反推下架)—— 那个判据在远端目录残缺的平台上
|
||||
// 会删掉可用模型(参考文件头有实测教训),已弃用。
|
||||
// 新语义的完整判据在 channels_dead_model_test.go。
|
||||
t.Run("model_clear_dead_清失效记录而非拿目录反推", func(t *testing.T) {
|
||||
// 没有失效记录时:removed=0,且文案要说明"无记录"(而不是假装清了什么)
|
||||
j := invoke(t, ps, "model_clear_dead", `{"platform":"minimax"}`)
|
||||
if n := len(asArr(j["removed"])); n != 0 {
|
||||
t.Fatalf("在册模型不算死模型, 实得 %d", n)
|
||||
if j["ok"] != true {
|
||||
t.Fatalf("model_clear_dead 应成功: %v", j)
|
||||
}
|
||||
// 手动放一个不在 models 表里的黑名单项
|
||||
invoke(t, ps, "model_set_disabled_many", `{"platform":"minimax","model_ids":["ghost-model"],"disabled":true}`)
|
||||
j = invoke(t, ps, "model_clear_dead", `{"platform":"minimax"}`)
|
||||
if n := len(asArr(j["removed"])); n != 1 {
|
||||
t.Fatalf("死模型应被清: %v", j)
|
||||
if n, _ := j["removed"].(float64); n != 0 {
|
||||
t.Fatalf("无失效记录时应为 0, 实得 %v", j["removed"])
|
||||
}
|
||||
// 黑名单(disabled)与失效记录是两回事:清失效**不得**动 disabled
|
||||
invoke(t, ps, "model_set_disabled", `{"platform":"minimax","model_id":"MiniMax-M3","disabled":true}`)
|
||||
invoke(t, ps, "model_clear_dead", `{"platform":"minimax"}`)
|
||||
ml := invoke(t, ps, "model_list", `{"platform":"minimax"}`)
|
||||
off := 0
|
||||
for _, m := range asArr(ml["models"]) {
|
||||
if mm, ok := m.(map[string]interface{}); ok && mm["disabled"] == true {
|
||||
off++
|
||||
}
|
||||
}
|
||||
if off != 1 {
|
||||
t.Fatalf("清失效记录不该动 disabled 黑名单, 仍应为 1, 实得 %d", off)
|
||||
}
|
||||
invoke(t, ps, "model_set_disabled", `{"platform":"minimax","model_id":"MiniMax-M3","disabled":false}`)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user