UPDATE: subcategories filtering, General rules updates

This commit is contained in:
pangkaicheng
2026-01-06 15:42:30 +08:00
parent e7ec547671
commit 9d6fbc92f1
9 changed files with 461 additions and 508 deletions

View File

@@ -43,7 +43,7 @@ class OccasionEnum(str, Enum):
class StylistResponse(BaseModel):
occasions: List[OccasionEnum] = Field(
description="A list of **applicable** occasions that are most strongly implied or explicitly requested by the user's conversation history. These occasions are used later in item retrieval for filtering and must strictly match the predefined OccasionEnum list."
description="A list of **applicable** occasions that are most strongly implied or explicitly requested by the user's conversation history. The first occasion in this list is the most applicable one. These occasions are used later in item retrieval for filtering and must strictly match the predefined OccasionEnum list."
)
summary: str = Field(
description="A detailed summary of the user's styling requirements, preferences, constraints, and specific item requests."
@@ -357,16 +357,17 @@ if __name__ == "__main__":
request_data = json.load(f)
tasks_with_metadata = []
for test_content in request_data[14:20]:
for test_content in request_data[0:3]:
occasions = test_content['occasions']
request_summary = test_content['request_summary']
for stylist_name in ["edi"]:
stylist_agent_kwages['outfit_id'] = test_content['test_case_id'] + "_" + "_".join(occasions) + f"_{stylist_name}"
for stylist_name in ["vera"]:
stylist_agent_kwages['outfit_id'] = test_content['test_case_id'] + '_' + test_content['occasions'][0].replace('/', '_') + f"_{stylist_name}"
stylist_agent_kwages['stylist_name'] = stylist_name
stylist_agent_kwages['gender'] = "female"
stylist_agent_kwages['callback_url'] = ""
agent = AsyncStylistAgent(**stylist_agent_kwages)
# coro = agent.run_iterative_styling(
coro = agent.run_quick_batch_styling(
coro = agent.run_iterative_styling(
# coro = agent.run_quick_batch_styling(
request_summary=request_summary,
occasions=occasions,
start_outfit=[],

View File

@@ -1,26 +1,3 @@
BASIC_PROMPT_OLD = """You are a professional, friendly, and insightful AI {gender}'s styling assistant.
Your primary mission is to engage in a multi-turn conversation with the user to fully understand their dressing intent. You must adopt a professional yet approachable tone.
CONVERSATION GOALS:
1. **Occasion:** Determine the specific event (e.g., romantic dinner, summer wedding, business meeting).
2. **Style:** Pinpoint the desired aesthetic (e.g., classic elegance, edgy, minimalist, bohemian).
3. **Vibe/Details:** Gather any mood or specific constraints (e.g., needs to be comfortable, requires light colors, no bare shoulders).
4. **Item Preference:** Ask the user if they have any specific preferences for an item type or silhouette (e.g., preference for a dress, skirt, tailored pants, or a particular neckline/length).
GUIDANCE FOR RESPONSE GENERATION:
- After the user's initial request (e.g., "I want a chic outfit for dinner."), immediately reply with a friendly, targeted follow-up question to elicit the most crucial missing information (usually a combination of **Occasion** and **Style**).
- Be concise. Ask only 1 to 2 essential questions per turn.
- You must gather sufficient, clear intent before proceeding to actual clothing recommendations.
OUTPUT FORMAT INSTRUCTION:
- **DO NOT** use any Markdown formatting whatsoever (e.g., do not use asterisks (*), bold text (**), lists, or code blocks).
- **ONLY** output the plain text response spoken by the AI Assistant.
Example Follow-up (mimicking a conversational flow):
User: I want a chic outfit for dinner.
Your Response: Hey there! A chic dinner outfit, I love that! To give you the perfect recommendations, tell me: is this a romantic date, business dinner, or celebration with friends? And what's your go-to style vibe: classic elegance or something with more edge?"""
BASIC_PROMPT = """
You are a professional, friendly, and insightful AI {gender}'s styling assistant. You are smart, young, and enthusiastic, turning styling into an exciting experience. Your tone is warm, confident, composed, and genuinely curious about the user's context.
@@ -64,159 +41,223 @@ Your task is to:
Extract this information accurately from the chat history.
"""
from app.taxonomy import FASHION_TAXONOMY, IGNORE_SUBCATEGORY, ALL_SUBCATEGORY_LIST
from app.taxonomy import FASHION_TAXONOMY, IGNORE_SUBCATEGORY
GENERAL_RULES = """## General Rule
### **Color:**
* Limit your outfit in two-three color shades max.
* Pair one accent color item with neutral colors
* Don't show neon color items
* Pair colors in warm tone/cool tone
* Denim and black and white is a neutral color matches with every other color.
### **Layering:**
* Style at least 2-3 layers
"""
GENERAL_RULES_DICT = {
'clothing': """### **Body items mix & match:**
* Loose/oversized pair with fitted bottoms. Wide bottoms pair with fitted top.
* With loose clothing, use a belt or a "French tuck" (tucking just the front of the shirt) to define waistline
* Pair casual items with dressy items. Wear a blazer with a graphic tee, or a silk skirt with a chunky knit sweater.
* Always add an outer on a top and bottom to finish the outfit, mix and match in at least 2 layers.
* Mix menswear-inspired pieces (blazers, loafers, trousers) with feminine pieces (lace, silk, florals) for a dynamic look.
* one print per outfit""",
'bags': """### **Bag matching:**
* Large totes are for day/work; clutches and mini bags are for evening. Never bring a giant work tote to a cocktail event.
* A smooth satin dress pair with a leather, a beaded or velvet bag ; Heavy wool pair with a sleek leather bag
* Match bag and shoes in same color or match bag and coat in same color
* Straw and canvas bags for spring/summer. Velvet and faux fur bags for autumn/winter. Leather works year-round.""",
'shoes': """### **Shoes matching:**
* Dress with heels ; Jeans and trousers with heels or loafers
* Match shoes with skin tone or the bottom piece
* Chunky winter layers pair with a boot, platform shoe (boot, platform).
* Crop pants pair with ankle boots, don't show skin ; Don't show calf length pants ; Always suggest wide leg floor length pants
* Wide-leg trousers pair with a heel ; loafer ; heel boots ; or a sneaker.""",
'accessories': """### **Accessory matching:**
* Acceesory wear in a set of same metal colour (gold earrings with gold belt buckle)
* Wear chokers or short necklaces with V-necks; wear long pendants with crew necks or turtlenecks.
* Wear a belt in tuck-in outfits
* No watches, No hats, No sunglasses""",
}
core_outfit_template = f"""
You are a professional fashion stylist Agent, specialized in creating complete, tailored outfits for {{gender}}. Your current task is to recommend items for the **{{current_category}}** stage, strictly **mimicking the style and preference** specified in the following Stylist Guide.
# ROLE: Professional Fashion Stylist Agent
You are a professional fashion stylist for {{gender}}. Your current task is to recommend the next logical item for the **{{current_category}}** stage.
Your task is to **create a cohesive and complete outfit**, strictly adhering to **BOTH** the user's explicit **Request Summary** and the **Outfit Style Guide**. You must decide the next logical item to add to the outfit based on the current stage and constraints. Descriptions of current outfit combination is listed in user's message.
## 1. INTEGRATION LOGIC (How to Think)
1. **Analyze User Request**: Identify the target occasion, mood, and specific color/item preferences from the [Request Summary].
2. **Apply Stylist Filter**: Use the [Stylist Guide] as the aesthetic filter. If the user request and Stylist Guide conflict, the user request takes precedence.
3. **Synthesis with Material**: Incorporate the [Material Hint] into your item descriptions to ensure the outfit is contextually appropriate for the {{occasion}} and facilitates high-accuracy vector search.
4. **Contextual Coordination**: Review the already selected items (provided in the user's message) to ensure the next item maintains silhouette balance (Loose vs. Fitted) and color harmony.
## RULE HIERARCHY (Priority Order):
1. **STYLIST CORE RULES**: Adhere to the specific logic provided in the Stylist Guide.
2. **GENERAL COORDINATION RULES**:
- Limit outfit to **three main colors** within the **same color tone**.
- Preferred composition: **Two neutrals and one accent color**.
- **Silhouette Balance**: Loose top with fitted bottom, OR fitted top with loose bottom.
- **Pants Logic**: Strictly NO mid-calf length pants. Cropped pants MUST be paired with loafers or ankle boots.
## 2. RULE HIERARCHY
1. **USER REQUEST**: (Highest Priority)
2. **STYLIST CORE RULES**: (Secondary Priority)
3. **GENERAL COORDINATION RULES**: (Standard Professional Logic)
---
## Request from the User:
## 3. CONTEXT & GUIDANCE
### [User Request Summary]
{{request_summary}}
## Core Guidance Document: Stylist Guide
### [Target Occasion]
{{occasion}}
### [Stylist Guide]
{{stylist_guide}}
### [Material Hint]
{{material_hint}}
### [General Rules]
{{general_rule}}
---
## Your Workflow and Constraints
## 4. CONSTRAINTS & WORKFLOW (Iterative Mode)
1. **Selection Pool**: You MUST only choose items from the following **[Allowed Subcategories]**.
**ALLOWED**: {{allowed_subcategories}}
1. **Style Adherence**: You must follow the Rule Hierarchy. If a rule in the Stylist Guide conflicts with the General Rules, the Stylist Guide takes precedence. You should strictly consider all rules in the Style Guide and general rules concerning **color palette, fit, layering principles, pattern restrictions , shoe coordination**.
2. **Uniqueness Mandate**: Every item must follow the **absolute no-repeat rule for subcategories** within its stage. Each subcategory from the allowed list can appear **exactly once** in the entire outfit. Furthermore, the categories 'dresses' and 'pants' and 'skirts' are mutually exclusive; they NORMALLY cannot be included in the same outfit.
3. **Step Planning**: The styling sequence must follow a logical approach (e.g., top-down, inside-out for clothing). Prioritize unused subcategories from the allowed list to avoid repetition.
4. **Structured Output**: Your output MUST be a valid JSON object. The strict JSON structure and field requirements are provided separately via the API schema.
2. **Uniqueness Mandate**: Every item must follow the **absolute no-repeat rule for subcategories**. Each subcategory can appear **exactly once** in the entire outfit.
You must only output one of two actions: "recommend_item" or "stop".
4.1. **recommend_item**: Use this action to suggest the next single item.
* **subcategory**: Must be strictly no repeats, and drawn from the allowed list.
* **description**: This must be an **extremely detailed and precise** description for the vector search. It MUST include: **Color, Fit/Silhouette, Material/Detail, and Role in the Outfit.**
You must strictly use the **JSON format** for your output, as follows:
```json
{{{{
"action": "recommend_item",
"subcategory": "YOUR_ITEM_SUBCATEGORY",
"description": "YOUR_DETAILED_DESCRIPTION",
"reason": "YOUR_RECOMMENDATION_REASON"
}}}}
3. **Action Selection**: You must output only one of two actions: "recommend_item" or "stop".
- **recommend_item**: Suggest the next single item following a logical sequence (e.g., top-down, inside-out).
- **stop**: Use ONLY when the Termination Conditions below are fully met.
4.2. **stop**: Use this action when the Termination Condition is met.
* **reason**: This field is mandatory when stopping, and must clearly state why the outfit is complete.
You must strictly use the **JSON format** for your output, as follows:
{{{{
"action": "stop",
"subcategory": "",
"description": "",
"reason": "CORE_OUTFIT_COMPLETE"
}}}}
4. **Termination Conditions**:
- **CLOTHING Stage**: Achieved full body coverage (Top + Bottom OR Dress) AND satisfied all mandatory style elements. (Typically {{max_len}} items).
- **SHOES Stage**: Exactly one (1) pair has been recommended.
- **BAGS Stage**: Exactly one (1) item recommended, OR skipped if not mandated for the occasion.
5. **Termination Condition**: Terminate when the below condition is fully met
5.1. **CLOTHING Stage**: The core clothing part of the outfit is complete, meaning the combination of items effectively achieves **full body coverage** (e.g., includes both a top/upper garment and a bottom/lower garment, or a single full-body piece like a dress/jumpsuit). Additionally, **all mandatory elements** stipulated in the Style Guide are satisfied. *(Note: Typically, {{max_len}} items are sufficient for this stage.)*
5.2. **SHOES Stage**: **Exactly one (1) item** has been successfully recommended, as shoes are a **mandatory component** for any complete outfit.
5.3. **BAGS Stage**: **Exactly one (1) item** has been successfully recommended, **OR** the recommendation is skipped if the Style Guide or the User Request **does not mandate** a bag for the specific occasion (i.e., the bag is considered optional).
---
6. **Context Dependency**: The user's next input (if not Start) will contain the **image and description of the selected item**. When recommending the next item:
a) First verify the subcategories of all already selected items to ensure no duplicates;
b) Select an unused subcategory from the allowed list as the priority;
c) Ensure the recommended item coordinates with the already selected items and complies with all rules in the Style Guide.
Now, please start building an outfit (with strictly unique categories for all items) and output the JSON for the first item.
## 5. OUTPUT REQUIREMENT
- **Format**: Valid JSON object.
- **Description Quality**: Each 'description' field must be a precise string for vector search: **subcategory + Color + Fit/Silhouette + Material/Detail + Role in the Outfit.**
- **Reasoning**: Explain why this item is the next logical step and how it balances the User Request with Stylist DNA.
Generate the JSON for the next item now.
"""
accessories_template = f"""
You are a professional fashion stylist Agent, specialized in creating complete, tailored outfits for {{gender}}. Your current task is to finalize the look by recommending accessories for the **{{current_category}}** stage, strictly **mimicking the style and preference** specified in the following Accessories Guide.
# ROLE: Professional Fashion Stylist Agent
You are a professional fashion stylist for {{gender}}. Your current task is to finalize the look by recommending a complete set of accessories for the **{{current_category}}** stage.
Your final task is to **select the perfect set of accessories** to complete the given outfit. You must strictly adhere to **BOTH** the user's **Request Summary** and the **ACCESSORIES Style Guide**. The **full description of the existing outfit** is provided in the user's message.
## 1. INTEGRATION LOGIC (How to Think)
1. **Outfit Coordination**: Analyze the existing clothing, bags and shoes (provided in the user's message). Accessories must enhance, not overwhelm, the established look.
2. **Apply Stylist Filter**: Strictly follow the [Stylist's Accessories Guide] regarding metal mixing (Gold/Silver), layering, and vintage/worn aesthetic preferences.
3. **Synthesis with Material**: Integrate [Material Hint] keywords into your descriptions to maintain consistency with the {{occasion}}.
4. **Prohibition Check**: Ensure NO items from the [Exclusion List] are included, regardless of user preference.
## RULE HIERARCHY (Priority Order):
1. **ABSOLUTE PROHIBITION (Highest Priority)**:
- **NO WATCHES**.
- **NO HATS**.
- **NO SOCKS**.
- Do not recommend these subcategories under any circumstances, even if requested or implied.
2. **STYLIST CORE RULES**: Follow specific accessory preferences in the [Stylist's Accessories Guide].
3. **GENERAL COORDINATION RULES**:
- Accessories must fit within the **three main colors** and **same color tone** established by the clothing.
- Maintain the **two neutrals and one accent color** balance.
## 2. RULE HIERARCHY
1. **ABSOLUTE PROHIBITION**: (Highest Priority - No {",".join(IGNORE_SUBCATEGORY)})
2. **USER REQUEST**: (Secondary Priority)
3. **STYLIST CORE RULES**: (Aesthetic Filter)
4. **GENERAL COORDINATION RULES**: (Standard Professional Logic)
---
## CONTEXT
[User Request]: {{request_summary}}
## 3. CONTEXT & GUIDANCE
### [User Request Summary]
{{request_summary}}
[Stylist's Accessories Guide]:
### [Target Occasion]
{{occasion}}
### [Existing Outfit Description]
(The existing items will be provided in the user's prompt)
### [Stylist's Accessories Guide]
{{stylist_guide}}
### [Material Hint]
{{material_hint}}
### [General Rules]
{{general_rule}}
---
## ACCESSORIES GENERATION RULES
## 4. CONSTRAINTS & WORKFLOW (Batch Mode)
1. **Batch Recommendation**: You must output the **COMPLETE LIST of accessories** in a single response using the 'recommended_accessories' list.
2. **Quantity Constraint**: Recommend between 1 and {{max_len}} distinct items.
3. **Selection Pool**: Choose ONLY from: {{allowed_subcategories}}.
4. **Exclusion List**: Strictly FORBIDDEN to recommend: {",".join(IGNORE_SUBCATEGORY)}.
5. **Harmony**: Ensure all metals match the stylist's metal-mixing logic and all colors stay within the 3-tone limit.
1. **Batch Recommendation**: You must output the **COMPLETE LIST of accessories** in a single response using the 'recommended_accessories' list defined in the schema. Do not recommend items one by one.
2. **Quantity Constraint**: The total number of accessories recommended in the list must not exceed **{{max_len}}** items. Typically, 1 to {{max_len}} distinct items are required to complete a look.
3. **Harmony & Guide Compliance**:
- Assess the existing outfit (provided in the user's message) and ensure all accessories complement its style, color palette, and occasion.
- **Strictly follow the [Accessories Style Guide]** regarding material types (e.g., metals like gold/silver), total numbers allowed, and specific layering requirements (e.g., mandated watch or jewelry layering).
4. **Exclusion List**: Subcategories in the following list are strictly excluded from recommendation: ({IGNORE_SUBCATEGORY}).
5. **Description Quality**: The 'description' field for each accessory must be **extremely detailed and precise** for high-accuracy vector search, including: **Color, Material/Detail, and the specific Role in the Outfit.**
---
Generate the final, complete accessories list now.
## 5. OUTPUT REQUIREMENT
- **Format**: Valid JSON matching the accessory API schema.
- **Description Quality**: Precise string for vector search: **subcategory + Color + Material/Detail + Specific Role in this Look.**
- **Reasoning**: Justify the accessory choices based on the clothing's silhouette and the user's requested mood.
Generate the final accessory set now.
"""
all_items_template = f"""
You are a professional fashion stylist Agent, specialized in creating complete, tailored outfits for {{gender}}. Your task is to **generate a Complete, Head-to-Toe Outfit** in a **Single Batch**, strictly **mimicking the style and preference** specified in the Stylist Guide.
# ROLE: Professional Fashion Stylist Agent
You are a professional fashion stylist for {{gender}}. Your goal is to generate a Complete, Head-to-Toe Outfit in a Single Batch.
You must create a cohesive look that includes **Clothing, Shoes, Bags, and Accessories**. You must strictly adhere to **BOTH** the user's **Request Summary** and the **Combined Style Guide**.
## 1. INTEGRATION LOGIC (How to Think)
1. **Analyze User Request**: Identify the target occasion, mood, and specific color/item preferences from the [Request Summary].
2. **Apply Stylist Filter**: Use the [Stylist Guide] as the primary aesthetic filter. If the user request and Stylist Guide conflict, the user request takes precedence.
3. **Synthesis with Material**: Incorporate the [Material Hint] into your item descriptions to ensure the outfit is contextually appropriate for the {{occasion}} and better performance in item retrieval.
4. **Final Rule Check**: Validate the outfit against [General Rules] and ensure no items from the [Exclusion List] are included.
## RULE HIERARCHY (Priority Order):
1. **ABSOLUTE PROHIBITION**: Strictly NO hats or watches or socks.
2. **STYLIST CORE RULES**: Adhere to the specific logic provided in the Stylist Guide.
3. **GENERAL COORDINATION RULES**:
- Limit outfit to **three main colors** within the **same color tone**.
- Preferred composition: **Two neutrals and one accent color**.
- **Silhouette Balance**: Loose top with fitted bottom, OR fitted top with loose bottom.
- **Pants Logic**: Strictly NO mid-calf length pants. Cropped pants MUST be paired with loafers or ankle boots.
## 2. RULE HIERARCHY
1. **USER REQUEST**: (Highest Priority)
1. **STYLIST CORE RULES**: (Secondary Priority)
2. **GENERAL COORDINATION RULES**: (Standard Professional Logic)
---
## Request from the User:
## 3. CONTEXT & GUIDANCE
### [User Request Summary]
{{request_summary}}
## Core Guidance Document: Combined Style Guide
### [Target Occasion]
{{occasion}}
### [Stylist Guide]
{{stylist_guide}}
### [Material Hint]
{{material_hint}}
### [General Rules]
{{general_rule}}
---
## GENERATION WORKFLOW & RULES
## 4. CONSTRAINTS & WORKFLOW
1. **Selection Pool (Mandatory)**: You MUST only choose items from the following **[Allowed Subcategories]**. Do not recommend any subcategory outside this list.
**ALLOWED**: {{allowed_subcategories}}
1. **Single Batch Output**: Visualize the final look and output **ALL** items in a single JSON response using the `recommended_items` list.
2. **Exclusion List**: Strictly FORBIDDEN to recommend: {",".join(IGNORE_SUBCATEGORY)}.
2. **Full Body Coverage & Composition (Mandatory)**:
* **Clothing**: You MUST ensure **full body coverage**. This requires including either [Top + Bottom] OR [a One-piece item like a Dress/Jumpsuit].
* **Mutual Exclusivity**: 'Dresses' and 'Skirts/Pants' are mutually exclusive; do not recommend both in one outfit.
* **Shoes**: Exactly **one (1) pair** of shoes is MANDATORY for every outfit.
* **Bags**: Recommend **0 or 1 bag**.
* **Accessories**: Recommend a set of accessories (e.g., jewelry, eyewear).
3. **Mandatory Composition**:
- **Clothing**: Follow the stylist and general rules for clothing selection.
- **Shoes**: Exactly one (1) pair is mandatory.
- **Bags & Accessories**: These are **CONDITIONAL**.
- If NO subcategories related to Bags or Accessories appear in the **[ALLOWED]** list, DO NOT recommend them.
- Simply omit these categories from your output if they are not in the allowed pool for the current {{occasion}}.
- Do not invent subcategories that are not explicitly listed in the [ALLOWED] section.
3. **Strict Uniqueness Mandate (No Repeats)**:
* Each **subcategory** (e.g., 'sunglasses', 'earrings', 't-shirts') can appear **EXACTLY ONCE** in the entire outfit.
* DO NOT recommend two items from the same subcategory (e.g., do not recommend two different necklaces or two pairs of sunglasses).
4. **Uniqueness**: Each **subcategory** (e.g., 'earrings', 't-shirts') can appear **EXACTLY ONCE**. No repeats.
4. **Style Adherence**:
- **Proportions**: Explicitly describe the fit (Loose vs. Fitted) to show the required silhouette balance.
- **Color Harmony**: Ensure the combined colors of clothing, shoes, bags, and accessories stay within the 3-color tone limit.
5. **Visual Balance**: Explicitly describe the fit (Loose vs. Fitted) to maintain silhouette balance according to General Rules.
5. **Exclusion List**:
- The following subcategories are **STRICTLY FORBIDDEN**: ({IGNORE_SUBCATEGORY}).
6. Max item number is {{max_len}}.
6. **Description Quality**:
- Each `description` MUST include: **Color, Fit/Silhouette, Material/Detail, and Role in the Outfit.**
---
## OUTPUT FORMAT
Output a valid JSON object matching the provided API schema. The `recommended_items` array must contain all the items for this outfit.
## 5. OUTPUT REQUIREMENT
- **Format**: Valid JSON matching the provided API schema (`recommended_items` list).
- **Description Quality**: Each 'description' field must be a precise string for vector search: **subcategory + Color + Fit/Silhouette + Material/Detail.**
- **Reasoning**: In the 'reason' field, explain how the outfit satisfies the User's Request while maintaining the Stylist's DNA.
Generate the complete outfit list now.
Generate the complete outfit list now
"""
@@ -241,14 +282,12 @@ def build_iterative_schema(current_category):
return schema
def build_batch_schema(specified_category: str = ""):
assert (specified_category in FASHION_TAXONOMY.keys() or specified_category == "")
if not specified_category:
category_range_desc = "the complete final outfit (including all categories)"
subcategory_list = ALL_SUBCATEGORY_LIST
def build_batch_schema(specified_category: str = "", subcategory_list: list = []):
assert (specified_category in FASHION_TAXONOMY.keys() or specified_category == "all")
if specified_category == "all":
category_range_desc = "all categories of the outfit"
else:
category_range_desc = specified_category
subcategory_list = FASHION_TAXONOMY[specified_category]
category_range_desc = f"only *{specified_category}* part of the outfit"
schema = {
"type": "object",
"properties": {
@@ -262,7 +301,7 @@ def build_batch_schema(specified_category: str = ""):
"items": {
"type": "object",
"properties": {
"description": {"type": "string", "description": f"The detailed description for this {specified_category} item."},
"description": {"type": "string", "description": f"The detailed description for this recommended item."},
"subcategory": {
"type": "string",
"description": "The subcategory of the recommended item.",

View File

@@ -15,13 +15,15 @@ from app.server.utils.minio_client import minio_client, oss_upload_image
from app.server.utils.request_post import post_request
from app.config import settings
from app.server.ChatbotAgent.core.prompt import (
GENERAL_RULES,
GENERAL_RULES_DICT,
core_outfit_template,
accessories_template,
all_items_template,
build_iterative_schema,
build_batch_schema
)
from app.taxonomy import FASHION_TAXONOMY, ALL_SUBCATEGORY_LIST
from app.taxonomy import FASHION_TAXONOMY, ALL_SUBCATEGORY_LIST, OCCASION_CATEGORY_MAP, OCCASION_MATERIAL_MAP, SUBCATEGORY_MERGE_MAP
logger = logging.getLogger(__name__)
@@ -166,13 +168,15 @@ class AsyncStylistAgent:
"""
# 1. 生成查询嵌入
query_embedding = self.local_db.get_clip_embedding(item_description, is_image=False)
search_subcategories = SUBCATEGORY_MERGE_MAP.get(subcategory, [subcategory])
# 特殊逻辑处理Clutch 和 Crossbody 在同一场合下不应互换(根据你的规则)
# 2. 执行查询,并过滤类别
try:
results = self.local_db.get_matched_item(
query_embedding,
category,
subcategory,
search_subcategories,
occasions=occasions,
batch_sources=batch_sources,
gender=gender,
@@ -185,11 +189,11 @@ class AsyncStylistAgent:
if not results:
self.post_operation(
status="failed",
message=f"数据库中未找到符合 '{category}' 和描述的单品。",
message=f"数据库中未找到符合 '{category}/{subcategory}' 和描述的单品。",
callback_url=self.callback_url,
img_path="",
)
raise Exception(f"数据库中未找到符合 '{category}' 和描述的单品。")
raise Exception(f"数据库中未找到符合 '{category}/{subcategory}' 和描述的单品。")
# 3. 模拟 Agent 审核(实际应用中,你需要将图片发回给 Agent进行审核)
best_meta = results[0] # 第一个 batch 的第一个 metadata
@@ -203,18 +207,22 @@ class AsyncStylistAgent:
"brand": best_meta['brand'],
"gpt_description": item_description,
"gpt_subcategory": subcategory,
# 假设 'item_path' 存储在 metadata 中,或从 'item_id' 推导
# 这里假设 item_id 就是文件名的一部分
"image_path": os.path.join(settings.DATA_ROOT, batch_source, 'image_data', f"{item_id}.jpg")
}
def _build_system_prompt(self, template: str, request_summary: str = "", stylist_guide: str = "", current_category: str = "clothing", max_len: int = 4) -> str:
def _build_system_prompt(self, template: str, general_rule: str, request_summary: str = "", occasion: str = "", stylist_guide: str = "", current_category: str = "clothing", allowed_subcategories: list = [], max_len: int = 4) -> str:
# 1. 材质偏好 (Occasion Material Map)
material_hint = OCCASION_MATERIAL_MAP.get(occasion, "")
# Insert the style_guide content into the template
sys_template = template.format(
gender=self.gender,
current_category=current_category.upper(),
general_rule=general_rule,
request_summary=request_summary,
stylist_guide=stylist_guide,
occasion=occasion,
material_hint=material_hint,
allowed_subcategories=','.join(allowed_subcategories),
max_len=max_len
)
return sys_template.strip()
@@ -321,7 +329,8 @@ class AsyncStylistAgent:
description = gemini_data.get('description')
# 4a. 检查类别是否有效 (重要步骤)
if subcategory not in FASHION_TAXONOMY[current_category]:
allowed_subcategories = self._get_allowed_subcategories(occasions[0], current_category)
if subcategory not in allowed_subcategories:
self.post_operation(
status="continue",
message=f"Invalid subcategory recommended by Agent: {subcategory}. Requesting Agent to re-output.",
@@ -408,22 +417,25 @@ class AsyncStylistAgent:
for idx, rec_item in enumerate(recommended_items):
subcategory = rec_item.get('subcategory')
description = rec_item.get('description')
# 4a. 检查类别是否有效 (重要步骤)
if subcategory not in ALL_SUBCATEGORY_LIST:
continue
# 4b. 在本地 DB 中查询单品
# we need first determine the category if current category is 'all'
if current_category == "all":
for category, subcategories_list in FASHION_TAXONOMY.items():
# 将子类别列表转换为集合 (set) 可以提高查找效率,
# 特别是当列表很长时。
if subcategory in subcategories_list:
break
category = self._identify_category(subcategory)
else:
category = current_category
allowed_subcategories = self._get_allowed_subcategories(occasions[0], category)
# 4a. 检查类别是否有效 (重要步骤)
if subcategory not in allowed_subcategories:
self.post_operation(
status="continue",
message=f"Invalid subcategory recommended by Agent: {subcategory} not in allowed subcategory list: {allowed_subcategories}. Ignore and continue",
callback_url=url,
img_path=merged_image_path,
)
failed_found_item_count += 1
continue
# 4b. 在本地 DB 中查询单品
new_item = self._get_next_item(description, category, subcategory, occasions, batch_sources, self.gender)
if not new_item or new_item['item_id'] in [x['item_id'] for x in self.outfit_items]:
failed_found_item_count += 1
@@ -442,6 +454,28 @@ class AsyncStylistAgent:
)
raise Exception(f"There are {failed_found_item_count} items (total {len(recommended_items)}) are not found in the database")
return reason
def _get_allowed_subcategories(self, occasion: str, category: str) -> List[str]:
"""根据场景和分类获取合法的子类别列表"""
# 如果 occasion 不在配置中,回退到全局分类
occ_config = OCCASION_CATEGORY_MAP.get(occasion, {})
if category == "all":
# 获取该场景下所有分类的合集
all_subs = []
for cat_subs in occ_config.values():
all_subs.extend(cat_subs)
return all_subs if all_subs else ALL_SUBCATEGORY_LIST
# 返回特定分类下的子类
return occ_config.get(category, FASHION_TAXONOMY.get(category, []))
def _identify_category(self, subcategory: str) -> str:
"""反向查找:通过子类确定它属于哪个大类"""
for cat, subs in FASHION_TAXONOMY.items():
if subcategory in subs:
return cat
return "unknown"
async def run_iterative_styling(self, request_summary, occasions, start_outfit: Optional[List] = None, batch_sources: List = [], user_id="test", callback_url=""):
start_time = time.monotonic()
@@ -453,36 +487,65 @@ class AsyncStylistAgent:
self.outfit_items = deepcopy(start_outfit)
stylist_guide, accessories_guide = self._load_style_guide(self.stylist_name)
url = f'{callback_url}/api/style/callback'
if not occasions:
occasions = ["Casual"]
"""主流程控制循环。"""
print(f"--- Starting Agent (Outfit ID: {self.outfit_id}) ---")
for current_category in STAGES:
max_len = 4 if current_category == 'clothing' else 1
system_prompt = self._build_system_prompt(core_outfit_template, request_summary, stylist_guide, current_category, max_len)
allowed_subcategories = self._get_allowed_subcategories(occasions[0], current_category)
max_len = min(4, len(allowed_subcategories)) if current_category == 'clothing' else 1
await self._execute_iterative_recommendation(
current_category,
system_prompt,
build_iterative_schema(current_category),
max_len,
general_rule = GENERAL_RULES + GENERAL_RULES_DICT.get(current_category, "")
system_prompt = self._build_system_prompt(
core_outfit_template,
general_rule,
request_summary,
occasions[0],
stylist_guide,
current_category,
allowed_subcategories,
max_len
)
if allowed_subcategories:
await self._execute_iterative_recommendation(
current_category,
system_prompt,
build_iterative_schema(current_category),
max_len,
occasions,
batch_sources,
user_id,
url
)
# 根据stylist要求增加配饰 3-4个配饰
MAX_LEN_ACC = 3
current_category = 'accessories'
general_rule = GENERAL_RULES + GENERAL_RULES_DICT.get(current_category, "")
allowed_subcategories = self._get_allowed_subcategories(occasions[0], current_category)
acc_system_prompt = self._build_system_prompt(
accessories_template,
general_rule,
request_summary,
occasions[0],
accessories_guide,
current_category,
allowed_subcategories,
MAX_LEN_ACC
)
if allowed_subcategories:
reason = await self._execute_batch_recommendation(
current_category, # can be 'accessories' or 'all'
acc_system_prompt,
build_batch_schema(specified_category=current_category, subcategory_list=allowed_subcategories),
occasions,
batch_sources,
user_id,
url
)
# 根据stylist要求增加配饰 3-4个配饰
MAX_LEN_ACC = 3
acc_system_prompt = self._build_system_prompt(accessories_template, request_summary, accessories_guide, 'accessories', MAX_LEN_ACC)
reason = await self._execute_batch_recommendation(
'accessories', # can be 'accessories' or 'all'
acc_system_prompt,
build_batch_schema(current_category),
occasions,
batch_sources,
user_id,
url
)
else:
reason = "No allowed subcategories for accessories, skipping accessories recommendation."
final_image_path, _ = await self._merge_images(self.outfit_id, user_id, self.stylist_name)
# 推荐完成返回
@@ -515,16 +578,31 @@ class AsyncStylistAgent:
print(f"--- Starting Agent (Outfit ID: {self.outfit_id}) ---")
MAX_LEN = 9
system_prompt = self._build_system_prompt(all_items_template, request_summary, stylist_guide + accessories_guide, "", MAX_LEN)
if not occasions:
occasions = ["Casual"]
general_rules = "\n".join(GENERAL_RULES_DICT.values())
allowed_subcategories = self._get_allowed_subcategories(occasions[0], "all")
system_prompt = self._build_system_prompt(
all_items_template,
general_rules,
request_summary,
occasions[0],
stylist_guide + accessories_guide,
"",
allowed_subcategories,
MAX_LEN
)
reason = await self._execute_batch_recommendation(
'all', # can be 'accessories' or 'all'
system_prompt,
build_batch_schema(),
build_batch_schema(specified_category="all", subcategory_list=allowed_subcategories),
occasions,
batch_sources,
user_id,
url
)
# 推荐即将完成 回调通知前端
self.post_operation(
status="almost_done",

View File

@@ -46,7 +46,9 @@ class VectorDatabase():
return features.cpu().numpy().flatten().tolist()
def get_matched_item(self, embedding: List[float], category: str, subcategory: str, occasions: List[str] = [], batch_sources: List[str] = [], gender: str = 'female', n_results: int = 1) -> List[Dict[str, Any]]:
def get_matched_item(self, embedding: List[float], category: str, search_subcategories: List[str], occasions: List[str] = [], batch_sources: List[str] = [], gender: str = 'female', n_results: int = 1) -> List[Dict[str, Any]]:
if 'ties' in search_subcategories or 'cufflinks' in search_subcategories:
gender = "male"
if category not in CATEGORY_LIST:
raise ValueError(f"Recommended {category} is not valid.")
@@ -57,7 +59,7 @@ class VectorDatabase():
{"gender": gender},
{"gender": "unisex"},
]},
{"subcategory": {"$nin": IGNORE_SUBCATEGORY}}
{"subcategory": {"$in": search_subcategories}}
]
# 加了一条限制但是部署到生产的时候把他设定为False
@@ -86,16 +88,17 @@ class VectorDatabase():
return []
metadatas = results['metadatas'][0] # List[Dict[str, Any]]
candidate_pool = []
item_rank_list = []
all_scores = []
for idx, metadata in enumerate(metadatas):
dist_img = results['distances'][0][idx]
score_vec = 1 - dist_img # cosine similarity range: [-1, 1]
score_subcategory = 0.0
if subcategory == metadata['subcategory']:
score_subcategory = 1
score_subcategory = 1.0
# if subcategory == metadata['subcategory']:
# score_subcategory = 1
score_occ = 0.0
occasions = occasions[0:1] # 目前只考虑第一个场合
if occasions:
count = 0
for occ in occasions:
@@ -114,28 +117,26 @@ class VectorDatabase():
final_score = 0.5 * score_vec + 0.1 * score_occ + 0.5 * score_subcategory
all_scores.append(final_score)
if final_score > 0:
candidate_pool.append({
"score": final_score,
"metadata": metadata
})
item_rank_list.append({
"score": final_score,
"metadata": metadata
})
item_rank_list.sort(key=lambda x: x['score'], reverse=True)
candidate_pool = [item for item in item_rank_list if item['score'] > 0.0]
if not candidate_pool:
print(f"All scores are negative: Ignore")
return []
print(f"Warning: No positive scores found for {search_subcategories}. Falling back to top matches.")
return [item['metadata'] for item in item_rank_list[:n_results]]
# 采取topk截断
candidate_pool.sort(key=lambda x: x['score'], reverse=True)
current_k = min(10, len(candidate_pool))
top_candidates = candidate_pool[:current_k]
top_scores = np.array([x['score'] for x in top_candidates])
#降低温度,使得选择稳定
temperature = 0.2
scaled_scores = top_scores / temperature
# Softmax: 将分数转换为概率
exp_scores = np.exp(scaled_scores - np.max(scaled_scores))
exp_scores = np.exp((top_scores - np.max(top_scores)) / temperature)
probabilities = exp_scores / np.sum(exp_scores)
# 采样 (或直接取 Top 1)

View File

@@ -91,7 +91,8 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
raise ValueError("No valid images were loaded.")
if num_images > max_len:
raise ValueError(f"Valid item number {num_images} exceed max limit {max_len}")
valid_images = valid_images[:max_len]
print(f"Valid item number {num_images} exceed max limit {max_len}, only first {max_len} items will be processed.")
# Get the correct list of target areas based on the number of valid images
target_areas = quadrants.get(num_images, [])
@@ -103,6 +104,7 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
for i, (img, item) in enumerate(zip(valid_images, outfit_items)):
item_id = item['item_id']
category = item['category']
subcategory = item['subcategory']
if i >= len(target_areas):
# This should not happen if num_images <= 4
break
@@ -155,7 +157,7 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
# --- 文本居中与定位 ---
full_text = f"ID: {item_id}, Category: {category}"
full_text = f"ID: {item_id}, Subcategory: {subcategory}"
if add_text:
try:
# 推荐使用:计算文本的实际尺寸 (width, height)

View File

@@ -15,7 +15,6 @@ FASHION_TAXONOMY = {
'blouses', # 女式衬衫
'polo shirts', # Polo衫
'tank tops', # 背心/坎肩
'camisoles', # 吊带背心
# --- Knits/Sweaters ---
'sweaters', # 毛衣 (泛指)
'cardigans', # 开衫
@@ -91,7 +90,7 @@ FASHION_TAXONOMY = {
CATEGORY_LIST = list(FASHION_TAXONOMY.keys())
ALL_SUBCATEGORY_LIST = sum(FASHION_TAXONOMY.values(), [])
IGNORE_SUBCATEGORY = ['socks', 'watches', 'hats']
IGNORE_SUBCATEGORY = ['socks', 'watches', 'hats', 'eyewear']
BRAND_WHITELIST = [
"ALAÏA",
@@ -130,4 +129,140 @@ BRAND_WHITELIST = [
"THOM BROWNE",
"THE FRANKIE SHOP",
"TOTEME",
]
]
OCCASION_CATEGORY_MAP = {
"Casual": {
"clothing": ["trousers", "pants", "jeans", "t-shirts", "tank tops", "polo shirts", "hoodies", "leggings", "shorts", "skirts"],
"shoes": ["sneakers", "flats", "boots"],
"bags": ["shoulder bags", "crossbody"],
"accessories": ["earrings", "necklaces", "bracelets", "rings"]
},
"Formal": {
"clothing": ["suits", "trousers", "shirts", "blazers", "skirts", "dresses", "coats"],
"shoes": ["formal shoes"],
"bags": [],
"accessories": ["ties", "earrings", "necklaces", "bracelets", "rings"]
},
"Activewear": {
"clothing": ["leggings", "tank tops", "pants", "joggers", "hoodies", "jackets"],
"shoes": ["sneakers"],
"bags": ["travel bags"],
"accessories": ["earrings", "bracelets"]
},
"Resort": {
"clothing": ["dresses", "shorts", "tank tops", "swimwear"],
"shoes": ["sandals"],
"bags": ["tote bags"],
"accessories": ["earrings", "necklaces", "bracelets", "rings", "scarves"]
},
"Evening": {
"clothing": ["dresses", "blazers", "coats"],
"shoes": ["heels"],
"bags": ["clutch bags"],
"accessories": ["earrings", "necklaces", "bracelets", "rings"]
},
"Outdoor": {
"clothing": ["jackets", "jeans", "sweaters"],
"shoes": ["boots"],
"bags": ["backpacks", "travel bags"],
"accessories": []
},
"Business / workwear": {
"clothing": ["trousers", "blouses", "blazers", "skirts"],
"shoes": ["formal shoes", "heels", "flats"],
"bags": ["tote bags", "shoulder bags"],
"accessories": []
},
"Cocktail / Semi-Formal": {
"clothing": ["dresses", "jumpsuits", "skirts", "blouses", "blazers", "coats"],
"shoes": ["heels"],
"bags": ["clutch bags", "shoulder bags"],
"accessories": ["earrings", "necklaces", "bracelets", "rings", "scarves"]
},
"Black Tie / White Tie": {
"clothing": ["dresses", "suits"],
"shoes": ["formal shoes"],
"bags": ["clutch bags"],
"accessories": ["earrings", "necklaces", "bracelets", "rings"]
},
"Bridal / Wedding": {
"clothing": ["dresses", "suits"],
"shoes": ["heels", "formal shoes"],
"bags": ["clutch bags"],
"accessories": ["earrings", "necklaces", "bracelets", "rings"]
},
"Festival / Concert": {
"clothing": ["jackets", "shorts", "tank tops", "skirts"],
"shoes": ["boots"],
"bags": ["crossbody", "shoulder bags"],
"accessories": ["earrings", "necklaces", "bracelets", "rings"]
},
"Party / Clubbing": {
"clothing": ["jackets", "dresses", "skirts", "bodysuits"],
"shoes": ["heels", "boots"],
"bags": ["clutch bags"],
"accessories": []
},
"Travel / Transit": {
"clothing": ["joggers", "sweatshirts", "t-shirts", "hoodies", "sweaters", "jackets"],
"shoes": ["sneakers"],
"bags": ["backpacks", "travel bags", "tote bags"],
"accessories": []
},
"Athleisure": {
"clothing": ["leggings", "hoodies", "tank tops", "joggers", "jackets"],
"shoes": ["sneakers"],
"bags": ["travel bags", "tote bags"],
"accessories": ["earrings", "bracelets"]
},
"Beach / Swim": {
"clothing": ["swimwear", "shorts", "dresses"],
"shoes": ["sandals"],
"bags": ["tote bags"],
"accessories": ["earrings", "necklaces", "bracelets", "rings"]
},
"Ski / Snow / Mountain": {
"clothing": ["jackets", "coats", "sweaters", "hoodies", "pants"],
"shoes": ["boots"],
"bags": [],
"accessories": ["gloves"]
},
"Garden Party / Daytime Event": {
"clothing": ["dresses", "skirts", "blouses", "cardigans"],
"shoes": ["sandals"],
"bags": ["shoulder bags", "crossbody"],
"accessories": ["earrings", "necklaces", "bracelets", "rings"]
}
}
OCCASION_MATERIAL_MAP = {
"Casual": "Cotton, denim, jersey, fleece, relaxed fits, distressed textures.",
"Formal": "Wool, silk, crisp cotton, structured tailoring, cufflinks, leather soles.",
"Activewear": "Spandex, moisture-wicking synthetics, mesh, breathable fabrics, compression.",
"Resort": "Linen, crochet, chiffon, straw/raffia, tropical prints, breezy silhouettes.",
"Evening": "Silk, satin, velvet, sequins, lace, sheer panels, metallic finishes.",
"Outdoor": "Gore-Tex, fleece, down, wool, waterproof leather, corduroy, flannel.",
"Business / workwear": "Gabardine, crepe, silk blends, modest cuts, structured leather bags.",
"Cocktail / Semi-Formal": "Satin, crepe, lace details, ruffles, asymmetric hems, polished hardware.",
"Black Tie / White Tie": "Tulle, silk taffeta, fine wool, crystals, pearls, opera gloves.",
"Bridal / Wedding": "Chiffon, lace, silk, floral prints (for guests), pastel tones.",
"Festival / Concert": "Denim, fringe, leather, crochet, sequins, bold prints, band tees.",
"Party / Clubbing": "Sequins, leather/pleather, mesh, cut-outs, bodycon fits, latex.",
"Travel / Transit": "Cotton jersey, cashmere blends, stretch denim, soft knits, layers.",
"Athleisure": "Scuba fabric, tech fleece, rib-knit, clean lines, logo details.",
"Beach / Swim": "Lycra, terry cloth, linen, straw, quick-dry fabrics, sheer voile.",
"Ski / Snow / Mountain": "Down feathers, wool, thermal tech, faux fur, waterproof shells.",
"Garden Party / Daytime Event": "Floral prints, linen, eyelet lace, cotton poplin, gingham, straw accessories."
}
SUBCATEGORY_MERGE_MAP = {
"clutch bags": ["clutch bags", "shoulder bags"],
"shoulder bags": ["shoulder bags", "clutch bags"],
"crossbody": ["crossbody", "shoulder bags"],
"luggage": ["luggage", "backpacks", "travel bags"],
"jumpsuits": ["jumpsuits", "dresses"],
"bodysuits": ["bodysuits", "dresses"],
"suits": ["suits", "dresses"],
"pullovers": ["pullovers", "sweaters"],
}