Files
ModelRouter/plan.md

25 KiB
Raw Blame History

ModelRouter 调度层重构方案(基于线上实测)

调研对象192.168.2.60 生产实例(/usr/local/bin/llmsproxy -config /etc/llmsproxy/config.yamlsystemd 托管) 调研时间2026-08-10审计文件 /etc/llmsproxy/runtime.json.audit.jsonl3978 行 / 1042 条请求记录)


一、线上实测问题(证据链)

P1核心用户实测编辑 AUTO 优先级不重载退避状态

  • 证据:/api/status 实时返回 qijiar available=false, live_available=truezen available=false, live_available=true
  • 根因:Core.SaveAutoRulescore.go:232只写 runtime.json不重建 Provider退避状态存在 Provider.healthprovider.go:24-51只有 rebuildRegistrycore.go:290会重建。用户在 WebUI 改完优先级后,被退避锁死的源依旧被跳过。
  • 线上复现AUTO 链 4 槽 [qijiar/gpt-5.5→zen/flash-free→frank/gpt-5.6-sol→zen/nemotron],实时请求 POST /v1/chat/completions {"model":"AUTO"}{"error":{"message":"no provider available","type":"upstream_error"}}0ms 返回;其中 2 个源探活确认在线。

P2 源级黑名单(粒度错)

  • 证据:zen429 FreeUsage 退避(审计 ×4→ 其 9 个配置模型全部不可调度;gpt-5.5 的 qijiar 一次失败 → 源整体跳过。
  • 根因:健康标志是 (源) 级而非 (源, 模型)provider.go:24一个模型失败黑掉全源。

P3 永久黑名单(无法自愈)

  • 证据:frank 收到一次 401 API_KEY_DISABLEDmarkPermanent()provider.go:48-51, 281-283此后 AUTO 永远跳过它,且有 30 分钟周期的显式探测持续打它产生 502502 又推高退避)。
  • 根因:permanent 无过期、无重置入口;显式请求路径不检查 Available(),照打不误。

P4 并发打满 = 排队 60s而非立即切换

  • 证据/源码:Provider.Acquireprovider.go:306-324满则等待 QueueTimeout(默认 60sconfig.go:154AUTO 逐槽串行,打满的档位拖死整条链。
  • TryAcquire 语义,路由层无法区分"忙"与"故障"。

P5 探活与调度状态脱节

  • 探活GET /models不污染调度状态是正确设计探活结果没有任何一条通道能恢复调度健康——线上 qijiar/zen "探活通、调度死" 的矛盾即因此产生。

P6 流式成功不清退避

  • 证据/源码:ChatStreamprovider.go:406-490全程无 reportOK();流式源一旦退避,后续成功流也不复位。

P7 两套 AUTO 逻辑并存,配置互相矛盾

  • 线上config.yaml 里 qijiar priority 95/90/85、deepseek 40/30runtime AUTO 链却是 tier1 gpt-5.5 → tier2 zen/flash-free → tier3 frank/gpt-5.6-sol → tier4 zen/nemotron
  • Registry.Resolve("AUTO")registry.go:83-121按"源的最大模型 priority + 健康优先")与 autoPlanschat.go:630按 runtime 槽位行为不一致deepseek 全部模型不在 AUTO 链中,但运维以为"priority 高"会被 AUTO 选中。

P8 错误码策略粗糙

  • deepseek 402 欠费 ×17ReportStatus 不触发任何退避也不在 UI 提示402 不属于 401/403/429/5xx 分支),欠费源被无限重试。
  • 400 Invalid schema ×19客户端工具定义问题同样无区分。
  • 链全灭时只返回 no provider available12 条),无分槽错误汇总,无法定位是谁挂了。

P9 其他(顺带)

  • 种子 admin key sk-gw-local-0001 未更换README 明确要求换);listen: 0.0.0.0:8081 全端口暴露。
  • AUTO 链只有 4 槽13+ 配置模型不参与 AUTO且 webui 编辑链后无任何"健康复位"提示。
  • 审计文件 jsonl 无限增长(当前 547KBAppendAudit 每事件一次文件 open。

P10 全量代码审查发现2026-08-11全部核心文件通读后确认

  • P10-1已确认handleSourcesAPI 无 admin 校验api.go:77-123全方法无 reqRole 检查,对比 handleAdaptersAPI api.go:21、handleStatsAPI api.go:138 均有)→ 任何 user 级 key 可 GET /api/sources 读取全部上游明文 api_keyconfig.Source.APIKeyjson:"api_key"config.go:42GET 分支直接返回 g.core.Sources()),并可 POST/DELETE 增删改源配置、篡改路由。越权最高优先级修复项。
  • P10-2推断hasScopeModel 不剥前缀chat.go:219直接用原始模型串与 scope 比对 → 受限 user key 用 zen:model 前缀调用会被误判 403未实测待确认修复
  • P10-3已确认runtime.json 源无超时JSON 源不序列化 timeoutmergedSources 后 Timeout=0无限→ 生产 4 个源全部无超时;上游假死不返回时请求永久挂起、占满 max_concurrent 槽(历史上 2 条 120s context canceled 均为客户端主动断开)。补默认超时需注意 http.Client.Timeout 对 SSE 流式同样生效,需区分流式/非流式策略。
  • P10-4stats.go:失败请求也计入配额消耗(aggregateLocked 无条件累计 tokensnhour 配额按整点小时桶粒度不精确(WindowTokens 按桶过滤user 视角 Snapshotby_status 仍是全局分布(轻微泄露);Record 持 mu 时做文件 I/Oappend/rotate高 QPS 下有锁竞争。
  • P10-5scheduler.goChat/ChatStream/Image 重试次数受 len(cands) 限制MaxRetries 超出候选数时无效);chainDrive 有界等待期间重跑整档(硬失败会正确 break可接受
  • 健全项(评审确认)TryAcquire 忙不记分、chainDrive 硬失败正确打断等待、探活不污染调度状态、冷却必然到期自愈(无永久黑名单)、ResolvePinned 前缀解析41c9b0e与超时/退避公式5s·2ⁿ 封顶 30min正确。

二、目标架构(定稿)

2.1 分层(横向三层 + 状态基座横切)

┌────────────────────────────────────────────────┐
│  直连调度              AUTO 调度               │  ← 同级,共享底座
│  source:model 直派     链快照 + 偏好表         │    直连失败即返回(不重试)
├────────────────────────────────────────────────┤
│  源抽象层(每源)                               │
│   · 信号量 = max_concurrentTryAcquire 非阻塞  │  ← 唯一"忙/闲"判定
│   · Chat / ChatStream / Image / 事件上报        │
├────────────────────────────────────────────────┤
│  adapter 池层(每适配器,启动预热)             │
│   worker = Σ(使用该适配器的源 × max_concurrent) │
└────────────────────────────────────────────────┘
                 ▲ 状态基座(不属任何层)
   ModelState 表  (source,model) → {pref, failCount, cooldownUntil}
   tier 游标表    tier → atomic next记录上次分配位置

事件流:所有成功/失败/冷却由源抽象层上报到状态基座auto 与直连都只读。直连失败同样写入状态。

2.2 数据结构

// 调度链:保存时整体新建、原子替换;调度期只读快照
type Chain struct { Tiers []TierNode }          // 按 tier 降序
type TierNode struct { Tier int; Slots []*Slot }
type Slot struct {                              // 静态配置,链构建时冻结配额
    Model, Source string
    Kind          string
    Quota         int64; Period string; Hours int64
    state         *ModelState                   // 指针 → 跨链存活
}

// 状态基座key=(source,model),配置移除后回收)
type ModelState struct {
    pref          atomic.Int64  // +1/-5clamp[-20,+20]
    failCount     atomic.Int64
    cooldownUntil atomic.Int64  // 惰性指数退避,无定时器
}

2.3 调度算法AUTO

snap := chain.Snapshot()                  // O(n) 拷指针,调度期链只读
for tier := snap.Tiers 降序 {
    初筛 := 剔除 [冷却中 | 配额超限(slot.Quota>0 && windowTokens>=Quota)] 的槽
    if 初筛为空 → 该 tier 顺延,记录原因
    base := cursor[tier].Add(1)           // per-tier 原子游标(记录上次位置)
    ordered := 稳定排序(初筛, 按 pref 降序)  // 负面模型沉底
    for i := base; i < base+len(ordered); i++ {
        slot := ordered[i % n]
        if slot.state 冷却中 → continue        // 硬跳过(复验)
        if !src.TryAcquire() → continue        // 忙 = 软跳过(不记分)
        resp, err := src.Chat(slot.model)
        if err != nil {
            src.Release()
            state.RecordFailure()             // failCount++, pref-5, 冷却 5s·2^n(≤30min)
            continue                          // 单请求内不重试已失败槽
        }
        state.RecordSuccess()                 // pref+1, failCount/cooldown 清零
        return resp, slot.model, slot.source
    }
}
return 503 { 各 tier 错误汇总 }               // 全部档位失败才报错,可定位

关键语义:

  • 主序 = 游标轮转(均衡),偏好只做同起点排序(自适应);负偏好"沉底不跳过"——仅当同 tier 无非负候选时才尝试负面模型,成功 +1 自愈。无永久黑名单、无定时器
  • 忙 ≠ 失败:不扣分、不计冷却;同 tier 全忙 → 有界等待≤2s 轮询 TryAcquire尊重 ctx再顺延避免高 QPS 时全线降级。
  • 冷却是唯一硬跳过cooldownUntil = lastFail + min(5s·2^failCount, 30min),必然到期 → 自愈。
  • 401/403failCount 一次打高(不设永久位),可被冷却到期/手动重置恢复。
  • 流式:失败→切换仅限首 chunk 前;首块后固定。首块前失败 -5正常结束 +1。
  • 生图:不进 AUTO 链,直连 kind:image 模型,同一状态机制。

2.4 生命周期(修 P1

事件 动作
WebUI 保存 AUTO 链 构造新 Chain → 写锁原子替换 → 链内全部 ModelState 冷却清零(偏好保留)
增删/编辑源 rebuildRegistry 重建 ProviderModelState 复用/回收
(source,model) 移出配置 回收其 ModelState偏好一并丢弃
进行中请求 不受 swap 影响(入口 Snapshot 已拷贝引用)

2.5 直连

p := resolve(source, model)         // 唯一归属,无 AUTO 逻辑
p==nil → 400/404!TryAcquire → 429 busy快速失败
成功/失败 → RecordSuccess/RecordFailure同样写状态基座返回不重试

三、实施计划(分阶段上线)

Phase 0 — 止血热修(改动最小,当天可上)

  1. core.goSaveAutoRules 成功后调用 rebuildRegistry();给 Provider 增加 ResetHealth()(清 failCount/permanent/cooldownrebuild 时顺带重置。
  2. provider.goChatStream 成功(收到 [DONE] 或正常结束)调用 reportOK()
  3. 状态页:/api/status 增加 last_backoffpermanent 展示 + admin 可"重置源健康"按钮(调用 ResetHealth
  • 验证:改优先级 → AUTO 立即按新链调度qijiar/zen 场景恢复;回归 go test

Phase 1 — 状态基座落地provider.go 重构)

  1. 引入 ModelState(每 (source,model)pref/failCount/cooldownUntil原子
  2. Provider 增加 TryAcquire(ctx) error(非阻塞)与 RecordFailure(model, code) / RecordSuccess(model)ReportStatus 改为按模型记账与 401/403 不再永久化。
  3. 删除 permanent 语义(由有界冷却 + 重置通道取代)。
  • 验证单测退避按模型隔离、429/401 行为、TryAcquire 满即返)。

Phase 2 — 调度层重写scheduler.go + chat.go

  1. 新增 Chain/TierNode/Slot 与 Snapshot/原子替换;autoPlansrotateSameTier 删除,singleChatAuto/streamChatAuto 按 2.3 伪码重写。
  2. Registry.Resolve 移除 AUTO 排序/健康优先职责(只留归属 + pin 解析;AUTOChain/Default 不再用于调度)。
  3. 503 响应携带分槽错误汇总(哪个 tier 哪个源什么错)。
  4. 同 tier 全忙时 ≤2s 有界等待再降级。
  • 验证scheduler 单测tier 分桶、游标均衡、偏好沉底自愈、配额初筛、忙不记分e2e 增加"上游 429 → 切同 tier → 再切下 tier"、"编辑优先级后退避清零"用例。

Phase 3 — 收尾

  1. UI优先级页展示链上模型的冷却/偏好状态;状态页按模型展示健康。
  2. WebUI 保存链时前端提示"健康状态已复位"。
  3. 审计错误码分类402 欠费、400 schema 计入独立统计jsonl 轮转(按天/大小)。
  4. 运维:更换 admin keylisten 收敛到内网地址0.0.0.0:8081 → 127.0.0.1 或内网 IP + 反向代理)。

回归与上线

  • 构建:go build -tags luajit(生产机 Debian amd64 已具备工具链,可直接在 60 上编译);本地 Windows 需先补 LuaJIT 库才能链接。
  • 测试:go test -tags luajit ./... 全绿后按 Phase 0→3 灰度,每个 Phase 观察审计中 no provider available 计数与 502 分布。
  • 观察指标:no provider available 计数归零qijiar/zen 由 available=false 恢复;zen 429 触发时仅 zen 对应模型短冷却deepseek/qijiar 不受牵连。

四、与既有代码的对应改动文件

文件 改动
internal/provider/provider.go health→ModelState 表TryAcquireReportStatus/ResetHealthChatStream reportOK
internal/provider/registry.go 删除 Resolve(AUTO) 排序/健康逻辑,保留归属/pin
internal/scheduler/scheduler.go 新增 Chain/Slot/游标/偏好排序迭代器
internal/gateway/chat.go 删除 autoPlans/rotateSameTier重写单次/流式 AUTO错误汇总
internal/core/core.go SaveAutoRules 原子换链 + 冷却清零ModelState 生命周期
internal/gateway/api.go / keys.go 状态页/优先级页 UI 展示与重置接口
e2e/e2e_test.go 等 新场景用例

五、实施状态追踪(每项改动后更新)

Phase 0 — 止血热修

  • P0-12026-08-10core.goSaveAutoRules 持久化后调用 rebuildRegistry()——新建 Provider 即退避归零,编辑优先级立即生效(修 P1 主诉);新增 Core.ResetHealth() 供管理端手动清退避。
  • P0-22026-08-10provider.go:新增 Provider.ResetHealth() / Provider.HealthInfo()failCount/cooldownUntil/permanent 只读暴露);ChatStream 流式正常结束([DONE]/EOF、非客户端断开、非读错误调用 reportOK()——流式成功可恢复退避(修 P6
  • P0-32026-08-10registry.go + server.goSourceStatus 增加 fail_count/backoff_until/permanent 字段(状态页可分辨"探活通但调度退避");新增 POST /api/status/resetadmin 专属,写审计)。
  • P0-42026-08-10验证go build ./internal/... 通过;go test ./internal/config/... 通过Windows 缺 LuaJIT 库,带 cgo 的包无法本地链接,待生产机验证)。

说明P0-1 采用"重建 Provider"实现退避归零(非目标架构的 ModelState 粒度属临时止血Phase 1 引入 per-(源,模型) ModelState 后,ResetHealth 语义将迁移到模型级。

Phase 1 — 状态基座落地

  • P1-12026-08-10provider.goModelState 表落地pref±1/-5、failCount、cooldownUntil 原子,惰性求值无定时器);新增 ErrBusyTryAcquire(非阻塞,满即返)、ModelAvailable(model)RecordFailure(model, code) / RecordSuccess(model)Provider.Pref(model)ReportStatus 改按模型记账401/403 → 打满档冷却 + 双倍偏好惩罚,不再永久化5xx/429 → 指数退避400/402 不惩罚);ResetHealth 清全源模型状态;HealthInfo 源级聚合permanent 恒 falseChat/ChatStream/Image 全部按命中模型记账 + 内部 AcquireTryAcquire(忙即 429不再排队 60s修 P4流式干净收尾 RecordSuccess(修 P6
  • P1-22026-08-10chat.go:直连/生图路径 errors.Is(err, ErrBusy) → HTTP 429upstreamErrStatusAUTO 槽改用 ModelAvailable(slot.model) 模型级冷却跳过frank 401 后其模型不被 AUTO 反复打,修 P3 的一半——永久黑名单已随 P1-1 移除)。
  • P1-32026-08-10单测provider_test.go模型级退避隔离m1 失败 m2 照常、401 → failCount 打满 capN + 冷却 ~30min + 偏好 -10非永久reset 即恢复)、单次失败冷却 ≈5s 且成功复位 +1、TryAcquire 满即返、Chat 忙时快速 ErrBusy、流式成功清理冷却P6 回归)。
  • P1-42026-08-10本地验证go build ./internal/...go vet ./internal/...go test ./internal/config/... 全绿provider/gateway 测试因 Windows 缺 -llua 仅静态检查通过,待生产机 -tags luajit 全量跑(同 P0-4 约束)。
  • 生产机192.168.2.60go test -tags luajit ./internal/provider/... 回归 — 待 Phase 2/3 完成后一并部署验证。

Phase 2 — 调度层重写

  • P2-12026-08-10scheduler.goChain/TierNode/Slot/Rule 落地tier 降序;槽静态配置,同 tier 保持配置序per-tier next atomic.Int64 游标始 -1首请求从配置序开始BuildChain 丢弃 provider 解析失败的槽;runTier 按 2.3 语义重写——冷却复验硬跳过、busy 软跳过不记分、硬失败记账后同档继续下一槽(单请求不重试已失败槽)、整档遍历完才顺延;chainDrive 初筛(配额/冷却)→ 同 tier 按 Pref 稳定降序排序 → NextStart() 起步轮转 → 全忙/全冷却有界等待(busyWait 2s/busyPoll 100ms期间刷新冷却再降级ChainErr 携带各档 TierError + 顺延原因Error() 输出 all auto tiers failed: tier N src/model: err; ...ChainChat/ChainChatStream 返回 (resp, source, model, err);直连 Chat/ChatStream/Image 保持不变;scheduler 不再 import providerFromRegistry 移入 gateway 为 toSchedulerscheduler 单测不拉 LuaJIT 链接。
  • P2-22026-08-10registry.go + core.goRegistry.Resolve 移除 AUTO 排序/健康优先AUTO→按配置序全量仅用于生图/tool 锚定未知模型→nil→gateway 404AUTOChain/Default 删除);CoreautoChain atomic.Pointer[scheduler.Chain] + AutoChain()buildAutoChain(过滤 image-kind 与已不存在槽,槽 Source 规范化为所有权源使汇总/audit/配额窗口键一致)随 rebuildRegistrySaveAutoRules 重建;SaveAutoRules = 持久化 → 原子换链 → 链内每槽 ResetModelCooldown(偏好保留,不重建 Provider——修 P1 主诉(编辑优先级立即生效)。
  • P2-32026-08-10chat.goAUTO 分支改走 AutoChain()+quotaExhaustedQuota<=0 不过滤;AutoPeriodSeconds+stats.WindowTokens 实时判定);singleChatAuto/streamChatAutoChainChat/ChainChatStream 重写,总失败在任何 SSE 字节前写 JSONupstreamErrStatusChainErr→503错误消息即分档汇总ErrBusy→429、其余→502失败记录取首个 TierError 填 audit source/model直连无候选→404 model_not_found
  • P2-42026-08-10单测scheduler_test.gotier 分桶/降序、同 tier 游标轮转交替s1,s2,s1,s2、负偏好沉底仍可达硬失败换槽后由负面槽承接、busy 跳过不记分、整档全忙有界等待(实测 <1s后降级、配额耗尽槽不调度、全灭 503 汇总Tiers+Skipped 文本断言)、流式首 chunk 前失败换槽、游标首帧从 0 起。
  • P2-52026-08-10本地验证Windows 已补齐 Lua:把 golua 自带 Lua 5.1 头对应的源码编成 liblua.a 放入 golua 模块目录golua 官方 Windows 做法),本机 go build/vet/test ./... 全绿——provider/gateway/lua/e2e 全部首次真正跑通,并借此揪出三处从未被发现的存量问题:① RecordFailure(auth) 只把 failCount 上限用于冷却算式、未落盘计数器已修auth 时 failCount.Store(backoffCapN));② gateway 测试种子 key 未进 runtime store 导致全 401已修测试 cfg 补 GatewayKeys);③ e2e failover 用例只有一个 chat 源、AUTO 全灭必然 503已修新增第二 chat 源 fallback真故障转移e2e buildBinary Windows 回退无 tag 构建bundled Lua透传适配器行为一致生产机仍优先 -tags luajit
  • P2-62026-08-10Phase 2 场景测试gateway 新增——同 tier 硬失败顺延承接(TestChatAutoChainTierFailover)、全灭 503 含 a/a-m 分档汇总(TestChatAutoChain503Summary)、配额耗尽槽跳过且不再打上游(TestChatAutoQuotaSkip)、PUT /api/auto 后冷却立即复位并恢复调度(TestAutoSaveResetsCooldown,修 P1 回归e2e 新增 TestEndToEndAuto503(真实二进制 503 汇总)。全部本地跑通。
  • P2-7生产机192.168.2.60go test -tags luajit ./... 最终回归 + 灰度部署luajit 与 bundled Lua 的适配器行为差异由生产验证兜底)。

Phase 3 — 收尾

  • P3-12026-08-10UI 链上健康展示GET /api/auto 增加 statesCore.AutoSlotStates 遍历当前 Chain 槽 × Provider.ModelHealthInfopref/failCount/cooldownUntil/cooling优先级页每块按 model|source 渲染徽标(冷却红/失败橙×N/±偏好蓝title 说明);状态页新增"状态码分布"卡片。
  • P3-22026-08-10保存链健康复位提示saveSort toast 变更为 排序已保存并热重载 · 链上冷却已复位zh/en
  • P3-32026-08-10审计分类与 jsonl 轮转Stats.byStatus map[int]*Stat402 欠费/400 schema 等按状态码独立计数,不触发 provider 退避,Snapshot.by_status 有序输出audit 文件超 auditRotateBytes(64MB, var 可测) 轮转 rename <path>.<unix>.old 并保留最新 auditKeepOld(10) 份——rotateAuditLocked 持 mu 在 Record/AppendAudit 内触发。
  • P3-42026-08-10运维项代码部分main.go 启动告警——gateway_keys 为空 / 命中种子 keysk-gw-local-0001 等提示轮换、listen 绑定 0.0.0.0/:: 提示收敛内网。生产实践(换 admin key、内网绑定随本次上线执行。
  • P3-52026-08-10测试:新增 stats_test.goby_status 断言、轮转→保留上限→Record 路径轮转gateway 新增 TestAutoStatesReportChainHealthstates 契约:失败后 fail_count>0+coolingPUT 复位归零);go vet ./... + go test ./... 全绿。
  • P3-62026-08-10WebUI 右键菜单无法关闭(用户实测):根因——showCtx 创建菜单后从未赋值 ctxElhideCtx() 恒为空操作),任何路径(点菜单项/点外部/二次右键)都关不掉;补 ctxEl = w,优先级页与密钥页共用 showCtx 一并修复(这也是最初版本就存在的 bug
  • P3-7:推送 origin → 生产机 pull → -tags luajit 全量回归(含 P2-7→ 部署 /usr/local/bin/llmsproxy + 重启 service → 观察。
  • P3-82026-08-11审计回放修复用户实测:重启后统计只剩最后 3000 行≈5.3M tokens历史 273M 消失、且记录里全是 access 脏行——根因 LoadAudit:① 只取文件尾 3000 行(而 92% 行是每 3s 的 access 事件);② access/事件行type 空)被当请求灌入聚合;③ Scanner 默认 64KB 截断风险。修复:回放全部真实请求行进聚合(恢复 totals/配额窗口),仅视图环形缓冲限 maxRecs跳过 type 空的行;sc.Buffer 抬到 16MB。新增 TestLoadAuditFullReplayaccess/坏 JSON/超大行混合回放断言)。

Phase 4 — 2026-08-11 WebUI 修复与全量代码审查(今日会话)

  • P4-12026-08-11WebUI 聊天"每次回复都失败"(用户实测):根因——sendChat()addMsg('assistant', '') 未传 reason 参数 → thinkEl/hintEl 为 null → 首个携带 reasoning_content 的流式 chunk 到达时 index.html 抛 Cannot set properties of null,前端表现为请求失败。修复:addMsg('assistant', '', true) + 初始隐藏 think 框;提交 7647b7d,已推送并部署生产(服务 active、/api/status 正常),go test ./internal/gateway/... 通过。
  • P4-22026-08-1141s AUTO 延迟归因:审计 09:54:42 stream lat=41810 model=deepseek-v4-flash-free src=zen ok=200 pt=17 ct=109——链上调度仅 ~0.6stier1 qijiar 失败 lat=657ms 后顺延),其余 ~41s 为 zen 上游生成耗时(~380ms/token非网关调度问题
  • P4-32026-08-11全量代码审查provider.go812 行)/stats.go459 行)/scheduler.go398 行)/registry.go258 行)/api.go/chat.go/server.go/config 全部通读完毕,确认 P10-1~P10-5见上
  • P4-4handleSourcesAPI 补 admin 校验P10-1一行仿 api.go:21— 待用户确认后实施。
  • P4-5hasScopeModel 剥前缀比对P10-2— 待确认。
  • P4-6runtime 源默认超时策略P10-3区分流式/非流式)— 待确认。