feat(ai-recommend): 保障搭配完整性并支持自动补齐
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
---
|
||||
name: AI推荐搭配完整性保障
|
||||
overview: 修复 AI 穿搭推荐可能只输出「外套 + 下装」等不完整搭配的问题:prompt 写死完整性规则,服务端新增完整性校验(缺失品类/冲突品类),不满足时带纠偏提示重试一次,仍不满足则由服务端从衣橱确定性补齐并标注「系统补齐」。
|
||||
todos:
|
||||
- id: tokenhub-completeness
|
||||
content: 用 [skill:lsp-code-analysis] 核对引用点后,实现完整性判定/同品类去重/确定性自动补齐与纠偏提示,并强化提示词
|
||||
status: completed
|
||||
- id: tokenhub-tests
|
||||
content: 新增 pkg/tokenhub 单测:规则判定、冲突剔除、补齐确定性与提示词拼接
|
||||
status: completed
|
||||
dependencies:
|
||||
- tokenhub-completeness
|
||||
- id: service-orchestration
|
||||
content: 改造 airecommend.generate:带纠偏提示重试一次(含时间预算保护)并在仍不满足时服务端补齐,补充日志
|
||||
status: completed
|
||||
dependencies:
|
||||
- tokenhub-completeness
|
||||
- id: regression-verify
|
||||
content: 跑通 go build、go vet 与全量 go test 回归,确认额度/失败语义与接口契约未变
|
||||
status: completed
|
||||
dependencies:
|
||||
- tokenhub-tests
|
||||
- service-orchestration
|
||||
- id: sync-ai-docs
|
||||
content: 同步接口文档 5.6 与 README:搭配完整性规则、重试与自动补齐说明
|
||||
status: completed
|
||||
dependencies:
|
||||
- service-orchestration
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
修复 AI 穿搭推荐结果不完整的问题:当前会生成「只有外套 + 裤子」这类缺少上装/内搭的搭配,用户拿到的是穿不出去的不完整方案。需要在提示词与服务端双重保障下,确保推荐结果是一套完整可穿的搭配。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- **完整性硬规则(按衣橱实际存在的品类判定)**:
|
||||
- 必须包含「上装」或「连衣裙」二者之一(衣橱存在其中任一时)
|
||||
- 选择「外套」时不得孤立:必须同时搭配「上装」或「连衣裙」
|
||||
- 未选择「连衣裙」且衣橱存在「下装」时,必须包含「下装」
|
||||
- 衣橱存在「鞋」时必须包含「鞋」;「配饰」为可选点缀,不强制
|
||||
- 选择「连衣裙」时不再搭配「下装」(二者互斥)
|
||||
- **模型输出不满足时的处理**:先带「缺上装」等纠偏提示重试一次模型调用;仍不满足则由服务端从衣橱自动补齐缺失品类(补齐单品标注「系统补齐」)、并剔除冲突项,不判定任务失败、不浪费用户 AI 额度
|
||||
- **同品类唯一**:同一品类最多 1 件(超出保留首件),与既有提示词规则一致
|
||||
- **补齐确定性**:同输入必得同输出(同风格优先 → 主色一致优先 → 最近上传优先),便于线上排查与单测
|
||||
- **体验保持**:接口出入参、额度计费、失败退额度语义、前端轮询方式均不变;用户最终看到的是完整搭配而非卡单或失败
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Go + Gin + GORM(沿用现有分层:`pkg/tokenhub` 放提示词与纯校验/补齐函数,`internal/service` 负责编排、日志与落库),不新增依赖、不改数据库结构、不改 DTO 契约
|
||||
- 测试:Go 标准测试(`pkg/tokenhub` 目前无测试文件,新增 `outfit_recommend_test.go`),纯函数可直接断言,无需 sqlmock/网络
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 核心思路
|
||||
|
||||
在 `pkg/tokenhub` 补齐「完整性规则 + 纠偏提示 + 确定性自动补齐」三件纯函数能力,并把完整性要求写进提示词;在 `internal/service/airecommend.go` 的 `generate` 中编排为「调用 → 校验(含同品类去重)→ 不满足则带纠偏提示重试一次 → 仍不满足则服务端补齐」,形成提示词与服务端双重保障。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[调模型生成搭配] --> B[ValidateClothesIDs 过滤幻觉/纠品类]
|
||||
B --> C[NormalizeCategories 同品类去重]
|
||||
C --> D{完整性检查<br/>CompletenessIssues}
|
||||
D -->|完整| G[落库待保存穿搭]
|
||||
D -->|缺品类/冲突| E[带纠偏提示重试一次模型]
|
||||
E --> F{再检查}
|
||||
F -->|完整| G
|
||||
F -->|仍不完整| H[ApplyAutoComplete<br/>从衣橱补齐缺失品类 + 剔除冲突]
|
||||
H --> G
|
||||
```
|
||||
|
||||
### 关键决策与权衡
|
||||
|
||||
1. **规则放在 `pkg/tokenhub` 而非 service**:完整性判定只依赖「推荐结果 + 衣橱列表」,是纯函数,放 tokenhub 可与提示词同源、便于单测;service 只做编排。
|
||||
2. **重试只 1 次且带时间预算检查**:`aiRecommendRunTimeout` 维持 3 分钟不变(避免扩大前端等待 SLA、不必改文档中的轮询上限);调用前用 `ctx.Deadline()` 判断剩余预算,剩余不足(< 60s)时跳过模型重试、直接进入服务端补齐路径,避免因重试导致任务超时失败(客户端单次调用上限 120s)。
|
||||
3. **自动补齐而非失败**(已与用户确认):补齐是纯内存操作、确定性排序,保证「同输入同输出」,并把补齐品类写进 `reason`(如「系统按规则补齐:原推荐缺少上装」),便于用户理解与线上排查。
|
||||
4. **补齐优先级**:同风格(与已选单品风格众数一致)→ 主色与已选主色众数一致 → 视觉冲击力接近 → ID 最大(最近上传),全部为确定性排序键,不做随机。
|
||||
5. **不扩权**:不改 `category_main` 分类逻辑、不改前端展示、不新增数据库列、不改额度计费;`RecommendLLMResponse`/`AIRecommendCreateResponse` 结构保持兼容。
|
||||
|
||||
### 性能与可靠性
|
||||
|
||||
- 最坏情况由 1 次模型调用变为 2 次(仅当首次结果不完整/无有效单品时触发),有预算检查兜底,任务总耗时不超过既有 3 分钟上限;补齐为 O(衣橱件数) 的单次遍历,可忽略。
|
||||
- 失败路径完全复用既有 `finishFailed`(退额度 + 写失败流水),新增逻辑不改变错误码与额度语义。
|
||||
- 提示词注入防护:纠偏提示仅由固定枚举品类名拼装(不含任何用户输入),现有 `sanitizePromptField` 逻辑不变。
|
||||
|
||||
## 架构设计
|
||||
|
||||
沿用现有分层与调用链,改动集中在两处:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph service
|
||||
A[AIRecommendService.generate] --> B[tokenhub.Client.GenerateOutfitRecommendation]
|
||||
A --> C[ValidateClothesIDs / NormalizeCategories]
|
||||
A --> D[CompletenessIssues 判定]
|
||||
A --> E[ApplyAutoComplete 补齐]
|
||||
A --> F[落库 Outfit + OutfitItem + 流水]
|
||||
end
|
||||
subgraph pkg/tokenhub
|
||||
B --> P[buildRecommendPrompt 含完整性规则与纠偏段]
|
||||
D --> Q[完整性规则纯函数]
|
||||
E --> Q
|
||||
E --> R[确定性补齐排序]
|
||||
end
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
zt-closet-backend/
|
||||
├── pkg/tokenhub/
|
||||
│ ├── outfit_recommend.go # [MODIFY] 提示词补充完整性硬规则与「上次输出问题」纠偏段;GenerateOutfitRecommendation 增加 retryHint 参数;
|
||||
│ │ # 新增 NormalizeCategories(同品类去重)、CompletenessIssues(缺失/冲突判定)、
|
||||
│ │ # ApplyAutoComplete(确定性补齐+剔冲突)、BuildCorrectionHint 与严格选品提示常量
|
||||
│ └── outfit_recommend_test.go # [NEW] 规则判定/冲突/去重/补齐确定性/提示词拼接单测
|
||||
├── internal/service/
|
||||
│ └── airecommend.go # [MODIFY] generate 编排:校验后不满足则带提示重试一次(含剩余时间预算检查),
|
||||
│ # 仍不满足则服务端补齐;新增结构化日志;落库/额度/错误码语义不变
|
||||
├── 接口文档.md # [MODIFY] 5.6 AI 推荐模块业务逻辑补充「搭配完整性保障(重试+服务端补齐)」,第 4 条输入说明与轮询说明保持一致
|
||||
└── README.md # [MODIFY] 「### 6. AI 推荐」处理流程补充完整性规则与补齐说明
|
||||
```
|
||||
|
||||
## 关键代码结构
|
||||
|
||||
```
|
||||
// pkg/tokenhub/outfit_recommend.go
|
||||
// GenerateOutfitRecommendation 新增 retryHint 参数(为空表示首次调用;非空时作为「上次输出问题」注入 prompt)。
|
||||
func (c *Client) GenerateOutfitRecommendation(ctx context.Context, zodiac string, fortune *FortuneItem,
|
||||
clothes []mysqlmodel.Clothes, scene, retryHint string) (*RecommendLLMResponse, error)
|
||||
|
||||
// NormalizeCategories 同一品类最多保留 1 件(保留首次出现),返回被移除的品类。
|
||||
func (r *RecommendLLMResponse) NormalizeCategories() (removed []string)
|
||||
|
||||
// CompletenessIssues 按衣橱实际存在的品类判定,返回缺失品类与冲突品类;两者皆空表示搭配完整。
|
||||
// 规则:内层(上装或连衣裙)必选;外套不可孤立;未选连衣裙时下装必需;衣橱有鞋则鞋必选;配饰可选。
|
||||
func (r *RecommendLLMResponse) CompletenessIssues(clothes []mysqlmodel.Clothes) (missing []string, conflicts []string)
|
||||
|
||||
// ApplyAutoComplete 确定性补齐缺失品类并剔除冲突项(如连衣裙+下装),返回新增与移除的品类。
|
||||
func (r *RecommendLLMResponse) ApplyAutoComplete(clothes []mysqlmodel.Clothes) (added []string, removed []string)
|
||||
|
||||
// BuildCorrectionHint 生成纠偏提示文本(仅由固定品类枚举拼装,不含用户输入);无问题时返回空串。
|
||||
func BuildCorrectionHint(missing, conflicts []string) string
|
||||
|
||||
// CorrectionHintStrictSelection 首次输出无任何有效单品时的兜底纠偏提示(严格从衣橱选品)。
|
||||
const CorrectionHintStrictSelection = "..."
|
||||
```
|
||||
|
||||
## 实现要点
|
||||
|
||||
- 判定口径:衣橱品类取自 `clothes[].CategoryMain` 且仅统计 6 类(`outfitCategories`);衣橱不含「上装/连衣裙」时不强制内层;衣橱不含「下装/鞋」时不强制对应品类(避免"强制补齐不存在品类"导致空结果)。
|
||||
- 补齐实现:候选 = 该品类中未被选中的衣物;排序键依次为「风格==已选风格众数」「主色==已选主色众数」「视觉冲击力接近已选众数」「ID 降序」;补齐项 `Type/ClothesUUID` 取衣橱真实值,`Reason` 标注系统补齐;补齐后再跑一次 `CompletenessIssues` 校验(防御性),并限制最多补齐 3 件(上装/连衣裙、下装、鞋)。
|
||||
- 重试编排:仅当「无有效单品」或「不完整」时重试一次;重试结果需再次 `ValidateClothesIDs`,且仅在 `len(Items) > 0` 时替换首次结果(防止重试反而变空);重试失败(网络/超时)仅告警,继续走原有逻辑与补齐。
|
||||
- 日志:沿用 `slog.*Context`,重试打 Info(含 `user_id/task_id/missing/conflicts/remaining_budget`),补齐打 Warn(含 `added_categories/removed_categories`),不打印衣橱明细与用户隐私字段。
|
||||
- 兼容性:`GenerateOutfitRecommendation` 全项目仅 1 处调用(`internal/service/airecommend.go`),签名调整安全;`ValidateClothesIDs` 行为不变,新增方法不破坏既有契约;`ResultContent` 流水落库的是补齐后的最终结果,天然可审计。
|
||||
- 边界(不做):不改提示词中的运势/场景逻辑与 JSON Schema 结构;不改衣物分类;不改前端轮询与展示;不新增数据库列;重试不额外扣 AI 额度。
|
||||
|
||||
## Agent Extensions
|
||||
|
||||
### Skill
|
||||
|
||||
- **lsp-code-analysis**
|
||||
- Purpose: 在调整 `GenerateOutfitRecommendation` 签名与新增 `RecommendLLMResponse` 方法前,用语义分析确认全部引用点(调用方、字段读取、既有测试),并核对 `NormalizeCategories/CompletenessIssues/ApplyAutoComplete/BuildCorrectionHint` 等新命名在 `pkg/tokenhub` 内无冲突
|
||||
- Expected outcome: 输出完整引用清单与受影响文件列表,确保签名调整无遗漏、无编译期破坏,新增导出符号命名唯一
|
||||
Reference in New Issue
Block a user