AGC 模型目录改为启动期从上游同步,退役写死的 gpt-6-astra/luna 与别名(#506) #507

Merged
suzmii merged 6 commits from enhance/default-model into master 2026-09-24 19:15:22 +08:00
Member

问题

AGC 平台模型目录在数据库里没有配置时,read_agc_model_catalog 会兜底返回写死的初始目录(质量 → gpt-6-astra快速 → gpt-5.6-luna)。这两个模型已从上游 Router 移除,于是从未配置过目录的环境(新库、清库、本地调试)会把两个不存在的模型下发给客户端,选中后上游 model_not_found,必须人工在后台保存一次目录才恢复。

关 issue:#506

方案(只改“初始值从哪来”,保持既有格式与契约不变)

  1. 初始目录从上游同步:api-server(API/All 角色)启动时若目录缺失、结构与当前定义不符或校验不通过,就请求上游 Router 控制面的分组定价列表 GET {控制面}/api/pricing?group=taonier(公开只读、不带凭据),按 data[].model_name 排序生成目录:modelIdalias 用上游原名(不再填“高质量/快速”),id 用模型名 slug(glm-5.3glm-5-3,冲突追加 -2),全部 enabled,默认项取排序后第一项,并以存量 revision 写回自增。
  2. 拉不到就报错、下次启动重试:网络失败、非 2xx、空列表、响应超 1 MiB、缺可解析 revision、写回失败都只记录 error,不写任何替代目录;本次启动保持未初始化,下一次启动继续重试,直到目录里有数据。启动本身不因同步失败而失败。
  3. 失败关闭:未初始化期间 GET /api/llm/modelsPOST /api/llm/responses/api/llm/chat/completions 与后台 GET/PUT /admin/api/agc-models 返回 503“模型目录未初始化”,不回退任何内置模型名。恢复路径:修好上游可达性后重启,或运维清空该行后重启。
  4. 不引入不兼容变更:目录字段(id/alias/modelId/defaultModelId)、后台页面与 admin DTO、GET /api/llm/models 形状、客户端 select_game_creator_model 的标识校验全部保持原样 —— id 用 slug 正是为了让已发布客户端的 [A-Za-z0-9_-] 校验继续成立,客户端不需要发版。
  5. 顺带清理:module 缺行语义改为报错;上游请求 10s 超时、1 MiB 流式上限、禁止重定向;目标校验只校验地址(不再被已下线的固定模型哨兵挡住);AGC 客户端写死的 gpt-6-astra 默认值改为 platform-default;模型弹层去掉滚动条并按最长模型名自动拓宽。

验证证据

结果
cargo test -p module-runtime --lib agc_models:: 4 passed(slug/去重/默认项/校验/32 项上限)
cargo test -p api-server --bin api-server agc / llm:: 30 passed(1 ignored)/ 18 passed
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bins configuration:: 45 passed
cargo fmt --all -- --check(两套 workspace)+ prettier + eslint 通过
npm run check:encoding / check:doc-index / git diff --check 通过(5057 文件 / 215 篇文档)
真实上游 smoke(隔离 SpacetimeDB + 真实 Router) 空库启动 → 已按上游模型列表初始化 AGC 模型目录 revision=1 model_count=6;落库为原格式 {id: "glm-5-3", alias: "glm-5.3", modelId: "glm-5.3", enabled: true}defaultModelId: "deepseek-flash"
同上,GET /api/llm/models 200:id 是 slug、displayName 是上游原名(glm-5.3/qwen3.8-flash…),6/6 个 id 满足客户端 [A-Za-z0-9_-]{1,64} 校验,defaultModelId=deepseek-flashrevision=1
同上,目录已存在时重启 不重写(无同步日志,revision 保持 1)

上游不可达/非 2xx/空列表的失败关闭由单测覆盖(fetch_upstream_model_names_fails_closed_when_upstream_unavailable_or_empty:401/500/空列表),并且本轮改动前的迭代里在真实 Router 上实测过同一条链路(401 → 启动 error → 接口 503 → 目录保持未初始化、不写替代目录)。

待确认

  • 分组定价列表当前含 qwen-image-3.0(图像模型),是否对 AGC 隐藏由 owner 在后台停用;目录只在未初始化时重建,不会自动跟随上游变化。
  • 默认项是排序后第一项(当前 deepseek-flash),要指定别的默认模型在后台「AGC 模型」页改即可。
  • 存量目录(含旧版写死的 quality/fast)结构合法时不会自动重建,需要 owner 在后台改掉或清空该行后重启。
  • 发布/回滚:module 的缺行语义变了,module 与 api-server 需同批发布/回滚(后台 DTO 未变,admin-web 可独立发布)。
## 问题 AGC 平台模型目录在数据库里没有配置时,`read_agc_model_catalog` 会兜底返回写死的初始目录(`质量 → gpt-6-astra`、`快速 → gpt-5.6-luna`)。这两个模型已从上游 Router 移除,于是从未配置过目录的环境(新库、清库、本地调试)会把两个不存在的模型下发给客户端,选中后上游 `model_not_found`,必须人工在后台保存一次目录才恢复。 关 issue:#506 ## 方案(只改“初始值从哪来”,保持既有格式与契约不变) 1. **初始目录从上游同步**:api-server(API/All 角色)启动时若目录缺失、结构与当前定义不符或校验不通过,就请求上游 Router 控制面的分组定价列表 `GET {控制面}/api/pricing?group=taonier`(公开只读、不带凭据),按 `data[].model_name` 排序生成目录:`modelId` 与 `alias` 用上游原名(不再填“高质量/快速”),`id` 用模型名 slug(`glm-5.3` → `glm-5-3`,冲突追加 `-2`),全部 enabled,默认项取排序后第一项,并以存量 revision 写回自增。 2. **拉不到就报错、下次启动重试**:网络失败、非 2xx、空列表、响应超 1 MiB、缺可解析 revision、写回失败都只记录 error,不写任何替代目录;本次启动保持未初始化,下一次启动继续重试,直到目录里有数据。启动本身不因同步失败而失败。 3. **失败关闭**:未初始化期间 `GET /api/llm/models`、`POST /api/llm/responses`、`/api/llm/chat/completions` 与后台 `GET/PUT /admin/api/agc-models` 返回 `503`“模型目录未初始化”,不回退任何内置模型名。恢复路径:修好上游可达性后重启,或运维清空该行后重启。 4. **不引入不兼容变更**:目录字段(`id`/`alias`/`modelId`/`defaultModelId`)、后台页面与 admin DTO、`GET /api/llm/models` 形状、客户端 `select_game_creator_model` 的标识校验全部保持原样 —— `id` 用 slug 正是为了让已发布客户端的 `[A-Za-z0-9_-]` 校验继续成立,客户端不需要发版。 5. 顺带清理:module 缺行语义改为报错;上游请求 10s 超时、1 MiB 流式上限、禁止重定向;目标校验只校验地址(不再被已下线的固定模型哨兵挡住);AGC 客户端写死的 `gpt-6-astra` 默认值改为 `platform-default`;模型弹层去掉滚动条并按最长模型名自动拓宽。 ## 验证证据 | 项 | 结果 | | --- | --- | | `cargo test -p module-runtime --lib agc_models::` | 4 passed(slug/去重/默认项/校验/32 项上限) | | `cargo test -p api-server --bin api-server agc` / `llm::` | 30 passed(1 ignored)/ 18 passed | | `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bins configuration::` | 45 passed | | `cargo fmt --all -- --check`(两套 workspace)+ prettier + eslint | 通过 | | `npm run check:encoding` / `check:doc-index` / `git diff --check` | 通过(5057 文件 / 215 篇文档) | | **真实上游 smoke**(隔离 SpacetimeDB + 真实 Router) | 空库启动 → `已按上游模型列表初始化 AGC 模型目录 revision=1 model_count=6`;落库为原格式 `{id: "glm-5-3", alias: "glm-5.3", modelId: "glm-5.3", enabled: true}`、`defaultModelId: "deepseek-flash"` | | 同上,`GET /api/llm/models` | 200:`id` 是 slug、`displayName` 是上游原名(`glm-5.3`/`qwen3.8-flash`…),6/6 个 `id` 满足客户端 `[A-Za-z0-9_-]{1,64}` 校验,`defaultModelId=deepseek-flash`,`revision=1` | | 同上,目录已存在时重启 | 不重写(无同步日志,revision 保持 1) | 上游不可达/非 2xx/空列表的失败关闭由单测覆盖(`fetch_upstream_model_names_fails_closed_when_upstream_unavailable_or_empty`:401/500/空列表),并且本轮改动前的迭代里在真实 Router 上实测过同一条链路(401 → 启动 error → 接口 503 → 目录保持未初始化、不写替代目录)。 ## 待确认 - 分组定价列表当前含 `qwen-image-3.0`(图像模型),是否对 AGC 隐藏由 owner 在后台停用;目录只在未初始化时重建,不会自动跟随上游变化。 - 默认项是排序后第一项(当前 `deepseek-flash`),要指定别的默认模型在后台「AGC 模型」页改即可。 - 存量目录(含旧版写死的 `quality/fast`)结构合法时不会自动重建,需要 owner 在后台改掉或清空该行后重启。 - 发布/回滚:module 的缺行语义变了,module 与 api-server 需同批发布/回滚(后台 DTO 未变,admin-web 可独立发布)。
suzmii added the Kind/Bug
Priority
High
2
labels 2026-09-24 11:39:17 +08:00
Author
Member

上游来源修正(2026-09-24 真机实测后)

在本地 dev 栈 + 真实 Router 上试跑发现最初设想的来源走不通,已按实测结论改口径:

候选来源 实测 采用
GET {Router}/v1/models + 管理 token 401 Invalid token(该端点要 Router 用户 Key,平台没有服务级 Key)
GET {Router}/api/models/(管理面模型注册表) 200,含 gpt-6-astra/gpt-6-luna/gpt-6-solendpoints 为空、已下线无路由绑定的条目
GET {Router}/api/pricing?group=taonier(分组定价列表,公开只读) 200,当日 6 个在售模型

同时修掉一个实测出来的缺陷:定价列表返回顺序每次都可能不同,原“默认项取首个”会导致默认模型随机漂移;现改为按模型名字典序去重排序后建目录,目录内容只取决于模型集合(同一集合重复同步得到完全一致的目录与默认项)。

真实上游验收(本地 dev DB xushi-p4wfr

  1. 清空 agc_model_catalog → 启动 api-server:日志 已按上游模型列表初始化 AGC 模型目录 revision=1 model_count=6
  2. 落库内容:{"revision":1,"defaultModel":"deepseek-flash","models":[deepseek-flash, deepseek-v4-pro, glm-5.3, glm-5.3-flash, qwen-image-3.0, qwen3.8-flash]}
  3. 登录后 GET /api/llm/models → 200,id/displayName 就是上游原名,defaultModelId=deepseek-flash;未带鉴权 401;
  4. 目录已存在时重启 → 不重写(revision 保持 1);上游不可达那一轮 → 启动只打印 error、接口 503“模型目录未初始化”,不回退内置模型。

待确认

  • 定价列表含 qwen-image-3.0(图像模型),是否要在 AGC 下拉隐藏 → owner 可在后台「AGC 模型」停用;目录只在未初始化时重建,不会自动跟随上游变化。
  • 默认项为排序后第一个(当前 deepseek-flash);如需指定别的默认模型,后台「AGC 模型」页可直接改。
## 上游来源修正(2026-09-24 真机实测后) 在本地 dev 栈 + 真实 Router 上试跑发现最初设想的来源走不通,已按实测结论改口径: | 候选来源 | 实测 | 采用 | | --- | --- | --- | | `GET {Router}/v1/models` + 管理 token | **401 Invalid token**(该端点要 Router 用户 Key,平台没有服务级 Key) | 否 | | `GET {Router}/api/models/`(管理面模型注册表) | 200,含 `gpt-6-astra/gpt-6-luna/gpt-6-sol` 等 `endpoints` 为空、已下线无路由绑定的条目 | 否 | | `GET {Router}/api/pricing?group=taonier`(分组定价列表,公开只读) | 200,当日 6 个在售模型 | **是** | 同时修掉一个实测出来的缺陷:定价列表**返回顺序每次都可能不同**,原“默认项取首个”会导致默认模型随机漂移;现改为按模型名字典序去重排序后建目录,目录内容只取决于模型集合(同一集合重复同步得到完全一致的目录与默认项)。 ## 真实上游验收(本地 dev DB `xushi-p4wfr`) 1. 清空 `agc_model_catalog` → 启动 api-server:日志 `已按上游模型列表初始化 AGC 模型目录 revision=1 model_count=6`; 2. 落库内容:`{"revision":1,"defaultModel":"deepseek-flash","models":[deepseek-flash, deepseek-v4-pro, glm-5.3, glm-5.3-flash, qwen-image-3.0, qwen3.8-flash]}`; 3. 登录后 `GET /api/llm/models` → 200,`id`/`displayName` 就是上游原名,`defaultModelId=deepseek-flash`;未带鉴权 401; 4. 目录已存在时重启 → 不重写(revision 保持 1);上游不可达那一轮 → 启动只打印 error、接口 503“模型目录未初始化”,不回退内置模型。 ## 待确认 - 定价列表含 `qwen-image-3.0`(图像模型),是否要在 AGC 下拉隐藏 → owner 可在后台「AGC 模型」停用;目录只在未初始化时重建,不会自动跟随上游变化。 - 默认项为排序后第一个(当前 `deepseek-flash`);如需指定别的默认模型,后台「AGC 模型」页可直接改。
Author
Member

UI 修正:模型弹层去掉滚动条并按最长模型名自动拓宽

目录项现在直接是上游原始模型名(deepseek-v4-pro 这类),旧弹层规则会把它截断并出现滚动条:

  • 旧:min-width: 150px; max-width: 190px; max-height: min(240px, 40vh); overflow: auto
  • 新:width: max-content; min-width: 150px; max-width: calc(100vw - 24px),去掉 max-height / overflow,菜单按目录项展开

真实 Chromium 验证(一次性 Vite 夹具挂真实组件 + styles.css,目录用真实上游 6 个模型名 mock;桌面 1280 与移动 390 视口):

规则 宽度 滚动条 文字截断
旧(页内回放) 150px
154px(按最宽项自动定宽)

两种视口下弹层都完整落在屏幕内。前端 29 passed(conversationModelSelect.test.tsx)、typecheck 3/3、encoding / diff / prettier 通过;一次性夹具与截图已删除。

已知取舍:弹层高度跟随目录项数展开(当前上游在售 6 个),若目录规模大幅增长需要重新评估是否保留滚动兜底;输入框里的触发器仍保留 180px 上限的省略号行为(窄布局约束)。

## UI 修正:模型弹层去掉滚动条并按最长模型名自动拓宽 目录项现在直接是上游原始模型名(`deepseek-v4-pro` 这类),旧弹层规则会把它截断并出现滚动条: - 旧:`min-width: 150px; max-width: 190px; max-height: min(240px, 40vh); overflow: auto` - 新:`width: max-content; min-width: 150px; max-width: calc(100vw - 24px)`,去掉 `max-height` / `overflow`,菜单按目录项展开 真实 Chromium 验证(一次性 Vite 夹具挂真实组件 + `styles.css`,目录用真实上游 6 个模型名 mock;桌面 1280 与移动 390 视口): | 规则 | 宽度 | 滚动条 | 文字截断 | | --- | --- | --- | --- | | 旧(页内回放) | 150px | 有 | — | | 新 | 154px(按最宽项自动定宽) | 无 | 无 | 两种视口下弹层都完整落在屏幕内。前端 29 passed(`conversationModelSelect.test.tsx`)、typecheck 3/3、encoding / diff / prettier 通过;一次性夹具与截图已删除。 已知取舍:弹层高度跟随目录项数展开(当前上游在售 6 个),若目录规模大幅增长需要重新评估是否保留滚动兜底;输入框里的触发器仍保留 180px 上限的省略号行为(窄布局约束)。
suzmii added 5 commits 2026-09-24 16:25:08 +08:00
- 主规范补充目录初始化来源、失败关闭与重试口径,并记录本变更不改变目录格式与客户端契约
- 后端数据契约补充缺行语义与启动期同步
- 运维文档新增「AGC 模型目录上游同步」小节(来源、恢复路径、同批发布约束)
- decision-log 记录本次决策、真实上游实测结论与残留项
- 新增里程碑《AGC 模型目录初始值改为上游同步》与对应实施计划
- module-runtime:删除写死的初始目录(高质量→gpt-6-astra、快速→gpt-5.6-luna),新增 from_upstream_models:id 用模型名 slug、alias/modelId 用上游原名、默认项取排序后第一项;字段与校验规则不变
- spacetime-module:read_agc_model_catalog 缺行返回 AGC_MODEL_CATALOG_NOT_INITIALIZED,不再返回内置目录
- api-server:新增启动期 ensure_agc_model_catalog_initialized,未初始化时从分组定价列表 GET /api/pricing?group=taonier 生成目录并按存量 revision 写回;拉不到只记录 error、不写替代目录,下次启动重试
- api-server:目录未初始化时 AGC 目录/对话接口与后台目录接口返回 503,不回落任何内置模型名;后台 PUT 同样要求已初始化
- api-server:上游请求 10s 超时、1 MiB 流式上限、禁止重定向、不带凭据;目标校验只校验地址,不再绑定已下线的固定模型哨兵;写回冲突后重读校验既有目录
- main.rs:DEFAULT_GAME_CREATOR_LLM_MODEL 改用官方占位标识 platform-default
- game-creator.config.json:首启模板 llm.model 同步为 platform-default
- check-config.mjs 与客户端测试同步该口径,去掉已退役模型名的断言
- .conversation-model-menu 宽度改为 max-content(保留 min-width 150px 与视口宽度上限),不再用固定 150–190px 截断
- 去掉 max-height/overflow 滚动,菜单按目录项展开
忽略嵌套的 server-rs/.data 运行期产物
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m33s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 53s
Project CI / Backend tests (pull_request) Successful in 4m24s
Project CI / Frontend tests (pull_request) Successful in 2m12s
Project CI / Native shell tests (pull_request) Successful in 6m2s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m52s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m1s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m55s
Project CI / Repository checks (pull_request) Successful in 2m34s
8514f1094c
- 以 crates/api-server 为 cwd 启动时会生成 server-rs/crates/api-server/server-rs/.data,补充 **/server-rs/.data/ 忽略规则
suzmii force-pushed enhance/default-model from 19af64757e to 8514f1094c 2026-09-24 16:25:08 +08:00 Compare
suzmii added 1 commit 2026-09-24 18:02:02 +08:00
Merge branch 'master' into enhance/default-model
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m35s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m5s
Project CI / Backend tests (pull_request) Successful in 4m40s
Project CI / Native shell tests (pull_request) Successful in 6m13s
Project CI / Frontend tests (pull_request) Successful in 2m7s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m13s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m55s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m34s
Project CI / Repository checks (pull_request) Successful in 1m58s
997eba2b7b
suzmii merged commit a9f57c98ce into master 2026-09-24 19:15:22 +08:00
suzmii deleted branch enhance/default-model 2026-09-24 19:15:23 +08:00
Sign in to join this conversation.