mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
12 KiB
12 KiB
Context
动机见 proposal.md。与方案相关的现状:
- Codex Model Manifest 入口是
OpenAIGatewayHandler.CodexModels:先尝试用账号模型映射本地生成;否则循环SelectAccountForModelWithExclusions选账号、FetchCodexModelsManifest拉取、CompleteAPIKeyCodexModelsManifestForClient补全、MergeGroupConfiguredCodexModels做分组过滤与 ETag。 FetchCodexModelsManifest对 API Key 账号走fetchCachedAPIKeyCodexModelsManifest(30 秒新鲜、5 分钟乐观、单飞后台刷新),对 OAuth 账号直接请求上游且带 agent identity 任务恢复逻辑,不缓存。- 分组配置落地链路:ent schema → SQL 迁移 →
service.Group→group_repo创建与更新 setter →api_key_repo分组字段投影与groupEntityToService→ 认证快照api_key_auth_cache.go结构体及 impl 两处映射 → 管理端 DTO / mapper →admin_group.go创建与更新 → 分组复制。models_list_config是完整样板。 - 前端
GroupsView.vue已有 7000 行;模型路由的账号选择用「标签 + 搜索输入 + 下拉」内联实现,Select.vue仅支持单选。components/admin/group/下已有抽出的表单片段组件(如ReasoningEffortPolicyFields.vue)。 - 后端管理端账号列表接口支持
platform、group、search过滤。
Goals / Non-Goals
Goals:
- 固定账号模式的运行时逻辑放在 service 层,handler 只做分支与错误映射。
- 合并逻辑是纯函数,可独立单测。
- 缓存策略只有一套实现,OAuth 与 API Key 账号共用;固定账号模式不引入第二层分组级缓存。
- 新字段在所有分组加载路径上都可见,特别是认证快照与 API Key 投影。
Non-Goals:
- 不改动调度器逻辑与普通请求的账号选择。
- 不为账号解绑或删除增加对该配置的级联清理,运行时容忍失效 ID。
- 固定账号关闭时保留本地生成 manifest 的路径;开启时以指定账号上游发现为先,映射在发现后应用。
- 创建分组对话框不提供该配置。
Decisions
D1:配置以单个 JSONB 列存储
groups.codex_models_manifest_config,领域类型 domain.GroupCodexModelsManifestConfig{Enabled bool; AccountIDs []int64; FallbackToScheduler bool},JSON 键为 enabled、account_ids、fallback_to_scheduler。
- 备选:三个独立列。否决:三个字段语义耦合,JSON 保证原子写入,且与
models_list_config一致。 - 迁移文件
234_group_codex_models_manifest_config.sql:ADD COLUMN IF NOT EXISTS ... JSONB NOT NULL DEFAULT '{}'::jsonb。ent schema 使用field.JSON(...).Default(domain.GroupCodexModelsManifestConfig{})。
D2:校验放在 admin service,创建路径拒绝开启
- 新文件
group_codex_models_manifest.go提供normalizeCodexModelsManifestConfig(平台非 openai 归零;去重保序;enabled=false时保留列表便于再次启用)与validateCodexModelsManifestConfig(ctx, groupID, cfg)(enabled=true时:非空、≤10、全部属于accountRepo.ListByGroup(groupID)且平台 openai)。 - 创建路径:
enabled=true直接 400。原因:账号绑定发生在分组创建之后,创建时无法校验成员关系;前端也不展示。 - 备选:校验放 handler。否决:需要访问账号仓储,且创建与更新两条路径共用,service 是唯一收口。
D3:运行时入口与回退策略
- handler 在本地生成分支之前判断
group.Platform == openai && cfg.Enabled,调用gatewayService.FetchPinnedCodexModelsManifest(ctx, group, clientVersion)。 - 返回值:
(*OpenAIModelsResponse, *Account, error),*Account为配置顺序中第一个成功账号,用于setOpsSelectedAccount。 - 「无可用账号」以哨兵错误
ErrNoPinnedCodexModelsAccounts表示;「全部失败」返回最后一个上游错误。handler 根据FallbackToScheduler决定:true 时跌入现有调度器循环;false 时无可用账号返回 503、全部失败按infraerrors.Code(err)返回。 - 可用性判定:
IsActive() && Schedulable && !(AutoPauseOnExpired && 已过期)。刻意不使用IsSchedulable(),因为它包含限流、过载与临时不可调度窗口。 - 账号来源:
accountRepo.ListByGroup(group.ID)(active 成员),按配置顺序筛选并跳过缺失 ID。 - 备选:复用
BuildGroupConfiguredCodexModelsManifest已加载的账号列表以省一次查询。否决:那两个列表是可调度集合,会漏掉限流中的账号;一次按分组的查询成本可接受。
D4:并发拉取与纯函数合并
- 用
sync.WaitGroup对每个可用账号并发执行:FetchCodexModelsManifest(ctx, acc, clientVersion, "")→ API Key 账号再CompleteAPIKeyCodexModelsManifestForClient。结果写入按配置顺序索引的切片,失败记录到同下标的错误切片;各账号独立完成,不因单账号失败取消其他请求。 - Codex 合并函数
mergeCodexModelsManifestBodies(bodies [][]byte) ([]byte, error):以第一个 body 的顶层信封为基底,models按 slug 并集,先出现者优先;slug 为空或解析失败的条目按出现顺序保留一次。输出后设置ETag = codexModelsManifestBodyETag(body),再交给MergeGroupConfiguredCodexModels做分组过滤与 304 判断。 - 部分失败:
slog.Warn带 group_id 与失败账号 ID 列表。 - 实现放在新文件
openai_codex_models_pinned.go,避免继续膨胀 2300 行的主文件。
D5:缓存统一并调整时效
- 将
fetchCachedAPIKeyCodexModelsManifest泛化为fetchCachedOpenAIModels(ctx, request, fetch, ifNoneMatch),fetch func(ctx, ifNoneMatch) (*CodexModelsManifest, error)由调用方提供:API Key 路径传fetchCodexModelsManifestUpstream;OAuth 路径传一个包含 agent identity 任务恢复逻辑的闭包。handleCodexModelsManifestAccountAuthError在 OAuth 闭包返回错误时照旧调用。 - 常量:
openAIModelsCacheTTL30s → 60s;openAIModelsCacheStaleTTL保持 5 分钟;超过 5 分钟get删除条目返回 miss,调用方同步等待单飞结果,这与「超期强制等待上游刷新」一致,无需新状态。 - 缓存键已包含 Authorization 与 Version 头,令牌刷新自然失效旧条目;
client_version不同的客户端各占一个条目。 - 缓存键不含分组信息,多个分组共用同一账号时共享同一条目,新鲜期内零上游请求,乐观期与超期刷新均按键单飞,同一时刻一个账号只发一次上游请求;分组级处理(并集合并、自定义列表过滤、别名合并)都在缓存体的克隆上进行,不写回缓存。
openAIModelsCacheMaxEntries64 → 512。OAuth 路径接入后条目数为「账号数 × 客户端版本数」,64 条在大规模部署下会被淘汰导致额外上游请求;单条清单通常几十 KB,最坏情况内存占用为几十 MB 量级。- 后台刷新使用
context.Background()加上游 15 秒超时,与现状一致。 - 备选:为固定账号模式增加分组级合并结果缓存。否决:账号级缓存命中后合并只是内存操作,再加一层会引入两套时效与失效问题。
D6:前端拆出独立组件
- 新组件
components/admin/group/CodexManifestAccountsField.vue:props 为groupId、modelValue({enabled, account_ids, fallback_to_scheduler})与已选账号名称映射;内部实现开关、标签列表、带防抖的搜索输入与下拉、回退子开关。搜索调用adminAPI.accounts.list(1, 20, {search, platform: 'openai', group: String(groupId)})。 GroupsView.vue只在编辑对话框 OpenAI 区块挂载组件,打开编辑时用adminAPI.accounts.getById解析已存 ID 的名称,失败则显示#<id>。提交时开关打开且列表为空则 toast 报错并阻止。- 备选:在 GroupsView 内联复制模型路由的搜索状态。否决:会再引入一套按 key 索引的搜索状态,文件已过大。
Risks / Trade-offs
- [固定账号模式下每次缓存超期会向 N 个账号并发请求] → 账号数上限 10;账号级缓存 1 分钟新鲜期把稳态请求量压到低于现状。
- [限流中的账号被强制用于拉取 manifest 可能加重其 429] → 这是需求明确要求的行为;manifest 请求不占并发槽,且被缓存吸收;失败时按部分合并处理。
- [部分账号失败导致模型列表短暂缺项] → 记录警告日志;下一次超期刷新恢复。用户已确认接受。
- [OAuth manifest 首次接入缓存,乐观期内令牌已被撤销仍会返回旧内容最多 4 分钟] → manifest 非敏感数据,且撤销后的下一次刷新会失败并按现有 401 处理流程处理账号。
- [配置引用的账号被解绑或删除后成为脏 ID] → 运行时跳过;编辑对话框显示
#<id>提示管理员清理;全部失效时按回退配置处理。 - [认证快照漏投影新字段会让开关静默失效] → 仓库已有投影对账集成测试,任务中包含更新该测试。
Migration Plan
- 部署包含迁移 234 的后端版本;迁移幂等,默认值
{}反序列化为关闭状态,存量分组行为不变。 - 缓存时效变化随部署即时生效,无需数据迁移。
- 回滚:回退代码即可,列保留无副作用;如需彻底清理可手工
DROP COLUMN。
D7:普通模型列表共用固定账号来源
GatewayHandler.Models在 OpenAI 分组开启固定账号时调用FetchPinnedOpenAIModelsList;普通/models别名共用该入口。- 共享原有配置、固定账号成员筛选、并发收集与部分失败规则;标准列表失败且允许回退时用现有 OpenAI 调度器选账,再调用普通目录获取。无账号为 503,全部上游失败保留上游错误语义,不以静态默认列表掩盖失败。
- API Key 使用标准模型 URL,不添加 Codex
client_version或身份请求头;从管理端抽出 API Key 请求构造。OAuth 复用FetchCodexModelsManifest和规范客户端版本,保留凭据引用、Agent Identity 恢复、401 状态处理。 - 响应/缓存元数据类型泛化为
OpenAIModelsResponse;抽出原始 HTTP 响应获取,Codex 专用转换/元数据补全仅在 manifest 路径执行。两种请求使用同一缓存实现,以响应格式、完整 URL、账号、凭据、代理和请求头区分条目;相同 OAuth 请求共享条目。 - 标准列表保留上游
id、created、owned_by等字段。OAuth slug 转成标准条目;无创建时间采用 0。有效空数组是成功结果,缺失或错误的数组结构是上游错误。
D8:固定账号目录的映射与过滤
- 每个成功账号先基于它的原始目录做公开名称投影,随后按配置顺序取并集。透传或无映射账号保留上游具体 ID;有映射账号仅暴露映射目标存在于该账号目录的具体公开名称。
- 具体映射键以及分组选择的具体名称可作为别名候选;通配映射可匹配上游/分组提供的具体候选,但通配规则本身不作为模型 ID 返回。复用
ResolveMappedModel的匹配优先级。 - Codex 别名继承对应上游条目的能力元数据;固定账号开启时不再注入其他分组账号的本地别名。固定账号回退选出的账号也应用相同投影。
- 分组列表过滤最后执行,遵守配置顺序;过滤后为空返回 200 空数组,不使用静态默认模型。最终响应体计算 ETag 并处理 304。
- 前端文案说明该配置覆盖普通模型列表和 Codex Model Manifest。持久化配置名称和结构不变。
D9:扩展验证
新增路由/handler 与 service 测试覆盖普通两条路由、固定账号优先、原始媒体和未知模型、映射与通配别名、部分/全部失败及调度器回退、凭据/请求协议隔离、OAuth 缓存复用、三段缓存时效、分组过滤隔离与空目录、最终 ETag。运行受影响 Go 测试(含 race)、完整 unit 检查及前端类型/组件检查。