package agent import ( "encoding/json" "fmt" "strings" ) // PlanOutput 大模型输出的穿搭方案集合 type PlanOutput struct { Plans []PlanCandidate `json:"plans"` } // PlanCandidate 一套穿搭方案 type PlanCandidate struct { Title string `json:"title"` Hairstyle string `json:"hairstyle"` // 发型名称(匹配资产库) HairColor string `json:"hair_color"` // 如 #A0522D Items []PlanItemOut `json:"items"` } // PlanItemOut 方案内一件单品 type PlanItemOut struct { Slot string `json:"slot"` // 上衣/下装/鞋/配饰 ItemId int64 `json:"item_id,omitempty"` // 衣橱条目(wardrobe 来源) Name string `json:"name"` // 单品名 Desc string `json:"desc"` // 搭配说明 NewItem bool `json:"new_item"` // 是否为推荐新服装 } // ParsePlanOutput 解析并校验 LLM 输出(去除 markdown 代码围栏后 json.Unmarshal) func ParsePlanOutput(raw string) (*PlanOutput, error) { text := strings.TrimSpace(raw) // 容忍 ```json ... ``` 代码围栏 if strings.HasPrefix(text, "```") { text = strings.TrimPrefix(text, "```") if idx := strings.Index(text, "\n"); idx >= 0 { text = text[idx+1:] } text = strings.TrimSuffix(strings.TrimSpace(text), "```") } var out PlanOutput if err := json.Unmarshal([]byte(text), &out); err != nil { return nil, fmt.Errorf("方案 JSON 解析失败: %w", err) } if len(out.Plans) == 0 { return nil, fmt.Errorf("方案输出为空(plans 缺失)") } for i, p := range out.Plans { if strings.TrimSpace(p.Title) == "" { return nil, fmt.Errorf("方案 %d 缺少 title", i+1) } if len(p.Items) == 0 { return nil, fmt.Errorf("方案 %d 缺少 items", i+1) } for _, it := range p.Items { if !isValidSlot(it.Slot) { return nil, fmt.Errorf("方案 %d 含非法 slot: %s", i+1, it.Slot) } } } return &out, nil } func isValidSlot(slot string) bool { switch slot { case "上衣", "下装", "鞋", "配饰": return true } return false }