12 KiB
12 KiB
Phase 1 骨架审核报告(T-101 ~ T-104)
审核人:Claude Code(全栈视角)|日期:2026-07-02|结论:验收通过,但最高风险未退。 Phase 1 是全项目最高风险段(AI 上游同步调用 + 可插拔供应商 + 密钥)。代码质量高、三模型机制忠实落地;但 T-104 只验证了管路,没退掉「图片同步耗时」这个核心风险,且
parameters透传存在资金安全隐患。 本文面向 codex 执行:每条修补项给出「症状 / 位置 / 怎么改 / 怎么验证」。修补任务见06-tasks.md的 T-105。
一、验收核对
| 任务 | 验收要点 | 结果 |
|---|---|---|
| T-101 | Provider 接口 + 按 api_type 注册;移植 cmbot 调用去桌面依赖;3 模型机制分别解析;mock 单测 | ✅ |
| T-102 | AiModel/ModelAlias + api_key 加密存储(admin 脱敏不回显);resolve_alias() 按能力校验;导入 ai_models.json;后台改配置热生效 |
✅ |
| T-103 | AiModel/ModelAlias/密钥后台变更留痕(谁/何时/改了什么);admin 可查 | ✅ |
| T-104 | 用别名 + 最小输入跑通一次生成,结论写入 progress(含耗时,验证图片同步可行性与超时配置) | ⚠️ 部分:管路跑通,但只跑了 recorded 标题,图片同步耗时/超时这一核心目的未验证(见 P1-2) |
二、做对的(勿在修补中回退)
以下是已正确落地的关键点,T-105 修补时不要改坏:
- 三模型机制忠实落地:
chat(GPT-5.5 文本)、auto→chat多模态返图(Nano Banana 2 走ChatCompletionsProvider.generate_image)、images_edits改图(GPT Image 2,ImagesEditsProvider强制要求原图)。注册表按api_type选适配器,auto按 URL 探测(detect_api_type),与04-architecture.md3.1 一致。 - 超时策略修对了桌面端的坑:
timeout_seconds=0不再是「无限」,而是按分辨率映射到有限值(512→180s / 1K→240s / 2K→360s / 4K→600s,见providers/utils.py:RESOLUTION_TIMEOUTS);connect/read 分离。这正是同步部署最关键的一处。 - 密钥零泄露闭环:Fernet 加密、
fernet:前缀、拒绝明文 fallback、缺AI_KEY_ENCRYPTION_KEY时_fernet()fail-closed 报错;admin 用写入型api_key字段(PasswordInput,不回显)、api_key_encrypted设editable=False;审计只记api_key: empty/set状态,绝不记明文或密文(audit._sanitize_value)。测试正向断言了「密文不含明文、不含fernet:」。 - 审计 admin 真只读:
AiConfigAuditLogAdmin的 add/change/delete 权限全False,get_readonly_fields覆盖全字段;delete_queryset也逐条留痕,不只delete_model。 - 测试与运行卫生:
session.trust_env=False(防止串到系统代理);smoke 命令用override_settings + transaction.atomic + set_rollback(True)临时建配置跑完即回滚,不落业务库、不打印Bearer/占位 key,并有测试断言。 - 热生效 + 能力前置校验:
resolve_alias()每次查库取 active 配置(无进程内缓存到失效);标题要求text能力、图片要求image能力,能力不匹配抛ModelCapabilityError。 - 别名唯一性 DB 级:
ModelAlias有UniqueConstraint(operation_type, alias);单默认由clean()/save(full_clean)兜。
三、修补清单
P1 · 进 T-302(对外生成接口)落地前必须处理
P1-1 parameters / extra_body 无差别覆盖核心字段 —— 资金安全隐患
- 症状:
providers/openai_compatible.py:apply_extra_body()做的是payload.update(model.extra_body)后再payload.update(parameters)。parameters是调用方透传(见api.md)。这意味着 T-302 把外部请求的parameters直接喂进来时,调用方可以覆盖服务端固定的核心/计费字段:- 覆盖
model→ 实际调用与「计费别名」不同的上游 SKU(更贵/未授权),直接击穿「按别名计费、换底层模型不影响计费」的契约(05-coding-rules.md§8、04-architecture.md3.1)。 - 注入/覆盖
n(chat/images 一次返多图)、size/resolution/aspect_ratio→ 绕过按分辨率定价,一次请求放大上游成本。 - 覆盖
messages/stream→ 破坏请求编排。 images_edits走的是 multipartdata,apply_extra_body(data, ...)同样能被覆盖model/size/n。
- 覆盖
- 位置:
apps/ai/providers/openai_compatible.py(apply_extra_body及各 provider 组装 payload 处)。 - 怎么改:把「服务端权威字段」与「调用方可透传字段」分离——
- 服务端固定字段(
model、messages/contents、stream、n、size、resolution、aspect_ratio、image*)在 payload 组装后最后写入,不允许被 parameters 覆盖; parameters只允许一个白名单安全子集(如temperature、top_p、max_tokens、seed等无计费/无路由影响的),未知键丢弃或显式拒绝;extra_body(后台运营配置,可信)可保留覆盖能力,但仍不应覆盖model。
- 服务端固定字段(
- 归属:这个 seam 在 T-101 建成,真正被外部输入触达是在 T-302。至少要在 T-302 开工前修好,或在本层加显式约束 + 注释,避免 T-302 顺手
parameters=request.data造成越权计费。 - 验证:新增单测——传
parameters={"model":"gpt-image-2","n":5,"temperature":0.2},断言最终 payload 的model仍是ResolvedModel.model、n未被改,temperature生效。
P1-2 T-104 未退掉「图片同步耗时」这一全项目最高风险
- 症状:Phase 1 存在的唯一理由是验证最高风险假设——「同步请求上游图片生成,在 Gunicorn(默认 30s worker timeout)/ Nginx 下不会超时」。T-104 的验收要点原文即「验证图片同步可行性与超时配置」。但实际只跑了
smoke_ai_generation title --recorded:- 是 标题(chat 文本),不是图片;
- 是 recorded(
RecordedSession本地返回,无网络),elapsed_ms=149对「同步耗时够不够」这个问题没有任何信息量。 - 结论:管路(别名解析 → provider 选择 → 响应解析 → 耗时记录)已验证;核心风险未退。
- 位置:
apps/ai/management/commands/smoke_ai_generation.py(目前operation只接受title);风险登记见06-tasks.md/progress.md。 - 怎么改:
- 把 T-104 的结论明确改写为「管路已验、图片同步风险未退」,不要留下「T-104 = 同步可行性已验证」的错觉;
- 在依赖同步部署前(最迟 T-403 部署、建议随 T-302 一并)跑一次真实或贴近真实延迟的「图片」生成(Nano Banana 2 或 GPT Image 2),记录真实耗时,据此定 Gunicorn
--timeout与 Nginxproxy_read_timeout; - smoke 命令补
operation=image(可先 recorded 验证图片解析路径,再在配置真实 key 后去掉--recorded实测)。
- 不阻塞:Phase 2 计费编码可先行;但这条风险必须在看板显式登记为未决,不能被 T-104 的 DONE 状态掩盖。
- 验证:progress 中出现一条带真实网络耗时的图片生成记录(毫秒级不算),且 T-403 的超时配置引用该实测值。
P2 · 规范性 / 一致性
P2-1 .env.example 的 AI 超时默认键代码未读取
- 症状:
.env.example声明了AI_DEFAULT_CONNECT_TIMEOUT_SECONDS=10/AI_DEFAULT_READ_TIMEOUT_SECONDS=300,但全代码库无任何地方读取(provider 用的是model.connect_timeout_seconds默认 30 与分辨率超时表)。属于「文档承诺了、代码没兑现」的死配置,会误导运维以为改这两个值能生效。 - 位置:
.env.example/docs/env.md↔apps/ai/providers/。 - 怎么改(二选一):A(推荐) 在 provider/
ResolvedModel缺省值处接上这两个 env 作为全局兜底默认(模型未单独配置时用它),保持文档兑现;B 从.env.example与env.md删除这两个键,避免误导。选哪个都要让文档与代码一致。 - 验证:
grep确认「声明即被读取」或「已删除」;若走 A,加一条断言全局默认生效的测试。
P2-2 resolution_to_size 未做大小写归一
- 症状:
utils.resolution_timeout()有.upper()归一,但resolution_to_size()没有。传入"1k"(小写)时,超时表能命中,size 表 miss → 原样把"1k"作为size发给images/edits,上游大概率 400。 - 位置:
apps/ai/providers/utils.py:resolution_to_size()。 - 怎么改:与
resolution_timeout一致地归一化键(如统一.strip().upper()后查表,或在入口统一 resolution 取值)。 - 验证:单测
resolution_to_size("1k") == "1024x1024"。
P2-3 导入器默认别名 image-standard 与文档示例 image-hd 不一致
- 症状:
importers._ensure_default_aliases建的默认图片别名叫image-standard;而api.md/02-requirements.md/04-architecture.md的示例别名是image-hd。别名是运营可配的、非硬契约,功能无害,但对外文档与默认落地口径不一致,容易让接入方困惑。 - 位置:
apps/ai/importers.py↔docs/api.md等。 - 怎么改:二选一对齐——把默认别名改成
image-hd,或在文档注明「image-hd仅为示例,导入器默认建image-standard,以后台实际配置为准」。 - 验证:文档与导入器口径一致。
P3 · 登记 / 后续处理(不在 T-105 硬性范围)
Provider.capabilities()半冗余:全项目无调用点(能力校验实际走AiModel.capabilities_set())。保留无害,建议加注释说明用途,或在 T-302 用作「provider 能力 ∩ 模型声明能力」的二次校验。- 图生图 vs 文生图都归
operation_type=IMAGE:当别名指向images_edits模型而调用方没传原图,当前抛AiCapabilityError。T-302 需把它翻译成干净的 400(缺原图) 而不是 500。 - CallRecord 写入禁止 dump
result.raw:raw可能含 base64 大图/上游敏感字段;T-201/T-302 落记录时只存摘要/引用,别整包入库或打日志。 - Provider 密钥无轮换:单
AI_KEY_ENCRYPTION_KEY(非MultiFernet),env.md已注明「生产不可更换除非完成密钥轮换」,但轮换流程未实现。登记 Backlog(现有条目是 user API Key 轮换,补「provider key 轮换」)。 - ModelAlias 单默认/唯一仅 app 级保证:
clean()有理论并发 race;后台改配置低并发,可接受。codex 已记「MySQL partial unique 约束留待评估」,保留登记即可。
四、说明:未本地复跑
审核机(WSL)无 python3.12,未本地复跑 check/test。本报告基于:静态审查(providers / models / security / aliases / audit / importers / admin / tests / 迁移 / settings)+ codex 的执行记录(T-101 7 tests、T-102 18 tests、T-103 23 tests、T-104 recorded smoke,含真实 MySQL 建库超时的诚实记录,可信度高)。
其中 T-104 的 elapsed_ms=149 是 recorded 本地耗时,不能作为「图片同步可行性」的证据(见 P1-2)。
T-105 修补后,请重跑 check / test / init,并把命令与结果记入 progress.md(按 06-tasks.md 使用规则第 5 条)。
五、T-105 完成定义
- P1-1 已处理:
parameters透传白名单化 / 核心字段不可被覆盖,且有单测;或在本层加显式约束 + 注释并在 T-302 验收项中明确落地要求。 - P1-2 已处理:T-104 结论改写为「图片同步风险未退」,
06-tasks.md/progress.md显式登记该未决风险并挂到 T-302 / T-403;smoke 命令支持image。(真实图片实测可延到有真实 key 时,但登记不能少。) - P2-1 / P2-2 / P2-3 全部处理(文档与代码一致、
resolution_to_size归一、别名口径对齐)。 check0 issues、test全绿、init通过,证据入progress.md。- P3 各项已在
06-tasks.md对应任务(T-302 / T-403)或 Backlog 有登记,不遗失。