mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
## 发现路径:查"功能都正常了吗",逐源核对时发现的 生产 `config.yaml` 里有 21 个源,其中 `channels-buddy-585b335b64cf` 的账号 `585b335b64cf` **早已删除**(v2.0.23/24 的无凭据账号清理), 但它的源**仍在对外服务**: - 独占提供 `hy4-preview-x` ⇒ 实测报 `404 Route Not Found`; - 对 **6 个共享模型**则排在新源**前面**先被尝试(config 文件序即注册序) ⇒ 每次都撞 404 再回退,**每次白付一次上游往返**(实测 hy4-preview 稳定 3.1~3.5s); - 它的 base_url 还是 v2.0.30 之前的旧口径 (`https://copilot.tencent.com/v2/chat/completions`,侵占了 endpoint 路径)。 **根因**:这正是 v2.0.28(`remove_source` 漏声明 ⇒ 撤源全被拒)日记里 那句"账号删了、它的源还在对外服务"的**存量残骸**。 v2.0.28 修了声明,**只防新增、不清理存量**。 **发现的一个助力**:v2.0.38 刚修好的错误文案让它暴露得更清楚 —— 客户端拿到的是 `404 Route Not Found`(APISIX 原文)而不是 `unknown error`。 ## 修法 1. **立即清理**:走内核正式 API `DELETE /api/sources/<name>` 删掉该孤儿源 (不手工改配置)。清理前备份配置,三重确认它确无对应账号。 2. **结构性修复**:新增 `reap_orphan_sources` 正式入口 (`POST channels/reap_orphan_sources`,admin),识别 `channels-<已知平台>-<账号id>` 且账号**不在**池中的源。 ### 安全性(破坏性操作两条纪律,照 `reap_loopbacks` 先例) - **只删该删的**:按**已知平台 id 前缀**逐个匹配。 ⚠️ **不能用** `name:match("^channels%-(.+)-(.+)$")` —— Lua 的 `-` 是**贪婪**的, 会把 `channels-buddy-buddy-main` 切成 plat="buddy-buddy"/acct="main" (账号 id 含 `-` 就错,而 `buddy-main` 就含)⇒ **孤儿漏杀**。 运维手工配的源、旧版单账号源 `channels-<平台>` 一律不碰。 - **`dry_run` 默认开启**:不带 `dry_run=false` 只报告不删。 ## 判据(`channels_orphan_source_test.go`,变异 2/2) - 造出**真实形态**:`account_add` 建账号 → hidden 探针摘池(保留源), 模拟"源留下"这个状态(不走 `account_delete`:那条路径会把源一并撤掉, 而我们要的恰恰是它留下的形态)。 - 断言三件事:dry_run 只报告不删、真删后孤儿消失、**手工源全程存活**。 - **判别性用例**:第二个孤儿的账号 id 含 `-`(`dead-acct-1`)。 这是唯一能区分"按已知平台前缀匹配"与"贪婪 match"的用例 —— 实测不含 `-` 的 id 两种实现结果相同,**咬不住**(第一版就栽在这)。 - 变异:① `dry_run` 默认改 false ⇒ 报红;② 改回贪婪 match ⇒ 报红。 ## ★ 过程中踩到的三个坑(都已记进代码注释) 1. **`Plugins.Call` 的参数是平铺的,不是 `req.body`** —— `bodyOf(req)` 返回空表, `dry_run=false` 被静默丢成默认 true ⇒ "真删"永远没发生。 必须同时兜住两种调用形态(HTTP 路由走 body,插件 Call 平铺)。 2. **内核 `add_source` 取 `args["source"]`(嵌套一层)**,平铺字段被拒 ("add_source requires a source table")。 3. **`config.Model` 要求 models 是对象数组 `{id=...}`**,传字符串数组被拒。 4. **判据只查 Go 侧 `err` 会静默通过**:内核把失败放在**返回体**里, Go `err` 仍是 nil ⇒ "源根本没注册"看起来像"没有孤儿"。