fix: address phase 1 ai review
This commit is contained in:
@@ -38,12 +38,12 @@
|
||||
|
||||
## 当前阶段
|
||||
|
||||
当前项目处于:**Phase 2 前置**。T-101 Provider 适配器层、T-102 AiModel/ModelAlias 数据模型、T-103 配置变更审计与 T-104 录制标题生成 smoke 已完成,下一步进入 T-201 用户钱包 / API Key / 流水 / 调用记录模型。
|
||||
当前项目处于:**Phase 2 前置已收尾**。T-101 Provider 适配器层、T-102 AiModel/ModelAlias 数据模型、T-103 配置变更审计、T-104 录制标题生成 smoke 与 T-105 Phase 1 审核修补已完成,下一步进入 T-201 用户钱包 / API Key / 流水 / 调用记录模型。
|
||||
|
||||
优先路径:
|
||||
|
||||
1. Phase 0:Django 骨架可运行、**自定义 User 模型在首次迁移前定好**、django-admin 可登录;T-004 审核修补项已完成。
|
||||
2. Phase 1:最高风险功能原型 —— T-101/T-102/T-103/T-104 已完成 provider 层、模型配置表、别名解析、配置审计与录制标题生成 smoke;真实上游调用待配置 Fernet 主密钥与 AiModel/ModelAlias 后执行。
|
||||
2. Phase 1:最高风险功能原型 —— T-101/T-102/T-103/T-104/T-105 已完成 provider 层、模型配置表、别名解析、配置审计、录制标题/图片 smoke 与审核修补;真实图片同步耗时待配置 Fernet 主密钥、AiModel/ModelAlias 与真实上游后在 T-302/T-403 前补测。
|
||||
3. Phase 2:计费核心 —— User/UserWallet/ApiKey 模型 + 点数扣减(并发安全,锁 Wallet 行)+ 计费规则 + 调用记录。
|
||||
4. Phase 3:对外 API 与充值 —— Key 鉴权、生成接口、余额查询、充值回调、扫码下单与轮询。
|
||||
5. Phase 4:用户端(Django 模板 SSR)—— 注册登录、API Key 管理、个人中心/记录页、充值页。
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
| 创建后台管理员 | T-003 后执行 `py -3.12 manage.py createsuperuser` |
|
||||
| 数据库迁移 | T-002 接入 MySQL 后执行 `makemigrations` / `migrate` |
|
||||
| 导入 cmbot AI 模型配置 | `py -3.12 manage.py import_ai_models path\to\ai_models.json --create-default-aliases` |
|
||||
| AI 生成 smoke(录制) | `py -3.12 manage.py smoke_ai_generation title --recorded` |
|
||||
| AI 生成 smoke(录制标题) | `py -3.12 manage.py smoke_ai_generation title --recorded` |
|
||||
| AI 生成 smoke(录制图片) | `py -3.12 manage.py smoke_ai_generation image --recorded` |
|
||||
| 格式化 / 静态检查 | `ruff check .`(待定,确定后写死) |
|
||||
|
||||
统一入口仍是根目录 `./init.ps1`(Windows)或 `./init.sh`(Unix/WSL)。T-001 阶段只做框架检查,不跑迁移;T-002 接入 MySQL 与自定义 User 后再启用迁移路径。
|
||||
|
||||
@@ -50,13 +50,13 @@
|
||||
- **Provider 适配器**:每个供应商一个适配器(`apps/ai/providers/`),实现统一接口 `generate_text()` / `generate_image()` / `capabilities()`;用注册表按 `api_type` 选适配器,取代一长串 `if api_type == ...`。移植 `cmbot/src/services/ai_text_service.py`、`ai_image_service.py` 的上游调用与解析逻辑到对应适配器内。**注意当前 3 个模型调用机制不同(见 3.1):文本走标准 chat,`nano-banana2` 走 chat 多模态返图、`gpt-image-2` 走 images/edits 改图,两图片模型非标准生成、需分别解析返回。**
|
||||
- **能力校验前置**:按目标模型声明的 `capabilities` 校验请求(如图片模型不能生成文字),在扣点和调上游**之前**拒掉非法组合。
|
||||
- **配置热生效**:模型配置从数据库读取,后台修改后运行时及时生效(每次查库或带缓存失效),不在进程内缓存到失效。
|
||||
- 输入:prompt、可选图片、能力别名、分辨率、`parameters` 透传;输出:文字或图片,或明确错误。
|
||||
- 输入:prompt、可选图片、能力别名、分辨率、`parameters` 安全子集;输出:文字或图片,或明确错误。`parameters` / `extra_body` 不得覆盖服务端固定的核心/计费字段(如 `model`、`n`、`size`、`resolution`、`messages`、`image*`)。
|
||||
- 处理超时、错误,向上层抛出可分类的异常(区分「上游失败」与「参数错误」)。不在本层写点数逻辑。
|
||||
|
||||
**数据库 / 存储**
|
||||
|
||||
- 持久化:账号、API Key、点数余额、计费规则、汇率、充值订单、点数流水、调用记录、模型配置。
|
||||
- 点数余额是权威账本;不持久化上游返回的大图原文时,调用记录只存引用/路径/摘要(大对象存储策略 MVP 待定)。
|
||||
- 点数余额是权威账本;不持久化上游返回的大图原文,调用记录只存引用/路径/摘要,不整包保存 provider `raw` 或 base64 图片(大对象存储策略 MVP 待定)。
|
||||
- 点数余额可变但只能经计费层修改;流水与调用记录只追加、不可改。
|
||||
|
||||
## 三、数据模型
|
||||
@@ -86,6 +86,7 @@
|
||||
- `api_key` **使用 Fernet 加密存储到 `api_key_encrypted`**,admin 只提供写入型 `api_key` 字段、脱敏显示、不回显明文;模型/密钥/别名映射的变更需审计留痕。
|
||||
- `AiConfigAuditLog` 只追加、不在后台编辑删除;密钥变更只记录 `api_key: empty/set` 状态变化,不记录明文 key 或 Fernet 密文。
|
||||
- 汇率在**充值下单创建订单时**锁定并记录到订单,同时计算预计到账点数;后续改汇率不影响已创建订单。支付回调入账时使用订单上的 `exchange_rate` / `points_granted`,不重新读取当前汇率。
|
||||
- Provider 适配器只允许白名单安全参数透传;外部调用方或后台 `extra_body` 传入核心字段时忽略,由服务端按别名解析和计费口径固定。
|
||||
|
||||
#### 配置表目标 schema
|
||||
|
||||
@@ -226,7 +227,7 @@ CREATE TABLE call_record (
|
||||
status TEXT NOT NULL, -- pending / success / failed
|
||||
upstream_latency_ms INTEGER,
|
||||
error_message TEXT,
|
||||
result_ref TEXT, -- 结果引用(URL/路径/摘要)
|
||||
result_ref TEXT, -- 结果引用(URL/路径/摘要),不存 raw/base64
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
@@ -305,7 +306,7 @@ CREATE TABLE call_record (
|
||||
| 图片生成耗时长 | 同步等待几十秒到分钟级;后台可换上慢模型拖垮 worker | 每模型 timeout 必须生效并设上限;worker 数/网关超时按最慢模型预留;V2 异步化 |
|
||||
| 上游错误分类 | 区分参数错误与上游故障 | AI 层抛分类异常;上游故障退点 |
|
||||
| 凭证安全 | 上游 api_key、支付密钥 | 应用层加密存储,admin 脱敏不回显;其余密钥走环境变量,不入代码与样例 |
|
||||
| 抽象泄漏 | 各供应商入参出参不一致(resolution/aspect/vision/返回形态) | Provider 适配器 + capabilities 声明 + `parameters` 透传,核心字段稳定不取交集 |
|
||||
| 抽象泄漏 / 参数越权 | 各供应商入参出参不一致;若调用方 `parameters` 可覆盖 `model`/`n`/`size` 会击穿别名计费 | Provider 适配器 + capabilities 声明 + `parameters` 白名单过滤;核心/计费字段服务端固定,不取交集、不允许覆盖 |
|
||||
| 供应商耦合 | 调用方绑具体 SKU 则换模型要通知所有接入方 | 对外绑能力别名,后台改别名→模型映射即可换供应商 |
|
||||
| 配置热生效 | 后台改模型/密钥后运行时仍用旧值 | 不在进程内长缓存;每次查库或保存时失效缓存 |
|
||||
| 配置变更审计 | 改密钥/模型/别名映射无痕 | `AiConfigAuditLog` 自动记录后台 create/update/delete;密钥只记录 empty/set 变化,日志只读 |
|
||||
@@ -320,7 +321,7 @@ CREATE TABLE call_record (
|
||||
|
||||
桌面端 cmbot 可以**不改交互骨架**,继续用同步方式调 cmhub、cmhub 再同步转中转站,正常使用——决定成败的不是同步/异步本身,而是下面两件事有没有配对好;配好就稳,配不好会「小量正常、上量假死」:
|
||||
|
||||
1. **超时链路层层放大且对齐**(外层 ≥ 内层):桌面端读超时 ≥ Gunicorn `--timeout` / 网关 `proxy_read_timeout` ≥ cmhub→中转站读超时。三个默认值是雷:`AiModel.timeout_seconds`(`ai_models.json` 现为 `0`=无限等,迁入须改有限值)、Gunicorn 默认 `30s`(会直接杀掉跑图片的 worker)、Nginx 默认 `60s`。图片链路建议统一放到 `300s` 量级。
|
||||
1. **超时链路层层放大且对齐**(外层 ≥ 内层):桌面端读超时 ≥ Gunicorn `--timeout` / 网关 `proxy_read_timeout` ≥ cmhub→中转站读超时。三个默认值是雷:`AiModel.timeout_seconds`(`0` 表示按 Provider 分辨率默认值,而不是无限等待)、Gunicorn 默认 `30s`(会直接杀掉跑图片的 worker)、Nginx 默认 `60s`。图片链路建议统一放到 `300s` 量级,并在 T-302/T-403 前用一次真实图片生成耗时校准。
|
||||
2. **worker/线程数按峰值总并发预留**:同步下每个在飞请求占住一个 worker 整个生成周期(几十秒~分钟)。用 Gunicorn `gthread`(`--threads`)或多 sync worker,数量 ≥ 预期峰值总并发 + 余量,避免慢图片把快的生文接口和后台挤死。
|
||||
|
||||
适用边界:**单接入方、小并发批量**(桌面端 `concurrency` 1~4)完全够用,点数一致性也更简单。当出现「worker 被长连接占满拖慢快接口/后台」「接入方总并发明显上涨」「需要关窗重连/任务持久化」任一信号时,才转 V2 异步(队列),在此之前保持同步;但适配器接口与 `call_record` 的 `pending/success/failed` 三态需为异步预留口子(见 4.1、七、八)。
|
||||
|
||||
+6
-5
@@ -34,13 +34,14 @@
|
||||
| T-101 | Provider 适配器层 + 移植 cmbot 调用 | T-002 | 定义 `Provider` 接口(`capabilities`/`generate_text`/`generate_image`),按 `api_type` 注册;把 `ai_text_service.py`/`ai_image_service.py` 搬进 `apps/ai/providers/` 并去除桌面依赖;**注意 3 模型机制不同(chat / chat 多模态返图 nano-banana2 / images_edits 改图 gpt-image-2,见 `04` 3.1),两图片模型非标准生成需分别解析、首次对接抓真实响应**;mock 上游单测验证解析与适配器选取 | DONE |
|
||||
| T-102 | AiModel + ModelAlias 模型 + 别名解析 | T-101 | AiModel 含 `capabilities`、`api_key` **加密存储**(admin 脱敏不回显);ModelAlias 映射别名→模型;`resolve_alias()` 能解析并按能力校验;配置迁移自 `ai_models.json`;后台改配置运行时热生效 | DONE |
|
||||
| T-103 | 配置变更审计 | T-102 | AiModel/ModelAlias/密钥的后台变更留痕(谁、何时、改了什么);可在 admin 查看 | DONE |
|
||||
| T-104 | 跑通一次真实/录制的标题或图片生成 | T-102 | 用别名 + 最小输入跑通一次生成,结论写入 `progress.md`(含耗时,验证图片同步可行性与超时配置) | DONE |
|
||||
| T-104 | 跑通一次真实/录制的标题或图片生成 | T-102 | 用别名 + 最小输入跑通一次生成,结论写入 `progress.md`(含耗时,验证图片同步可行性与超时配置) | DONE(管路已验;**图片同步耗时风险未退,已挂到 T-302/T-403,见 phase-1-review P1-2**) |
|
||||
| T-105 | Phase 1 AI 层审核修补 | T-104 | 按 [`phase-1-review.md`](phase-1-review.md) 修 **P1**(`parameters`/`extra_body` 透传白名单化,核心/计费字段不可被调用方覆盖,含单测;T-104 结论改写为「图片同步风险未退」并把该未决风险显式登记到 T-302/T-403)与 **P2**(`.env.example` 的 `AI_DEFAULT_*` 超时键接上或删除、`resolution_to_size` 大小写归一、默认别名口径对齐);`check`/`test`/`init` 全绿并在 `../progress.md` 留证据。**P1-1 建议先于 T-302 完成**(否则易顺手 `parameters=request.data` 造成越权计费) | DONE |
|
||||
|
||||
## Phase 2 · 计费核心
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-201 | User / UserWallet / ApiKey / PointsLedger / CallRecord 模型 | T-002 | 表结构符合 `04-architecture.md`;`UserWallet.points_balance>=0` 约束;ApiKey **哈希存储**(key_hash+key_prefix,明文只创建时返回);CallRecord 含 `user`/`api_key`/`alias`/`model_used`;admin 注册 | TODO |
|
||||
| T-201 | User / UserWallet / ApiKey / PointsLedger / CallRecord 模型 | T-002 | 表结构符合 `04-architecture.md`;`UserWallet.points_balance>=0` 约束;ApiKey **哈希存储**(key_hash+key_prefix,明文只创建时返回);CallRecord 含 `user`/`api_key`/`alias`/`model_used`;调用结果只存 `result_ref`/摘要,不 dump provider `raw`、base64 图片或敏感上游字段;admin 注册 | TODO |
|
||||
| T-202 | PricingRule / ExchangeRate 模型 + 计费计算 | T-201, T-102 | **按「操作 + 能力别名(+ 可选分辨率)」定价**;换底层模型不影响计费;缺规则返回 `no_pricing_rule` | TODO |
|
||||
| T-203 | 并发安全扣点 / 退点(billing 层) | T-201 | 锁 `UserWallet` 行或 F() 原子扣减;并发测试不超扣、不为负;失败退点写流水;含测试 | TODO |
|
||||
|
||||
@@ -49,7 +50,7 @@
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-301 | API Key 鉴权(DRF Authentication) | T-201 | 对请求 Key 哈希比对定位 ApiKey→User;**只挂 Key 认证、不挂 Session**;无效/缺失 401;用户或 Key 禁用 403 | TODO |
|
||||
| T-302 | 生成标题 / 图片接口 | T-104, T-203, T-301 | 按 `api.md` 实现;请求传**能力别名**+ `parameters` 透传;编排「别名解析+能力校验→预扣→调上游→成功确认/失败退点→写记录」;点数不足返回 402;含测试 | TODO |
|
||||
| T-302 | 生成标题 / 图片接口 | T-104, T-203, T-301 | 按 `api.md` 实现;请求传**能力别名**+ `parameters`,但 `parameters` 只能经 Provider 白名单透传,核心/计费字段不可被覆盖;编排「别名解析+能力校验→预扣→调上游→成功确认/失败退点→写记录」;评估 `Provider.capabilities()` 与模型声明能力的二次校验;`images_edits` 缺原图 / `AiCapabilityError` 翻译为 400,不落 500;调用记录不保存 provider `raw` / base64;点数不足返回 402;含测试;建议随本任务或 T-403 前跑一次真实图片生成并记录真实耗时 | TODO |
|
||||
| T-303 | 余额查询接口 | T-301 | 返回余额等于流水累加;含测试 | TODO |
|
||||
| T-304 | 充值回调(微信/支付宝验签 + 幂等入账) | T-202 | 两端点 `@csrf_exempt`;微信 SDK 验签解密、支付宝 SDK verify;验签失败不入账;同一 order_no 重复回调只入账一次;校验回调金额与订单金额一致;使用订单创建时锁定的 `points_granted` 锁 wallet 入账写流水;补主动查单兜底;含幂等测试 | TODO |
|
||||
| T-305 | 扫码充值下单 + 轮询(create/status) | T-304 | 支持 weixin(native,金额分)/alipay(precreate,金额元);建 pending 订单绑定 user,并在下单时锁定汇率/预计点数→取 code_url/qr_code→前端渲染 + 轮询 status;缺商户密钥时 mock | TODO |
|
||||
@@ -69,7 +70,7 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-401 | 运营后台完善 | T-302, T-304 | admin 可管理用户/钱包/ApiKey(脱敏)、配规则/汇率、检索充值订单/流水/调用记录;手工调点带原因并经计费层写流水 | TODO |
|
||||
| T-402 | 完整验收 MVP | T-401, T-504 | `02-requirements.md` 的 P0 验收全部通过(含用户端注册/充值/API Key/记录) | TODO |
|
||||
| T-403 | 部署 / 运行文档 | T-402 | 新环境可按文档运行;明确图片接口超时配置 | TODO |
|
||||
| T-403 | 部署 / 运行文档 | T-402 | 新环境可按文档运行;明确图片接口超时配置;上线前必须引用一次真实图片生成耗时来设置 Gunicorn `--timeout`、Nginx `proxy_read_timeout`、客户端 read timeout;若尚无真实耗时,部署文档必须显式标注图片同步风险未退,不得声称已验证 | TODO |
|
||||
|
||||
## 里程碑
|
||||
|
||||
@@ -86,4 +87,4 @@
|
||||
- 别名按比例分流到多个模型(灰度 / A/B / 故障转移);供应商 A 故障自动切 B。
|
||||
- 注册赠点 / 试用额度(需邮箱/图形验证码 + 限流防薅羊毛)。
|
||||
- 用量统计报表。
|
||||
- 退款对账自动化、API Key 轮换、限流细化。
|
||||
- 退款对账自动化、API Key 轮换、Provider key 轮换(MultiFernet / 双 key 迁移)、限流细化。
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
- [编码规则](05-coding-rules.md):硬性约束,含资金/点数安全专项(第 8 节)。
|
||||
- [任务看板](06-tasks.md):按依赖拆分的小任务,agent 每轮只做一个。
|
||||
- [Phase 0 骨架审核](phase-0-review.md):T-001~003 代码审核结论与修补清单(P1/P2/P3),对应任务 T-004。
|
||||
- [Phase 1 AI 层审核](phase-1-review.md):T-101~104 代码审核结论与修补清单(P1/P2/P3),对应任务 T-105;重点提示图片同步风险未退与 `parameters` 越权计费隐患。
|
||||
- [API 合约](api.md):对外接口、支付回调、AI 调用模块合约、错误码。
|
||||
- [路由与页面结构](routes.md):API 路由与 django-admin 后台职责。
|
||||
- [环境变量与配置](env.md):Django、数据库、AI 密钥加密、支付、对象存储等配置项。
|
||||
|
||||
+6
-6
@@ -42,7 +42,7 @@
|
||||
|
||||
## 对外接口
|
||||
|
||||
> **重要约定**:`model` 字段传的是**能力别名**(如 `title-standard` / `image-hd`),不是具体供应商模型名。后台把别名映射到当前的具体模型,换供应商时调用方零改动。供应商特有参数放 `parameters` 透传对象,核心字段保持稳定。
|
||||
> **重要约定**:`model` 字段传的是**能力别名**(如 `title-standard` / `image-hd`),不是具体供应商模型名。后台把别名映射到当前的具体模型,换供应商时调用方零改动。供应商特有参数放 `parameters`,但只允许 Provider 白名单内的安全参数透传;`model`、`n`、`size`、`resolution`、`messages`、`image*` 等核心/计费字段由服务端固定,调用方传入时忽略。
|
||||
|
||||
### `POST /api/v1/generate/title`
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"model": "title-standard", // 能力别名,非具体模型名;可选,缺省用该操作的默认别名
|
||||
"image_url": "https://...", // 可选,看图生成时传商品图(或 image_base64)
|
||||
"resolution": "1K", // 可选,影响计费规则匹配
|
||||
"parameters": {} // 可选,供应商特有参数透传,未知字段忽略
|
||||
"parameters": {} // 可选,安全供应商参数;未知字段/核心字段忽略
|
||||
}
|
||||
```
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
"image_base64": "data:image/png;base64,...", // 或 image_url;改图类模型(如映射到 gpt-image-2)原图必传
|
||||
"resolution": "1K",
|
||||
"aspect_ratio": "1:1",
|
||||
"parameters": {} // 可选,供应商特有参数透传
|
||||
"parameters": {} // 可选,安全供应商参数;未知字段/核心字段忽略
|
||||
}
|
||||
```
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
要点:上游失败返回 `upstream_error` 且**不扣点**(已预扣则退回)。结果默认存对象存储返回 `image_url`,避免同步响应体过大。
|
||||
要点:改图类模型缺原图返回 `bad_request`,不落 500;上游失败返回 `upstream_error` 且**不扣点**(已预扣则退回)。结果默认存对象存储返回 `image_url`,避免同步响应体过大。
|
||||
|
||||
### `GET /api/v1/balance`
|
||||
|
||||
@@ -217,9 +217,9 @@ class Provider(Protocol):
|
||||
要点:
|
||||
|
||||
- `ResolvedModel` 来自数据库 AiModel(形状同 `cmbot/config/ai_models.json`,外加 `capabilities`;`api_key` 在库中以 Fernet 加密,使用时解密,不落明文)。
|
||||
- `TextGenerationResult` 包含 `text`、清洗后的 `titles`、`model_used`、`raw`;`ImageGenerationResult` 包含图片 bytes、`model_used`、`raw`。图片落对象存储并返回 URL 属 T-302 之后的 API 编排职责。
|
||||
- `TextGenerationResult` 包含 `text`、清洗后的 `titles`、`model_used`、`raw`;`ImageGenerationResult` 包含图片 bytes、`model_used`、`raw`。`raw` 仅供本次解析/排障摘要使用,调用记录不得整包保存或打印,避免 base64 大图和上游敏感字段入库;图片落对象存储并返回 URL 属 T-302 之后的 API 编排职责。
|
||||
- 适配器按 `api_type`(`chat`/`gemini`/`images`/`images_edits`/`auto`)从注册表选取,新增供应商 = 新增一个适配器,不改对外接口。
|
||||
- `parameters` 为供应商特有参数透传;适配器负责把统一入参翻译成各家上游格式。
|
||||
- `parameters` 为供应商特有参数的安全子集;适配器负责白名单过滤并把统一入参翻译成各家上游格式,核心字段不可被 `parameters` 或 `extra_body` 覆盖。
|
||||
- 配置热生效:每次调用读当前 AiModel/ModelAlias,后台改动及时反映(或带缓存失效)。
|
||||
- 配置审计:后台保存/删除 AiModel、ModelAlias 时写 `AiConfigAuditLog`;记录 actor/action/target/changed_fields/changes/created_at,admin 只读查看;密钥变更只记录 empty/set 状态。
|
||||
- 区分异常:别名/能力不匹配 → 业务错误(400 类,如 `model_not_allowed`);上游网络/超时/服务错误 → `upstream_error`(502,触发退点)。
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-07-02
|
||||
- 阶段:Phase 1 已完成;下一步 T-201
|
||||
- 阶段:Phase 1 已完成并完成 T-105 审核修补;下一步进入 Phase 2 的 T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型
|
||||
- 技术栈:系统 Python 3.12.3 + Django 5.2.15 + DRF 3.16.1 + PyMySQL 1.1.3 + cryptography 46.0.7 + requests 2.34.2 + django-admin;MySQL 8.4 已接入 settings;用户端(模板 SSR/Bootstrap/allauth) 后续任务落地;详见 `03-tech-stack.md`
|
||||
- 生产代码:已有最小 Django 工程骨架:`manage.py`、`config/`;T-002 已创建 `apps/users|portal|billing|ai|api`;T-003 已把自定义 `User` 注册进 django-admin;T-004 已完成 email 唯一性、init 版本断言、app 顺序、`.env.example` 与 `pyproject.toml`;T-101 已新增 `apps/ai/providers/`(Provider 接口、注册表、chat/gemini/images/images_edits 适配器);T-102 已新增 `AiModel` / `ModelAlias`、Fernet 加密密钥存储、别名解析、admin 配置页、`import_ai_models` 导入命令;T-103 已新增 `AiConfigAuditLog` 审计表、admin 只读页面和后台保存/删除审计 hook;T-104 已新增 `smoke_ai_generation` 管理命令并跑通录制标题生成 smoke
|
||||
- 测试:`smoke_ai_generation title --recorded` 通过(录制标题,149ms);`manage.py test apps.ai.tests.AiGenerationSmokeCommandTests --noinput --keepdb` 通过(1 test);`manage.py check` 通过;`makemigrations --check` 通过;`compileall apps` 通过;`./init.ps1` 通过。`manage.py test apps.ai --noinput --keepdb` 多次在远程 MySQL 连接阶段超时,非断言失败
|
||||
- 生产代码:已有最小 Django 工程骨架:`manage.py`、`config/`;T-002 已创建 `apps/users|portal|billing|ai|api`;T-003 已把自定义 `User` 注册进 django-admin;T-004 已完成 email 唯一性、init 版本断言、app 顺序、`.env.example` 与 `pyproject.toml`;T-101 已新增 `apps/ai/providers/`(Provider 接口、注册表、chat/gemini/images/images_edits 适配器);T-102 已新增 `AiModel` / `ModelAlias`、Fernet 加密密钥存储、别名解析、admin 配置页、`import_ai_models` 导入命令;T-103 已新增 `AiConfigAuditLog` 审计表、admin 只读页面和后台保存/删除审计 hook;T-104 已新增 `smoke_ai_generation title --recorded`;T-105 已完成 provider 参数白名单、`resolution_to_size` 归一、默认图片别名 `image-hd`、`smoke_ai_generation image --recorded` 和后续任务风险登记
|
||||
- 测试:T-105 相关 10 tests 通过;`smoke_ai_generation image --recorded` 通过(录制图片,`image_bytes=20`);`smoke_ai_generation title --recorded` 通过(录制标题,92ms);`manage.py check` 通过;`makemigrations --check` 通过;`compileall apps` 通过;`git diff --check` 仅 Windows CRLF 提示;`./init.ps1` 通过。`manage.py test apps.ai --noinput --keepdb` 重试时在远程 MySQL `43.128.3.240:3306` 连接/重连阶段超时(非断言失败)
|
||||
- 数据:AI 上游调用与模型配置参考 `D:\chengma\cmbot`(`src/services/ai_text_service.py`、`ai_image_service.py`、`config/ai_models.json`);真实 `ai_models.json` 不提交,需通过 `import_ai_models` 命令加密导入
|
||||
- 标准启动路径:Windows 用 `./init.ps1`;Unix/WSL 用 `./init.sh`
|
||||
- 标准验证路径:Windows 用 `py -3.12 manage.py check` / `py -3.12 manage.py test`
|
||||
- 设计基线:**自助用户端 + 对外 API + 运营后台**三合一单体;用户模型 `User`(auth)/`UserWallet`(点数,锁 wallet 扣点)/`ApiKey`(1:N,哈希存储);对外两接口 + **能力别名 + Provider 适配器**(可插拔供应商);自助扫码充值;注册不送点数。详见 `04-architecture.md` 与 2026-06-29 / 2026-07-01 的 `progress.md` 决策
|
||||
- 配置基线:运行环境变量集中见 `docs/env.md`;真实密钥/支付凭证不得写入代码或文档样例。充值订单在创建时锁定汇率与预计点数,回调入账使用订单值,不按新汇率重算
|
||||
- 当前 blocker:无。支付商户密钥/证书仍缺真实值,但不阻塞当前 Phase 2;真实 AI 上游 smoke 需要先配置 `AI_KEY_ENCRYPTION_KEY` 并导入 AiModel/ModelAlias。
|
||||
- 当前 blocker:无。支付商户密钥/证书仍缺真实值,但不阻塞当前 Phase 2;真实 AI 上游 smoke 需要先配置 `AI_KEY_ENCRYPTION_KEY` 并导入 AiModel/ModelAlias。图片同步真实耗时风险仍未退,已登记到 T-302/T-403。
|
||||
|
||||
## 当前目录要点
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
| `init.sh` / `init.ps1` | 已有 | 启动验证入口,已固定系统 Python 3.12 命令,并校验解释器版本 `>=3.12,<3.14` |
|
||||
| `requirements.txt` / `pyproject.toml` | 已有 | `requirements.txt` 管运行依赖;`pyproject.toml` 落地 `requires-python`;T-101 新增 `requests`;T-102 使用既有 `cryptography` 做 Fernet 加密 |
|
||||
| `config/`(Django 工程) | 已有 | T-001 创建,含 settings / urls / wsgi / asgi |
|
||||
| `apps/`(users/portal/billing/ai/api) | 已有 | T-002 创建;`apps/users` 已定义自定义 `User`;T-003 已注册 admin 与 admin smoke test;T-004 已给 `User.email` 加唯一约束;T-101 已新增 `apps/ai/providers`;T-102 已新增 `apps/ai/security.py`、`aliases.py`、`importers.py`、management command 与 `ai.0001_initial` 迁移;T-103 已新增 `apps/ai/audit.py` 与 `ai.0002_aiconfigauditlog` 迁移;T-104 已新增 `smoke_ai_generation` 录制 smoke 命令 |
|
||||
| `apps/`(users/portal/billing/ai/api) | 已有 | T-002 创建;`apps/users` 已定义自定义 `User`;T-003 已注册 admin 与 admin smoke test;T-004 已给 `User.email` 加唯一约束;T-101 已新增 `apps/ai/providers`;T-102 已新增 `apps/ai/security.py`、`aliases.py`、`importers.py`、management command 与 `ai.0001_initial` 迁移;T-103 已新增 `apps/ai/audit.py` 与 `ai.0002_aiconfigauditlog` 迁移;T-104/T-105 已新增 `smoke_ai_generation` 录制 title/image smoke 命令,provider 参数白名单与分辨率归一已落地 |
|
||||
| `manage.py` | 已有 | T-001 创建 |
|
||||
| `tests/` | 待建 | 随各任务补充 |
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
任务状态以 [`06-tasks.md`](06-tasks.md) 为准,历史执行记录见 [`../progress.md`](../progress.md)。
|
||||
|
||||
- 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试;T-004 Phase 0 骨架审核修补;T-101 Provider 适配器层 + 移植 cmbot 调用;T-102 AiModel + ModelAlias 模型 + 别名解析;T-103 配置变更审计;T-104 跑通一次录制标题生成。
|
||||
- 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试;T-004 Phase 0 骨架审核修补;T-101 Provider 适配器层 + 移植 cmbot 调用;T-102 AiModel + ModelAlias 模型 + 别名解析;T-103 配置变更审计;T-104 跑通一次录制标题生成;T-105 Phase 1 AI 层审核修补。
|
||||
- 正在进行:无。
|
||||
- 当前 blocker:无。
|
||||
- 下一个可领取任务:**T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型**。
|
||||
@@ -56,6 +56,7 @@ py -3.12 manage.py test
|
||||
py -3.12 manage.py runserver
|
||||
py -3.12 manage.py import_ai_models path\to\ai_models.json --create-default-aliases
|
||||
py -3.12 manage.py smoke_ai_generation title --recorded
|
||||
py -3.12 manage.py smoke_ai_generation image --recorded
|
||||
|
||||
# Unix / WSL
|
||||
python3.12 -m pip install -r requirements.txt
|
||||
@@ -64,9 +65,10 @@ python3.12 manage.py test
|
||||
python3.12 manage.py runserver
|
||||
python3.12 manage.py import_ai_models path/to/ai_models.json --create-default-aliases
|
||||
python3.12 manage.py smoke_ai_generation title --recorded
|
||||
python3.12 manage.py smoke_ai_generation image --recorded
|
||||
```
|
||||
|
||||
当前骨架可运行。T-002 已在首次迁移前创建自定义 User,并按 `env.md` 接入 MySQL 8.4 / utf8mb4;远程 MySQL 已完成 Django 初始迁移。T-003 已接通 django-admin,测试可创建/销毁 `test_cmhub` 测试库;当前远程 MySQL 对频繁建库/销库存在间歇超时,必要时用 `--keepdb` 验证。T-004 已应用 `users.0002_alter_user_email`,`user.email` 已有唯一索引。T-101 的 AI provider 层只做 HTTP 调用与响应解析;T-102 已把 provider 运行配置接到数据库 `AiModel` / `ModelAlias`,`resolve_alias()` 每次查当前 active 配置并按 `text` / `image` 能力校验。T-103 已补 `AiConfigAuditLog`,admin 保存/删除 `AiModel` / `ModelAlias` 时记录 actor、action、target、changed_fields、changes、created_at,密钥只记录 empty/set 状态。T-104 已用临时回滚配置跑通录制标题生成:`alias=t-104-recorded-title-c054d919`,`model_used=recorded-title-model`,耗时 149ms,输出 3 个标题。真实上游生成未执行,原因是当前环境未配置 `AI_KEY_ENCRYPTION_KEY` 且数据库没有 AiModel/ModelAlias;后续配置后可用 `import_ai_models` 导入,再用同一 smoke 命令去掉 `--recorded` 跑真实标题。
|
||||
当前骨架可运行。T-002 已在首次迁移前创建自定义 User,并按 `env.md` 接入 MySQL 8.4 / utf8mb4;远程 MySQL 已完成 Django 初始迁移。T-003 已接通 django-admin,测试可创建/销毁 `test_cmhub` 测试库;当前远程 MySQL 对频繁建库/销库存在间歇超时,必要时用 `--keepdb` 验证。T-004 已应用 `users.0002_alter_user_email`,`user.email` 已有唯一索引。T-101 的 AI provider 层只做 HTTP 调用与响应解析;T-102 已把 provider 运行配置接到数据库 `AiModel` / `ModelAlias`,`resolve_alias()` 每次查当前 active 配置并按 `text` / `image` 能力校验。T-103 已补 `AiConfigAuditLog`,admin 保存/删除 `AiModel` / `ModelAlias` 时记录 actor、action、target、changed_fields、changes、created_at,密钥只记录 empty/set 状态。T-104/T-105 已用临时回滚配置跑通录制标题和录制图片生成,临时 `AiModel` / `ModelAlias` 不持久化、不打印 Bearer/key。真实上游生成未执行,原因是当前环境未配置 `AI_KEY_ENCRYPTION_KEY` 且数据库没有 AiModel/ModelAlias;后续配置后可用 `import_ai_models` 导入,再用同一 smoke 命令去掉 `--recorded` 跑真实标题/图片。
|
||||
|
||||
## 开始编码前检查
|
||||
|
||||
|
||||
+2
-2
@@ -38,13 +38,13 @@
|
||||
| 变量 | 必填 | 示例 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `AI_KEY_ENCRYPTION_KEY` | 是 | `base64-fernet-key` | Fernet 主密钥,用于加密 `AiModel.api_key_encrypted`;生产不可更换,除非完成密钥轮换 |
|
||||
| `AI_DEFAULT_CONNECT_TIMEOUT_SECONDS` | 否 | `10` | 上游连接超时默认值 |
|
||||
| `AI_DEFAULT_READ_TIMEOUT_SECONDS` | 否 | `300` | 图片同步生成链路建议 300 秒量级 |
|
||||
|
||||
`AI_KEY_ENCRYPTION_KEY` 必须是 `cryptography.fernet.Fernet.generate_key()` 生成的 base64 字符串,可用 `py -3.12 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` 生成。
|
||||
|
||||
`AiModel.url`、`AiModel.model`、`AiModel.api_type`、`AiModel.capabilities` 与加密后的 `api_key` 由后台或数据迁移维护,不通过环境变量硬编码具体模型。
|
||||
|
||||
AI 上游连接超时与读取超时不走全局环境变量:连接超时由 `AiModel.connect_timeout_seconds` 控制,读取超时由 `AiModel.timeout_seconds` 控制;当读取超时为 `0` 时,Provider 按分辨率使用内置默认值。
|
||||
|
||||
## 五、支付配置
|
||||
|
||||
### 微信 V3 native
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Phase 1 骨架审核报告(T-101 ~ T-104)
|
||||
|
||||
> 审核人:Claude Code(全栈视角)|日期:2026-07-02|结论:**验收通过,但最高风险未退**。
|
||||
> Phase 1 是全项目最高风险段(AI 上游同步调用 + 可插拔供应商 + 密钥)。代码质量高、三模型机制忠实落地;但 **T-104 只验证了管路,没退掉「图片同步耗时」这个核心风险**,且 `parameters` 透传存在资金安全隐患。
|
||||
> 本文面向 codex 执行:每条修补项给出「症状 / 位置 / 怎么改 / 怎么验证」。修补任务见 [`06-tasks.md`](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 修补时**不要改坏**:
|
||||
|
||||
1. **三模型机制忠实落地**:`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.md` 3.1 一致。
|
||||
2. **超时策略修对了桌面端的坑**:`timeout_seconds=0` 不再是「无限」,而是按分辨率映射到有限值(512→180s / 1K→240s / 2K→360s / 4K→600s,见 `providers/utils.py:RESOLUTION_TIMEOUTS`);connect/read 分离。这正是同步部署最关键的一处。
|
||||
3. **密钥零泄露闭环**: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:`」。
|
||||
4. **审计 admin 真只读**:`AiConfigAuditLogAdmin` 的 add/change/delete 权限全 `False`,`get_readonly_fields` 覆盖全字段;`delete_queryset` 也逐条留痕,不只 `delete_model`。
|
||||
5. **测试与运行卫生**:`session.trust_env=False`(防止串到系统代理);smoke 命令用 `override_settings + transaction.atomic + set_rollback(True)` 临时建配置跑完即回滚,不落业务库、不打印 `Bearer`/占位 key,并有测试断言。
|
||||
6. **热生效 + 能力前置校验**:`resolve_alias()` 每次查库取 active 配置(无进程内缓存到失效);标题要求 `text` 能力、图片要求 `image` 能力,能力不匹配抛 `ModelCapabilityError`。
|
||||
7. **别名唯一性 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.md` 3.1)。
|
||||
- 注入/覆盖 `n`(chat/images 一次返多图)、`size`/`resolution`/`aspect_ratio` → **绕过按分辨率定价**,一次请求放大上游成本。
|
||||
- 覆盖 `messages`/`stream` → 破坏请求编排。
|
||||
- `images_edits` 走的是 multipart `data`,`apply_extra_body(data, ...)` 同样能被覆盖 `model`/`size`/`n`。
|
||||
- **位置**:`apps/ai/providers/openai_compatible.py`(`apply_extra_body` 及各 provider 组装 payload 处)。
|
||||
- **怎么改**:把「服务端权威字段」与「调用方可透传字段」分离——
|
||||
1. 服务端固定字段(`model`、`messages`/`contents`、`stream`、`n`、`size`、`resolution`、`aspect_ratio`、`image*`)在 payload 组装后**最后写入,不允许被 parameters 覆盖**;
|
||||
2. `parameters` 只允许一个**白名单安全子集**(如 `temperature`、`top_p`、`max_tokens`、`seed` 等无计费/无路由影响的),未知键丢弃或显式拒绝;
|
||||
3. `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`。
|
||||
- **怎么改**:
|
||||
1. 把 T-104 的结论明确改写为「管路已验、图片同步风险未退」,不要留下「T-104 = 同步可行性已验证」的错觉;
|
||||
2. 在依赖同步部署前(最迟 **T-403 部署**、建议随 T-302 一并)**跑一次真实或贴近真实延迟的「图片」生成**(Nano Banana 2 或 GPT Image 2),记录**真实耗时**,据此定 Gunicorn `--timeout` 与 Nginx `proxy_read_timeout`;
|
||||
3. 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` 归一、别名口径对齐)。
|
||||
- `check` 0 issues、`test` 全绿、`init` 通过,证据入 `progress.md`。
|
||||
- P3 各项已在 `06-tasks.md` 对应任务(T-302 / T-403)或 Backlog 有登记,不遗失。
|
||||
Reference in New Issue
Block a user