Feat/gpt-image-2.5 集成 #422

Open
k88936 wants to merge 88 commits from feat/gptimage2to2.5 into master
Member
No description provided.
k88936 self-assigned this 2026-09-18 18:40:20 +08:00
k88936 added 5 commits 2026-09-18 18:40:20 +08:00
更新业务模型、具体模型与 provider client 领域术语

记录 Tiantoken/VectorEngine 双 client 启动约束

同步开发运维文档中的图片路由与环境变量要求
将 platform-image 图片协议从 vector_engine 目录迁移到 image_provider

引入 ImageProviderClient 与 VectorEngine/Tiantoken provider 类型

按 concrete model 白名单拒绝未知模型并移除跨模型回退
启动时分别构造 VectorEngine 与 Tiantoken 图片 client

按生成、编辑和 nanobanana 模型选择具体 provider

迁移 api-server、编辑器 Agent 与 raw edit 调用方

拆分 GPT Image 2.5 生成与编辑定价并隐藏 provider 具体值
读取历史 gpt-image-2-c 时按当前 GPT Image 2.5 业务任务处理

保留历史字符串不改写并沿用新的生成与编辑定价路由
整理图片 provider 模块层次并拆出 raw edit
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m26s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m18s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m39s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m0s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m37s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m28s
Project CI / AI game creator shell web tests (pull_request) Failing after 4m26s
Project CI / Frontend tests (pull_request) Failing after 5m49s
Project CI / Native shell tests (pull_request) Successful in 10m45s
1574f206a2
将 raw image edit 提取到独立 raw_image_edit 模块

按 protocol、runtime、transport 目录收拢 image_provider 实现

同步后端架构与 raw edit 技术文档路径
k88936 added 3 commits 2026-09-19 12:52:39 +08:00
更新 OpenAPI 与外部编辑器模型说明

补充历史模型兼容解析与参数回退告警约定

将现役 ImageGen 工具脚本切换到 GPT Image 2.5
将 GPT 专用新任务与锁定模型切换为 gpt-image-2.5

保留 nanobanana 普通生成默认行为

将历史 gpt-image-2 与 gpt-image-2-c 归一并触发参数回退告警
补齐 GPT Image 2.5 前端回归测试
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m51s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m53s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m28s
Project CI / Backend tests (pull_request) Failing after 13s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 6m49s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m0s
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m56s
Project CI / AI game creator shell web tests (pull_request) Failing after 4m52s
Project CI / Frontend tests (pull_request) Failing after 14m17s
Project CI / Native shell tests (pull_request) Successful in 16m11s
5b1b0ddb9a
更新模型标签、提交 payload 与固定模型断言

覆盖 gpt-image-2-c 历史恢复及参数回退告警

保留历史 fixture 原值并验证 nanobanana 默认不变
k88936 added 32 commits 2026-09-19 15:38:45 +08:00
去掉 transport 模块上多余的 #[path] 声明,恢复默认模块解析

保持 client / curl 模块导出与人眼可见的目录结构一致
集成测试改用 GPT_IMAGE_2_5_GENERATION_MODEL / GPT_IMAGE_2_5_EDIT_MODEL 常量拼接断言

避免常量改名后遗留过期的 wire model 字符串断言
将 image_generation_uses_gpt_image_2_without_fallback_on_success 更名为 image_generation_uses_default_generation_model_on_success

测试实际覆盖 GPT Image 2.5 默认模型且跨模型回退已删除,旧名称会误导后续模型迁移
resolve_image_provider 改用 NANOBANANA_2_MODEL 常量匹配 VectorEngine 模型

避免常量更新后路由仍匹配写死的旧模型字符串
手动实现 Debug 并在其中把 api_key 渲染为 <redacted>

settings() 公开返回该结构体,默认 derive 会让 {:?} 直接打印 provider 凭据
移除 generation / edit 重试路径中恒为 false 的 should_fallback_to_gpt_image_2_c 判断与 record_image_model_fallback 调用

删除为计算该判断而额外调用 handle_image_response 的预解析,避免每次重试都白解析响应并下载图片

删除只断言该函数恒为 false 的单元测试,跨模型不切换已由集成测试覆盖
handle_image_response 与 build_failure_audit 接收实际 provider,不再写死 vector-engine

executor 生成 / 编辑路径传入 settings.provider,nanobanana 路径传入 VectorEngine

budget / curl / raw edit 的审计先沿用原常量,待各自按实际 provider 收口
parse_json_payload 增加 ImageProvider 参数,ResponseParse 使用实际 provider

共享的 handle_image_response 解析失败时不再把 Tiantoken 响应记成 vector-engine
request_budget_exhausted_error 接收实际 provider,审计、日志与 Request 错误不再写死 vector-engine

download_remote_image / download_images_from_urls / resolve_reference_images / parse_reference_image_data_url 逐层透传 provider

executor 与 raw image edit 调用处传入各自 settings.provider,nanobanana 下载路径传入 VectorEngine
map_curl_error 接收实际 provider,审计、日志与 Request 错误不再写死 vector-engine

executor 生成 / 编辑路径传入 settings.provider,nanobanana 路径传入 VectorEngine
created_raw_image_edit 用 settings.provider 统一驱动审计、日志与错误构造

invalid_request / invalid_input / request_error 增加 provider 参数,不再写死 vector-engine
生成 / 编辑路径的 tracing 字段与参考图数量校验错误改用 settings.provider

nanobanana 路径改用 VectorEngine provider

重试辅助函数 retry_image_send_after_delay / retry_image_upstream_status_after_delay 增加 provider 参数
auditable_image_model 识别 gpt-image-2.5、gpt-image-2.5-flare-c、gpt-image-2.5-sunburst-c 与 nanobanana2

避免新模型请求的审计 image_model 与错误元数据丢失模型归因
image_provider/mod.rs 重新声明 #[cfg(test)] mod tests,tests.rs 去掉失效的模块包裹并补齐 helper 导入

提取器 extract_image_urls / extract_b64_images 放宽到 pub(crate),parse_reference_image_data_url 调用补 provider 参数

补回 5 个此前被静默跳过的单元测试
openai_image_generation 重新导出 platform_image 的 GPT_IMAGE_2_C_MODEL

修复 editor_project 兼容历史 gpt-image-2-c 时引入的未解析导入与随之产生的模式绑定错误
editor_project_icon 的 EditorImageProviderRequest 增加 provider_model 字段

参考图存在时按编辑模型 gpt-image-2.5-sunburst-c 提交,nanobanana 与无参考图分支分别沿用自身模型
注释改为与实现一致:未显式传模型时归一为 gpt-image-2.5,而非继续走 gpt-image-2
generate_editor_image_for_owner 在参考图存在时改用 gpt-image-2.5-sunburst-c(编辑模型)

无参考图生成继续使用 gpt-image-2.5-flare-c,nanobanana 分支沿用自身模型
UI 设计图 spritesheet 提取走后端编辑端点,模型由 gpt-image-2.5-flare-c 改为 gpt-image-2.5-sunburst-c
角色主形象带参考图时走后端编辑端点,模型改用 gpt-image-2.5-sunburst-c

无参考图生成继续使用 gpt-image-2.5-flare-c
platform-image 新增 missing_reference_images_error 并在 create_image_edit_with_references_and_model 中复用

api-server 两个多参考图编辑入口在解析 provider 客户端前先校验参考图,返回 400 而不是 panic

恢复 vector_engine_multi_reference_edit_rejects_empty_references 期望的缺少参考图错误
spec_model_mud_points 在模型为空时显式回退 GPT Image 2.5 生成档,避免落到 nanobanana 档

补中文注释说明与 normalize_editor_generation_options 的缺省模型保持一致
executor 三个请求入口改用 ensure_provider_matches_model,模型不属于当前 provider 时直接返回 InvalidRequest

新增单测覆盖 provider 不一致的快速失败,并把 GPT 模型相关集成测试的 settings 调整为 tiantoken
normalize_image_model 的空白模型回退改为 GPT_IMAGE_2_5_GENERATION_MODEL,与 build_image_request_body 默认值一致

补充单测断言空白模型与默认生成模型产出同一请求体
api-server 的图片类 Agent 工具在反序列化前把 gpt-image-2 / gpt-image-2-c 归一为 gpt-image-2.5,旧持久化工具调用不再被校验直接拒绝

EditImageTool 计价改用 gpt-image-2.5 业务模型常量,并新增别名归一单测、更新受影响的校验与计价断言
源码文本断言从 Some(GPT_IMAGE_2_MODEL) 更新为 Some(GPT_IMAGE_2_5_BUSINESS_NAME),与当前锁定实现一致

用例改名为 publication_material_generation_is_locked_to_gpt_image_2_5
公开定价断言改为读取业务模型名 gpt-image-2.5 的生成档价格

补充断言公开投影不暴露 gpt-image-2 与两个 provider 具体 key
后台保存 payload 用 gpt-image-2.5-flare-c 与 gpt-image-2.5-sunburst-c 替换历史单 key,保存重新返回 200

公开定价断言改为读取业务模型名 gpt-image-2.5 的生成档价格,并新增 admin 定价读回具体 key 的断言
- 将 editor_generation_pricing_rejects_missing_required_image_size 的 fixture 从已退役的 gpt-image-2 换成 gpt-image-2.5-flare-c / gpt-image-2.5-sunburst-c
- 同步断言文案为 models.gpt-image-2.5-flare-c 缺少 2K 的泥点配置,使缺档校验仍覆盖真实必填模型
- 将 backfill_legacy_gpt_image_2_5_pricing 提升为 pub(crate) 并补充文档说明其覆盖磁盘 override 与 SpacetimeDB record 两条加载路径
- editor_generation_pricing_from_record 在 validate 前先对旧 record 做受控 backfill,避免升级前只写单个 gpt-image-2 key 时被必填校验拒绝
- 新增用例覆盖旧 record 自动补齐两个 GPT Image 2.5 key,以及显式配置不被回填覆盖
- 更新兼容回填的 TODO 注释,说明移除条件还需包含 SpacetimeDB record
- AppConfig 新增 tiantoken_base_url / tiantoken_api_key 字段,from_env 只读 TIANTOKEN_* 环境变量,不回退 VectorEngine 凭据
- AppState 改用配置字段构造编辑器 LLM 与 Tiantoken 图片 client,删除现读环境变量的 config::tiantoken_* 访问器
- editor_background_music_prompt_assist / editor_sound_effect_prompt_assist / sound_effect_translation 三处 mock LLM 夹具改用 tiantoken_* 字段,恢复 28 条失败用例
- config 用例改为断言 from_env 的 Tiantoken 凭据来源,state 用例改名为从 Tiantoken 设置构造编辑器 LLM client
- 同步更新后端架构文档与 decision-log 记录
Merge remote-tracking branch 'refs/remotes/origin/master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
c8311a47b0
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
#	server-rs/crates/api-server/src/state.rs
#	src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts
k88936 added 1 commit 2026-09-19 15:39:56 +08:00
Merge remote-tracking branch 'refs/remotes/origin/master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m17s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m25s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m34s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m37s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m3s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m23s
Project CI / Frontend tests (pull_request) Failing after 14m1s
Project CI / Repository checks (pull_request) Successful in 12m35s
Project CI / Native shell tests (pull_request) Successful in 17m2s
Project CI / Backend tests (pull_request) Successful in 17m18s
Project CI / AI game creator shell web tests (pull_request) Failing after 14m48s
3da0936c1f
Author
Member
  • 1 server-rs/crates/platform-image/src/raw_image_edit/mod.rs:151

    • 当前实现:该函数已接收通用 ImageProviderSettings,并在函数入口取 provider_label = settings.provider.as_str(),错误变体、审计与 tracing 统一用它。
    • 问题(成立):改动前所有 PlatformImageError、build_failure_audit、日志都硬编码 VECTOR_ENGINE_PROVIDER,而本函数用的是 GPT_IMAGE_2_5_EDIT_MODEL(Tiantoken),失败会被错误归因到 vector-engine。
    • 已修复:89587c9e9 修正 raw image edit 的 provider 归属。
  • 2 server-rs/crates/platform-image/src/image_provider/transport/curl.rs:198

    • 当前实现:map_curl_error(provider: ImageProvider, ...),日志与 PlatformImageError::Request 都用参数化 provider。
    • 问题(成立):transport 已泛化,但 map_curl_error 仍硬编码 VECTOR_ENGINE_PROVIDER,Tiantoken 的 libcurl 传输失败被误标。
    • 已修复:5cba2dbeb 修正 libcurl 传输错误的 provider 归属。
  • 3 server-rs/crates/platform-image/src/image_provider/transport/client.rs:14

    • 当前实现:build_image_http_client 保留 .pool_max_idle_per_host(0),即关闭 idle keep-alive。
    • 问题:评审担心每次请求(含同一次生成里的多张图片下载)都要重新做 TCP/TLS 建连。
    • 结论:核实后确认是刻意设置,本次不改。该行由 08577b66c「fix: stabilize puzzle vector engine asset generation」加入,属针对上游网关的稳定性规避;且图片生成本身是 30–120 秒级的长请求,建连开销占比很低(评审自己也标 performance · low)。若要恢复连接复用,建议单独做生产实测与对比,不要顺手改。
  • 4 server-rs/crates/platform-image/src/image_provider/transport/mod.rs:1

    • 当前实现:mod client; / mod curl; 普通模块声明。
    • 问题(成立):#[path = "client.rs"] 这类属性冗余,且暗示非标准布局。
    • 已修复:99081455e 移除 image_provider transport 冗余 path 属性。
  • 5 server-rs/crates/platform-image/tests/image_provider.rs:533-534

    • 当前实现:断言改为用常量拼装,例如 format!("\"model\":\"{GPT_IMAGE_2_5_GENERATION_MODEL}\""),并导入 GPT_IMAGE_2_5_EDIT_MODEL 覆盖编辑体断言。
    • 问题(成立):硬编码 wire 模型字符串,常量改名后会留下陈旧断言。
    • 已修复:345c9c3a9 收敛 ImageProvider 集成测试的模型断言。
  • 6 server-rs/crates/platform-image/tests/image_provider.rs:539

    • 当前实现:用例改名为 image_generation_uses_default_generation_model_on_success。
    • 问题(成立):旧名仍含 gpt_image_2 / without_fallback,与实际断言的 2.5 + 无跨模型回退不符。
    • 已修复:fa2b36654 修正 ImageProvider 生成测试的错误命名。
  • 7 server-rs/crates/platform-image/src/image_provider/protocol/response.rs:15

    • 当前实现:handle_image_response(..., provider: ImageProvider, ...),Upstream / ResponseParse / MissingImage 与 tracing 字段都用 provider.as_str()。
    • 问题(成立):共享响应处理硬编码 vector-engine,Tiantoken 失败会 PlatformImageError::provider() == "vector-engine",污染重试/审计/预算判定。
    • 已修复:90b77d6bd 修正共享响应处理的 provider 归属。
  • 8 server-rs/crates/platform-image/src/image_provider/protocol/request.rs:20

    • 当前实现:normalize_image_model 把空/空白输入归一到 GPT_IMAGE_2_5_GENERATION_MODEL,与 build_image_request_body 的默认值一致;显式传入的模型(含历史 gpt-image-2)原样保留,交给 resolve_image_provider 路由。
    • 问题(成立):build_image_request_body_with_model("") 会悄悄用旧 gpt-image-2,与新的默认生成模型不一致。
    • 已修复:939c35a9b 统一缺省图片模型的归一化结果(并补测试断言两者产出同一请求体)。
  • 9 server-rs/crates/platform-image/src/image_provider/protocol/request.rs:107

    • 当前实现:resolve_image_provider 用 NANOBANANA_2_MODEL 常量匹配 nanobanana 分支。
    • 问题(成立):原实现匹配字面量,常量更新后会路由不到。
    • 已修复:874d061cb 统一 ImageProvider 模型路由使用的 nanobanana 常量。
  • 10 server-rs/crates/platform-editor-agent/src/agent/tools/generate_ui_design.rs:37

    • 当前实现:UI 设计的默认值与 schema enum 是 GPT_IMAGE_2_5_BUSINESS_NAME;api-server 适配层在反序列化前把 gpt-image-2 / gpt-image-2-c 归一为 gpt-image-2.5。
    • 问题(成立):平台层只接受 nanobanana2 与 gpt-image-2.5,历史持久化的 UI 设计参数会被 validate_args 判 InvalidModel,并带崩 3 个 api-server 测试。
    • 已修复:bd0a06e3d 兼容图片工具的历史 gpt-image-2 模型别名(新增 normalize_legacy_image_model_args 并接入图片类工具的校验与计价入口)。
  • 11 server-rs/crates/platform-editor-agent/src/agent/tools/image_generation_options.rs:78

    • 当前实现:supported_image_sizes 只认 nanobanana2 / gpt-image-2.5;generate-image 的历史 gpt-image-2 参数由 api-server 边界先归一。
    • 问题(成立):同 10,generate-image 会直接拒绝 gpt-image-2(dyn_pricing_uses_the_supplied_runtime_snapshot 因此失败)。
    • 已修复:bd0a06e3d。
  • 12 server-rs/crates/platform-image/src/image_provider/runtime/executor.rs:70-76

    • 当前实现:生成 / nanobanana / 编辑三个入口统一走新增的 ensure_provider_matches_model:先 resolve_image_provider,再与 settings.provider 比对,不一致直接返回 InvalidRequest(含中文原因)。
    • 问题(成立):原实现丢弃 resolve_image_provider 结果,会把属于另一个 provider 的模型配上当前 provider 的 base_url / api_key 发出去(用错密钥打错端点);编辑入口下方的参考图数量错误分支还硬编码 VECTOR_ENGINE_PROVIDER。
    • 已修复:42fdb57ef 校验图片请求模型与 provider 绑定一致(含新增单测,并把 GPT 模型相关集成测试的 settings provider 调整为 tiantoken);参考图数量分支的 provider 归属由 e393a803f 一并修掉。
  • 13 server-rs/crates/platform-image/src/image_provider/runtime/executor.rs:855-858

    • 当前实现:恒为 false 的跨模型回退判断、record_image_model_fallback 及为它服务的“先解析响应/下载图片”预计算都已删除,重试只保留同模型重试。
    • 问题(成立):死分支不可达,且每次重试都白跑一次响应解析与图片下载。
    • 已修复:1c950df11 删除已失效的跨模型回退分支。
  • 14 server-rs/crates/platform-image/src/image_provider/runtime/budget.rs:89

    • 当前实现:request_budget_exhausted_error(provider: ImageProvider, ...),tracing 与错误字段都用该 provider;image_source.rs 的参考图下载错误同样带上 provider。
    • 问题(成立):共享 helper 硬编码 vector-engine,Tiantoken 预算耗尽被误标。
    • 已修复:d3407e891 修正预算与图片下载错误的 provider 归属。
  • 15 server-rs/crates/platform-image/src/image_provider/runtime/types.rs:18-22

    • 当前实现:ImageProviderSettings 手写 Debug,api_key 固定输出 <redacted>。
    • 问题(成立):派生 Debug 配合公开的 settings() 访问器会打印凭据。
    • 已修复:6c33459b9 为 ImageProviderSettings 增加脱敏 Debug 实现。
  • 16 server-rs/crates/api-server/src/editor_project.rs:3191-3195

    • 当前实现:provider_model 按分支选择——nanobanana2 用自身;无参考图用 GPT_IMAGE_2_5_GENERATION_MODEL;有参考图(走 images/edits)用 GPT_IMAGE_2_5_EDIT_MODEL。
    • 问题(成立):原实现所有非 nanobanana 请求都用生成模型,快速编辑/参考图生成会拿 flare-c 打编辑端点。
    • 已修复:ae257a6ff 按端点选择编辑器生成的 provider_model。
  • 17 server-rs/crates/api-server/src/editor_project.rs:8517

    • 当前实现:该 spritesheet 素材提取调用点传 GPT_IMAGE_2_5_EDIT_MODEL。
    • 问题(成立):这是编辑操作,却传了生成模型。
    • 已修复:8924bf335 修正 UI 设计素材提取的编辑模型。
  • 18 server-rs/crates/api-server/src/character_visual_assets.rs:952

    • 当前实现:按 reference_images 是否为空在 GPT_IMAGE_2_5_GENERATION_MODEL / GPT_IMAGE_2_5_EDIT_MODEL 间选择。
    • 问题(成立):图生图角色形象走编辑端点却保留生成模型。
    • 已修复:5ce267ae8 按参考图选择角色主形象的 provider 模型。
  • 19 server-rs/crates/api-server/src/openai_image_generation.rs:410-420

    • 当前实现:在 client_for_model 之前先做本地空参考图校验,直接返回 400「缺少参考图」;client_for_model 仍保留 expect(依赖启动期必然初始化客户端这一前提)。
    • 问题(成立):原实现先 expect 出客户端,再走参考图校验,缺参考图的请求会 panic。
    • 已修复:1a2e60bf0 修复图片编辑缺少参考图时的 panic(vector_engine_multi_reference_edit_rejects_empty_references 恢复 400 + 缺少参考图)。残留可选优化:client_for_model 的 expect 仍是硬失败,若要彻底改成 Result 属于接口级调整,未在本次范围。
  • 20 server-rs/crates/api-server/src/editor_project.rs:4007

    • 当前实现:注释已改为「未显式传模型的旧普通生成、快速编辑和生成规范统一归一到 gpt-image-2.5;角色 / 图标素材入口由前端显式传入 nanobanana2 默认值」。
    • 问题(成立):旧注释仍写回退到 gpt-image-2,与实现不符。
    • 已修复:ff4f42d2f 修正编辑器生成模型归一化注释。
  • 21 src/components/image-editor/ImageCanvasGenerationModel.ts:395-398

    • 当前实现:EDITOR_MODEL_MUD_POINT_CONFIG 里是 [IMAGE_MODEL_GPT_IMAGE_2_5];normalizeEditorImageModel 会把 gpt-image-2 / gpt-image-2-c 映射为 gpt-image-2.5;calculateEditorImageModelPrice 先归一再查价;EDITOR_IMAGE_MODEL_MUD_POINT_CONFIG 仍保留历史 gpt-image-2 档位供测试与对读。
    • 问题:评审的前提是「normalizeEditorImageModel 仍原样返回 gpt-image-2,导致回落到 nanobanana 价(12/24)」。该前提在当前代码已不成立(属过期结论)。
    • 结论:已由 normalizeEditorImageModel 的映射解决,无需再改。现有用例「支持用后端下发的模型定价覆盖按钮泥点」只下发 gpt-image-2.5 的价格,仍断言 calculateEditorImageModelPrice('gpt-image-2','1K') === 29,当前通过。
  • 22 server-rs/crates/api-server/src/editor_generation_config.rs:164

    • 当前实现:spec_model_mud_points 对 None / 空模型显式回退 EDITOR_IMAGE_MODEL_GPT_IMAGE_2_5_GENERATION,只有非空才走 normalize_editor_image_model。
    • 问题(成立):normalize_editor_image_model(None) 会落到 nanobanana 档,生成规范计价会从 5 意外变成 24。
    • 已修复:0c54cca81 修正生成规范缺省模型的计价档。
  • 23 server-rs/crates/platform-image/src/image_provider/protocol/payload.rs:3

    • 当前实现:parse_json_payload(..., provider: ImageProvider),ResponseParse 用 provider.as_str()。
    • 问题(成立):共享 JSON 解析函数硬编码 vector-engine。
    • 已修复:4888a0eb0 修正响应解析错误的 provider 归属。
  • 24 server-rs/crates/platform-editor-agent/src/agent/tools/edit_image.rs:158

    • 当前实现:EditImageTool::validate_args 只接受 GPT_IMAGE_2_5_BUSINESS_NAME;api-server 适配层在进入校验前归一历史别名,EditImageTool::pricing 也已从 GPT_IMAGE_2_MODEL 改为 GPT_IMAGE_2_5_BUSINESS_NAME。
    • 问题(成立):历史持久化的 edit-image 调用 model: "gpt-image-2" 会判 InvalidModel;pricing 仍引旧常量。
    • 已修复:bd0a06e3d。
  • 25 server-rs/crates/platform-image/src/image_provider/runtime/executor.rs:840

    • 当前实现:auditable_image_model 覆盖 gpt-image-2、gpt-image-2-c、gpt-image-2.5、gpt-image-2.5-flare-c、gpt-image-2.5-sunburst-c、gemini-3.1-flash-image-preview。
    • 问题(成立):只认旧常量,新默认路径的审计 image_model 丢字段。
    • 已修复:0817ea815 补齐审计模型白名单的 GPT Image 2.5 与 nanobanana 模型。
  • 26 server-rs/crates/platform-image/src/image_provider/tests.rs:9

    • 当前实现:image_provider/mod.rs 声明了 #[cfg(test)] mod tests;,extract_image_urls / extract_b64_images 提升为 pub(crate),测试模块补齐导入与 base64::Engine 用法。
    • 问题(成立):该单测模块被孤立,cargo test 会静默跳过其中所有用例。
    • 已修复:c86ac0422 恢复 image_provider 单元测试模块(cargo test -p platform-image 现在会真正执行这些用例)。
  • 27 src/components/image-editor/ImageCanvasGenerationModel.ts:137

    • 当前实现:EDITOR_IMAGE_MODEL_OPTIONS 只剩 nanobanana2 与 gpt-image-2.5;decodeCanvasGenerationInputs 的 normalizeStringOption 会先跑 normalizeEditorImageModel(历史值 → gpt-image-2.5)再判断是否在选项内,并对历史值追加 model 回退告警。
    • 问题:评审担心历史 gpt-image-2 会因不在选项里被重写成 DEFAULT_IMAGE_MODEL(nanobanana2)。当前代码已不会这样:归一发生在选项校验之前,历史值稳定迁移到 gpt-image-2.5。
    • 结论:已解决(同批 5b1b0ddb9 已补回归用例「restores the concrete legacy GPT image value to GPT Image 2.5 with a warning」,当前通过)。无需再改。
  • 28 server-rs/crates/api-server/src/editor_generation_config.rs:271-276 —— 下面先把背景讲透

    A. 这套「泥点定价配置」是什么

    • 编辑器每次生成(图片 / 视频 / 音效 / 音乐)都要按「模型 + 尺寸档位」扣虚拟货币「泥点」。价格表不是写死在代码里,而是一份 JSON:
      models: { "<模型 key>": { unit: "perGeneration" | "perSecond", price: 数字 } 或 { prices: { "1K": 数字, "2K": 数字 } } }。
    • 代码:EditorGenerationPricingConfig + EditorGenerationPricingStore(editor_generation_config.rs)。
    • 三份来源,优先级从低到高:
      1. 编译进二进制的默认 JSON:crates/api-server/config/editor-generation-pricing.default.json(include_str!)。
      2. 磁盘 override 文件:/var/lib/genarrative/editor-generation-pricing/editor-generation-pricing.override.json(或仓库里的 .app/editor-generation-pricing.override.json)。
      3. SpacetimeDB 全局配置表里的 record(后台 admin 保存的就是它)。
    • 读价格时(state.rs::editor_generation_pricing())会先拉 SpacetimeDB record,成功就用它;失败则 warn! 后回退到本地缓存/override/default 快照。

    B. 本次迁移对 key 做了什么

    • 旧:GPT 图片只有一个定价 key gpt-image-2。
    • 新:拆成两个具体 provider model 的 key —— gpt-image-2.5-flare-c(生成,含带参考图的普通生成)与 gpt-image-2.5-sunburst-c(编辑 / 快速编辑 / 原位修改 / raw edit)。
    • validate() 现在把这两个 key 连同尺寸档位(各需 1K、2K)列为必填,和其它模型(nanobanana、视频、音效、音乐)一起校验。

    C. 为什么会出现 backfill(这就是你问的那个函数)

    • 价格表是已经持久化的数据:磁盘上的 override 文件、SpacetimeDB 里的 record,都是在这次拆分之前写下的,里面只有旧的单 key gpt-image-2。
    • 新增的必填校验会直接把这类历史数据判为非法。
    • backfill_legacy_gpt_image_2_5_pricing(editor_generation_config.rs:396-430)就是针对这件事的一次性兼容垫片,逻辑只有四步:
      1. 两个新 key 都已经存在 → 直接返回,什么都不做;
      2. 否则从当前配置里取旧 key gpt-image-2,取不到就退回默认 JSON 里的 gpt-image-2;
      3. 用这份旧价格 or_insert 到 gpt-image-2.5-flare-c 与 gpt-image-2.5-sunburst-c(只补缺失的 key,绝不覆盖已有值);
      4. 连旧 key 都找不到就报错,避免静默产出一份没有 GPT 图片价格的配置。
    • 一句话:它是「旧单 key 配置 → 新双 key 配置」的只读兼容补齐,只在读取内存里补,不改写任何持久化数据。ADR(docs/adr/【ADR】GPT Image 2.5模型路由与历史值兼容-2026-09-18.md)明确写了「旧单 key 配置只允许受控 backfill,并留下兼容 TODO」「不执行数据库历史迁移」,就是这个意思。

    D. 问题(成立)

    • 这个垫片目前只挂在「磁盘 override 文件」这一条加载路径上(load_editor_generation_pricing_from_candidates 里读完 override JSON 后调用)。
    • SpacetimeDB 那条路径走的是另一个函数:crates/api-server/src/state.rs::editor_generation_pricing_from_record()(从第 444 行起)。它把 record 里的 models 拼成 EditorGenerationPricingConfig,只补了音效模型(ElevenLabs),然后直接 config.validate() —— 没有调用 GPT 图片的 backfill。
    • 结果:升级前就已写进 SpacetimeDB 的定价记录(只有 gpt-image-2)读回来时校验失败。

    E. 影响有多大(说清楚,不用慌)

    • 失败会落进 editor_generation_pricing() 的 Err(error) 分支 → warn! 后返回本地快照。所以不会 500,也不会算成 0 泥点。
    • 真实后果:后台在 SpacetimeDB 里配过的价格被静默忽略,回落到默认/override 快照,直到有人在后台重新保存一次定价(保存路径会写出完整的新 key)。表现是「后台显示的价与实际扣费不一致」。

    F. 建议修法(很小,但会碰到线上已持久化数据,所以留给你点头)

    1. backfill_legacy_gpt_image_2_5_pricing 由 fn 改为 pub(crate) fn;
    2. 在 state.rs::editor_generation_pricing_from_record 里,let config = EditorGenerationPricingConfig { models }; 之后、config.validate()?; 之前插入一行
      crate::editor_generation_config::backfill_legacy_gpt_image_2_5_pricing(&mut config, legacy_fallback, "SpacetimeDB 模型定价配置")?;
      (legacy_fallback 已经是该函数的入参、就是本地快照,正好当兜底来源);
    3. 顺手把 # TODO: 注释改成「record 与 override 两条路径都已回填」,等线上数据全部重存过一轮后再删掉这块兼容。
    • 可选的其它做法都不推荐:在 validate() 里塞兼容(把兼容逻辑散进校验)、或做一次性数据迁移(ADR 明确不迁移历史记录)。
    • 已修复:045910989 按上面 1 / 2 / 3 落地(backfill_legacy_gpt_image_2_5_pricing 改 pub(crate)、editor_generation_pricing_from_record 在 validate() 前先回填、TODO 注释改成覆盖 override 与 record 两条路径),并补两条用例:旧 record 自动补齐两个 2.5 key、显式 2.5 key 不被回填覆盖。
  • 29 server-rs/crates/api-server/src/editor_generation_config.rs:168(实际是 editor_generation_config.rs:1103 的用例)

    • 当前实现:用例 editor_generation_pricing_rejects_missing_required_image_size 构造一份 JSON,GPT 图片只有 "gpt-image-2": { "1K": 20 }(故意缺 2K),想验证「缺尺寸档位要报错」,末尾断言错误文案包含 gpt-image-2 缺少 2K。
    • 问题(成立,现在就是红的):validate() 的顺序是 nanobanana → gpt-image-2.5-flare-c → gpt-image-2.5-sunburst-c → 视频……。这份 JSON 里两个新 key 根本不存在,所以第一个报错是 models 缺少模型 gpt-image-2.5-flare-c 的泥点配置,走不到「缺 2K」那一步。用例的意图(缺档位要拦下来)依然有效,只是夹具和断言过期了。
    • 建议修法(推荐 a):
      • (a) 夹具改成两个新 key:gpt-image-2.5-flare-c 给 { "1K": 20 }(缺 2K),gpt-image-2.5-sunburst-c 给 { "1K": 3, "2K": 5 },断言改成包含 models.gpt-image-2.5-flare-c 缺少 2K 的泥点配置。既保留原意又对齐新 key。
      • (b) 保留旧夹具、断言改成 缺少模型 gpt-image-2.5-flare-c:不推荐,容易被误读成「校验只看新 key」,也测不到缺档位分支。
    • 为什么留给你:29 和 28 是同一件事的两面(28 改兼容回填、29 改夹具与断言)。你按 28 的方案改完,顺手跑一次 cargo test -p api-server editor_generation_config 一起收口最省事;我不想在你的迁移决策落地前先把测试改成只反映当前(可能还会变)的实现。
    • 已修复:6318baf7c 按 (a) 落地:夹具换成两个新 key(gpt-image-2.5-flare-c 只给 1K 缺 2K,gpt-image-2.5-sunburst-c 给 1K / 2K),断言改为包含 models.gpt-image-2.5-flare-c 缺少 2K 的泥点配置,用例仍覆盖「缺档位必须拦下来」。
  • 30 server-rs/crates/api-server/src/editor_project.rs:380

    • 当前实现:editor_project_icon.rs 的 EditorImageProviderRequest 构造点已补 provider_model,且因该路径始终带参考图,取值是 GPT_IMAGE_2_5_EDIT_MODEL。
    • 问题(成立):新增必填字段会让该构造点 E0063: missing field provider_model 编译失败(critical)。
    • 已修复:2e2ed47be 补齐图标 spritesheet 请求的 provider_model。(另:dedc58cce 顺带修掉同批遗留的 GPT_IMAGE_2_C_MODEL 未重导出编译错误。)

附:原 review 之外的当前失败项

  • A crates/api-server/src/editor_project.rs::publication_material_generation_is_locked_to_gpt_image_2

    • 当前实现:该用例扫描 generate_editor_image_for_owner 的源码文本,要求出现 Some(GPT_IMAGE_2_MODEL)。
    • 问题(成立):实现已改成 Some(GPT_IMAGE_2_5_BUSINESS_NAME),文本断言过期,用例失败。
    • 已修复:fa84a364d 修正宣发素材生成锁定的模型断言(顺带把用例改名为 ..._gpt_image_2_5)。
  • B crates/api-server/src/app.rs::public_editor_generation_pricing_route_returns_default_config

    • 当前实现:用例断言公开投影 payload["models"]["gpt-image-2"]["prices"]["1K"] == 3。
    • 问题(成立):public_projection() 现在会删除 gpt-image-2、把 gpt-image-2.5-flare-c 投影成业务名 gpt-image-2.5,所以旧 key 读出来是 Null,用例失败。
    • 已修复:4415723f7 同步公开定价路由用例到 GPT Image 2.5 公开投影(断言 gpt-image-2.5 的 3 / 5,并断言 gpt-image-2、gpt-image-2.5-flare-c、gpt-image-2.5-sunburst-c 都不公开)。
  • C crates/api-server/src/app.rs::admin_editor_generation_pricing_route_saves_config_and_updates_public_route

    • 当前实现:后台保存用例提交一份只含旧单 key gpt-image-2 的定价 JSON,期望 200。
    • 问题(成立):validate() 新增两个必填 2.5 档位后该 JSON 非法,接口返回 400(save_editor_generation_pricing → store.replace → validate → Invalid),用例失败。
    • 已修复:28e6e67e9 补齐后台定价保存用例的 GPT Image 2.5 档位(payload 换成 gpt-image-2.5-flare-c 31/62 与 gpt-image-2.5-sunburst-c 41/82;公开断言按生成档 62 校验并确认不透出具体 key;新增 admin 读回具体 key 的断言,覆盖 ADR 里「admin 定价接口是 concrete key 的例外」)。
  • D 环境/外部依赖类失败(原判断为「与本次改动无关」,复核后为可修复的代码/夹具错配)

    • editor_background_music_prompt_assist::tests::*(16 项)、editor_sound_effect_prompt_assist::tests::*(4 项)、vector_engine_audio_generation::sound_effect_translation::tests::*(8 项)、state::tests::app_state_builds_editor_agent_llm_client_from_vector_engine_settings(1 项)。
    • 现象:多为 LLM / vector-engine 音频链路的 mock、缺凭据或客户端未初始化导致(例如 editor agent LLM client should exist)。
    • 复核结论(原判断不成立):不需要凭据也不需要网络。根因是 b294cbc3d 把 Tiantoken 凭据改成只从进程环境变量 TIANTOKEN_* 现读之后,这 3 组 mock 夹具仍用 AppConfig.vector_engine_base_url / vector_engine_api_key 构造 AppState,state.vector_engine_llm_client() 于是全是 None,路由统一回 503 editor_llm_unavailable;state 那条同源,断言里的 base URL 与配置来源都已过期。
    • 已修复:1c1deff73 把读取点从 AppState::new 前移到 AppConfig::from_env,新增 AppConfig.tiantoken_base_url / AppConfig.tiantoken_api_key(只读 TIANTOKEN_*,仍不回退 VectorEngine 凭据),删除现读环境变量的 config::tiantoken_* 访问器;三处 mock 夹具与 state 用例改用 config 字段,避免在并行用例里 set_var 互相踩 TIANTOKEN_*。cargo test -p api-server 现为 1060 passed / 0 failed / 6 ignored。
- [x] 1 `server-rs/crates/platform-image/src/raw_image_edit/mod.rs:151` - 当前实现:该函数已接收通用 `ImageProviderSettings`,并在函数入口取 `provider_label = settings.provider.as_str()`,错误变体、审计与 tracing 统一用它。 - 问题(成立):改动前所有 `PlatformImageError`、`build_failure_audit`、日志都硬编码 `VECTOR_ENGINE_PROVIDER`,而本函数用的是 `GPT_IMAGE_2_5_EDIT_MODEL`(Tiantoken),失败会被错误归因到 vector-engine。 - 已修复:`89587c9e9` 修正 raw image edit 的 provider 归属。 - [x] 2 `server-rs/crates/platform-image/src/image_provider/transport/curl.rs:198` - 当前实现:`map_curl_error(provider: ImageProvider, ...)`,日志与 `PlatformImageError::Request` 都用参数化 provider。 - 问题(成立):transport 已泛化,但 `map_curl_error` 仍硬编码 `VECTOR_ENGINE_PROVIDER`,Tiantoken 的 libcurl 传输失败被误标。 - 已修复:`5cba2dbeb` 修正 libcurl 传输错误的 provider 归属。 - [x] 3 `server-rs/crates/platform-image/src/image_provider/transport/client.rs:14` - 当前实现:`build_image_http_client` 保留 `.pool_max_idle_per_host(0)`,即关闭 idle keep-alive。 - 问题:评审担心每次请求(含同一次生成里的多张图片下载)都要重新做 TCP/TLS 建连。 - 结论:**核实后确认是刻意设置,本次不改**。该行由 `08577b66c`「fix: stabilize puzzle vector engine asset generation」加入,属针对上游网关的稳定性规避;且图片生成本身是 30–120 秒级的长请求,建连开销占比很低(评审自己也标 `performance · low`)。若要恢复连接复用,建议单独做生产实测与对比,不要顺手改。 - [x] 4 `server-rs/crates/platform-image/src/image_provider/transport/mod.rs:1` - 当前实现:`mod client;` / `mod curl;` 普通模块声明。 - 问题(成立):`#[path = "client.rs"]` 这类属性冗余,且暗示非标准布局。 - 已修复:`99081455e` 移除 image_provider transport 冗余 path 属性。 - [x] 5 `server-rs/crates/platform-image/tests/image_provider.rs:533-534` - 当前实现:断言改为用常量拼装,例如 `format!("\"model\":\"{GPT_IMAGE_2_5_GENERATION_MODEL}\"")`,并导入 `GPT_IMAGE_2_5_EDIT_MODEL` 覆盖编辑体断言。 - 问题(成立):硬编码 wire 模型字符串,常量改名后会留下陈旧断言。 - 已修复:`345c9c3a9` 收敛 ImageProvider 集成测试的模型断言。 - [x] 6 `server-rs/crates/platform-image/tests/image_provider.rs:539` - 当前实现:用例改名为 `image_generation_uses_default_generation_model_on_success`。 - 问题(成立):旧名仍含 `gpt_image_2` / `without_fallback`,与实际断言的 2.5 + 无跨模型回退不符。 - 已修复:`fa2b36654` 修正 ImageProvider 生成测试的错误命名。 - [x] 7 `server-rs/crates/platform-image/src/image_provider/protocol/response.rs:15` - 当前实现:`handle_image_response(..., provider: ImageProvider, ...)`,`Upstream` / `ResponseParse` / `MissingImage` 与 tracing 字段都用 `provider.as_str()`。 - 问题(成立):共享响应处理硬编码 vector-engine,Tiantoken 失败会 `PlatformImageError::provider() == "vector-engine"`,污染重试/审计/预算判定。 - 已修复:`90b77d6bd` 修正共享响应处理的 provider 归属。 - [x] 8 `server-rs/crates/platform-image/src/image_provider/protocol/request.rs:20` - 当前实现:`normalize_image_model` 把空/空白输入归一到 `GPT_IMAGE_2_5_GENERATION_MODEL`,与 `build_image_request_body` 的默认值一致;显式传入的模型(含历史 `gpt-image-2`)原样保留,交给 `resolve_image_provider` 路由。 - 问题(成立):`build_image_request_body_with_model("")` 会悄悄用旧 `gpt-image-2`,与新的默认生成模型不一致。 - 已修复:`939c35a9b` 统一缺省图片模型的归一化结果(并补测试断言两者产出同一请求体)。 - [x] 9 `server-rs/crates/platform-image/src/image_provider/protocol/request.rs:107` - 当前实现:`resolve_image_provider` 用 `NANOBANANA_2_MODEL` 常量匹配 nanobanana 分支。 - 问题(成立):原实现匹配字面量,常量更新后会路由不到。 - 已修复:`874d061cb` 统一 ImageProvider 模型路由使用的 nanobanana 常量。 - [x] 10 `server-rs/crates/platform-editor-agent/src/agent/tools/generate_ui_design.rs:37` - 当前实现:UI 设计的默认值与 schema enum 是 `GPT_IMAGE_2_5_BUSINESS_NAME`;api-server 适配层在反序列化前把 `gpt-image-2` / `gpt-image-2-c` 归一为 `gpt-image-2.5`。 - 问题(成立):平台层只接受 nanobanana2 与 gpt-image-2.5,历史持久化的 UI 设计参数会被 `validate_args` 判 `InvalidModel`,并带崩 3 个 api-server 测试。 - 已修复:`bd0a06e3d` 兼容图片工具的历史 gpt-image-2 模型别名(新增 `normalize_legacy_image_model_args` 并接入图片类工具的校验与计价入口)。 - [x] 11 `server-rs/crates/platform-editor-agent/src/agent/tools/image_generation_options.rs:78` - 当前实现:`supported_image_sizes` 只认 nanobanana2 / gpt-image-2.5;`generate-image` 的历史 `gpt-image-2` 参数由 api-server 边界先归一。 - 问题(成立):同 10,`generate-image` 会直接拒绝 `gpt-image-2`(`dyn_pricing_uses_the_supplied_runtime_snapshot` 因此失败)。 - 已修复:`bd0a06e3d`。 - [x] 12 `server-rs/crates/platform-image/src/image_provider/runtime/executor.rs:70-76` - 当前实现:生成 / nanobanana / 编辑三个入口统一走新增的 `ensure_provider_matches_model`:先 `resolve_image_provider`,再与 `settings.provider` 比对,不一致直接返回 `InvalidRequest`(含中文原因)。 - 问题(成立):原实现丢弃 `resolve_image_provider` 结果,会把属于另一个 provider 的模型配上当前 provider 的 `base_url` / `api_key` 发出去(用错密钥打错端点);编辑入口下方的参考图数量错误分支还硬编码 `VECTOR_ENGINE_PROVIDER`。 - 已修复:`42fdb57ef` 校验图片请求模型与 provider 绑定一致(含新增单测,并把 GPT 模型相关集成测试的 settings provider 调整为 tiantoken);参考图数量分支的 provider 归属由 `e393a803f` 一并修掉。 - [x] 13 `server-rs/crates/platform-image/src/image_provider/runtime/executor.rs:855-858` - 当前实现:恒为 false 的跨模型回退判断、`record_image_model_fallback` 及为它服务的“先解析响应/下载图片”预计算都已删除,重试只保留同模型重试。 - 问题(成立):死分支不可达,且每次重试都白跑一次响应解析与图片下载。 - 已修复:`1c950df11` 删除已失效的跨模型回退分支。 - [x] 14 `server-rs/crates/platform-image/src/image_provider/runtime/budget.rs:89` - 当前实现:`request_budget_exhausted_error(provider: ImageProvider, ...)`,tracing 与错误字段都用该 provider;`image_source.rs` 的参考图下载错误同样带上 provider。 - 问题(成立):共享 helper 硬编码 vector-engine,Tiantoken 预算耗尽被误标。 - 已修复:`d3407e891` 修正预算与图片下载错误的 provider 归属。 - [x] 15 `server-rs/crates/platform-image/src/image_provider/runtime/types.rs:18-22` - 当前实现:`ImageProviderSettings` 手写 `Debug`,`api_key` 固定输出 `<redacted>`。 - 问题(成立):派生 `Debug` 配合公开的 `settings()` 访问器会打印凭据。 - 已修复:`6c33459b9` 为 ImageProviderSettings 增加脱敏 Debug 实现。 - [x] 16 `server-rs/crates/api-server/src/editor_project.rs:3191-3195` - 当前实现:`provider_model` 按分支选择——nanobanana2 用自身;无参考图用 `GPT_IMAGE_2_5_GENERATION_MODEL`;有参考图(走 `images/edits`)用 `GPT_IMAGE_2_5_EDIT_MODEL`。 - 问题(成立):原实现所有非 nanobanana 请求都用生成模型,快速编辑/参考图生成会拿 `flare-c` 打编辑端点。 - 已修复:`ae257a6ff` 按端点选择编辑器生成的 provider_model。 - [x] 17 `server-rs/crates/api-server/src/editor_project.rs:8517` - 当前实现:该 spritesheet 素材提取调用点传 `GPT_IMAGE_2_5_EDIT_MODEL`。 - 问题(成立):这是编辑操作,却传了生成模型。 - 已修复:`8924bf335` 修正 UI 设计素材提取的编辑模型。 - [x] 18 `server-rs/crates/api-server/src/character_visual_assets.rs:952` - 当前实现:按 `reference_images` 是否为空在 `GPT_IMAGE_2_5_GENERATION_MODEL` / `GPT_IMAGE_2_5_EDIT_MODEL` 间选择。 - 问题(成立):图生图角色形象走编辑端点却保留生成模型。 - 已修复:`5ce267ae8` 按参考图选择角色主形象的 provider 模型。 - [x] 19 `server-rs/crates/api-server/src/openai_image_generation.rs:410-420` - 当前实现:在 `client_for_model` 之前先做本地空参考图校验,直接返回 400「缺少参考图」;`client_for_model` 仍保留 `expect`(依赖启动期必然初始化客户端这一前提)。 - 问题(成立):原实现先 `expect` 出客户端,再走参考图校验,缺参考图的请求会 panic。 - 已修复:`1a2e60bf0` 修复图片编辑缺少参考图时的 panic(`vector_engine_multi_reference_edit_rejects_empty_references` 恢复 400 + 缺少参考图)。残留可选优化:`client_for_model` 的 `expect` 仍是硬失败,若要彻底改成 `Result` 属于接口级调整,未在本次范围。 - [x] 20 `server-rs/crates/api-server/src/editor_project.rs:4007` - 当前实现:注释已改为「未显式传模型的旧普通生成、快速编辑和生成规范统一归一到 gpt-image-2.5;角色 / 图标素材入口由前端显式传入 nanobanana2 默认值」。 - 问题(成立):旧注释仍写回退到 `gpt-image-2`,与实现不符。 - 已修复:`ff4f42d2f` 修正编辑器生成模型归一化注释。 - [x] 21 `src/components/image-editor/ImageCanvasGenerationModel.ts:395-398` - 当前实现:`EDITOR_MODEL_MUD_POINT_CONFIG` 里是 `[IMAGE_MODEL_GPT_IMAGE_2_5]`;`normalizeEditorImageModel` 会把 `gpt-image-2` / `gpt-image-2-c` 映射为 `gpt-image-2.5`;`calculateEditorImageModelPrice` 先归一再查价;`EDITOR_IMAGE_MODEL_MUD_POINT_CONFIG` 仍保留历史 `gpt-image-2` 档位供测试与对读。 - 问题:评审的前提是「`normalizeEditorImageModel` 仍原样返回 `gpt-image-2`,导致回落到 nanobanana 价(12/24)」。**该前提在当前代码已不成立**(属过期结论)。 - 结论:已由 `normalizeEditorImageModel` 的映射解决,无需再改。现有用例「支持用后端下发的模型定价覆盖按钮泥点」只下发 `gpt-image-2.5` 的价格,仍断言 `calculateEditorImageModelPrice('gpt-image-2','1K') === 29`,当前通过。 - [x] 22 `server-rs/crates/api-server/src/editor_generation_config.rs:164` - 当前实现:`spec_model_mud_points` 对 `None` / 空模型显式回退 `EDITOR_IMAGE_MODEL_GPT_IMAGE_2_5_GENERATION`,只有非空才走 `normalize_editor_image_model`。 - 问题(成立):`normalize_editor_image_model(None)` 会落到 nanobanana 档,生成规范计价会从 5 意外变成 24。 - 已修复:`0c54cca81` 修正生成规范缺省模型的计价档。 - [x] 23 `server-rs/crates/platform-image/src/image_provider/protocol/payload.rs:3` - 当前实现:`parse_json_payload(..., provider: ImageProvider)`,`ResponseParse` 用 `provider.as_str()`。 - 问题(成立):共享 JSON 解析函数硬编码 vector-engine。 - 已修复:`4888a0eb0` 修正响应解析错误的 provider 归属。 - [x] 24 `server-rs/crates/platform-editor-agent/src/agent/tools/edit_image.rs:158` - 当前实现:`EditImageTool::validate_args` 只接受 `GPT_IMAGE_2_5_BUSINESS_NAME`;api-server 适配层在进入校验前归一历史别名,`EditImageTool::pricing` 也已从 `GPT_IMAGE_2_MODEL` 改为 `GPT_IMAGE_2_5_BUSINESS_NAME`。 - 问题(成立):历史持久化的 `edit-image` 调用 `model: "gpt-image-2"` 会判 `InvalidModel`;pricing 仍引旧常量。 - 已修复:`bd0a06e3d`。 - [x] 25 `server-rs/crates/platform-image/src/image_provider/runtime/executor.rs:840` - 当前实现:`auditable_image_model` 覆盖 `gpt-image-2`、`gpt-image-2-c`、`gpt-image-2.5`、`gpt-image-2.5-flare-c`、`gpt-image-2.5-sunburst-c`、`gemini-3.1-flash-image-preview`。 - 问题(成立):只认旧常量,新默认路径的审计 `image_model` 丢字段。 - 已修复:`0817ea815` 补齐审计模型白名单的 GPT Image 2.5 与 nanobanana 模型。 - [x] 26 `server-rs/crates/platform-image/src/image_provider/tests.rs:9` - 当前实现:`image_provider/mod.rs` 声明了 `#[cfg(test)] mod tests;`,`extract_image_urls` / `extract_b64_images` 提升为 `pub(crate)`,测试模块补齐导入与 `base64::Engine` 用法。 - 问题(成立):该单测模块被孤立,`cargo test` 会静默跳过其中所有用例。 - 已修复:`c86ac0422` 恢复 image_provider 单元测试模块(`cargo test -p platform-image` 现在会真正执行这些用例)。 - [x] 27 `src/components/image-editor/ImageCanvasGenerationModel.ts:137` - 当前实现:`EDITOR_IMAGE_MODEL_OPTIONS` 只剩 nanobanana2 与 gpt-image-2.5;`decodeCanvasGenerationInputs` 的 `normalizeStringOption` 会先跑 `normalizeEditorImageModel`(历史值 → `gpt-image-2.5`)再判断是否在选项内,并对历史值追加 `model` 回退告警。 - 问题:评审担心历史 `gpt-image-2` 会因不在选项里被重写成 `DEFAULT_IMAGE_MODEL`(nanobanana2)。**当前代码已不会这样**:归一发生在选项校验之前,历史值稳定迁移到 `gpt-image-2.5`。 - 结论:已解决(同批 `5b1b0ddb9` 已补回归用例「restores the concrete legacy GPT image value to GPT Image 2.5 with a warning」,当前通过)。无需再改。 - [x] 28 `server-rs/crates/api-server/src/editor_generation_config.rs:271-276` —— 下面先把背景讲透 **A. 这套「泥点定价配置」是什么** - 编辑器每次生成(图片 / 视频 / 音效 / 音乐)都要按「模型 + 尺寸档位」扣虚拟货币「泥点」。价格表不是写死在代码里,而是一份 JSON: `models: { "<模型 key>": { unit: "perGeneration" | "perSecond", price: 数字 } 或 { prices: { "1K": 数字, "2K": 数字 } } }`。 - 代码:`EditorGenerationPricingConfig` + `EditorGenerationPricingStore`(`editor_generation_config.rs`)。 - 三份来源,优先级从低到高: 1. 编译进二进制的默认 JSON:`crates/api-server/config/editor-generation-pricing.default.json`(`include_str!`)。 2. 磁盘 override 文件:`/var/lib/genarrative/editor-generation-pricing/editor-generation-pricing.override.json`(或仓库里的 `.app/editor-generation-pricing.override.json`)。 3. SpacetimeDB 全局配置表里的 record(后台 admin 保存的就是它)。 - 读价格时(`state.rs::editor_generation_pricing()`)会先拉 SpacetimeDB record,成功就用它;失败则 `warn!` 后回退到本地缓存/override/default 快照。 **B. 本次迁移对 key 做了什么** - 旧:GPT 图片只有一个定价 key `gpt-image-2`。 - 新:拆成两个具体 provider model 的 key —— `gpt-image-2.5-flare-c`(生成,含带参考图的普通生成)与 `gpt-image-2.5-sunburst-c`(编辑 / 快速编辑 / 原位修改 / raw edit)。 - `validate()` 现在把这两个 key 连同尺寸档位(各需 1K、2K)列为**必填**,和其它模型(nanobanana、视频、音效、音乐)一起校验。 **C. 为什么会出现 backfill(这就是你问的那个函数)** - 价格表是**已经持久化的数据**:磁盘上的 override 文件、SpacetimeDB 里的 record,都是在这次拆分之前写下的,里面只有旧的单 key `gpt-image-2`。 - 新增的必填校验会直接把这类历史数据判为非法。 - `backfill_legacy_gpt_image_2_5_pricing`(`editor_generation_config.rs:396-430`)就是针对这件事的一次性兼容垫片,逻辑只有四步: 1. 两个新 key 都已经存在 → 直接返回,什么都不做; 2. 否则从当前配置里取旧 key `gpt-image-2`,取不到就退回默认 JSON 里的 `gpt-image-2`; 3. 用这份旧价格 `or_insert` 到 `gpt-image-2.5-flare-c` 与 `gpt-image-2.5-sunburst-c`(只补缺失的 key,**绝不覆盖**已有值); 4. 连旧 key 都找不到就报错,避免静默产出一份没有 GPT 图片价格的配置。 - 一句话:它是「旧单 key 配置 → 新双 key 配置」的**只读兼容补齐**,只在读取内存里补,不改写任何持久化数据。ADR(`docs/adr/【ADR】GPT Image 2.5模型路由与历史值兼容-2026-09-18.md`)明确写了「旧单 key 配置只允许受控 backfill,并留下兼容 TODO」「不执行数据库历史迁移」,就是这个意思。 **D. 问题(成立)** - 这个垫片目前只挂在「磁盘 override 文件」这一条加载路径上(`load_editor_generation_pricing_from_candidates` 里读完 override JSON 后调用)。 - SpacetimeDB 那条路径走的是另一个函数:`crates/api-server/src/state.rs::editor_generation_pricing_from_record()`(从第 444 行起)。它把 record 里的 models 拼成 `EditorGenerationPricingConfig`,只补了音效模型(ElevenLabs),然后直接 `config.validate()` —— **没有调用 GPT 图片的 backfill**。 - 结果:升级前就已写进 SpacetimeDB 的定价记录(只有 `gpt-image-2`)读回来时校验失败。 **E. 影响有多大(说清楚,不用慌)** - 失败会落进 `editor_generation_pricing()` 的 `Err(error)` 分支 → `warn!` 后返回本地快照。所以**不会 500,也不会算成 0 泥点**。 - 真实后果:后台在 SpacetimeDB 里配过的价格被**静默忽略**,回落到默认/override 快照,直到有人在后台重新保存一次定价(保存路径会写出完整的新 key)。表现是「后台显示的价与实际扣费不一致」。 **F. 建议修法(很小,但会碰到线上已持久化数据,所以留给你点头)** 1. `backfill_legacy_gpt_image_2_5_pricing` 由 `fn` 改为 `pub(crate) fn`; 2. 在 `state.rs::editor_generation_pricing_from_record` 里,`let config = EditorGenerationPricingConfig { models };` 之后、`config.validate()?;` 之前插入一行 `crate::editor_generation_config::backfill_legacy_gpt_image_2_5_pricing(&mut config, legacy_fallback, "SpacetimeDB 模型定价配置")?;` (`legacy_fallback` 已经是该函数的入参、就是本地快照,正好当兜底来源); 3. 顺手把 `# TODO:` 注释改成「record 与 override 两条路径都已回填」,等线上数据全部重存过一轮后再删掉这块兼容。 - 可选的其它做法都不推荐:在 `validate()` 里塞兼容(把兼容逻辑散进校验)、或做一次性数据迁移(ADR 明确不迁移历史记录)。 - 已修复:`045910989` 按上面 1 / 2 / 3 落地(`backfill_legacy_gpt_image_2_5_pricing` 改 `pub(crate)`、`editor_generation_pricing_from_record` 在 `validate()` 前先回填、TODO 注释改成覆盖 override 与 record 两条路径),并补两条用例:旧 record 自动补齐两个 2.5 key、显式 2.5 key 不被回填覆盖。 - [x] 29 `server-rs/crates/api-server/src/editor_generation_config.rs:168`(实际是 `editor_generation_config.rs:1103` 的用例) - 当前实现:用例 `editor_generation_pricing_rejects_missing_required_image_size` 构造一份 JSON,GPT 图片只有 `"gpt-image-2": { "1K": 20 }`(故意缺 2K),想验证「缺尺寸档位要报错」,末尾断言错误文案包含 `gpt-image-2 缺少 2K`。 - 问题(成立,现在就是红的):`validate()` 的顺序是 nanobanana → `gpt-image-2.5-flare-c` → `gpt-image-2.5-sunburst-c` → 视频……。这份 JSON 里两个新 key 根本不存在,所以第一个报错是 `models 缺少模型 gpt-image-2.5-flare-c 的泥点配置`,走不到「缺 2K」那一步。用例的意图(缺档位要拦下来)依然有效,只是夹具和断言过期了。 - 建议修法(推荐 a): - (a) 夹具改成两个新 key:`gpt-image-2.5-flare-c` 给 `{ "1K": 20 }`(缺 2K),`gpt-image-2.5-sunburst-c` 给 `{ "1K": 3, "2K": 5 }`,断言改成包含 `models.gpt-image-2.5-flare-c 缺少 2K 的泥点配置`。既保留原意又对齐新 key。 - (b) 保留旧夹具、断言改成 `缺少模型 gpt-image-2.5-flare-c`:不推荐,容易被误读成「校验只看新 key」,也测不到缺档位分支。 - 为什么留给你:29 和 28 是同一件事的两面(28 改兼容回填、29 改夹具与断言)。你按 28 的方案改完,顺手跑一次 `cargo test -p api-server editor_generation_config` 一起收口最省事;我不想在你的迁移决策落地前先把测试改成只反映当前(可能还会变)的实现。 - 已修复:`6318baf7c` 按 (a) 落地:夹具换成两个新 key(`gpt-image-2.5-flare-c` 只给 `1K` 缺 `2K`,`gpt-image-2.5-sunburst-c` 给 `1K` / `2K`),断言改为包含 `models.gpt-image-2.5-flare-c 缺少 2K 的泥点配置`,用例仍覆盖「缺档位必须拦下来」。 - [x] 30 `server-rs/crates/api-server/src/editor_project.rs:380` - 当前实现:`editor_project_icon.rs` 的 `EditorImageProviderRequest` 构造点已补 `provider_model`,且因该路径始终带参考图,取值是 `GPT_IMAGE_2_5_EDIT_MODEL`。 - 问题(成立):新增必填字段会让该构造点 `E0063: missing field provider_model` 编译失败(critical)。 - 已修复:`2e2ed47be` 补齐图标 spritesheet 请求的 provider_model。(另:`dedc58cce` 顺带修掉同批遗留的 `GPT_IMAGE_2_C_MODEL` 未重导出编译错误。) --- ## 附:原 review 之外的当前失败项 - [x] A `crates/api-server/src/editor_project.rs::publication_material_generation_is_locked_to_gpt_image_2` - 当前实现:该用例扫描 `generate_editor_image_for_owner` 的源码文本,要求出现 `Some(GPT_IMAGE_2_MODEL)`。 - 问题(成立):实现已改成 `Some(GPT_IMAGE_2_5_BUSINESS_NAME)`,文本断言过期,用例失败。 - 已修复:`fa84a364d` 修正宣发素材生成锁定的模型断言(顺带把用例改名为 `..._gpt_image_2_5`)。 - [x] B `crates/api-server/src/app.rs::public_editor_generation_pricing_route_returns_default_config` - 当前实现:用例断言公开投影 `payload["models"]["gpt-image-2"]["prices"]["1K"] == 3`。 - 问题(成立):`public_projection()` 现在会删除 `gpt-image-2`、把 `gpt-image-2.5-flare-c` 投影成业务名 `gpt-image-2.5`,所以旧 key 读出来是 `Null`,用例失败。 - 已修复:`4415723f7` 同步公开定价路由用例到 GPT Image 2.5 公开投影(断言 `gpt-image-2.5` 的 3 / 5,并断言 `gpt-image-2`、`gpt-image-2.5-flare-c`、`gpt-image-2.5-sunburst-c` 都不公开)。 - [x] C `crates/api-server/src/app.rs::admin_editor_generation_pricing_route_saves_config_and_updates_public_route` - 当前实现:后台保存用例提交一份只含旧单 key `gpt-image-2` 的定价 JSON,期望 200。 - 问题(成立):`validate()` 新增两个必填 2.5 档位后该 JSON 非法,接口返回 400(`save_editor_generation_pricing` → `store.replace` → `validate` → `Invalid`),用例失败。 - 已修复:`28e6e67e9` 补齐后台定价保存用例的 GPT Image 2.5 档位(payload 换成 `gpt-image-2.5-flare-c` 31/62 与 `gpt-image-2.5-sunburst-c` 41/82;公开断言按生成档 62 校验并确认不透出具体 key;新增 admin 读回具体 key 的断言,覆盖 ADR 里「admin 定价接口是 concrete key 的例外」)。 - [x] D 环境/外部依赖类失败(原判断为「与本次改动无关」,复核后为可修复的代码/夹具错配) - `editor_background_music_prompt_assist::tests::*`(16 项)、`editor_sound_effect_prompt_assist::tests::*`(4 项)、`vector_engine_audio_generation::sound_effect_translation::tests::*`(8 项)、`state::tests::app_state_builds_editor_agent_llm_client_from_vector_engine_settings`(1 项)。 - 现象:多为 LLM / vector-engine 音频链路的 mock、缺凭据或客户端未初始化导致(例如 `editor agent LLM client should exist`)。 - 复核结论(原判断不成立):不需要凭据也不需要网络。根因是 `b294cbc3d` 把 Tiantoken 凭据改成只从进程环境变量 `TIANTOKEN_*` 现读之后,这 3 组 mock 夹具仍用 `AppConfig.vector_engine_base_url` / `vector_engine_api_key` 构造 `AppState`,`state.vector_engine_llm_client()` 于是全是 `None`,路由统一回 503 `editor_llm_unavailable`;`state` 那条同源,断言里的 base URL 与配置来源都已过期。 - 已修复:`1c1deff73` 把读取点从 `AppState::new` 前移到 `AppConfig::from_env`,新增 `AppConfig.tiantoken_base_url` / `AppConfig.tiantoken_api_key`(只读 `TIANTOKEN_*`,仍不回退 VectorEngine 凭据),删除现读环境变量的 `config::tiantoken_*` 访问器;三处 mock 夹具与 `state` 用例改用 config 字段,避免在并行用例里 `set_var` 互相踩 `TIANTOKEN_*`。`cargo test -p api-server` 现为 1060 passed / 0 failed / 6 ignored。
k88936 added 1 commit 2026-09-21 10:59:09 +08:00
合并 master 并保留 GPT Image 2.5 与外置提示词行为
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m1s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m54s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m52s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 8m1s
Project CI / Native shell tests (pull_request) Successful in 9m25s
Project CI / Backend tests (pull_request) Successful in 10m37s
Project CI / Repository checks (pull_request) Successful in 7m52s
Project CI / Frontend tests (pull_request) Failing after 10m40s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m25s
3b137ca369
保留编辑器图片工具使用 gpt-image-2.5 业务模型名

保留工具参数说明从 prompts 外置文件读取

同步外置模型与尺寸文案并完成冲突验证
k88936 marked the pull request as ready for review 2026-09-21 11:01:52 +08:00
k88936 added 1 commit 2026-09-21 13:39:18 +08:00
Merge branch 'master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m49s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m47s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 7m42s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 8m24s
Project CI / Backend tests (pull_request) Successful in 7m13s
Project CI / Frontend tests (pull_request) Failing after 4m19s
Project CI / Native shell tests (pull_request) Successful in 9m35s
Project CI / Repository checks (pull_request) Successful in 5m57s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m48s
c6f90c5b22
k88936 added 1 commit 2026-09-21 14:43:47 +08:00
Merge remote-tracking branch 'origin/master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m3s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m55s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 7m25s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 8m18s
Project CI / Backend tests (pull_request) Successful in 8m6s
Project CI / Native shell tests (pull_request) Successful in 9m16s
Project CI / Frontend tests (pull_request) Failing after 7m0s
Project CI / Repository checks (pull_request) Successful in 7m8s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m16s
27e3d9bfd0
lhk229 added 1 commit 2026-09-21 19:47:39 +08:00
Merge branch 'master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m20s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m59s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m32s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m27s
Project CI / Backend tests (pull_request) Successful in 5m36s
Project CI / Native shell tests (pull_request) Successful in 8m0s
Project CI / Frontend tests (pull_request) Failing after 4m13s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m55s
Project CI / Repository checks (pull_request) Successful in 4m7s
a6f99c8871
k88936 added 16 commits 2026-09-21 20:38:15 +08:00
移除单元素模型循环和无操作异常捕获

保留原有请求与错误语义
移除单元素模型循环和无操作异常捕获

保留原有请求与错误语义
干运行输出只保留实际请求信息
干运行输出只保留实际请求信息
取消 create_nanobanana_generate_content 中硬编码的 VectorEngine

错误、重试与审计统一读取 settings.provider
nanobanana_generate_content_url 空模型回落到 nanobanana 模型

新增空模型路径的单元测试
public_projection 同步移除 gpt-image-2-c 键
移除 create_openai_image_generation 及其未再用到的导入
create_openai_image_edit_with_references_and_model 错误归属与路由一致
normalize_legacy_image_model_args 改为按需返回 Cow

未命中归一时复用原始参数视图,避免多一次深拷贝
抽取 LEGACY_EDITOR_IMAGE_MODELS 作为单一来源

normalizeEditorImageModel 与 isLegacyEditorImageModel 复用该集合
resolve_image_provider 不再接受 gpt-image-2.5 业务名

业务名必须先在任务边界解析成 flare-c / sunburst-c 具体 key

新增白名单回归测试
gpt-image-2-c 仅保留历史 fallback 与审计字符串

resolve_image_provider 白名单不再接受该值

补充拒绝断言
缺少 BASE_URL / API_KEY 时点名对应环境变量

启动成功时记录 provider、base_url 与超时配置
ADR 补充业务名与 gpt-image-2-c 的拒绝规则

决策记录补充启动报错点名环境变量与启动日志
整体删除已退役的 gpt-image-2-c
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m59s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m47s
Project CI / Backend tests (pull_request) Successful in 5m4s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m30s
Project CI / Native shell tests (pull_request) Successful in 6m36s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m48s
Project CI / Frontend tests (pull_request) Failing after 3m14s
Project CI / Repository checks (pull_request) Successful in 2m46s
Project CI / AI game creator shell web tests (pull_request) Failing after 2m24s
f3c362b1ec
- 删除 platform-image 的 GPT_IMAGE_2_C_MODEL 常量、re-export、尺寸语义与审计标签
- 移除 api-server 三处任务边界对 gpt-image-2-c 的兼容解析分支
- 移除主站前端历史模型常量与恢复分支,并同步其测试
- 同步 External v1 OpenAPI 描述、ADR、CONTEXT、pitfalls 与后端/运维文档
- 数据库既有 gpt-image-2-c 审计字符串保持原样,不回写不改写
Author
Member

─── 1. 【已修复 3d498e280】 .codex/skills/gpt-image-2-apimart/scripts/generate-anthro-cat-illustrations.mjs:259 ───
现状:requestImagePayload 里是 for (const model of [preferredImageModel]) 包住整个请求,内部还有 catch (error) { throw error; },循环之后跟一条不可达的 throw。
问题:跨模型回退删除后,单元素循环和无操作 catch 都是死代码,会让人误以为仍保留 fallback。
建议修复:直接用 const model = preferredImageModel;,去掉单元素循环、无操作 catch 与不可达 throw。
本次处理:已按上述方式改写;请求体、超时、vectorEngineResponseParse/vectorEngineBody 等错误语义不变。已跑 node --check。

─── 2. 【已修复 13b40fd6a】 .codex/skills/gpt-image-2-apimart/scripts/generate-template-samples.mjs:229 ───
现状/问题:与第 1 项同形的单元素模型循环 + 无操作 catch。
建议修复:同样改为直接用 preferredImageModel,删除循环与 no-op catch。
本次处理:已修复;请求与错误语义不变。已跑 node --check。

─── 3. 【已修复 fc55bec48】 .codex/skills/gpt-image-2-apimart/scripts/generate-anthro-cat-illustrations.mjs:372 ───
现状:dry-run 输出里每个 request 都带 fallbackModel: null。
问题:该字段恒为 null,会让读取 dry-run 结果的人以为还存在跨模型回退。
建议修复:删除该字段,requests 只保留真实请求信息(id/title/body)。
本次处理:已删除。已跑 node --check。

─── 4. 【已修复 cf6b855c9】 .codex/skills/gpt-image-2-apimart/scripts/generate-template-samples.mjs:348 ───
现状/问题:与第 3 项相同的恒为 null 的 fallbackModel 字段。
建议修复:删除该字段。
本次处理:已删除。已跑 node --check。

─── 5. 【已修复 e7319dea5】 server-rs/crates/api-server/src/editor_agent/tool.rs:233-239 ───
现状:normalize_legacy_image_model_args 返回 owned Value,未命中别名时 value.clone();随后 parse_invalid_args / parse_internal 内部再 value.clone() 交给 null_tool_args_as_missing。validate / pricing / display / prepare 都会经过这里。
问题:常见「无需归一」路径要深拷贝两份 JSON 树。
建议修复:改用 Cow<'_, Value>——未改写返回 Borrowed、改写返回 Owned;包装函数先绑定 let normalized = ... 再传 &normalized,让常见路径从两次深拷贝降为一次。
本次处理:已按上述方式实现;非图片工具仍保留原来的 &Value 接口,未扩大改动面。已跑 cargo check -p api-server 与 cargo test -p api-server editor_agent::tool::tests(13 项通过,含 dyn_validation_normalizes_legacy_gpt_image_2_alias)。

─── 6. 【保留给你】 server-rs/crates/api-server/src/state.rs:638-650 ───
现状:AppState::new 启动时无条件构造 VectorEngine 与 Tiantoken 两个图片 provider client;任一 base URL / API key 缺失就直接 AppStateInitError,api-server 起不来。两个 client 还共用 config.vector_engine_image_request_timeout_ms 同一个超时字段(TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS 会写进该字段并覆盖它)。
问题:这是有意的失败关闭,能保证运行期不缺配置;代价是每次部署都要同时备齐两套凭据——只跑玩法 / nanobanana 的环境也得配 TIANTOKEN_*,只用编辑器 GPT Image 2.5 的环境也得配 VECTOR_ENGINE_*。
建议修复(需你拍板):若是有意的运维收口,保持现状,并把两套 env 在 deploy/env/api-server.env.example 与运维文档里标成必需项;若要放宽,改成按已启用模型集合校验必需凭据,或首次使用懒加载 + 缓存。
未改原因:这是启动期行为与部署契约变更,会影响现网 compose 与环境变量,必须由你确认。

─── 7. 【保留给你】 server-rs/crates/api-server/src/editor_project.rs:3192-3198 ───
现状:带参考图的非 nanobanana 生成,provider_model 取 GPT_IMAGE_2_5_EDIT_MODEL(gpt-image-2.5-sunburst-c)走编辑端点;但预扣费仍走 image_generation_mud_points(normalized_kind, model, size)。
问题:生成档与编辑档价格不同(默认编辑档更贵),带参考图的生成会「按生成档收费、实际调用编辑模型/端点」。
建议修复(需你确认产品口径):若参考图存在时本就该按编辑档计费,就把计费切到 image_edit_model_mud_points;若是有意吸收编辑成本,需要写明并保证展示价、预扣价、结算价一致。
未改原因:这是计费语义变更,必须由你拍板。

─── 8. 【已修复 ee7e6728e】 server-rs/crates/platform-image/src/image_provider/protocol/request.rs:105-115 ───
现状:resolve_image_provider 把 GPT_IMAGE_2_5_BUSINESS_NAME(gpt-image-2.5)与 gpt-image-2.5-flare-c / -sunburst-c 一起判为 Tiantoken;build_image_request_body_with_model 会把 model 原样发给 provider(normalize_image_model 只处理空值,preferred_image_upstream_model 是恒等函数),所以业务名一旦漏到这一层就会原样发给 provider。
问题:调用方若把业务名当 model 传入,provider 会收到可能不被支持的 gpt-image-2.5。核实结论:当前仓库调用链一律传具体 provider_model,业务名只参与计价/展示与路由判断,所以此前没有实际触发,是公开 API 的潜在陷阱。
建议修复:按你选的 (a) —— 在 resolve_image_provider 里拒绝业务名,收紧白名单,业务名必须先在任务边界解析成具体 key。
本次处理:已移除白名单里的 GPT_IMAGE_2_5_BUSINESS_NAME;新增 resolve_image_provider_rejects_business_model_and_accepts_concrete_models。cargo test -p platform-image 22 项通过,cargo test -p api-server 1115 项通过。

─── 9. 【已修复 9922704ae】 server-rs/crates/platform-image/src/image_provider/protocol/request.rs:284 ───
现状:nanobanana_generate_content_url 用 normalize_image_model(model) 拼路径,而该函数空值会回落到 GPT_IMAGE_2_5_GENERATION_MODEL(gpt-image-2.5-flare-c)。
问题:对 nanobanana 端点,空模型会拼出 .../v1beta/models/gpt-image-2.5-flare-c:generateContent,语义错误。执行器每次都会先归一,所以此前只影响这个公开 helper 的语义。
建议修复:空/空白模型在本地回落到 NANOBANANA_2_MODEL,并补一条单测。
本次处理:已修复并新增 nanobanana_generate_content_url_blank_model_falls_back_to_nanobanana_model;cargo test -p platform-image 21 项全通过。

─── 10. 【已修复 ee3a8c12d】 server-rs/crates/api-server/src/config.rs:1324-1326 ───
现状:Tiantoken 只读 TIANTOKEN_BASE_URL / TIANTOKEN_API_KEY,不再从 VECTOR_ENGINE_* 回退(源码注释写明「有意为之」);build_required_image_provider_client 缺 base_url 时只报“tiantoken 图片 provider 缺少 BASE_URL 配置”,没点名变量名。
问题:这是明确的 breaking 配置变更,新增报错文案不足以让运维直接定位到该配哪个变量;构造成功后也没有日志能确认实际生效的 provider 配置。
建议修复:保持「不回退、缺失即启动失败」,但把缺失配置的报错点名具体环境变量,并在启动成功时记录已启用的 provider。
本次处理:按你说的「ok,log 里给更多信息」——报错补上 VECTOR_ENGINE_BASE_URL / VECTOR_ENGINE_API_KEY 或 TIANTOKEN_BASE_URL / TIANTOKEN_API_KEY,并启动 info! 记录 provider、base_url、超时与对应 env 变量。cargo check -p api-server 通过,全量 1115 项测试 0 失败。

─── 11. 【已修复 75c286d5d】 server-rs/crates/api-server/src/editor_generation_config.rs:114-116 ───
现状:public_projection 移除了 gpt-image-2.5-flare-c / -sunburst-c / gpt-image-2,却保留了历史别名 gpt-image-2-c。
问题:normalize_editor_image_model 仍识别 gpt-image-2-c,validate 又允许额外模型键,若配置里存在该键,就会经公开定价接口把 provider 历史别名暴露出去,与该函数「隐藏 provider 专用键」的目的冲突。
建议修复:在 public_projection 里一并 models.remove(EDITOR_IMAGE_MODEL_GPT_IMAGE_2_C)。
本次处理:已按建议修复。

─── 12. 【已修复 ba32ab015】 server-rs/crates/platform-image/src/image_provider/runtime/executor.rs:307-311 ───
现状:create_nanobanana_generate_content 在 request_budget_exhausted_error、retry_image_upstream_status_after_delay、retry_image_send_after_delay、map_curl_error、handle_image_response 五处写死 ImageProvider::VectorEngine,而同函数的 InvalidRequest 与 tracing 用的是 settings.provider。
问题:今天结果正确(nanobanana 目前只路由到 VectorEngine),但审计/错误标签存在两处真相,未来若增加 provider 会静默不一致。
建议修复:这些位置统一改用 settings.provider。
本次处理:已按建议修复,语义不变。cargo test -p platform-image 通过。

─── 13. 【已修复 43191eaec】 server-rs/crates/api-server/src/openai_image_generation.rs:150 ───
现状:create_openai_image_generation(非 _with_model)在迁移到 create_openai_image_generation_with_model 后已无调用方,且把 model 写死为 GPT_IMAGE_2_5_GENERATION_MODEL、忽略 reference_images。
问题:pub(crate) 无调用方会触发 dead_code,也会误导后续调用者。
建议修复:按退役对象直接删除该函数,并同步移除不再使用的 create_image_generation 导入。
本次处理:已确认全仓库无其它调用方(含测试)后删除。cargo check -p api-server 通过。

─── 14. 【已修复 407a1bece】 server-rs/crates/api-server/src/openai_image_generation.rs:386-397 ───
现状:create_openai_image_edit_with_references_and_model 在参考图为空时用 missing_reference_images_error(ImageProvider::Tiantoken, ...),但非空路径用 client_for_model(model) 选 provider。
问题:若 model 是 vector-engine 模型走到该分支,错误归属(及派生失败审计)会标成 tiantoken。
建议修复:该分支按模型解析 provider,与非空路径保持一致。
本次处理:已改为 resolve_image_provider(model)(解析失败回落 Tiantoken 以保持旧行为)。注意这里不能直接用 client_for_model,否则会重新引入「provider 未配置即在参考图校验前 panic」的问题——原注释刻意避免这一点。cargo check -p api-server 通过。

─── 15. 【已修复 45295f55a】 src/components/image-editor/ImageCanvasGenerationModel.ts:651-657 ───
现状:normalizeEditorImageModel 与 isLegacyEditorImageModel 各自维护同一组 gpt-image-2 / gpt-image-2-c 判定。
问题:两处判断会随新别名漂移。
建议修复:抽取单一来源并复用。
本次处理:新增 LEGACY_EDITOR_IMAGE_MODELS 集合并让两处复用(normalize 复用它做归一、isLegacy 复用它做判定)。已跑 npx eslint、npx prettier --check 与 npx vitest run src/components/image-editor/ImageCanvasGenerationModel.test.ts(当时 35 项通过;第 16 项删掉 gpt-image-2-c 恢复用例后为 34 项通过)。

─── 16. 【已修复 9327eb628】 platform-image / api-server / 主站前端全链路(你指出的 gpt-image-2-c 退役;先 eae6ee6fd 只做白名单拒绝,后 9327eb628 整体删除) ───
现状:gpt-image-2-c 是 2026-07-21「首选 gpt-image-2、失败兜底 gpt-image-2-c」那版里的兜底 provider model。跨模型兜底在 GPT Image 2.5 迁移里已经删掉,但这个字符串仍残留在 6 个地方:platform-image 的 GPT_IMAGE_2_C_MODEL 常量与 re-export、is_gpt_image_2_family_model 的尺寸语义、auditable_image_model 的审计标签、api-server 的 3 处提交边界解析(画布参数 / Agent 工具参数 / 定价归一与 public_projection)、主站前端的常量与历史布局恢复分支、以及 External v1 与 ADR 的描述。
问题:这些残留让一个「从未进入业务模型、持久化 model 字段或前端契约」的历史值继续享有现役路由、兼容解析、尺寸语义、审计标签和文档承诺,读代码的人会以为它还受支持;is_gpt_image_2_family_model 命中它还会套用 GPT Image 2 的像素预算,语义上把一个已删除的模型当成现役模型处理。
核实结论(先按「不轻信原 review」重查过):api-server 持久化的模型一律取自 generation_options.model(业务值),前端在 2.5 迁移前只写 gpt-image-2,全仓库除测试与文档外没有任何写入 gpt-image-2-c 的路径;因此不存在需要读回它的持久化数据或客户端契约,可以整体删除而不是只加拒绝。
建议修复:整体删除——常量、re-export、尺寸语义、审计标签、api-server 三处解析分支、前端常量与历史恢复分支、External v1 / ADR / 共享记忆描述;数据库里既有的审计字符串保持原样。
本次处理:已按整体删除落地,单独提交 9327eb628(20 个文件)。platform-image 常量与 re-export 删除;is_gpt_image_2_family_model、auditable_image_model 去掉该分支;api-server editor_project / editor_generation_config / editor_agent::tool / openai_image_generation 去掉解析与 re-export;前端删 IMAGE_MODEL_GPT_IMAGE_2_C 与对应恢复用例;同步 External v1 OpenAPI 描述、ADR、decision-log、CONTEXT、pitfalls、后端架构与运维文档里「gpt-image-2 / gpt-image-2-c 仍按兼容规则解析」「切兜底模型 gpt-image-2-c」的过期表述。resolve_image_provider("gpt-image-2-c") 仍返回 err,但现在走的是「未知值」分支而非专门拒绝分支。
待你确认的相邻问题:provider 白名单里仍保留 gpt-image-2(见第 8 项)。它同样只是「提交边界解析」的历史值,但它同时是玩法图片链路和默认定价 JSON 的现役 key;要不要把 provider 边界收紧到只剩 gpt-image-2.5-flare-c / gpt-image-2.5-sunburst-c / nanobanana,需要你定,我没擅自改。
验证:cargo test -p platform-image 全绿(64 + 14 + 22 项);cargo test -p api-server 1111 通过 / 4 失败,失败全部是 editor_sound_effect_prompt_assist 的 mock LLM TCP 用例(并发负载下 5s 连接超时),单独跑该模块 10 项全通过,与本次改动无关;npx vitest run ...ImageCanvasGenerationModel.test.ts 34 项通过;eslint / prettier / check:encoding(5133 文件)/ check:doc-index / git diff --check 全部通过。

─── 1. 【已修复 3d498e280】 .codex/skills/gpt-image-2-apimart/scripts/generate-anthro-cat-illustrations.mjs:259 ─── 现状:requestImagePayload 里是 `for (const model of [preferredImageModel])` 包住整个请求,内部还有 `catch (error) { throw error; }`,循环之后跟一条不可达的 throw。 问题:跨模型回退删除后,单元素循环和无操作 catch 都是死代码,会让人误以为仍保留 fallback。 建议修复:直接用 `const model = preferredImageModel;`,去掉单元素循环、无操作 catch 与不可达 throw。 本次处理:已按上述方式改写;请求体、超时、vectorEngineResponseParse/vectorEngineBody 等错误语义不变。已跑 `node --check`。 ─── 2. 【已修复 13b40fd6a】 .codex/skills/gpt-image-2-apimart/scripts/generate-template-samples.mjs:229 ─── 现状/问题:与第 1 项同形的单元素模型循环 + 无操作 catch。 建议修复:同样改为直接用 preferredImageModel,删除循环与 no-op catch。 本次处理:已修复;请求与错误语义不变。已跑 `node --check`。 ─── 3. 【已修复 fc55bec48】 .codex/skills/gpt-image-2-apimart/scripts/generate-anthro-cat-illustrations.mjs:372 ─── 现状:dry-run 输出里每个 request 都带 `fallbackModel: null`。 问题:该字段恒为 null,会让读取 dry-run 结果的人以为还存在跨模型回退。 建议修复:删除该字段,requests 只保留真实请求信息(id/title/body)。 本次处理:已删除。已跑 `node --check`。 ─── 4. 【已修复 cf6b855c9】 .codex/skills/gpt-image-2-apimart/scripts/generate-template-samples.mjs:348 ─── 现状/问题:与第 3 项相同的恒为 null 的 fallbackModel 字段。 建议修复:删除该字段。 本次处理:已删除。已跑 `node --check`。 ─── 5. 【已修复 e7319dea5】 server-rs/crates/api-server/src/editor_agent/tool.rs:233-239 ─── 现状:normalize_legacy_image_model_args 返回 owned Value,未命中别名时 `value.clone()`;随后 parse_invalid_args / parse_internal 内部再 `value.clone()` 交给 null_tool_args_as_missing。validate / pricing / display / prepare 都会经过这里。 问题:常见「无需归一」路径要深拷贝两份 JSON 树。 建议修复:改用 `Cow<'_, Value>`——未改写返回 Borrowed、改写返回 Owned;包装函数先绑定 `let normalized = ...` 再传 `&normalized`,让常见路径从两次深拷贝降为一次。 本次处理:已按上述方式实现;非图片工具仍保留原来的 `&Value` 接口,未扩大改动面。已跑 `cargo check -p api-server` 与 `cargo test -p api-server editor_agent::tool::tests`(13 项通过,含 `dyn_validation_normalizes_legacy_gpt_image_2_alias`)。 ─── 6. 【保留给你】 server-rs/crates/api-server/src/state.rs:638-650 ─── 现状:`AppState::new` 启动时无条件构造 VectorEngine 与 Tiantoken 两个图片 provider client;任一 base URL / API key 缺失就直接 `AppStateInitError`,api-server 起不来。两个 client 还共用 `config.vector_engine_image_request_timeout_ms` 同一个超时字段(`TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS` 会写进该字段并覆盖它)。 问题:这是有意的失败关闭,能保证运行期不缺配置;代价是每次部署都要同时备齐两套凭据——只跑玩法 / nanobanana 的环境也得配 `TIANTOKEN_*`,只用编辑器 GPT Image 2.5 的环境也得配 `VECTOR_ENGINE_*`。 建议修复(需你拍板):若是有意的运维收口,保持现状,并把两套 env 在 `deploy/env/api-server.env.example` 与运维文档里标成必需项;若要放宽,改成按已启用模型集合校验必需凭据,或首次使用懒加载 + 缓存。 未改原因:这是启动期行为与部署契约变更,会影响现网 compose 与环境变量,必须由你确认。 ─── 7. 【保留给你】 server-rs/crates/api-server/src/editor_project.rs:3192-3198 ─── 现状:带参考图的非 nanobanana 生成,provider_model 取 GPT_IMAGE_2_5_EDIT_MODEL(gpt-image-2.5-sunburst-c)走编辑端点;但预扣费仍走 `image_generation_mud_points(normalized_kind, model, size)`。 问题:生成档与编辑档价格不同(默认编辑档更贵),带参考图的生成会「按生成档收费、实际调用编辑模型/端点」。 建议修复(需你确认产品口径):若参考图存在时本就该按编辑档计费,就把计费切到 `image_edit_model_mud_points`;若是有意吸收编辑成本,需要写明并保证展示价、预扣价、结算价一致。 未改原因:这是计费语义变更,必须由你拍板。 ─── 8. 【已修复 ee7e6728e】 server-rs/crates/platform-image/src/image_provider/protocol/request.rs:105-115 ─── 现状:resolve_image_provider 把 GPT_IMAGE_2_5_BUSINESS_NAME(gpt-image-2.5)与 gpt-image-2.5-flare-c / -sunburst-c 一起判为 Tiantoken;build_image_request_body_with_model 会把 model 原样发给 provider(normalize_image_model 只处理空值,preferred_image_upstream_model 是恒等函数),所以业务名一旦漏到这一层就会原样发给 provider。 问题:调用方若把业务名当 model 传入,provider 会收到可能不被支持的 gpt-image-2.5。核实结论:当前仓库调用链一律传具体 provider_model,业务名只参与计价/展示与路由判断,所以此前没有实际触发,是公开 API 的潜在陷阱。 建议修复:按你选的 (a) —— 在 resolve_image_provider 里拒绝业务名,收紧白名单,业务名必须先在任务边界解析成具体 key。 本次处理:已移除白名单里的 GPT_IMAGE_2_5_BUSINESS_NAME;新增 `resolve_image_provider_rejects_business_model_and_accepts_concrete_models`。`cargo test -p platform-image` 22 项通过,`cargo test -p api-server` 1115 项通过。 ─── 9. 【已修复 9922704ae】 server-rs/crates/platform-image/src/image_provider/protocol/request.rs:284 ─── 现状:nanobanana_generate_content_url 用 normalize_image_model(model) 拼路径,而该函数空值会回落到 GPT_IMAGE_2_5_GENERATION_MODEL(gpt-image-2.5-flare-c)。 问题:对 nanobanana 端点,空模型会拼出 `.../v1beta/models/gpt-image-2.5-flare-c:generateContent`,语义错误。执行器每次都会先归一,所以此前只影响这个公开 helper 的语义。 建议修复:空/空白模型在本地回落到 NANOBANANA_2_MODEL,并补一条单测。 本次处理:已修复并新增 `nanobanana_generate_content_url_blank_model_falls_back_to_nanobanana_model`;`cargo test -p platform-image` 21 项全通过。 ─── 10. 【已修复 ee3a8c12d】 server-rs/crates/api-server/src/config.rs:1324-1326 ─── 现状:Tiantoken 只读 TIANTOKEN_BASE_URL / TIANTOKEN_API_KEY,不再从 VECTOR_ENGINE_* 回退(源码注释写明「有意为之」);build_required_image_provider_client 缺 base_url 时只报“tiantoken 图片 provider 缺少 BASE_URL 配置”,没点名变量名。 问题:这是明确的 breaking 配置变更,新增报错文案不足以让运维直接定位到该配哪个变量;构造成功后也没有日志能确认实际生效的 provider 配置。 建议修复:保持「不回退、缺失即启动失败」,但把缺失配置的报错点名具体环境变量,并在启动成功时记录已启用的 provider。 本次处理:按你说的「ok,log 里给更多信息」——报错补上 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 或 `TIANTOKEN_BASE_URL` / `TIANTOKEN_API_KEY`,并启动 `info!` 记录 provider、base_url、超时与对应 env 变量。`cargo check -p api-server` 通过,全量 1115 项测试 0 失败。 ─── 11. 【已修复 75c286d5d】 server-rs/crates/api-server/src/editor_generation_config.rs:114-116 ─── 现状:public_projection 移除了 gpt-image-2.5-flare-c / -sunburst-c / gpt-image-2,却保留了历史别名 gpt-image-2-c。 问题:normalize_editor_image_model 仍识别 gpt-image-2-c,validate 又允许额外模型键,若配置里存在该键,就会经公开定价接口把 provider 历史别名暴露出去,与该函数「隐藏 provider 专用键」的目的冲突。 建议修复:在 public_projection 里一并 `models.remove(EDITOR_IMAGE_MODEL_GPT_IMAGE_2_C)`。 本次处理:已按建议修复。 ─── 12. 【已修复 ba32ab015】 server-rs/crates/platform-image/src/image_provider/runtime/executor.rs:307-311 ─── 现状:create_nanobanana_generate_content 在 request_budget_exhausted_error、retry_image_upstream_status_after_delay、retry_image_send_after_delay、map_curl_error、handle_image_response 五处写死 ImageProvider::VectorEngine,而同函数的 InvalidRequest 与 tracing 用的是 settings.provider。 问题:今天结果正确(nanobanana 目前只路由到 VectorEngine),但审计/错误标签存在两处真相,未来若增加 provider 会静默不一致。 建议修复:这些位置统一改用 settings.provider。 本次处理:已按建议修复,语义不变。`cargo test -p platform-image` 通过。 ─── 13. 【已修复 43191eaec】 server-rs/crates/api-server/src/openai_image_generation.rs:150 ─── 现状:create_openai_image_generation(非 _with_model)在迁移到 create_openai_image_generation_with_model 后已无调用方,且把 model 写死为 GPT_IMAGE_2_5_GENERATION_MODEL、忽略 reference_images。 问题:pub(crate) 无调用方会触发 dead_code,也会误导后续调用者。 建议修复:按退役对象直接删除该函数,并同步移除不再使用的 create_image_generation 导入。 本次处理:已确认全仓库无其它调用方(含测试)后删除。`cargo check -p api-server` 通过。 ─── 14. 【已修复 407a1bece】 server-rs/crates/api-server/src/openai_image_generation.rs:386-397 ─── 现状:create_openai_image_edit_with_references_and_model 在参考图为空时用 missing_reference_images_error(ImageProvider::Tiantoken, ...),但非空路径用 client_for_model(model) 选 provider。 问题:若 model 是 vector-engine 模型走到该分支,错误归属(及派生失败审计)会标成 tiantoken。 建议修复:该分支按模型解析 provider,与非空路径保持一致。 本次处理:已改为 `resolve_image_provider(model)`(解析失败回落 Tiantoken 以保持旧行为)。注意这里不能直接用 client_for_model,否则会重新引入「provider 未配置即在参考图校验前 panic」的问题——原注释刻意避免这一点。`cargo check -p api-server` 通过。 ─── 15. 【已修复 45295f55a】 src/components/image-editor/ImageCanvasGenerationModel.ts:651-657 ─── 现状:normalizeEditorImageModel 与 isLegacyEditorImageModel 各自维护同一组 gpt-image-2 / gpt-image-2-c 判定。 问题:两处判断会随新别名漂移。 建议修复:抽取单一来源并复用。 本次处理:新增 `LEGACY_EDITOR_IMAGE_MODELS` 集合并让两处复用(normalize 复用它做归一、isLegacy 复用它做判定)。已跑 `npx eslint`、`npx prettier --check` 与 `npx vitest run src/components/image-editor/ImageCanvasGenerationModel.test.ts`(当时 35 项通过;第 16 项删掉 gpt-image-2-c 恢复用例后为 34 项通过)。 ─── 16. 【已修复 9327eb628】 platform-image / api-server / 主站前端全链路(你指出的 gpt-image-2-c 退役;先 eae6ee6fd 只做白名单拒绝,后 9327eb628 整体删除) ─── 现状:`gpt-image-2-c` 是 2026-07-21「首选 gpt-image-2、失败兜底 gpt-image-2-c」那版里的兜底 provider model。跨模型兜底在 GPT Image 2.5 迁移里已经删掉,但这个字符串仍残留在 6 个地方:`platform-image` 的 `GPT_IMAGE_2_C_MODEL` 常量与 re-export、`is_gpt_image_2_family_model` 的尺寸语义、`auditable_image_model` 的审计标签、api-server 的 3 处提交边界解析(画布参数 / Agent 工具参数 / 定价归一与 public_projection)、主站前端的常量与历史布局恢复分支、以及 External v1 与 ADR 的描述。 问题:这些残留让一个「从未进入业务模型、持久化 `model` 字段或前端契约」的历史值继续享有现役路由、兼容解析、尺寸语义、审计标签和文档承诺,读代码的人会以为它还受支持;`is_gpt_image_2_family_model` 命中它还会套用 GPT Image 2 的像素预算,语义上把一个已删除的模型当成现役模型处理。 核实结论(先按「不轻信原 review」重查过):api-server 持久化的模型一律取自 `generation_options.model`(业务值),前端在 2.5 迁移前只写 `gpt-image-2`,全仓库除测试与文档外没有任何写入 `gpt-image-2-c` 的路径;因此不存在需要读回它的持久化数据或客户端契约,可以整体删除而不是只加拒绝。 建议修复:整体删除——常量、re-export、尺寸语义、审计标签、api-server 三处解析分支、前端常量与历史恢复分支、External v1 / ADR / 共享记忆描述;数据库里既有的审计字符串保持原样。 本次处理:已按整体删除落地,单独提交 `9327eb628`(20 个文件)。`platform-image` 常量与 re-export 删除;`is_gpt_image_2_family_model`、`auditable_image_model` 去掉该分支;api-server `editor_project` / `editor_generation_config` / `editor_agent::tool` / `openai_image_generation` 去掉解析与 re-export;前端删 `IMAGE_MODEL_GPT_IMAGE_2_C` 与对应恢复用例;同步 External v1 OpenAPI 描述、ADR、decision-log、CONTEXT、pitfalls、后端架构与运维文档里「gpt-image-2 / gpt-image-2-c 仍按兼容规则解析」「切兜底模型 gpt-image-2-c」的过期表述。`resolve_image_provider("gpt-image-2-c")` 仍返回 err,但现在走的是「未知值」分支而非专门拒绝分支。 待你确认的相邻问题:provider 白名单里仍保留 `gpt-image-2`(见第 8 项)。它同样只是「提交边界解析」的历史值,但它同时是玩法图片链路和默认定价 JSON 的现役 key;要不要把 provider 边界收紧到只剩 `gpt-image-2.5-flare-c` / `gpt-image-2.5-sunburst-c` / nanobanana,需要你定,我没擅自改。 验证:`cargo test -p platform-image` 全绿(64 + 14 + 22 项);`cargo test -p api-server` 1111 通过 / 4 失败,失败全部是 `editor_sound_effect_prompt_assist` 的 mock LLM TCP 用例(并发负载下 5s 连接超时),单独跑该模块 10 项全通过,与本次改动无关;`npx vitest run ...ImageCanvasGenerationModel.test.ts` 34 项通过;eslint / prettier / `check:encoding`(5133 文件)/ `check:doc-index` / `git diff --check` 全部通过。
k88936 requested review from suzmii 2026-09-21 20:55:56 +08:00
k88936 requested review from lhk229 2026-09-21 20:56:01 +08:00
Author
Member

暂时不要合并, 因为需要等待购买服务, 配置环境变量

暂时不要合并, 因为需要等待购买服务, 配置环境变量
Member

代码评审(PR #422 GPT Image 2.5 集成)

整体方向正确:provider-neutral 重构干净、Tiantoken 凭据零回退、双 client 启动期强制构造、legacy 值只在提交边界归一且不回写历史、公开投影不泄露 concrete key、凭据脱敏到位,测试对「不跨模型」语义覆盖扎实。但存在 2 个阻塞项、4 个严重项,建议修复后再合并。

阻塞(合并前必须处理)

  1. 生成入口按「是否带参考图」而非任务类型选择 concrete model,与本 PR 自己的 ADR/里程碑/decision-log 直接矛盾,且计价 key 与执行 key 分裂

    • server-rs/crates/api-server/src/editor_project.rs:3294-3300、editor_project_icon.rs:1777-1786、character_visual_assets.rs:949-955:只要生成任务带参考图,provider_model 就选 gpt-image-2.5-sunburst-c。
    • 而本 PR 新增的 ADR 明确写「普通生成即使因参考图使用 edits multipart,仍按生成 concrete model」,里程碑路由表写明 flare-c =「Generate,包括带参考图的普通生成」、sunburst-c =「Edit(快速编辑、原位修改、raw edit)」。
    • 次生问题:这些生成任务计价走生成档(flare-c key),实际发送与审计 imageModel 却是 sunburst-c。当前两档默认价相同用户无感,admin 一旦分开调价即错价。
    • 请确认是代码错还是文档错并统一;若按文档,应改为按任务类型分流,并补「生成带参考图 → flare-c / 编辑 → sunburst-c」的直接单测(当前该核心规则无直接测试)。
  2. cargo test -p platform-image 整包运行本地必红

    • server-rs/crates/platform-image/tests/image_provider.rs:422-432:mock server 只读到 \r\n\r\n(响应头结束)就断言请求体内容(aspectRatio/imageSize),TCP 分段下 body 未必随 header 同包到达。本机整包跑与单跑均复现失败(断言 panic → mock 不写响应 → 客户端超时)。
    • 该模式在 master 的 tests/vector_engine.rs 同名测试中已存在,但本 PR 整体迁移重写了该文件,建议顺手修复:复用同文件已有的 read_http_request(按 Content-Length 读完整 body)再断言。

严重

  1. Agent 编辑工具(EditImageTool)计价打到生成档:editor_agent/tool.rs:692-704 用 GPT_IMAGE_2_5_BUSINESS_NAME 走 image_generation_mud_points(归一后命中 flare-c 档),但该工具执行时固定 dispatch sunburst-c;直连快速编辑与 raw edit 都已改用 image_edit_model_mud_points。Agent 编辑是唯一仍按生成档计价的编辑入口,admin 调价后即错价,应统一改走编辑档。

  2. .codex/skills/gpt-image-2-apimart 两个现役脚本直连 provider 却发送业务模型值:scripts/generate-template-samples.mjs:21,73-87、generate-anthro-cat-illustrations.mjs:12 读 VECTOR_ENGINE_BASE_URL/API_KEY 直连 images/generations|edits,请求体 model: "gpt-image-2.5"。按 ADR,业务模型值只存在于 api-server 提交边界,provider 端只认 concrete model,且 GPT Image 2.5 走 Tiantoken 而非 VectorEngine——实盘运行必然被 provider 拒绝。请改用 concrete model + Tiantoken 凭据,或明确标注脚本已退役。

  3. platform-image 路由层放行 legacy/业务名并原样透传上游:image_provider/protocol/request.rs:105-115 把 gpt-image-2 / gpt-image-2-c / gpt-image-2.5 也路由到 Tiantoken,runtime/executor.rs:722-727 恒等透传 model 字符串。一旦有绕过 api-server dispatch 的调用方,provider 将收到字面的业务/历史值。与 ADR「严格白名单、未知 model 直接拒绝」表述不符,建议白名单只保留两个 concrete 2.5 模型 + nanobanana;若是有意兼容,需补钉死该行为的测试并更新 ADR。

  4. create_raw_image_edit 绕过 provider 白名单校验:raw_image_edit/mod.rs:119-153 硬编码 sunburst-c 但不校验 settings.provider,误传 VectorEngine 配置会把 Tiantoken 专有模型名连同 API key 发到 VectorEngine 网关。共享 executor 有 ensure_provider_matches_model 防线,raw 路径没有,建议入口加 provider 校验(当前唯一调用方硬编码 Tiantoken,属纵深防御缺失)。

次要

  1. 新增死代码:openai_image_generation.rs:132 create_openai_image_generation、:266 create_openai_image_edit、:305 create_openai_image_edit_with_references(仅测试使用)在全 crate 无调用方,cargo check 已报 dead_code 警告;所有 create_* 的 _http_client 参数被忽略。建议删除死函数、收敛签名。
  2. OpenAPI 与实现不一致:genarrative-external-v1.openapi.json UI 设计端点(约 3488 行)写 default: gemini-3.1-flash-image-preview +「未传时默认使用 nanobanana」,但 editor_project.rs:2876-2877 对 ui-design kind 强制 gpt-image-2.5。该矛盾 master 已存在,但本 PR 恰好改到这几行,按仓库规范 Rust 与 OpenAPI 不一致不应视为完成,建议顺手修正。
  3. state.rs:2521-2531 的 #[cfg(test)] 兜底使「Tiantoken 配置缺失阻止启动」这条 ADR 硬性要求没有任何测试保护,建议把必填校验拆成纯函数并补参数化单测。
  4. 审计/错误消息 provider 标签残留:raw_image.rs:167,190 tracking 仍传 VECTOR_ENGINE_PROVIDER;editor_project.rs:6155-6165 错误消息仍写 "provider": "vector-engine"(该分支现为 Tiantoken sunburst-c)。
  5. VECTOR_ENGINE_IMAGE_EDIT_MAX_REFERENCE_IMAGES = 5 原是 VectorEngine 约束,现沿用到 Tiantoken 编辑路径(constants.rs:10、executor.rs:494、image_source.rs:145),5 张是否为 Tiantoken 真实上限未见确认,常量名也与用途不符。
  6. editor_project.rs:4102-4103 concrete key 使用字符串字面量而非已 import 的 GPT_IMAGE_2_5_GENERATION_MODEL / GPT_IMAGE_2_5_EDIT_MODEL 常量。
  7. executor.rs:232/422/673 handle_image_response 的 task_prefix 硬编码 "vector-engine",Tiantoken 任务 fallback task_id 会以错误前缀进入持久化与审计。

建议(不阻塞合并)

  1. AGC 11 个文件(apps/ai-game-creator-shell/)与主站 src/services/apiClient.ts、editorProjectClient.ts 的改动经逐行核对为纯 prettier 格式化 churn,与本 PR 主题无关;其中 generated/DirectThreadRequestKind.ts 是绑定生成产物,手工格式化会在下次重新生成时被打回。建议拆出或在 PR 描述中说明来源。
  2. fallback 拆除后的死管线:error.rs 的 FallbackFailed / with_recovered_failure_audits / into_final_error、executor.rs:729 finish_image_model_fallback_error 等已无任何 push 点,crate 列表恒空(api-server 仍在消费公开 API,删除需跨 crate 跟进)。
  3. openai_image_generation.rs:440-452 client_for_model 用 expect panic 保证启动期不变量,建议改为返回 Result 变成可审计的 503。
  4. platform-editor-agent 残留 2 代命名:image_generation_options.rs:111 gpt_image_2_size_parameter_schema、prompt 文件 gpt-image-2-size.txt(内容已是 2.5 语义)。
  5. request.rs:96-99 空模型默认值在编辑上下文会落到生成模型 flare-c,严格白名单语义下空值应报错或按任务类型取默认。

已核实无问题的方面

  • Tiantoken 凭据无 VectorEngine 回退,有专项测试 tiantoken_credentials_never_fall_back_to_vector_engine_env;非 test 构建下任一 provider 缺配置即启动失败。
  • 跨模型/跨 provider fallback 已彻底移除,同 concrete model 重试保留且有 6 条集成测试钉死。
  • 前端不泄露 concrete model(src/、apps/、OpenAPI 零命中 flare/sunburst);normalizeEditorImageModel 兼容解析仅在使用端、命中 legacy 触发既有回退警告、历史 fixture 不回写且有断言保护;nanobanana 默认行为未变。
  • 定价 backfill 双路径(磁盘 override + SpacetimeDB record)受控且留 TODO;公开定价 API 经 public_projection() 只暴露业务名,admin 例外有测试固定。
  • ImageProviderSettings / OpenAiImageSettings Debug 脱敏,日志不含 key 与图片字节。
  • 「编辑缺参考图 panic」修复正确:发送网络请求前统一校验。

本地验证结果

  • cargo check -p api-server 通过(有本 PR 新增的 dead_code 警告,见次要项 7)
  • cargo test -p platform-image:1 个失败(见阻塞项 2),其余 19 个通过
  • npm run typecheck 通过;image-editor 三个核心 model 定向测试 115 个全部通过
  • npm run check:encoding、npm run check:doc-index、git diff --check 均通过

结论:阻塞项 1(代码与 ADR 矛盾 + 计价分裂)和阻塞项 2(测试门禁红)解决前不建议合并;严重项 3-6 建议同 PR 修复。

## 代码评审(PR #422 GPT Image 2.5 集成) 整体方向正确:provider-neutral 重构干净、Tiantoken 凭据零回退、双 client 启动期强制构造、legacy 值只在提交边界归一且不回写历史、公开投影不泄露 concrete key、凭据脱敏到位,测试对「不跨模型」语义覆盖扎实。但存在 **2 个阻塞项、4 个严重项**,建议修复后再合并。 ### 阻塞(合并前必须处理) 1. **生成入口按「是否带参考图」而非任务类型选择 concrete model,与本 PR 自己的 ADR/里程碑/decision-log 直接矛盾,且计价 key 与执行 key 分裂** - `server-rs/crates/api-server/src/editor_project.rs:3294-3300`、`editor_project_icon.rs:1777-1786`、`character_visual_assets.rs:949-955`:只要生成任务带参考图,`provider_model` 就选 `gpt-image-2.5-sunburst-c`。 - 而本 PR 新增的 ADR 明确写「普通生成即使因参考图使用 edits multipart,仍按生成 concrete model」,里程碑路由表写明 flare-c =「Generate,包括带参考图的普通生成」、sunburst-c =「Edit(快速编辑、原位修改、raw edit)」。 - 次生问题:这些生成任务计价走生成档(flare-c key),实际发送与审计 imageModel 却是 sunburst-c。当前两档默认价相同用户无感,admin 一旦分开调价即错价。 - 请确认是代码错还是文档错并统一;若按文档,应改为按任务类型分流,并补「生成带参考图 → flare-c / 编辑 → sunburst-c」的直接单测(当前该核心规则无直接测试)。 2. **`cargo test -p platform-image` 整包运行本地必红** - `server-rs/crates/platform-image/tests/image_provider.rs:422-432`:mock server 只读到 `\r\n\r\n`(响应头结束)就断言请求体内容(`aspectRatio`/`imageSize`),TCP 分段下 body 未必随 header 同包到达。本机整包跑与单跑均复现失败(断言 panic → mock 不写响应 → 客户端超时)。 - 该模式在 master 的 `tests/vector_engine.rs` 同名测试中已存在,但本 PR 整体迁移重写了该文件,建议顺手修复:复用同文件已有的 `read_http_request`(按 Content-Length 读完整 body)再断言。 ### 严重 3. **Agent 编辑工具(EditImageTool)计价打到生成档**:`editor_agent/tool.rs:692-704` 用 `GPT_IMAGE_2_5_BUSINESS_NAME` 走 `image_generation_mud_points`(归一后命中 flare-c 档),但该工具执行时固定 dispatch sunburst-c;直连快速编辑与 raw edit 都已改用 `image_edit_model_mud_points`。Agent 编辑是唯一仍按生成档计价的编辑入口,admin 调价后即错价,应统一改走编辑档。 4. **`.codex/skills/gpt-image-2-apimart` 两个现役脚本直连 provider 却发送业务模型值**:`scripts/generate-template-samples.mjs:21,73-87`、`generate-anthro-cat-illustrations.mjs:12` 读 `VECTOR_ENGINE_BASE_URL/API_KEY` 直连 `images/generations|edits`,请求体 `model: "gpt-image-2.5"`。按 ADR,业务模型值只存在于 api-server 提交边界,provider 端只认 concrete model,且 GPT Image 2.5 走 Tiantoken 而非 VectorEngine——实盘运行必然被 provider 拒绝。请改用 concrete model + Tiantoken 凭据,或明确标注脚本已退役。 5. **platform-image 路由层放行 legacy/业务名并原样透传上游**:`image_provider/protocol/request.rs:105-115` 把 `gpt-image-2` / `gpt-image-2-c` / `gpt-image-2.5` 也路由到 Tiantoken,`runtime/executor.rs:722-727` 恒等透传 model 字符串。一旦有绕过 api-server dispatch 的调用方,provider 将收到字面的业务/历史值。与 ADR「严格白名单、未知 model 直接拒绝」表述不符,建议白名单只保留两个 concrete 2.5 模型 + nanobanana;若是有意兼容,需补钉死该行为的测试并更新 ADR。 6. **`create_raw_image_edit` 绕过 provider 白名单校验**:`raw_image_edit/mod.rs:119-153` 硬编码 sunburst-c 但不校验 `settings.provider`,误传 VectorEngine 配置会把 Tiantoken 专有模型名连同 API key 发到 VectorEngine 网关。共享 executor 有 `ensure_provider_matches_model` 防线,raw 路径没有,建议入口加 provider 校验(当前唯一调用方硬编码 Tiantoken,属纵深防御缺失)。 ### 次要 7. 新增死代码:`openai_image_generation.rs:132` `create_openai_image_generation`、`:266` `create_openai_image_edit`、`:305` `create_openai_image_edit_with_references`(仅测试使用)在全 crate 无调用方,`cargo check` 已报 dead_code 警告;所有 `create_*` 的 `_http_client` 参数被忽略。建议删除死函数、收敛签名。 8. OpenAPI 与实现不一致:`genarrative-external-v1.openapi.json` UI 设计端点(约 3488 行)写 `default: gemini-3.1-flash-image-preview` +「未传时默认使用 nanobanana」,但 `editor_project.rs:2876-2877` 对 ui-design kind 强制 `gpt-image-2.5`。该矛盾 master 已存在,但本 PR 恰好改到这几行,按仓库规范 Rust 与 OpenAPI 不一致不应视为完成,建议顺手修正。 9. `state.rs:2521-2531` 的 `#[cfg(test)]` 兜底使「Tiantoken 配置缺失阻止启动」这条 ADR 硬性要求没有任何测试保护,建议把必填校验拆成纯函数并补参数化单测。 10. 审计/错误消息 provider 标签残留:`raw_image.rs:167,190` tracking 仍传 `VECTOR_ENGINE_PROVIDER`;`editor_project.rs:6155-6165` 错误消息仍写 `"provider": "vector-engine"`(该分支现为 Tiantoken sunburst-c)。 11. `VECTOR_ENGINE_IMAGE_EDIT_MAX_REFERENCE_IMAGES = 5` 原是 VectorEngine 约束,现沿用到 Tiantoken 编辑路径(`constants.rs:10`、`executor.rs:494`、`image_source.rs:145`),5 张是否为 Tiantoken 真实上限未见确认,常量名也与用途不符。 12. `editor_project.rs:4102-4103` concrete key 使用字符串字面量而非已 import 的 `GPT_IMAGE_2_5_GENERATION_MODEL` / `GPT_IMAGE_2_5_EDIT_MODEL` 常量。 13. `executor.rs:232/422/673` `handle_image_response` 的 `task_prefix` 硬编码 `"vector-engine"`,Tiantoken 任务 fallback task_id 会以错误前缀进入持久化与审计。 ### 建议(不阻塞合并) 14. AGC 11 个文件(`apps/ai-game-creator-shell/`)与主站 `src/services/apiClient.ts`、`editorProjectClient.ts` 的改动经逐行核对为纯 prettier 格式化 churn,与本 PR 主题无关;其中 `generated/DirectThreadRequestKind.ts` 是绑定生成产物,手工格式化会在下次重新生成时被打回。建议拆出或在 PR 描述中说明来源。 15. fallback 拆除后的死管线:`error.rs` 的 `FallbackFailed` / `with_recovered_failure_audits` / `into_final_error`、`executor.rs:729` `finish_image_model_fallback_error` 等已无任何 push 点,crate 列表恒空(api-server 仍在消费公开 API,删除需跨 crate 跟进)。 16. `openai_image_generation.rs:440-452` `client_for_model` 用 `expect` panic 保证启动期不变量,建议改为返回 `Result` 变成可审计的 503。 17. platform-editor-agent 残留 2 代命名:`image_generation_options.rs:111` `gpt_image_2_size_parameter_schema`、prompt 文件 `gpt-image-2-size.txt`(内容已是 2.5 语义)。 18. `request.rs:96-99` 空模型默认值在编辑上下文会落到生成模型 flare-c,严格白名单语义下空值应报错或按任务类型取默认。 ### 已核实无问题的方面 - Tiantoken 凭据无 VectorEngine 回退,有专项测试 `tiantoken_credentials_never_fall_back_to_vector_engine_env`;非 test 构建下任一 provider 缺配置即启动失败。 - 跨模型/跨 provider fallback 已彻底移除,同 concrete model 重试保留且有 6 条集成测试钉死。 - 前端不泄露 concrete model(`src/`、`apps/`、OpenAPI 零命中 flare/sunburst);`normalizeEditorImageModel` 兼容解析仅在使用端、命中 legacy 触发既有回退警告、历史 fixture 不回写且有断言保护;nanobanana 默认行为未变。 - 定价 backfill 双路径(磁盘 override + SpacetimeDB record)受控且留 TODO;公开定价 API 经 `public_projection()` 只暴露业务名,admin 例外有测试固定。 - `ImageProviderSettings` / `OpenAiImageSettings` Debug 脱敏,日志不含 key 与图片字节。 - 「编辑缺参考图 panic」修复正确:发送网络请求前统一校验。 ### 本地验证结果 - `cargo check -p api-server` 通过(有本 PR 新增的 dead_code 警告,见次要项 7) - `cargo test -p platform-image`:**1 个失败**(见阻塞项 2),其余 19 个通过 - `npm run typecheck` 通过;image-editor 三个核心 model 定向测试 115 个全部通过 - `npm run check:encoding`、`npm run check:doc-index`、`git diff --check` 均通过 **结论**:阻塞项 1(代码与 ADR 矛盾 + 计价分裂)和阻塞项 2(测试门禁红)解决前不建议合并;严重项 3-6 建议同 PR 修复。
Member

已过时,重审

已过时,重审
Member

复审(a6f99c887..f3c362b1e,16 个新提交)

上轮问题的处理情况

已解决:

  • ✅ 白名单收紧:resolve_image_provider 现在拒绝业务名 gpt-image-2.5 与已退役的 gpt-image-2-c,新增 resolve_image_provider_rejects_business_model_and_accepts_concrete_models 单测钉死(上轮严重项 5 的主体已解决)。
  • ✅ gpt-image-2-c 整体删除:常量、re-export、尺寸语义、审计标签、三处提交边界解析、前端常量与恢复分支全部移除,ADR / CONTEXT / OpenAPI / api-operations / pitfalls / decision-log 同步一致。数据库历史审计字符串不回写的策略表述清晰。
  • ✅ nanobanana 端点空模型回落修正并补测试(nanobanana_generate_content_url_blank_model_falls_back_to_nanobanana_model)。
  • ✅ 启动报错点名具体环境变量(TIANTOKEN_BASE_URL 等),client 构造成功后有 info 启动日志,ADR Consequences 已同步。
  • ✅ nanobanana 执行器错误 provider 标签从硬编码 VectorEngine 改为 settings.provider 透传。
  • ✅ 死代码 create_openai_image_generation 已删除;空参考图错误的 provider 改为按模型解析(openai_image_generation.rs:341)。
  • ✅ 前端 legacy 判定与归一合并为同一 LEGACY_EDITOR_IMAGE_MODELS 集合,ImageCanvasGenerationModel.test.ts 34 项本地通过。

仍未解决(沿用上轮意见):

  1. 🔴 阻塞:生成入口仍按「是否带参考图」分流到 sunburst-c——editor_project.rs:3294-3300、editor_project_icon.rs:1777-1786、character_visual_assets.rs:949-955 本轮未改动,而 ADR 中「普通生成即使因参考图使用 edits multipart,仍按生成 concrete model」的表述也保留原样。代码与文档的矛盾依然存在,且计价(flare-c 档)与执行/审计(sunburst-c)分裂的问题仍在。请明确二选一:改代码按任务类型分流,或修订三处文档并把生成带参考图的计价切到编辑档。
  2. 🔴 阻塞:cargo test -p platform-image 本地仍必红——tests/image_provider.rs 的 nanobanana_generate_content_posts_native_body_and_reads_inline_data mock 竞态未修复(mock 只读到 \r\n\r\n 就断言请求体),本机连续复现(21 passed / 1 failed)。decision-log 记录的「cargo test -p platform-image 全绿」与本机(Windows)结果不符,说明该 race 与平台/网络栈行为相关,CI 上同样有失败风险。建议用同文件已有的按 Content-Length 完整读 body 的 helper 重写该断言。
  3. 🟠 EditImageTool 计价仍走生成档:editor_agent/tool.rs:698-703 依旧用 image_generation_mud_points + GPT_IMAGE_2_5_BUSINESS_NAME(命中 flare-c 档),与直连快速编辑 / raw edit 已改用的 image_edit_model_mud_points(sunburst-c 档)不一致,admin 分开调价即错价。
  4. 🟠 .codex 两个脚本仍发送业务名直连 provider:本轮只清理了单模型循环与 fallbackModel 字段,generate-template-samples.mjs:21 仍是 preferredImageModel = 'gpt-image-2.5' 直连 VECTOR_ENGINE_BASE_URL。按新白名单,provider 端只认 concrete model 且 GPT Image 2.5 走 Tiantoken,实盘必败。请改用 concrete model + Tiantoken 凭据,或在 SKILL.md 标注脚本已退役。
  5. 🟠 create_raw_image_edit 仍无 provider 校验:raw_image_edit/mod.rs 本轮未动,硬编码 sunburst-c 但不校验 settings.provider,与共享 executor 的 ensure_provider_matches_model 防线不一致。

其他遗留(次要,不阻塞):

  • openai_image_generation.rs 的 create_openai_image_edit 与 create_openai_image_edit_with_references 仍是 dead_code(cargo check 有警告),建议一并删除。
  • executor.rs:231/421/672 的 task_prefix 仍硬编码 "vector-engine",Tiantoken 任务 fallback task_id 会带错误前缀进入持久化与审计。
  • resolve_image_provider 仍接受 gpt-image-2 → Tiantoken,且 executor preferred_image_upstream_model 恒等透传——legacy 字符串若绕过 api-server 边界会原样发往上游。ADR 已记录「gpt-image-2 继续作为历史可读值接受」为有意行为,建议在新增的白名单单测里补一条 gpt-image-2 → Tiantoken 的断言把该意图钉死,或让 executor 也拒绝。
  • OpenAPI ui-design 端点「未传时默认使用 nanobanana」与实现强制 gpt-image-2.5 的矛盾仍在(本轮该行又被改到但仍未修正)。
  • editor_project.rs:4101-4102 concrete key 仍是字符串字面量而非已 import 的常量。
  • 补充确认:gpt-image-2-c 在后端 normalize_editor_generation_options 走 _ => nanobanana 未知值分支静默回落,与 decision-log「走既有未知值分支」的表述一致,无异议。

本轮本地验证

  • cargo test -p platform-image:21 通过 / 1 失败(阻塞项 2 的既有竞态)
  • cargo check -p api-server:通过,仍有上述两个 dead_code 警告
  • npx vitest run src/components/image-editor/ImageCanvasGenerationModel.test.ts:34 项全部通过

结论:本轮更新质量高,白名单收紧与 gpt-image-2-c 清理做得干净彻底、文档同步完整。但上轮的 2 个阻塞项(参考图分流与 ADR 矛盾、平台相关测试竞态)与 3 个严重项(编辑工具计价、.codex 脚本、raw edit 防线)均未处理,维持「修复后再合并」的结论。

## 复审(a6f99c887..f3c362b1e,16 个新提交) ### 上轮问题的处理情况 **已解决:** - ✅ 白名单收紧:`resolve_image_provider` 现在拒绝业务名 `gpt-image-2.5` 与已退役的 `gpt-image-2-c`,新增 `resolve_image_provider_rejects_business_model_and_accepts_concrete_models` 单测钉死(上轮严重项 5 的主体已解决)。 - ✅ `gpt-image-2-c` 整体删除:常量、re-export、尺寸语义、审计标签、三处提交边界解析、前端常量与恢复分支全部移除,ADR / CONTEXT / OpenAPI / api-operations / pitfalls / decision-log 同步一致。数据库历史审计字符串不回写的策略表述清晰。 - ✅ nanobanana 端点空模型回落修正并补测试(`nanobanana_generate_content_url_blank_model_falls_back_to_nanobanana_model`)。 - ✅ 启动报错点名具体环境变量(`TIANTOKEN_BASE_URL` 等),client 构造成功后有 info 启动日志,ADR Consequences 已同步。 - ✅ nanobanana 执行器错误 provider 标签从硬编码 `VectorEngine` 改为 `settings.provider` 透传。 - ✅ 死代码 `create_openai_image_generation` 已删除;空参考图错误的 provider 改为按模型解析(`openai_image_generation.rs:341`)。 - ✅ 前端 legacy 判定与归一合并为同一 `LEGACY_EDITOR_IMAGE_MODELS` 集合,`ImageCanvasGenerationModel.test.ts` 34 项本地通过。 **仍未解决(沿用上轮意见):** 1. 🔴 **阻塞:生成入口仍按「是否带参考图」分流到 sunburst-c**——`editor_project.rs:3294-3300`、`editor_project_icon.rs:1777-1786`、`character_visual_assets.rs:949-955` 本轮未改动,而 ADR 中「普通生成即使因参考图使用 edits multipart,仍按生成 concrete model」的表述也保留原样。代码与文档的矛盾依然存在,且计价(flare-c 档)与执行/审计(sunburst-c)分裂的问题仍在。请明确二选一:改代码按任务类型分流,或修订三处文档并把生成带参考图的计价切到编辑档。 2. 🔴 **阻塞:`cargo test -p platform-image` 本地仍必红**——`tests/image_provider.rs` 的 `nanobanana_generate_content_posts_native_body_and_reads_inline_data` mock 竞态未修复(mock 只读到 `\r\n\r\n` 就断言请求体),本机连续复现(21 passed / 1 failed)。decision-log 记录的「cargo test -p platform-image 全绿」与本机(Windows)结果不符,说明该 race 与平台/网络栈行为相关,CI 上同样有失败风险。建议用同文件已有的按 Content-Length 完整读 body 的 helper 重写该断言。 3. 🟠 **EditImageTool 计价仍走生成档**:`editor_agent/tool.rs:698-703` 依旧用 `image_generation_mud_points` + `GPT_IMAGE_2_5_BUSINESS_NAME`(命中 flare-c 档),与直连快速编辑 / raw edit 已改用的 `image_edit_model_mud_points`(sunburst-c 档)不一致,admin 分开调价即错价。 4. 🟠 **`.codex` 两个脚本仍发送业务名直连 provider**:本轮只清理了单模型循环与 `fallbackModel` 字段,`generate-template-samples.mjs:21` 仍是 `preferredImageModel = 'gpt-image-2.5'` 直连 `VECTOR_ENGINE_BASE_URL`。按新白名单,provider 端只认 concrete model 且 GPT Image 2.5 走 Tiantoken,实盘必败。请改用 concrete model + Tiantoken 凭据,或在 SKILL.md 标注脚本已退役。 5. 🟠 **`create_raw_image_edit` 仍无 provider 校验**:`raw_image_edit/mod.rs` 本轮未动,硬编码 sunburst-c 但不校验 `settings.provider`,与共享 executor 的 `ensure_provider_matches_model` 防线不一致。 **其他遗留(次要,不阻塞):** - `openai_image_generation.rs` 的 `create_openai_image_edit` 与 `create_openai_image_edit_with_references` 仍是 dead_code(cargo check 有警告),建议一并删除。 - `executor.rs:231/421/672` 的 `task_prefix` 仍硬编码 `"vector-engine"`,Tiantoken 任务 fallback task_id 会带错误前缀进入持久化与审计。 - `resolve_image_provider` 仍接受 `gpt-image-2` → Tiantoken,且 executor `preferred_image_upstream_model` 恒等透传——legacy 字符串若绕过 api-server 边界会原样发往上游。ADR 已记录「gpt-image-2 继续作为历史可读值接受」为有意行为,建议在新增的白名单单测里补一条 `gpt-image-2 → Tiantoken` 的断言把该意图钉死,或让 executor 也拒绝。 - OpenAPI ui-design 端点「未传时默认使用 nanobanana」与实现强制 `gpt-image-2.5` 的矛盾仍在(本轮该行又被改到但仍未修正)。 - `editor_project.rs:4101-4102` concrete key 仍是字符串字面量而非已 import 的常量。 - 补充确认:`gpt-image-2-c` 在后端 `normalize_editor_generation_options` 走 `_ => nanobanana` 未知值分支静默回落,与 decision-log「走既有未知值分支」的表述一致,无异议。 ### 本轮本地验证 - `cargo test -p platform-image`:21 通过 / 1 失败(阻塞项 2 的既有竞态) - `cargo check -p api-server`:通过,仍有上述两个 dead_code 警告 - `npx vitest run src/components/image-editor/ImageCanvasGenerationModel.test.ts`:34 项全部通过 **结论**:本轮更新质量高,白名单收紧与 `gpt-image-2-c` 清理做得干净彻底、文档同步完整。但上轮的 2 个阻塞项(参考图分流与 ADR 矛盾、平台相关测试竞态)与 3 个严重项(编辑工具计价、.codex 脚本、raw edit 防线)均未处理,维持「修复后再合并」的结论。
k88936 added 8 commits 2026-09-21 22:09:09 +08:00
- 新增 editor_image_generation_concrete_model 作为生成类任务选择 concrete model 的唯一入口,dispatch 与计价共用同一判据
- image_generation_mud_points 增加 has_reference_images,命中编辑 concrete model 时改走 image_edit_model_mud_points
- editor_project/editor_project_icon 的 provider_model 改用共享解析函数,替换三处重复的带参考图 if 表达式
- character_visual_assets 的角色主形象生成改用共享解析函数
- 图标素材图集、UI 素材提取、图标规范带 referenceId、Agent 生成类工具带 referenceImageIds 的计价同步切到编辑档
- EditImageTool 计价从生成档改为编辑档,与直连快速编辑、raw edit 对齐
- normalize_editor_generation_options 的具体 provider key 改用常量替换字符串字面量
- 补充参考图档位路由与 Agent 工具档位路由单测
- platform-image 导出 ensure_provider_matches_model 供 crate 内复用
- create_raw_image_edit 在构造请求前校验 settings.provider 与固定的编辑 concrete model 一致
- executor 新增 image_task_id_prefix,按 settings.provider 拼接前缀
- 生成、nanobanana 生成与编辑三处不再硬编码 vector-engine 前缀,Tiantoken 任务的持久化与审计不再带错误前缀
- resolve_image_provider 单测增加 gpt-image-2 → Tiantoken 断言,钉死 ADR 记录的历史可读值意图
- 删除 create_openai_image_edit 与 create_openai_image_edit_with_references 两个 dead_code 封装
- 空参考图本地校验用例改走 create_openai_image_edit_with_references_and_model,保留原覆盖
- generate-template-samples 与 generate-anthro-cat-illustrations 的 preferredImageModel 改为 gpt-image-2.5-flare-c
- 两个脚本的 base URL / API key / 超时改为只读 TIANTOKEN_BASE_URL、TIANTOKEN_API_KEY、TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS,不再回退 VectorEngine
- 脚本内 URL 构造函数、错误文案与错误字段名从 VectorEngine 改为 provider 中性的 Tiantoken 口径
- SKILL.md 请求契约、环境变量与模型说明同步到 concrete model + Tiantoken,并说明带参考图的编辑走 gpt-image-2.5-sunburst-c
- ADR 改为「带参考图的生成与编辑都按编辑 concrete model 执行、审计与计价」,并补定价档位跟随 concrete model 的后果
- 里程碑文档路由表拆分 flare-c(仅无参考图生成)与 sunburst-c(编辑 + 带参考图的生成),必须成立的行为补第 7 条
- decision-log 修正 2026-09-18 条目,并新增本次「生成带参考图统一按编辑 concrete model 执行与计价」决策记录
- pitfalls、后端架构与开发运维文档同步 provider 具体模型与计价档位口径
修正 OpenAPI 中 model 字段与实现不一致的描述
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
e3ba986596
- EditorImageGenerationRequest.model 说明 kind=ui-design 与 publication-material 固定使用 gpt-image-2.5 并忽略传入 model,其余 kind 未传时默认 gpt-image-2.5
- EditorUiDesignAssetExtractionRequest.model 说明未传时默认 nanobanana,传 gpt-image-2.5 时按 GPT Image 2.5 编辑任务执行
k88936 added 1 commit 2026-09-21 22:09:47 +08:00
更新测试用例以反映按钮名称从 "gpt-image-2" 修改为 "GPT Image 2.5"
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m0s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m50s
Project CI / Backend tests (pull_request) Successful in 5m25s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m9s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m17s
Project CI / Native shell tests (pull_request) Successful in 7m0s
Project CI / Frontend tests (pull_request) Failing after 3m41s
Project CI / Repository checks (pull_request) Successful in 3m16s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m51s
97b0a4b9b8
k88936 added 3 commits 2026-09-22 11:59:37 +08:00
- nanobanana 生成用例的 mock server 改为复用同文件 read_http_request,按 Content-Length 读满请求体再断言,消除「只读到 \r\n\r\n 就断言头后 body」的竞态
- pitfalls 新增「测试内 mock HTTP server 必须按 Content-Length 读满请求体再断言」记录现象、原因与变异验证方式
- 6 个 image-editor 测试文件的旧 gpt-image-2 断言改为 gpt-image-2.5 / GPT Image 2.5,覆盖图集导出、元数据弹窗、UI 素材提取模型菜单与生成/快速编辑 dialog 参数快照
- 工作流测试夹具与提取模型按钮改用 IMAGE_MODEL_GPT_IMAGE_2_5 常量,legacy 夹具保留并断言提交边界归一为当前业务模型
- decision-log 2026-09-21 条目补 platform-image 与前端全量测试验证结论
Merge remote-tracking branch 'refs/remotes/origin/master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m2s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m55s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m46s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m12s
Project CI / Backend tests (pull_request) Successful in 9m12s
Project CI / Native shell tests (pull_request) Successful in 10m32s
Project CI / Frontend tests (pull_request) Successful in 7m1s
Project CI / Repository checks (pull_request) Successful in 5m43s
Project CI / AI game creator shell web tests (pull_request) Successful in 6m4s
a943048093
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
k88936 added 12 commits 2026-09-22 14:19:33 +08:00
- generate-anthro-cat-illustrations 的 preferredImageModel 重命名为 imageModel,并同步 requestImagePayload 与试运行请求体引用
- generate-template-samples 的 preferredImageModel 重命名为 imageModel,并同步 requestImagePayload 与试运行请求体引用
- 两个脚本新增 fetchJsonWithRetry:对 408/429/5xx 按 1s/2s 退避重试同一 concrete model,最多 3 次,不引入跨模型回退
- requestImagePayload 改用带 requestId 的重试入口,单次 408/429/5xx 不再中断整批生成
- 删除只写不读的 providerBody / providerResponseParse 错误字段,解析失败与缺图改为把响应片段写进错误信息
- extract_image_urls_and_b64_values_are_deduped 更名为 extract_image_urls_are_deduped_and_b64_values_keep_payload_order
- 测试名不再暗示 extract_b64_images 具备去重能力,实际只断言 URL 去重与 base64 / inline data 的载荷顺序
- 新增 normalize_nanobanana_model,把空模型字符串统一回落到 gemini-3.1-flash-image-preview
- create_nanobanana_generate_content 改用该判据,空模型不再被 normalize_image_model 改写成 gpt-image-2.5-flare-c 后被 provider 校验拒绝
- nanobanana_generate_content_url 复用同一判据,去掉重复的空值分支
- 新增 nanobanana_model_resolution_defaults_blank_values_to_nanobanana 单测
- image_generation_does_not_retry_as_another_model 增加两次请求的 model 断言,确认重试仍使用 gpt-image-2.5-flare-c
- 与 image_generation_stays_on_model_after_upstream_502 的断言口径对齐,防止回归成换模型重试
- EDITOR_IMAGE_MODEL_MUD_POINT_CONFIG 提取 GPT_IMAGE_2_5_TIER_PRICES,gpt-image-2 与 gpt-image-2.5 复用同一份泥点档位
- EDITOR_IMAGE_DIMENSION_OPTIONS 提取 GPT_IMAGE_2_5_DIMENSION_OPTIONS,两个 key 复用同一份尺寸档位
- 修正注释:legacy key 是持久化值的防御性别名,生产读取会先经 normalizeEditorImageModel 归一,不再声称它被正式读路径按旧值使用
- create_image_generation 在参考图非空时改用 gpt-image-2.5-sunburst-c,与 generate→edits 分支和任务边界 editor_image_generation_concrete_model 判据一致
- 新增 image_generation_with_references_uses_the_edit_concrete_model,断言带参考图的生成请求落到 /v1/images/edits 且 multipart 提交编辑 concrete model
- 为 is_gpt_image_2_family_model 补充文档注释:尺寸钳制族刻意包含业务名 gpt-image-2.5
- 明确 resolve_image_provider 仍坚持只收 concrete key,两处白名单不同是设计意图而非遗漏
- SKILL.md 明确脚本只做同 concrete model 的 408/429/5xx 退避重试,不做跨模型回退
- resolve_image_provider 错误类型从 &'static str 改为 String,未知模型返回「不支持的图片模型:{model}」
- ensure_provider_matches_model 去掉重复拼接的模型名,避免错误信息出现两遍输入
- openai_image_generation 缺少参考图的本地校验改用显式 match,不再用 unwrap_or 静默假定 Tiantoken
- 补充断言:业务模型名与 unknown-model 的拒绝原因里必须包含被拒绝的模型值
- AppConfig 新增 tiantoken_image_request_timeout_ms(默认 1000000),env 只读 TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS
- VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS 只写 vector_engine_image_request_timeout_ms,不再被 Tiantoken 变量覆盖
- Tiantoken 图片 client 与 OpenAiImageSettings.request_timeout_ms 改用新字段,两个 provider 各用自己的超时
- config 测试补 Tiantoken / VectorEngine 双向不覆盖断言,deploy env 示例补 VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000
- 同步开发运维文档与 decision-log
删除失效的图片模型兜底审计链路
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m0s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m50s
Project CI / Frontend tests (pull_request) Successful in 4m2s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m46s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m1s
Project CI / Native shell tests (pull_request) Successful in 7m3s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m37s
ff89951a9b
- platform-image 移除 PlatformImageError::FallbackFailed、recovered_failure_audits()、with_recovered_failure_audits 与 into_final_error
- GeneratedImages 去掉恒为空的 recovered_failure_audits 字段,image_source 两处构造同步收敛
- executor 去掉空 Vec 累积、成功路径空 append 与 finish_image_model_fallback_error 包装,失败分支直接返回原错误
- api-server 去掉成功摘要的 recoveredFailureCount 与两处 recovered audit 落库循环,map_platform_image_error 去掉不可达分支
- 测试移除恒空断言,image_generation_does_not_retry_as_another_model 改为断言终态 Upstream 401
- 同步开发运维文档与 decision-log(含说明 2026-07-21 兜底条目的 recoveredFailureCount 已退役)
Author
Member

留给你处理的 Review 剩余项(未自动修改)

说明:以下是从上一版 ./review.txt 中保留下来的、会影响生产行为 / 计费或需要基础设施判断的项,我没有自动改,等你确认。可机械替换、纯文档或跨 crate 死代码清理类的问题已逐项修复并单独提交(见文末「已修复」)。

    1. 【计费口径·high】图标图集 Agent 工具按生成档计价,执行与直连入口都按编辑档
    • 位置:server-rs/crates/api-server/src/editor_agent/tool.rs:852-855(pricing 中的第 5 个参数)
    • 现状:GenerateIconSpritesheetTool::pricing 用 !args.reference_image_ids.is_empty() 判断 has_reference_images,但 reference_image_id(图标规范图)是必填参数,执行时始终把它作为参考图提交,GPT Image 2.5 固定落到 /v1/images/edits + gpt-image-2.5-sunburst-c;直连入口 resolve_editor_icon_spritesheet_price(editor_project_icon.rs:236-252)已经硬编码传 true。
    • 问题:Agent 工具在 reference_image_ids 为空时按生成档 gpt-image-2.5-flare-c 计价,实际按编辑档执行,属于少收费;admin 单独调整编辑档价格后偏差会放大。与 2026-09-21 决策「图标素材图集(固定参考图)… 全部同规则」不一致。
    • 建议:把该处实参改成 true;顺带确认 build_display_args 展示价同步变化,并跑 cargo test -p api-server editor_agent::tool。
    • 为什么留给你:这会直接改变用户实际扣费金额(上调),需要你确认是否接受以及是否要同步公告 / 补偿。
    1. 【计费换算·medium】持久化定价只迁移了一半时,缺失档位从 legacy gpt-image-2 补齐而不是从同级的新档位推导
    • 位置:server-rs/crates/api-server/src/editor_generation_config.rs:408-446(backfill_legacy_gpt_image_2_5_pricing)
    • 现状:只要 gpt-image-2.5-flare-c / gpt-image-2.5-sunburst-c 任一缺失,就整体从 gpt-image-2(先查持久化 override,再查受控默认配置)克隆出两个 key,分别 or_insert。
    • 问题:当 override 里已经存在 gpt-image-2.5-flare-c、但缺 gpt-image-2.5-sunburst-c 时,sunburst-c 会拿到 gpt-image-2 的历史价格,而不是「同级那份已经存在的新档位」或受控默认里的 sunburst-c 价;admin 单独调过生成档价格时,编辑档会被静默回溯成旧值,且没有任何日志/标记说明这是补齐值。
    • 建议:先按已有 2.5 兄弟档位补齐(生成档存在就用生成档、编辑档存在就用编辑档),否则再回退 legacy gpt-image-2 或受控默认;每次实际写入补齐值时记 tracing::warn!(带 source / 模型 / 价格);补 editor_generation_config 的部分迁移用例。
    • 为什么留给你:涉及扣费金额,且要确认「编辑档 = 生成档」是否就是期望的默认换算关系。
    1. 【性能·low】图片下载 HTTP client 关闭了连接复用
    • 位置:server-rs/crates/platform-image/src/image_provider/transport/client.rs:6-21
    • 现状:启动期构造一次、之后每次 clone 复用,但 builder 是 .timeout(...).http1_only().pool_max_idle_per_host(0);该 client 实际只用于下载参考图 / provider 回图(runtime/image_source.rs:13-85,生成请求本身走 curl),而 api-server/src/state.rs 里其它长生命周期 client 用的是 pool_max_idle_per_host(8/64/128)。
    • 问题:pool_max_idle_per_host(0) 等于禁用 keep-alive,每次下载都要重新做 TCP + TLS 握手;批量下载参考图(最多 5 张)和 provider 回图都会多付这部分延迟。
    • 建议:确认网关没有「仅 HTTP/1.1 + 禁长连接」的要求后,把 pool_max_idle_per_host(0) 调到与其它 client 一致的量级(如 8),http1_only() 仅在确实需要时保留;改动后跑 cargo test -p platform-image 与一次真实图片下载冒烟。
    • 为什么留给你:是否需要 http1_only / 禁长连接取决于网关与 provider 侧约束,仓库代码里没有依据可判断。

已修复(按你的选择追加)

  • b0bb71a79 让 resolve_image_provider 的拒绝原因带上模型名 —— 上一版第 2 条(错误类型改 String 并携带模型名;ensure_provider_matches_model 去掉重复拼接;openai_image_generation.rs 的 unwrap_or(Tiantoken) 改为显式 match;补拒绝原因含模型名的断言)
  • c0d4e76a0 解耦 Tiantoken 与 VectorEngine 的图片请求超时 —— 上一版第 3 条(新增 tiantoken_image_request_timeout_ms,VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS 只作用于 VectorEngine;state / OpenAiImageSettings 各取自己的字段;deploy env 示例与运维文档同步)
  • ff89951a9 删除失效的图片模型兜底审计链路 —— 上一版第 6 条(删除 FallbackFailed、recovered_failure_audits、with_recovered_failure_audits、into_final_error、GeneratedImages 字段、executor 包装层、api-server recoveredFailureCount 与两处落库循环;同步运维文档与 decision-log)

已修复(上一轮)

  • 0b1caf728 修正图片脚本中误导性的首选模型变量名 —— 原 review 第 1、3 条
  • 827164ed7 为图片素材脚本补回同模型瞬时错误重试 —— 原 review 第 2、4、5、6 条(重试用 providerStatus,删掉只写不读的 providerBody / providerResponseParse)
  • d95a01cf0 同步图片技能文档的同模型重试说明 —— 上述脚本改动的 SKILL.md 文档同步
  • e901937bb 修正图片解析去重测试名与真实覆盖范围 —— 原 review 第 7 条
  • a4be08d51 修正 nanobanana 空模型被误归一为 GPT 生成模型 —— 原 review 第 9、13 条
  • fb6e00c96 消除前端 legacy gpt-image-2 档位的重复字面量 —— 原 review 第 12 条(保留 legacy key 兼容持久化值,改为复用同一常量消除漂移,并修正注释)
  • d3822a9a1 补齐图片生成重试测试的同模型断言 —— 原 review 第 17 条
  • 8e92bd295 修正带参考图的便捷生成入口模型选择 —— 原 review 第 19 条
  • 4f2591b50 说明 GPT Image 2 尺寸族与 provider 白名单的口径差异 —— 原 review 第 14 条(判定为有意设计,仅补注释)
# 留给你处理的 Review 剩余项(未自动修改) 说明:以下是从上一版 ./review.txt 中保留下来的、会影响生产行为 / 计费或需要基础设施判断的项,我没有自动改,等你确认。可机械替换、纯文档或跨 crate 死代码清理类的问题已逐项修复并单独提交(见文末「已修复」)。 - 1. 【计费口径·high】图标图集 Agent 工具按生成档计价,执行与直连入口都按编辑档 - 位置:server-rs/crates/api-server/src/editor_agent/tool.rs:852-855(pricing 中的第 5 个参数) - 现状:`GenerateIconSpritesheetTool::pricing` 用 `!args.reference_image_ids.is_empty()` 判断 `has_reference_images`,但 `reference_image_id`(图标规范图)是必填参数,执行时始终把它作为参考图提交,GPT Image 2.5 固定落到 `/v1/images/edits` + `gpt-image-2.5-sunburst-c`;直连入口 `resolve_editor_icon_spritesheet_price`(editor_project_icon.rs:236-252)已经硬编码传 `true`。 - 问题:Agent 工具在 `reference_image_ids` 为空时按生成档 `gpt-image-2.5-flare-c` 计价,实际按编辑档执行,属于少收费;admin 单独调整编辑档价格后偏差会放大。与 2026-09-21 决策「图标素材图集(固定参考图)… 全部同规则」不一致。 - 建议:把该处实参改成 `true`;顺带确认 `build_display_args` 展示价同步变化,并跑 `cargo test -p api-server editor_agent::tool`。 - 为什么留给你:这会直接改变用户实际扣费金额(上调),需要你确认是否接受以及是否要同步公告 / 补偿。 - 2. 【计费换算·medium】持久化定价只迁移了一半时,缺失档位从 legacy `gpt-image-2` 补齐而不是从同级的新档位推导 - 位置:server-rs/crates/api-server/src/editor_generation_config.rs:408-446(`backfill_legacy_gpt_image_2_5_pricing`) - 现状:只要 `gpt-image-2.5-flare-c` / `gpt-image-2.5-sunburst-c` 任一缺失,就整体从 `gpt-image-2`(先查持久化 override,再查受控默认配置)克隆出两个 key,分别 `or_insert`。 - 问题:当 override 里已经存在 `gpt-image-2.5-flare-c`、但缺 `gpt-image-2.5-sunburst-c` 时,sunburst-c 会拿到 `gpt-image-2` 的历史价格,而不是「同级那份已经存在的新档位」或受控默认里的 sunburst-c 价;admin 单独调过生成档价格时,编辑档会被静默回溯成旧值,且没有任何日志/标记说明这是补齐值。 - 建议:先按已有 2.5 兄弟档位补齐(生成档存在就用生成档、编辑档存在就用编辑档),否则再回退 legacy `gpt-image-2` 或受控默认;每次实际写入补齐值时记 `tracing::warn!`(带 source / 模型 / 价格);补 `editor_generation_config` 的部分迁移用例。 - 为什么留给你:涉及扣费金额,且要确认「编辑档 = 生成档」是否就是期望的默认换算关系。 - 3. 【性能·low】图片下载 HTTP client 关闭了连接复用 - 位置:server-rs/crates/platform-image/src/image_provider/transport/client.rs:6-21 - 现状:启动期构造一次、之后每次 clone 复用,但 builder 是 `.timeout(...).http1_only().pool_max_idle_per_host(0)`;该 client 实际只用于下载参考图 / provider 回图(`runtime/image_source.rs:13-85`,生成请求本身走 curl),而 api-server/src/state.rs 里其它长生命周期 client 用的是 `pool_max_idle_per_host(8/64/128)`。 - 问题:`pool_max_idle_per_host(0)` 等于禁用 keep-alive,每次下载都要重新做 TCP + TLS 握手;批量下载参考图(最多 5 张)和 provider 回图都会多付这部分延迟。 - 建议:确认网关没有「仅 HTTP/1.1 + 禁长连接」的要求后,把 `pool_max_idle_per_host(0)` 调到与其它 client 一致的量级(如 8),`http1_only()` 仅在确实需要时保留;改动后跑 `cargo test -p platform-image` 与一次真实图片下载冒烟。 - 为什么留给你:是否需要 `http1_only` / 禁长连接取决于网关与 provider 侧约束,仓库代码里没有依据可判断。 ## 已修复(按你的选择追加) - `b0bb71a79` 让 resolve_image_provider 的拒绝原因带上模型名 —— 上一版第 2 条(错误类型改 `String` 并携带模型名;`ensure_provider_matches_model` 去掉重复拼接;`openai_image_generation.rs` 的 `unwrap_or(Tiantoken)` 改为显式 match;补拒绝原因含模型名的断言) - `c0d4e76a0` 解耦 Tiantoken 与 VectorEngine 的图片请求超时 —— 上一版第 3 条(新增 `tiantoken_image_request_timeout_ms`,`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 只作用于 VectorEngine;state / OpenAiImageSettings 各取自己的字段;deploy env 示例与运维文档同步) - `ff89951a9` 删除失效的图片模型兜底审计链路 —— 上一版第 6 条(删除 `FallbackFailed`、`recovered_failure_audits`、`with_recovered_failure_audits`、`into_final_error`、`GeneratedImages` 字段、executor 包装层、api-server `recoveredFailureCount` 与两处落库循环;同步运维文档与 decision-log) ## 已修复(上一轮) - `0b1caf728` 修正图片脚本中误导性的首选模型变量名 —— 原 review 第 1、3 条 - `827164ed7` 为图片素材脚本补回同模型瞬时错误重试 —— 原 review 第 2、4、5、6 条(重试用 `providerStatus`,删掉只写不读的 `providerBody` / `providerResponseParse`) - `d95a01cf0` 同步图片技能文档的同模型重试说明 —— 上述脚本改动的 SKILL.md 文档同步 - `e901937bb` 修正图片解析去重测试名与真实覆盖范围 —— 原 review 第 7 条 - `a4be08d51` 修正 nanobanana 空模型被误归一为 GPT 生成模型 —— 原 review 第 9、13 条 - `fb6e00c96` 消除前端 legacy gpt-image-2 档位的重复字面量 —— 原 review 第 12 条(保留 legacy key 兼容持久化值,改为复用同一常量消除漂移,并修正注释) - `d3822a9a1` 补齐图片生成重试测试的同模型断言 —— 原 review 第 17 条 - `8e92bd295` 修正带参考图的便捷生成入口模型选择 —— 原 review 第 19 条 - `4f2591b50` 说明 GPT Image 2 尺寸族与 provider 白名单的口径差异 —— 原 review 第 14 条(判定为有意设计,仅补注释)
Author
Member

1低优不管了 2I will都给默认值了, 不留处理代码 3这是原行为

1低优不管了 2I will都给默认值了, 不留处理代码 3这是原行为
k88936 added 2 commits 2026-09-22 14:51:05 +08:00
- 新增 overlay_editor_generation_pricing,以编译内置默认为基线叠加持久化模型条目
- 删除 backfill_legacy_gpt_image_2_5_pricing 与 backfill_legacy_sfx_pricing,record 加载路径改为直接叠加
- 缺模型的旧 override / record 继承默认档位,写坏的条目仍由 validate() 拦截
- 更新 backfill 相关单测,补 legacy override 继承默认值与非法条目拒绝用例
- 同步 ADR 与决策记录里的受控 backfill 口径
同步定价文档里的受控 backfill 口径
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m36s
Project CI / Backend tests (pull_request) Failing after 13s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m14s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m17s
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m4s
Project CI / Frontend tests (pull_request) Successful in 4m45s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m0s
Project CI / Native shell tests (pull_request) Successful in 7m28s
7c76e7a3f2
- 更新 2026-06-22 定价配置管理方案:缺模型条目改为继承编译内置默认值,并写明只自定义过 gpt-image-2 价格时的计价差异
- 在 2026-09-18 决策条目上标注该受控 backfill 已被 2026-09-22 决策取代
k88936 added 1 commit 2026-09-22 15:58:24 +08:00
Merge branch 'master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m5s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m16s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m36s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m46s
Project CI / Backend tests (pull_request) Successful in 8m13s
Project CI / Repository checks (pull_request) Successful in 4m55s
Project CI / Frontend tests (pull_request) Successful in 5m53s
Project CI / Native shell tests (pull_request) Successful in 11m8s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m25s
47f5be12c0
Some checks are pending
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m5s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m16s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m36s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m46s
Project CI / Backend tests (pull_request) Successful in 8m13s
Project CI / Repository checks (pull_request) Successful in 4m55s
Project CI / Frontend tests (pull_request) Successful in 5m53s
Project CI / Native shell tests (pull_request) Successful in 11m8s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m25s
This pull request has changes conflicting with the target branch.
  • docs/project-memory/shared-memory/decision-log.md
  • docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md
  • server-rs/crates/api-server/src/editor_project.rs
  • server-rs/crates/api-server/src/openai_image_generation.rs
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/gptimage2to2.5:feat/gptimage2to2.5
git checkout feat/gptimage2to2.5
Sign in to join this conversation.