docs: record VIP98K live matrix and deployment evidence

This commit is contained in:
134 2026-09-11 15:10:20 +08:00
parent aaa650265c
commit 0aff83f61a
2 changed files with 86 additions and 1 deletions

View File

@ -0,0 +1,84 @@
# VIP98K 图片参数、参考图限制与详情替换状态修复
2026-09-11,北京时间。运行修复已上线至 `ai.yydd.ai`。服务器代码版本 `b26e2650`,构建 ID `bEps6MRbyVkKLfBQTUChg`;本地对应提交 `aaa65026`。
## 实测结论
读取[服务商公开目录](https://api.vip98k.com/api/v2/catalog/models)及当前账户授权列表,测试其中 9 个生图模型。不含 OCR、抠图、压缩等处理工具,也不包含其他服务商。共 77 次尺寸、比例及质量调用:64 次取得结果图,13 次接口失败;另完成 1 次超过 10MiB 原图继续编辑的完整参考图处理调用。出图成功不等于尺寸或比例符合要求。
[实测图片与全部记录](./qa-vip98k-0911/index.html)提供原图、实际像素、耗时、请求编号和 JSON;这些大体积验收附件保存在本地,未纳入 Git。测试使用合成蓝 A / 红 B 卡片,验证接口与返回参数,不代表复杂商品细节一致性的验收。
| 模型 | 真实结果 | 本次处理 |
| --- | --- | --- |
| gpt-image-2-vip | 方图 1K=1024²、2K=2048²、4K=2880²;横图亦成功。2K 覆盖 9 个固定比例 | 恢复 1K/2K/4K 与比例;同步目录缺字段不再覆盖已验证能力 |
| gpt-image-2.5 | 请求 1024²、2048²、2880² 都返回约 1254²;2K、4K 档位字符串也只返回约 1672×940 横图。9:16、4:5、5:4 成功 | 恢复比例,保留真实可用的 1K 档;不虚报 2K/4K |
| gpt-image-2.5-flare | 三档有实图;2K 的 2736×1536 会变成 3840×2160。4:5、5:4 即使显式传 aspect_ratio,仍变成 3:4、4:3 | 修正跨档边界为 2720×1536,实收 2048×1152;保留 7 个已验证固定比例 |
| nano-banana-2 | 方图三档 1024²/2048²/4096²;4K 横图 5504×3072 | 保留三档和比例 |
| nano-banana-pro | 方图三档 1024²/2048²/4096²;4K 横图 5504×3072 | 保留三档和比例 |
| gpt-image-2 | multipart edits 方图 2K 成功;初测超大尺寸遇到 502/503,调整后 2K、4K 横图成功 | 记录原生 edits 证据;网站尚未启用该 multipart 线路,未偷偷改为其他接口或模型 |
| gpt-image-2.5-sunburst | 三档成功;2K 横图同样存在升到 4K 的边界现象 | 保持网站原有隐藏状态 |
| nano-banana-2-lite | 双参考与无参考各一次均 HTTP 400 | 保持不可选 |
| nano-banana-fast | 双参考与无参考各一次均 HTTP 400 | 保持不可选 |
覆盖方式:各模型基础调用;可成功的主要线路补测三档方图及高分辨率横图;VIP/Flare 补测 2K 的全部原有固定比例,并对异常做对照。不是每个模型、每个比例、每个档位的完整笛卡尔积测试。
普通 GPT Image 2.5 的 2K/4K 不是“没公开所以推断不支持”,而是实际请求已接受、实际像素未提高。VIP 与 Flare 的 4K 方图是 2880²;不能把所有模型的 4K 都解释为 4096²。
## 为什么界面连 1K 都消失
目录缺少尺寸和比例数据时,原同步规则回退到“1K、auto”;此外,简洁参数面板把比例和分辨率放进同一数组,并按 `aspectRatio:imageSize` 去重,导致“auto”覆盖了“1K”按钮。
已拆成“比例”和“分辨率”两组,修正各自选中状态,并在收起后的按钮上显示当前分辨率。按验证结果更新了现有 5 个 VIP98K 模型的尺寸、比例能力,保留模型价格、启停状态和既有业务配置。Flare 的 4:5、5:4 因反复偏离请求未继续展示。
## 10MiB 只约束送入服务商的参考副本
网站上传上限是 20MiB;用户报错描述的是服务商单张参考图 10MiB。这不限制生成结果原图,也不会为了 10MiB 改写结果原件。
实际闭环:
- 将本次成功生成的 10,622,997 字节结果原图保存在对象存储。
- 使用修复后的真实服务商适配器,把它作为下一次生成的参考图。
- 生成 JPEG 参考副本 451,639 字节;准备阶段约 17.5 秒;完整测试约 83.2 秒并成功返回新图。
- 下载原件核对 SHA-256,内容完全未变。
统一覆盖外部图片地址、Data URL 和无默认 S3 配置分支;压缩失败停止提交,不再透传原始字节。压缩后检查字节上限,新缓存命名空间隔离历史未校验对象,缓存 HEAD 校验大小。仅参考副本沿用原有最长边 1920 策略,完整保留画面;结果原图归档流程未增加 10MiB 限制。
## 张华的三次详情替换与页面一直处理中
会话 `0f1c5be0-4522-4300-abcb-a93e17a60001`,三次都重跑“拼合切片 2”,实际使用 `gpt-image-2.5-flare`,每次提交 2 张参考图。
| 北京时间 | 总耗时 | 前置识别 | 生图调用阶段 | 结果 |
| --- | ---: | ---: | ---: | --- |
| 13:24:30–13:25:07 | 36.483s | 27.070s | 8.773s | HTTP 400 |
| 13:32:06–13:33:14 | 67.919s | 14.824s | 52.337s | 两次 502 后 HTTP 400 |
| 13:34:33–13:34:56 | 23.114s | 13.750s | 8.305s | HTTP 400 |
第二次有 15 秒和 30 秒重试等待,共 45 秒。参考图准备分别 394ms、2ms、0ms,不是上传大图造成这三次等待。
这些记录中的图片是失败后展示的参考图。原代码把回退参考图保存进 GeneratedImage 并递增成功数,造成“日志里有图片”的误导。现在失败保留清楚提示,但不再记作成功生成;没有删除历史三条记录。
页面另有独立缺陷:
1. 工作区没有消费后台 `data-appendMessage` 结果事件。
2. 历史同步只比较消息 ID,可能忽略同一消息的内容更新。
3. 进行中状态依赖流状态或结果数量,重跑旧卡片、并行乱序以及失败附带参考图时会判断失误。
已补齐结果事件处理、按请求 ID/卡片 ID 判定结束、内容同步、失败结束状态。切回页面、网络恢复和 pageshow 触发历史同步;所有目标卡片结束后主动关闭遗留流连接并清除本地“处理中”。旧的本地待处理记录也能从历史恢复。
没有证据证明张华当时一定切换过浏览器。后台标签页可能延迟前端刷新,但不能据此代替上述已确认的代码原因。
## 服务商 400 仍有未确认因素
原三次日志没有实际 size、quality 与服务商请求编号,无法追溯并确定是哪一个字段导致拒绝;也没有证据把用户贴出的 10MiB 错误与这三次绑定。
修复已记录尺寸、比例、质量和 `x-request-id`。上线后另一次 VIP 调用出现 400,但同质量合成测试成功。Flare 后续的高、中、自动质量都出现过 400;同一路线此前大量自动质量调用成功,中质量亦有成功。因此没有把“高质量不支持”当成定论,也未据此隐藏质量选项。对照请求号及结果保存在实测记录中,可交给服务商定位。不能承诺已经消除服务商自身的拒绝或波动。
## 上线与验证
- 42 项相关单元/回归测试通过;包含真实大于 10MiB 图片压缩、损坏数据拒绝、模型路由、像素边界及历史恢复。
- 浏览器使用真实 AI SDK 验证:结果乱序、失败附图、流保持打开、恢复历史后的状态结束;真实参数组件验证 4K/16:9 切换和仅 1K 时按钮不丢失。
- TypeScript 与生产构建通过。候选版本首页/登录页 HTTP 200,受保护模型/聊天 API 返回预期 401,风格化页返回预期认证跳转;生产域名检查通过。
- 先将新请求切到候选实例,等待原主进程连接归零后更新主实例,再将新请求切回主实例。保留旧静态资源,避免已打开页面加载旧分块失败。
- 回滚构建:服务器 `backups/next-before-vip98k-0911`;模型配置备份:`backups/vip98k-sizing-0911-1789109819739.json`;Nginx 原配置:`backups/vip98k-nginx-0911.conf`。
已经打开的页面需要刷新一次载入新前端。之后切回浏览器会主动补同步,无须靠反复刷新判断是否完成。

View File

@ -44,7 +44,8 @@ function b64(x){if(!x||typeof x!=='object')return null;if(x.b64_json)return x.b6
else {body.image=refs;body.size=variant==='bucket'?tier:size;if(variant==='bucket'||variant==='pixel-ratio')body.aspect_ratio=ratio;}
if(variant==='production'&&m.id.startsWith('gpt-image'))body.size=resolver.resolveVip98kImageTaskSize({imageSize:tier,aspectRatio:ratio,useGptImage25AspectSizing:true,preserveResolutionBucket:inv.path==='/v1/images/generations'});
if(process.env.PROBE_TEXT_ONLY==='1'){delete body.image;delete body.file_urls;body.prompt=`Two cards, a blue A on the left and a red B on the right, on a neutral studio background. Aspect ratio ${ratio}.`;}
record.path=inv.path;record.requestedSize=body.size;record.referenceCount=process.env.PROBE_TEXT_ONLY==='1'?0:2;record.route=headers['X-Relay-Route-Option']||null;
if(process.env.PROBE_QUALITY)body.quality=process.env.PROBE_QUALITY;
record.quality=body.quality;record.path=inv.path;record.requestedSize=body.size;record.referenceCount=process.env.PROBE_TEXT_ONLY==='1'?0:2;record.route=headers['X-Relay-Route-Option']||null;
if(inv.content_type==='multipart/form-data'){
const form=new FormData();for(const [k,v]of Object.entries(body))if(!['image','image[]','images'].includes(k)&&v!==null&&typeof v!=='object')form.append(k,String(v));
refBuffers.forEach((buf,i)=>form.append('image[]',new Blob([buf],{type:'image/png'}),`ref-${i}.png`));body=form;