fix(assets): smart-match 响应扁平化修复自动选素材 404/422 全链路断裂 #1565

Merged
xiaoxia merged 1 commits from fix/smart-match-flatten into develop 2026-08-31 13:35:06 +08:00
Owner

P0:smart-match 返回结构扁平化,修复 AI 自动选素材全链路断裂

线上现象(staging)

  • GET /api/v1/assets/undefined 404 连发 3 次
  • POST /api/v1/templates/{id}/clips/from-assets 422
  • 自动模式下预览视频无法生成、标题不显示

根因

POST /assets/smart-match 返回 SmartMatchItem{asset: AssetResponse, score, breakdown}——素材字段包在 item.asset 里;前端 smartMatchAssets 声明 Promise<{items: AssetItem[]}>items.map(a => a.id) 取到的全是 undefined

修复

  1. SmartMatchItem 扁平化schemas/asset.py):改为继承 AssetResponseid / usable / used_duration / available_duration / used_ratio / duration / mime_type / thumbnail_url 等素材字段全部在 item 顶层,前端拿到即可读;score / breakdown 评分字段保留。
  2. 域层不动packages.domain.smart_match.smart_select_assetsSmartMatchResult.asset 保持原样,generation_tasks.py 内部生成链路([r.asset.id for r in results])零影响。
  3. from-assets 422 防御ClipsFromAssetsRequest 增加 pre-validator 过滤 null/空串/空白 id(undefined 经 JSON 序列化为 null 直接 422);路由层二次归一化兜底,全非法时 400「素材列表为空」而非 422/500。

响应结构(最终,前端以此为准)

{
  "items": [
    {
      "id": "65c7ea70...",
      "name": "v1.mp4",
      "mime_type": "video/mp4",
      "duration": 30.0,
      "usable": true,
      "used_duration": 0.0,
      "available_duration": 30.0,
      "used_ratio": 0.0,
      "thumbnail_url": "...",
      "score": 96.0,
      "breakdown": {"quality": 36.0, "duration": 30.0, "recency": 20.0, "unused": 10.0}
    }
  ],
  "total_candidates": 3
}

items 元素与 GET /assets 列表项(AssetResponse)字段完全一致,仅多 score/breakdownasset 包装层

测试

  • 扁平结构 3 用例:顶层 id 可读、无 asset 包装、余量/usable 字段可读、score/breakdown 保留
  • 端点 TestClient 断言同步更新(item["id"] / "asset" not in item
  • from-assets 容错 4 用例:schema 过滤 null/空串、全非法 ValidationError、路由混合 id 正常创建、路由全非法 400
  • 相关 95 用例全过;手动模式(all)不经过 smart-match,不受影响
## P0:smart-match 返回结构扁平化,修复 AI 自动选素材全链路断裂 ### 线上现象(staging) - `GET /api/v1/assets/undefined` 404 连发 3 次 - `POST /api/v1/templates/{id}/clips/from-assets` 422 - 自动模式下预览视频无法生成、标题不显示 ### 根因 `POST /assets/smart-match` 返回 `SmartMatchItem{asset: AssetResponse, score, breakdown}`——素材字段包在 `item.asset` 里;前端 `smartMatchAssets` 声明 `Promise<{items: AssetItem[]}>`,`items.map(a => a.id)` 取到的全是 `undefined`。 ### 修复 1. **`SmartMatchItem` 扁平化**(`schemas/asset.py`):改为继承 `AssetResponse`,`id / usable / used_duration / available_duration / used_ratio / duration / mime_type / thumbnail_url` 等素材字段全部在 item 顶层,前端拿到即可读;`score / breakdown` 评分字段保留。 2. **域层不动**:`packages.domain.smart_match.smart_select_assets` 的 `SmartMatchResult.asset` 保持原样,`generation_tasks.py` 内部生成链路(`[r.asset.id for r in results]`)零影响。 3. **from-assets 422 防御**:`ClipsFromAssetsRequest` 增加 pre-validator 过滤 `null/空串/空白` id(undefined 经 JSON 序列化为 null 直接 422);路由层二次归一化兜底,全非法时 400「素材列表为空」而非 422/500。 ### 响应结构(最终,前端以此为准) ```json { "items": [ { "id": "65c7ea70...", "name": "v1.mp4", "mime_type": "video/mp4", "duration": 30.0, "usable": true, "used_duration": 0.0, "available_duration": 30.0, "used_ratio": 0.0, "thumbnail_url": "...", "score": 96.0, "breakdown": {"quality": 36.0, "duration": 30.0, "recency": 20.0, "unused": 10.0} } ], "total_candidates": 3 } ``` items 元素与 `GET /assets` 列表项(AssetResponse)字段完全一致,仅多 `score/breakdown`;**无 `asset` 包装层**。 ### 测试 - 扁平结构 3 用例:顶层 id 可读、无 `asset` 包装、余量/usable 字段可读、score/breakdown 保留 - 端点 TestClient 断言同步更新(`item["id"]` / `"asset" not in item`) - from-assets 容错 4 用例:schema 过滤 null/空串、全非法 ValidationError、路由混合 id 正常创建、路由全非法 400 - 相关 95 用例全过;手动模式(all)不经过 smart-match,不受影响

🚀 预览环境已部署

项目 详情
PR号 #1565
预览链接 https://pr-1565.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1565 | | 预览链接 | [https://pr-1565.preview.xiaoxiajianji.com](https://pr-1565.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
auto-approve-bot approved these changes 2026-08-31 01:00:32 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
auto-approve-bot approved these changes 2026-08-31 01:00:32 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
xiaoxia force-pushed fix/smart-match-flatten from 7546a416fe to 84252238cf 2026-08-31 08:58:16 +08:00 Compare
xiaoxia added 1 commit 2026-08-31 13:12:35 +08:00
fix(assets): smart-match 响应扁平化——item 顶层直出素材字段,修复自动选素材全链路 404/422
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 7s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3m9s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 3m53s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m3s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 5m15s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 3m13s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 3m55s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m32s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m27s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 6m46s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 7m22s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m28s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 13m44s
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 25s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 4m13s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 6m39s
90e6beea79
P0 线上事故:前端 smartMatchAssets 按 {items: AssetItem[]} 解析,
items.map(a => a.id) 直接取 id;但后端 SmartMatchItem 为
{asset: AssetResponse, score, breakdown} 包装结构,导致 id 全为
undefined → GET /assets/undefined 404 连发、POST /clips/from-assets
携带 null 422,自动模式预览无法生成、标题不显示。

修复:
- SmartMatchItem 改为继承 AssetResponse 的扁平结构:id/usable/
  used_duration/available_duration/used_ratio 等素材字段全部在 item
  顶层,前端拿到即可用;score/breakdown 评分字段保留
- 路由用 model_dump() 解包构造,packages.domain.smart_select_assets
  域层 SmartMatchResult.asset 不动,generation_tasks 内部生成链路
  ([r.asset.id for r in results])零影响
- ClipsFromAssetsRequest 加 pre validator 过滤 null/空串/空白 id;
  from-assets 路由层二次归一化兜底,全非法时 400 而非 422/500

测试:扁平结构 3 用例(顶层 id/无 asset 包装/余量字段可读/评分保留)、
端点 TestClient 断言更新、from-assets 容错 4 用例;相关 95 用例全过
xiaoxia force-pushed fix/smart-match-flatten from 84252238cf to 90e6beea79 2026-08-31 13:12:35 +08:00 Compare
Collaborator

【阻塞级判定】

  • 是否存在阻塞级问题:否
  • 阻塞级问题数量:0 个

📊 审查概览

  • 整体评价:通过
  • 建议级问题数量:2 个

🔴 阻塞级问题(必须修复)

💡 改进建议(不阻塞合并)

  1. [apps/api/app/api/routes/templates_editor/clips.py: 596] 建议拆分长行或提取变量

    • 具体内容:asset_ids 的列表推导式逻辑较长(包含类型判断、去空、strip),且 aid.strip() 被调用了两次。建议提取为局部函数或拆分写法以提高可读性和微小的性能优化。
    • 示例:
      def _normalize_asset_id(aid):
          return str(aid).strip() if isinstance(aid, str) else None
      
      asset_ids = [aid for aid in (_normalize_asset_id(a) for a in (body.asset_ids or [])) if aid]
      
  2. [apps/api/app/api/routes/templates_editor/clips.py: 405] 建议保持代码可读性

    • 具体内容:虽然代码压缩节省了行数,但 _safe_segment_duration 的调用参数较多,压缩为一行可能会影响部分场景下的断点调试和可读性。建议保持多行格式,除非团队有严格的行数限制。

良好实践

  1. 防御性编程:在 schemas.py 中增加 pre=True 的 validator 过滤无效 ID,并在 clips.py 路由层再次进行防御性检查,有效防止了前端传入 null/undefined 导致的后续 404 或 500 错误。
  2. 结构扁平化SmartMatchItem 继承 AssetResponse 并使用 model_dump() 展开字段,消除了前端访问 item.asset.id 的嵌套层级,优化了 API 响应结构,符合 RESTful 最佳实践。
  3. 测试覆盖:针对 ID 过滤和响应结构扁平化增加了详尽的单元测试(TestClipsFromAssetsInvalidIds, TestSmartMatchFlatStructure),确保了重构的正确性。

格式检查通过 | 逻辑审查通过 | 性能无明显问题


🤖 由 AI 代码审查机器人自动生成 | 2026-08-31 05:18:12 | 模型:

### 【阻塞级判定】 - 是否存在阻塞级问题:否 - 阻塞级问题数量:0 个 ### 📊 审查概览 - 整体评价:通过 - 建议级问题数量:2 个 ### 🔴 阻塞级问题(必须修复) 无 ### 💡 改进建议(不阻塞合并) 1. **[apps/api/app/api/routes/templates_editor/clips.py: 596] 建议拆分长行或提取变量** - 具体内容:`asset_ids` 的列表推导式逻辑较长(包含类型判断、去空、strip),且 `aid.strip()` 被调用了两次。建议提取为局部函数或拆分写法以提高可读性和微小的性能优化。 - 示例: ```python def _normalize_asset_id(aid): return str(aid).strip() if isinstance(aid, str) else None asset_ids = [aid for aid in (_normalize_asset_id(a) for a in (body.asset_ids or [])) if aid] ``` 2. **[apps/api/app/api/routes/templates_editor/clips.py: 405] 建议保持代码可读性** - 具体内容:虽然代码压缩节省了行数,但 `_safe_segment_duration` 的调用参数较多,压缩为一行可能会影响部分场景下的断点调试和可读性。建议保持多行格式,除非团队有严格的行数限制。 ### ✅ 良好实践 1. **防御性编程**:在 `schemas.py` 中增加 `pre=True` 的 validator 过滤无效 ID,并在 `clips.py` 路由层再次进行防御性检查,有效防止了前端传入 `null`/`undefined` 导致的后续 404 或 500 错误。 2. **结构扁平化**:`SmartMatchItem` 继承 `AssetResponse` 并使用 `model_dump()` 展开字段,消除了前端访问 `item.asset.id` 的嵌套层级,优化了 API 响应结构,符合 RESTful 最佳实践。 3. **测试覆盖**:针对 ID 过滤和响应结构扁平化增加了详尽的单元测试(`TestClipsFromAssetsInvalidIds`, `TestSmartMatchFlatStructure`),确保了重构的正确性。 --- ✅ 格式检查通过 | ✅ 逻辑审查通过 | ✅ 性能无明显问题 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-08-31 05:18:12 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
xiaoxia merged commit dc816991d6 into develop 2026-08-31 13:35:06 +08:00
xiaoxia deleted branch fix/smart-match-flatten 2026-08-31 13:35:06 +08:00

🗑️ 预览环境已清理

PR #1565 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1565 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.