UPDATE: 1. update general rule. 2. ADD retry feature in quick mode if outfit is considered as incomplete
This commit is contained in:
@@ -357,17 +357,17 @@ if __name__ == "__main__":
|
|||||||
request_data = json.load(f)
|
request_data = json.load(f)
|
||||||
|
|
||||||
tasks_with_metadata = []
|
tasks_with_metadata = []
|
||||||
for test_content in request_data[0:3]:
|
for test_content in request_data[8:10]:
|
||||||
occasions = test_content['occasions']
|
occasions = test_content['occasions']
|
||||||
request_summary = test_content['request_summary']
|
request_summary = test_content['request_summary']
|
||||||
for stylist_name in ["vera"]:
|
for stylist_name in ["edi"]:
|
||||||
stylist_agent_kwages['outfit_id'] = test_content['test_case_id'] + '_' + test_content['occasions'][0].replace('/', '_') + f"_{stylist_name}"
|
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['stylist_name'] = stylist_name
|
||||||
stylist_agent_kwages['gender'] = "female"
|
stylist_agent_kwages['gender'] = "female"
|
||||||
stylist_agent_kwages['callback_url'] = ""
|
stylist_agent_kwages['callback_url'] = ""
|
||||||
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
||||||
coro = agent.run_iterative_styling(
|
# coro = agent.run_iterative_styling(
|
||||||
# coro = agent.run_quick_batch_styling(
|
coro = agent.run_quick_batch_styling(
|
||||||
request_summary=request_summary,
|
request_summary=request_summary,
|
||||||
occasions=occasions,
|
occasions=occasions,
|
||||||
start_outfit=[],
|
start_outfit=[],
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ GENERAL_RULES_DICT = {
|
|||||||
* Match shoes with skin tone or the bottom piece
|
* Match shoes with skin tone or the bottom piece
|
||||||
* Chunky winter layers pair with a boot, platform shoe (boot, platform).
|
* 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
|
* 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.""",
|
* Wide-leg trousers pair with a heel ; loafer ; heel boots ; or a sneaker.
|
||||||
|
* No socks with sandals.""",
|
||||||
'accessories': """### **Accessory matching:**
|
'accessories': """### **Accessory matching:**
|
||||||
* Acceesory wear in a set of same metal colour (gold earrings with gold belt buckle)
|
* 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 chokers or short necklaces with V-necks; wear long pendants with crew necks or turtlenecks.
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class AsyncStylistAgent:
|
class AsyncStylistAgent:
|
||||||
|
ONE_PIECE_SUBS = {"dresses", "suits", "jumpsuits", "bodysuits", "swimwear", "underwear", "lingerie", "pajamas"}
|
||||||
|
TOP_SUBS = {"t-shirts", "shirts", "blouses", "polo shirts", "tank tops", "sweaters", "cardigans", "pullovers", "hoodies", "sweatshirts", "vests", "coats", "jackets", "blazers", "bras"}
|
||||||
|
BOTTOM_SUBS = {"trousers", "pants", "jeans", "joggers", "leggings", "shorts", "skirts", "skorts"}
|
||||||
|
|
||||||
def __init__(self, local_db: str, gemini_model_name: str, outfit_id: str, stylist_name: str, gender: str, callback_url: str):
|
def __init__(self, local_db: str, gemini_model_name: str, outfit_id: str, stylist_name: str, gender: str, callback_url: str):
|
||||||
# self.outfit_items: List[Dict[str, str]] = []
|
# self.outfit_items: List[Dict[str, str]] = []
|
||||||
self.outfit_id = outfit_id
|
self.outfit_id = outfit_id
|
||||||
@@ -56,6 +60,45 @@ class AsyncStylistAgent:
|
|||||||
self.minio_bucket = "lanecarford"
|
self.minio_bucket = "lanecarford"
|
||||||
self.callback_url = f'{callback_url}/api/style/callback'
|
self.callback_url = f'{callback_url}/api/style/callback'
|
||||||
|
|
||||||
|
def _is_outfit_complete(self, occasion: str, first_try: bool = True) -> bool:
|
||||||
|
"""
|
||||||
|
判断当前的 self.outfit_items 是否满足该场合的完整性要求
|
||||||
|
"""
|
||||||
|
if occasion not in OCCASION_CATEGORY_MAP:
|
||||||
|
# 兜底逻辑:如果场合没定义,至少要有衣服和鞋子
|
||||||
|
raise ValueError(f"Provided {occasion} is not defined in OCCASION_CATEGORY_MAP.")
|
||||||
|
|
||||||
|
requirements = OCCASION_CATEGORY_MAP[occasion]
|
||||||
|
current_subs = [item['subcategory'] for item in self.outfit_items]
|
||||||
|
current_cats = [item['category'] for item in self.outfit_items]
|
||||||
|
|
||||||
|
# --- 1. 鞋子检查 ---
|
||||||
|
# 如果该场合允许穿鞋(List不为空),则必须至少有一双鞋
|
||||||
|
if requirements.get("shoes") and "shoes" not in current_cats:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# --- 2. 衣服检查 (最核心) ---
|
||||||
|
# 检查是否有全身单品 (One-piece)
|
||||||
|
has_one_piece = any(sub in self.ONE_PIECE_SUBS for sub in current_subs)
|
||||||
|
|
||||||
|
if not has_one_piece:
|
||||||
|
# 如果没有连体衣,必须同时有上装和下装
|
||||||
|
has_top = any(sub in self.TOP_SUBS for sub in current_subs)
|
||||||
|
has_bottom = any(sub in self.BOTTOM_SUBS for sub in current_subs)
|
||||||
|
if not (has_top and has_bottom):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# --- 3. 包包检查 ---
|
||||||
|
# 如果该场合(如 Evening)明确需要包,且目前还没找到,可以返回 False 继续重试
|
||||||
|
if requirements.get("bags") and "bags" not in current_cats and first_try:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# --- 4. 配饰检查 ---
|
||||||
|
if requirements.get("accessories") and "accessories" not in current_cats and first_try:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _load_style_guide(self, stylist_name: str):
|
def _load_style_guide(self, stylist_name: str):
|
||||||
"""加载 markdown 风格指南内容。"""
|
"""加载 markdown 风格指南内容。"""
|
||||||
guide_path = os.path.join(settings.STYLIST_GUIDE_DIR, f"{stylist_name}_en.md")
|
guide_path = os.path.join(settings.STYLIST_GUIDE_DIR, f"{stylist_name}_en.md")
|
||||||
@@ -209,6 +252,8 @@ class AsyncStylistAgent:
|
|||||||
# 1. 材质偏好 (Occasion Material Map)
|
# 1. 材质偏好 (Occasion Material Map)
|
||||||
material_hint = OCCASION_MATERIAL_MAP.get(occasion, "")
|
material_hint = OCCASION_MATERIAL_MAP.get(occasion, "")
|
||||||
# Insert the style_guide content into the template
|
# Insert the style_guide content into the template
|
||||||
|
if occasion == "Bridal / Wedding":
|
||||||
|
request_summary += "IMPORTANT: Strictly only recommend colorful or white items (clothing, shoes, and bags)."
|
||||||
sys_template = template.format(
|
sys_template = template.format(
|
||||||
gender=self.gender,
|
gender=self.gender,
|
||||||
current_category=current_category.upper(),
|
current_category=current_category.upper(),
|
||||||
@@ -422,26 +467,26 @@ class AsyncStylistAgent:
|
|||||||
# 4a. 检查类别是否有效 (重要步骤)
|
# 4a. 检查类别是否有效 (重要步骤)
|
||||||
if subcategory not in allowed_subcategories:
|
if subcategory not in allowed_subcategories:
|
||||||
failed_found_item_count += 1
|
failed_found_item_count += 1
|
||||||
|
logger.warning(f"Failed to found Item {idx + 1}: ({subcategory}) {rec_item}, invalid subcategory {subcategory} recommended by Agent.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 4b. 在本地 DB 中查询单品
|
# 4b. 如果这个子类别已经被推荐过了,就舍弃
|
||||||
|
if subcategory in [x['subcategory'] for x in self.outfit_items]:
|
||||||
|
logger.warning(f"Subcategory {subcategory} already recommended, skipping.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 4c. 在本地 DB 中查询单品
|
||||||
new_item = self._get_next_item(description, category, subcategory, occasions, batch_sources, self.gender)
|
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]:
|
if not new_item:
|
||||||
failed_found_item_count += 1
|
failed_found_item_count += 1
|
||||||
|
logger.warning(f"Failed to found Item {idx + 1}: ({subcategory}) {rec_item}, no matching item is found.")
|
||||||
|
continue
|
||||||
|
elif new_item['item_id'] in [x['item_id'] for x in self.outfit_items]:
|
||||||
|
logger.warning(f"Found Item {idx + 1}: ({subcategory}) {rec_item}, found item is duplicated.")
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.outfit_items.append(new_item)
|
self.outfit_items.append(new_item)
|
||||||
print(f"Item {idx + 1}: ({subcategory}) {rec_item}, found item: {new_item}")
|
logger.info(f"Item {idx + 1}: ({subcategory}) {rec_item}, found item: {new_item}")
|
||||||
|
|
||||||
# 如果没有找到的item过于多,需要重试
|
|
||||||
if failed_found_item_count / len(recommended_items) > 0.5:
|
|
||||||
self.post_operation(
|
|
||||||
status="failed",
|
|
||||||
message=f"There are {failed_found_item_count} items (total {len(recommended_items)}) are not found in the database",
|
|
||||||
callback_url=url,
|
|
||||||
img_path=merged_image_path
|
|
||||||
)
|
|
||||||
raise Exception(f"There are {failed_found_item_count} items (total {len(recommended_items)}) are not found in the database")
|
|
||||||
return reason
|
return reason
|
||||||
|
|
||||||
def _get_allowed_subcategories(self, occasion: str, category: str) -> List[str]:
|
def _get_allowed_subcategories(self, occasion: str, category: str) -> List[str]:
|
||||||
@@ -564,11 +609,11 @@ class AsyncStylistAgent:
|
|||||||
stylist_guide, accessories_guide = self._load_style_guide(self.stylist_name)
|
stylist_guide, accessories_guide = self._load_style_guide(self.stylist_name)
|
||||||
url = f'{callback_url}/api/style/callback'
|
url = f'{callback_url}/api/style/callback'
|
||||||
|
|
||||||
print(f"--- Starting Agent (Outfit ID: {self.outfit_id}) ---")
|
|
||||||
|
|
||||||
MAX_LEN = 9
|
MAX_LEN = 9
|
||||||
if not occasions:
|
if not occasions:
|
||||||
occasions = ["Casual"]
|
occasions = ["Casual"]
|
||||||
|
logger.info(f"""--- Starting Agent (Outfit ID: {self.outfit_id}) ---
|
||||||
|
Occasion: {occasions[0]}. Stylist: {self.stylist_name}. User ID: {user_id}. Request Summary: {request_summary}. Batch sources: {batch_sources}""")
|
||||||
|
|
||||||
general_rules = "\n".join(GENERAL_RULES_DICT.values())
|
general_rules = "\n".join(GENERAL_RULES_DICT.values())
|
||||||
allowed_subcategories = self._get_allowed_subcategories(occasions[0], "all")
|
allowed_subcategories = self._get_allowed_subcategories(occasions[0], "all")
|
||||||
@@ -582,15 +627,31 @@ class AsyncStylistAgent:
|
|||||||
allowed_subcategories,
|
allowed_subcategories,
|
||||||
MAX_LEN
|
MAX_LEN
|
||||||
)
|
)
|
||||||
reason = await self._execute_batch_recommendation(
|
|
||||||
'all', # can be 'accessories' or 'all'
|
max_retries = 2
|
||||||
system_prompt,
|
for attempt in range(max_retries):
|
||||||
build_batch_schema(specified_category="all", subcategory_list=allowed_subcategories),
|
logger.info(f"Batch recommendation attempt {attempt + 1}")
|
||||||
occasions,
|
reason = await self._execute_batch_recommendation(
|
||||||
batch_sources,
|
'all', # can be 'accessories' or 'all'
|
||||||
user_id,
|
system_prompt,
|
||||||
url
|
build_batch_schema(specified_category="all", subcategory_list=allowed_subcategories),
|
||||||
)
|
occasions,
|
||||||
|
batch_sources,
|
||||||
|
user_id,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
if self._is_outfit_complete(occasions[0], first_try=(attempt==0)):
|
||||||
|
logger.info(f"Successfully assembled a complete outfit for {occasions[0]} after {attempt + 1} attempts. Reason: {reason}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.post_operation(
|
||||||
|
status="failed",
|
||||||
|
message=f"Failed to assemble a complete outfit after {max_retries} attempts for {occasions[0]}.",
|
||||||
|
callback_url=url,
|
||||||
|
img_path=""
|
||||||
|
)
|
||||||
|
logger.error(f"Failed to assemble a complete outfit after {max_retries} attempts for {occasions[0]}. Current items: {self.outfit_items}. Subcategories required by this occasion is: {allowed_subcategories}")
|
||||||
|
raise Exception(f"Failed to assemble a complete outfit after {max_retries} attempts for {occasions[0]}. Current items: {self.outfit_items}. Subcategories required by this occasion is: {allowed_subcategories}")
|
||||||
|
|
||||||
# 推荐即将完成 回调通知前端
|
# 推荐即将完成 回调通知前端
|
||||||
self.post_operation(
|
self.post_operation(
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ OCCASION_CATEGORY_MAP = {
|
|||||||
"clothing": ["leggings", "tank tops", "pants", "joggers", "hoodies", "jackets"],
|
"clothing": ["leggings", "tank tops", "pants", "joggers", "hoodies", "jackets"],
|
||||||
"shoes": ["sneakers"],
|
"shoes": ["sneakers"],
|
||||||
"bags": ["travel bags"],
|
"bags": ["travel bags"],
|
||||||
"accessories": ["earrings", "bracelets"]
|
"accessories": []
|
||||||
},
|
},
|
||||||
"Resort": {
|
"Resort": {
|
||||||
"clothing": ["dresses", "shorts", "tank tops", "swimwear"],
|
"clothing": ["dresses", "shorts", "tank tops", "swimwear"],
|
||||||
|
|||||||
Reference in New Issue
Block a user