Skip to content

v0.10.0

Choose a tag to compare

@RobbsLuo RobbsLuo released this 26 Oct 12:00
· 39 commits to main since this release

v0.10.0 修复版:出口排队重开与非流式保活、统计页重排与失败归因「已救回」,外加一批「静默出错」的收口。SCHEMA_VERSION 不变(29,无新列新表),升级必须重启后端。

v0.9.1 之后的 45 个提交以修复与收敛为主,但含新端点与新能力,故按 minor 发。

  • 新端点 GET /api/stats/percentile-timeline(operator+):延迟分位时序,P50 中位数 + P95 上界两条线,给出「典型值 + 长尾」两层。只认 latency / ttfb 两个指标,其余显式 400。
  • 排队重开(Qoder / TRAE 的 isQueued / RESOURCE_LIMIT):不冷却凭证、不换号,等 retryAfterSeconds 后在原号重开。轮次与单轮等待都有上限;已出帧的流不重开。排队不消耗换号预算(此前三次排队就把 max_rotate 吃满、直接 503)。
  • 非流式保活(NONSTREAM_KEEPALIVE_SECONDS,默认 0 = 关):stream=false 聚合完成前一个字节都不发 → CDN / 反代空闲超时直接切连接。宽限期内完成仍是逐字节相同的 JSONResponse,超时才转 chunked 先吐空白心跳再补正文。
  • 统计页重排:一张图一个系列;延迟另加分位图;时间轴补齐小时桶(稠密时间轴);口径说明折叠。
  • 失败归因区分「已救回」:失败数是尝试口径(换号重试每次上游拒绝各落一行),只看它会把「已救回的尝试」读成「用户受损」。新增 rescued(同用户同 request_id 另有成功行),前端展示「未救回 = failures − rescued」。救援关联限定同用户——request_id 取自入站 X-Request-ID,客户端可伪造,只按 ID 关联会跨用户误判(已复现)。
  • 图指标改语义化聚合:图表指标不再混算,口径写进 README / TECHNICAL。

一批「静默出错」的收口(每条都配了回归测试):

  • Qoder 保活帧打断整条流:上游在流中发 {"headers":…,"body":"null"} 保活,此前判成协议违规 → 抛异常 → 整条流失败,而模型其实已在正常输出,客户端只看到回复被截断。判据改用结构(json.loads 后是 None / 空 dict / 空 list),保活帧直接跳过。
  • 排队报文被漏判 → 好凭证被硬禁用:上游把「排队中」包成 403,排队标记藏在再序列化过的字符串里,parse_queue_hint 只认 dict/list → 漏判 → 403 落 DEAD → 可自愈的凭证被打进冷宫。改为下钻字符串 + 排队判据进 classify_error_code(任何入口都不漏判)。
  • 一条坏凭证连累整批(凭证解密失败:APP_SECRET 是否被更换过? → 500):解密失败早先直达顶层 → 整个聊天请求 500、整轮后台任务挂掉、整页模型列表 500。改为按路径分口径:批处理路径(选号、模型列表、后台任务、即时探测)捕获后跳过该凭证并记日志;单条探测端点照旧如实报 500——管理员该去处理这个号。
  • 探活判死只影响展示,别名登记保留:上游目录撒谎(挂着某个模型但推理端点 404)时,判死把该 (渠道, 模型) 从模型列表摘掉;旧实现连别名登记一起摘,执行侧因「没有渠道登记该模型」回退成全部渠道扇出 → 请求被发到一堆根本没有该模型的上游,各回「无此模型」,负缓存写在从没有该模型的渠道上。现补登记回别名表:仍收窄到真正持有它的渠道,快速 503「该模型暂时不可用」。
  • TLS 中断(ssl.SSLError)不再逃成 internal_error:它不是 httpx.TransportError,落不进任何分类分支 → 异常一路逃到出口,表现为一帧 internal_error:不换号、不冷却、统计里零痕迹,失败归因面板完全看不到。归 SOFT(仍只认具体类,不放开到 OSError 整体——磁盘满也是 OSError 子类,误判会去冷却一个完全健康的凭证)。
  • 国际站白名单混入国内站主机时一律拒绝:只校验「端点在白名单里」不够——把国内站主机加进国际站白名单后端点校验会通过,国际站账号的 Token 照样发往国内站网关(跨站发送即凭证泄露)。
  • JSON 溢出成 inf 的合法数字(1e999)→ 400 而非 500:字面量闸拦得住 NaN / Infinity,拦不住合法 JSON 数字溢出;在解析层加 parse_float 守卫,三个出口共用一个入口。
  • 指定凭证(pin)的类型与存在性校验:非字符串 credential_id → 400(放任进 sqlite3 绑定 → 500);不存在的 id → 400 且不清掉别人的 pin(此前端点回 200,管理员以为已指定,实则把指定清空了)。
  • 统计 metric 取值域显式 400:未知取值此前静默回落到「请求次数」,客户端拼错参数会拿到一份「看起来正常」的图,错误被完全吞掉。
  • 未匹配的 /api、/v1 对任何 HTTP 方法都回统一 JSON 404:只注册 GET 时,未匹配的 POST 会落到 Starlette 的 405 {"detail":…},与统一错误体形状不符。
  • ADMIN_USERNAMES 只在引导期提权:库里已有活跃 admin 时不再改写任何人的角色。此前每次启动都无条件提权,管理台刚降级的用户重启后又变回 admin,「降级立即生效」被重启撤销。
  • 加载失败 ≠ 空数据:告警页此前在请求失败时显示「暂无告警记录…一切正常时保持空白」,用户会把故障读成「系统健康」。改为显式错误态;统计页同款问题一并修。
  • 外链 scheme 白名单(safeHttpUrl):管理台外链不再直接透传上游给的 URL。
  • 冷却对齐上游正文点名的重置时刻:CodeBuddy 国际站 429+6004 只在 msg 文案里给恢复时刻、不给 Retry-After 头,原实现取不到 → 回落 600s 基数 → 到期重试再撞 429(实测一个号一天被爆 42 次)。
  • 冷却日志区分账号级与模型级作用域:此前账号级冷却也打「模型 <名>」,读起来像只锁了这一个模型。
  • 留存任务回收过期账号级冷却:过期 cooling_until 永留库里,管理台列表按原始值下发 → 排序失真 + 幽灵记录。
  • CodeArts 非 JSON 响应对外不带上游 URL:异常文本带 URL 只进日志。
  • 用户名单渠道式交互收口:TRAE 回调打不开时改用粘贴链接完成登录(回调由浏览器打开,NAS / 远程部署里常直接「网络错误」);回调基址落在回环 / 通配主机时改用当前请求 origin;Cookie 不可用时按 loginTraceID 反查发起人,绑定在那一次 pending 登录上。

其他:

  • 探活判死不误伤扇出:BLOCKED 独立成 model_not_found(原先并进 invalid_request,读起来像客户端参数写错,实际是上游把模型下架了)。
  • 签到失败不再是 500:上游拒绝(账号被禁用 / 登录过期)是这条凭证的真实答复,收成 ok=false + 可读 message。
  • 单渠道模型也下发 by_provider:此前只给多渠道,单渠道模型的原代号对外完全不可见(排障时看不到实际请求的 key)。
  • provider 层去重(净减约 210 行):四家 OpenAI 兼容渠道的 SSE 解析骨架合一(zen / kilo / officeace / codebuddy,净减 177 行)、AuthStateStore 泛型化(4 份 owner/consume/cancel/cleanup 合一)、华为云系凭证字段合一、懒建连接池统一到 LazyClients、pacer 的 wait_turn + release 配对收进 paced() 上下文管理器。
  • 任务调度 / 仓储层的机械重复收敛:runner 的 12 分支周期 if-chain 改字典派发、repo 的 27 处 int(now if now is not None else time.time()) 收进 _now()、executor 的 4 份排队重开逻辑收成 _plan_queue_restart()。
  • 依赖安全更新:body-parser 2.4.0(GHSA-27r3-jxmh-p828)、fast-uri 3.1.9、source-map-js 1.2.2(GHSA-68fv-2mgg-jv7q)、certifi 2026.10.19。
  • CI 抗抖动:npm registry 的两处单点加显式重试;pnpm audit 与 Dockerfile 取 pnpm 本体两处都加重试。
  • 前端依赖瘦身:.npmrc 加 auto-install-peers=false,lock 从 1023 个包降到 601 个,pnpm audit --prod 0 漏洞;Dockerfile COPY web/.npmrc(否则容器内 pnpm 退回默认 autoInstallPeers=true,与 lockfile 不匹配直接 ERR_PNPM_LOCKFILE_CONFIG_MISMATCH)。

文档(README 中英 / TECHNICAL / PROPOSAL)全量对齐当前代码:环境变量表覆盖 config.py 全部字段、schema 版本与表数、统计口径与图表指标语义、出口保活与排队重开的行为契约。