Compare commits

...

18 Commits

Author SHA1 Message Date
zcr
a36235d58e 1 2026-04-14 10:11:29 +08:00
zcr
433aa3e751 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	docker-compose.yml
2026-04-14 10:10:56 +08:00
zcr
7bf080b3e7 1 2026-04-14 10:10:00 +08:00
zcr
03c4759895 fix:更新occasion映射服装类别 2026-03-05 15:41:39 +08:00
03ff6605a3 更新 docker-compose.yml 2026-02-09 14:54:17 +08:00
4b3b0f6aa8 更新 .gitea/workflows/prod_build_manual.yaml 2026-02-09 14:50:25 +08:00
zcr
c798d37fdd fix:防止服务宕机,增加自动重启 2026-02-03 15:34:10 +08:00
zcr
43fd576da6 fix:启动端口改由环境变量控制 2026-01-27 10:40:50 +08:00
zcr
1bbb9c945e fix:启动端口改由环境变量控制 2026-01-27 10:33:35 +08:00
pangkaicheng
3ca6b16eaf UPDATE: update prompt. Make generate button bold 2026-01-19 13:18:21 +08:00
pangkaicheng
f9d83f6a99 UPDATE: chat basic prompt. Limit Chatbot to conclude within three turns. 2026-01-19 12:16:47 +08:00
pangkaicheng
496e7ad590 FIX: delete iterative mode 2026-01-14 12:22:13 +08:00
pangkaicheng
a7b101253b UPDATE; fullfill LC request for lacking layering of Evening/workwear occasions 2026-01-14 12:22:13 +08:00
pangkaicheng
077ceea219 merge clothing stage and accessories stage of stylist guide 2026-01-14 12:22:13 +08:00
zcr
4fa815158f fix:推荐逻辑更新 弃用慢推荐 2026-01-12 11:24:55 +08:00
pangkaicheng
46793ba271 UPDATE: add color constrain in vector database for wedding occasion avoiding black items. 2026-01-07 17:26:44 +08:00
pangkaicheng
773db4fcc3 UPDATE: 1. update general rule. 2. ADD retry feature in quick mode if outfit is considered as incomplete 2026-01-07 16:21:17 +08:00
pangkaicheng
13b99f4dd3 FIX: Failed to find single item will not terminate whole process 2026-01-07 12:19:50 +08:00
20 changed files with 323 additions and 479 deletions

View File

@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
REMOTE_DEPLOY_PATH: /workspace/Trinity/Litserve_LC_Prod/lc_stylist_agent REMOTE_DEPLOY_PATH: /workspace/LC_Workspace/LitServe_Server_Workspace/lc_stylist_agent
steps: steps:
- name: 1.检出代码 - name: 1.检出代码

View File

@@ -17,7 +17,11 @@ class Settings(BaseSettings):
extra='ignore' # 忽略环境变量中多余的键 extra='ignore' # 忽略环境变量中多余的键
) )
# 启动端口 # 启动端口
SERVE_PROD: int = Field(default=8000, description='') SERVE_PORT: int = Field(default=8000, description='')
# 换脸模型服务
RE_FACE_MODEL_URL: str = Field(default="10.1.1.240:10071", description='')
# 调试配饰 # 调试配饰
LOCAL: int = Field(default=0, description="是否在本地运行1表示本地运行0表示生产环境运行") LOCAL: int = Field(default=0, description="是否在本地运行1表示本地运行0表示生产环境运行")

View File

@@ -27,4 +27,4 @@ if __name__ == "__main__":
agent_api = LCAgent(enable_async=True, api_path='/api/v1/agent') agent_api = LCAgent(enable_async=True, api_path='/api/v1/agent')
reface_api = ReFace(api_path='/api/v1/reface') reface_api = ReFace(api_path='/api/v1/reface')
server = ls.LitServer([chat_boot_api, agent_api, reface_api]) server = ls.LitServer([chat_boot_api, agent_api, reface_api])
server.run(port=settings.SERVE_PROD) server.run(port=8000)

View File

@@ -196,14 +196,19 @@ class LCAgent(ls.LitAPI):
stylist_agent_kwages = self.stylist_agent_kwages.copy() stylist_agent_kwages = self.stylist_agent_kwages.copy()
tasks_mapping = {} tasks_mapping = {}
if num_outfits == 1:
tasks_mapping[outfit_ids[0]] = "fast" # 历史1快3慢推荐逻辑
else: # if num_outfits == 1:
# tasks_mapping[outfit_ids[0]] = "fast"
# else:
# for i in range(num_outfits):
# if i == 0:
# tasks_mapping[outfit_ids[i]] = "fast"
# else:
# tasks_mapping[outfit_ids[i]] = "slow"
for i in range(num_outfits): for i in range(num_outfits):
if i == 0:
tasks_mapping[outfit_ids[i]] = "fast" tasks_mapping[outfit_ids[i]] = "fast"
else:
tasks_mapping[outfit_ids[i]] = "slow"
for k, v in tasks_mapping.items(): for k, v in tasks_mapping.items():
logger.info(f"fast request outfit_id is : {k}") logger.info(f"fast request outfit_id is : {k}")
@@ -357,17 +362,16 @@ 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[6:7]:
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 ["crystal", "mini", "vera", "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_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=[],

View File

@@ -49,10 +49,18 @@ class LCChatBot(ls.LitAPI):
user_msg = Message(role=Role.USER, content=user_message) user_msg = Message(role=Role.USER, content=user_message)
chat_history = self.redis.get_history(session_id) chat_history = self.redis.get_history(session_id)
chat_history.append(user_msg) chat_history.append(user_msg)
turn_count = sum(1 for msg in chat_history if msg.role == Role.USER)
if request.gender == 'male': if request.gender == 'male':
prompt = BASIC_PROMPT.format(gender='men') system_instruction = BASIC_PROMPT.format(gender='men')
else: else:
prompt = BASIC_PROMPT.format(gender='women') system_instruction = BASIC_PROMPT.format(gender='women')
if turn_count >= 3:
system_instruction += "\n\nCRITICAL: This is the final turn. Do not ask questions. Provide a brief summary and use the mandatory closing phrase."
else:
system_instruction += f"\n\nCURRENT STATE: This is turn {turn_count} of 3. Keep gathering info efficiently or provide a brief summary and use the mandatory closing phrase if you have sufficient information."
contents = [] contents = []
@@ -68,7 +76,7 @@ class LCChatBot(ls.LitAPI):
model='gemini-2.5-flash', model='gemini-2.5-flash',
contents=contents, contents=contents,
config=types.GenerateContentConfig( config=types.GenerateContentConfig(
system_instruction=prompt, system_instruction=system_instruction,
# temperature=0.3, # temperature=0.3,
) )
) )
@@ -99,7 +107,7 @@ class LCChatBot(ls.LitAPI):
if __name__ == "__main__": if __name__ == "__main__":
sys.stdout = open('permanent.log', 'w', encoding='utf-8') # sys.stdout = open('permanent.log', 'w', encoding='utf-8')
import asyncio import asyncio
@@ -137,23 +145,67 @@ if __name__ == "__main__":
print(chunk, end="", flush=True) print(chunk, end="", flush=True)
text_list = [ # text_list = [
'我要去参加好朋友的婚礼,你能帮我挑一套衣服吗?', # '我要去参加好朋友的婚礼,你能帮我挑一套衣服吗?',
'I need something to wear for a big presentation at work tomorrow. I want to look powerful but still approachable.', # 'I need something to wear for a big presentation at work tomorrow. I want to look powerful but still approachable.',
'Who do you think is the best world leader right now?', # 'Who do you think is the best world leader right now?',
'Im going on a trip to Paris next week and need some outfits.', # 'Im going on a trip to Paris next week and need some outfits.',
'Help me find a cool outfit for a rock concert. I hate wearing dresses.', # 'Help me find a cool outfit for a rock concert. I hate wearing dresses.',
'I want to look very cool, 或者是那种很有个性的风格 for a gallery opening.', # 'I want to look very cool, 或者是那种很有个性的风格 for a gallery opening.',
"I'm going to a gala. Please list 5 different dress styles for me and use bold text for the names.", # "I'm going to a gala. Please list 5 different dress styles for me and use bold text for the names.",
"I'm feeling really sad today and just want an outfit that matches my mood." # "I'm feeling really sad today and just want an outfit that matches my mood."
] # ]
for text in text_list: # for text in text_list:
asyncio.run(run_simple_test(text)) # asyncio.run(run_simple_test(text))
# print("\n" + "=" * 50)
# # 启动异步事件循环
# try: async def run_interactive_test():
# asyncio.run(run_simple_test()) """
# except Exception as e: 手动输入测试:模拟真实用户与 AI 的多轮对话
# print(f"\n发生致命错误: {e}") """
# # 1. 初始化
sys.stdout.close() chatbot_api = LCChatBot()
chatbot_api.setup(device="cpu")
session_id = "manual_test_session"
user_id = "test_user"
print("--- 👗 欢迎进入 AI 造型师测试 (输入 'quit' 退出) ---")
# 清空旧的测试记录,开始新会话
chatbot_api.redis.clear_history(session_id)
print("✅ 已清空历史记录,开始新会话。")
while True:
# 2. 获取手动输入
user_input = input("\nUser (你): ")
if user_input.lower() in ['quit', 'exit', '退出']:
break
# 3. 构造请求
request_data = PredictRequest(
user_id=user_id,
session_id=session_id,
user_message=user_input,
gender="female" # 或者根据需要修改
)
print("Agent (AI): ", end="")
# 4. 调用并流式打印
# 注意:这里的 predict 内部现在会计算 turn_count
response_generator = chatbot_api.predict(request_data)
full_response = ""
async for chunk in response_generator:
print(chunk, end="", flush=True)
full_response += chunk
# 将 AI 的回复存入 Redis (如果你的 predict 函数里没存的话)
# chatbot_api.redis.add_message(session_id, Role.ASSISTANT, full_response)
print() # 换行
asyncio.run(run_interactive_test())

View File

@@ -7,6 +7,16 @@ CONVERSATION GOALS:
3. **Vibe/Details:** Gather mood or constraints (e.g., comfort, specific colors). 3. **Vibe/Details:** Gather mood or constraints (e.g., comfort, specific colors).
4. **Item Preference:** Identify preferences for silhouettes or specific items. 4. **Item Preference:** Identify preferences for silhouettes or specific items.
CONVERSATION FLOW:
- You have a maximum of 3 turns (one turn = one user message and one assistant response) to gather information.
- On the 3rd turn, or *once you have sufficient info*, do NOT ask further questions.
- MANDATORY CLOSING: You must end your final response using the FINAL TURN CLOSING RULE below.
FINAL TURN CLOSING RULE:
- Once requirements are gathered, do NOT ask any more questions.
- End your final response with a creative and friendly variation of the closing phrase.
- REQUIRED COMPONENTS: Every variation MUST mention the "**Generate button**" (this phrase must be bounded using double asterisks).
PERSONALITY AND COMMUNICATION RULES: PERSONALITY AND COMMUNICATION RULES:
- Always reply in English only. If the user speaks another language, state in English that you cannot speak that language and steer back to styling. - Always reply in English only. If the user speaks another language, state in English that you cannot speak that language and steer back to styling.
- Keep responses extremely SHORT (maximum 2 sentences). - Keep responses extremely SHORT (maximum 2 sentences).
@@ -24,7 +34,7 @@ OUTPUT FORMAT INSTRUCTION:
- **ONLY** output the plain text response spoken by the AI Assistant. - **ONLY** output the plain text response spoken by the AI Assistant.
EXAMPLE DIALOGUES: EXAMPLE DIALOGUES:
User: 我想找件衣服参加婚礼。 Response: I am so sorry, but I can only speak English! I would love to help you find the perfect wedding guest look if you can tell me the dress code or the venue vibe. User: 我想找件衣服参加婚礼。 Response: I would love to help you find the perfect wedding guest look if you can tell me the dress code or the venue vibe.
User: I need a chic outfit for dinner. Response: That sounds like a fabulous evening! Is this a romantic date or a casual night out with friends, and do you prefer a sleek dress or tailored separates? User: I need a chic outfit for dinner. Response: That sounds like a fabulous evening! Is this a romantic date or a casual night out with friends, and do you prefer a sleek dress or tailored separates?
@@ -52,7 +62,8 @@ GENERAL_RULES = """## General Rule
### **Layering:** ### **Layering:**
* Style at least 2-3 layers * Style at least 2-3 layers
* Show white t-shirt, shirt hem under pull over cardigan and hoodies; Don't show hem in layerinmg except sweaters, cardigan and hoodies (if capable)
* Style hoodie under blazer/ bomber jacker, leathe jacket/ trechcoat to create a street style casual look (if capable)
""" """
GENERAL_RULES_DICT = { GENERAL_RULES_DICT = {
@@ -73,127 +84,16 @@ 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.
* Wear a belt in tuck-in outfits * Wear a belt in tuck-in outfits
* Style silk scarf for the neck; tie it on your bag handle, around your wrist, or use it as a belt for a pop of color.
* No watches, No hats, No sunglasses""", * No watches, No hats, No sunglasses""",
} }
core_outfit_template = f"""
# 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.
## 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.
## 2. RULE HIERARCHY
1. **USER REQUEST**: (Highest Priority)
2. **STYLIST CORE RULES**: (Secondary Priority)
3. **GENERAL COORDINATION RULES**: (Standard Professional Logic)
---
## 3. CONTEXT & GUIDANCE
### [User Request Summary]
{{request_summary}}
### [Target Occasion]
{{occasion}}
### [Stylist Guide]
{{stylist_guide}}
### [Material Hint]
{{material_hint}}
### [General Rules]
{{general_rule}}
---
## 4. CONSTRAINTS & WORKFLOW (Iterative Mode)
1. **Selection Pool**: You MUST only choose items from the following **[Allowed Subcategories]**.
**ALLOWED**: {{allowed_subcategories}}
2. **Uniqueness Mandate**: Every item must follow the **absolute no-repeat rule for subcategories**. Each subcategory can appear **exactly once** in the entire outfit.
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. **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. 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"""
# 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.
## 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.
## 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)
---
## 3. CONTEXT & GUIDANCE
### [User Request Summary]
{{request_summary}}
### [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}}
---
## 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.
---
## 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""" all_items_template = f"""
# ROLE: Professional Fashion Stylist Agent # ROLE: Professional Fashion Stylist Agent

View File

@@ -17,8 +17,6 @@ from app.config import settings
from app.server.ChatbotAgent.core.prompt import ( from app.server.ChatbotAgent.core.prompt import (
GENERAL_RULES, GENERAL_RULES,
GENERAL_RULES_DICT, GENERAL_RULES_DICT,
core_outfit_template,
accessories_template,
all_items_template, all_items_template,
build_iterative_schema, build_iterative_schema,
build_batch_schema build_batch_schema
@@ -29,6 +27,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,18 +58,54 @@ 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")
acc_guide_path = os.path.join(settings.STYLIST_GUIDE_DIR, f"{stylist_name}_acc.md")
try: try:
with open(guide_path, 'r', encoding='utf-8') as file: with open(guide_path, 'r', encoding='utf-8') as file:
stylist_guide = file.read() stylist_guide = file.read()
with open(acc_guide_path, 'r', encoding='utf-8') as file: return stylist_guide
accessories_guide = file.read()
return stylist_guide, accessories_guide
except Exception as e: except Exception as e:
raise Exception(f"Failed to load style guide from {guide_path}, {acc_guide_path}: {e}") raise Exception(f"Failed to load style guide from {guide_path}: {e}")
async def _call_gemini(self, user_input: str, user_id: str, file_name: str, output_schema: Dict[str, Any], image_bytes: bytes = None, system_prompt: str = "") -> str: async def _call_gemini(self, user_input: str, user_id: str, file_name: str, output_schema: Dict[str, Any], image_bytes: bytes = None, system_prompt: str = "") -> str:
""" """
@@ -182,18 +220,10 @@ class AsyncStylistAgent:
gender=gender, gender=gender,
n_results=1 n_results=1
) )
except ValueError as e:
print(f"检测到无效参数错误:{e}")
results = []
if not results: if not results:
self.post_operation( print(f"No matching item found for description: {item_description}, category: {category}, subcategory: {subcategory}")
status="failed", return None
message=f"数据库中未找到符合 '{category}/{subcategory}' 和描述的单品。",
callback_url=self.callback_url,
img_path="",
)
raise Exception(f"数据库中未找到符合 '{category}/{subcategory}' 和描述的单品。")
# 3. 模拟 Agent 审核(实际应用中,你需要将图片发回给 Agent进行审核) # 3. 模拟 Agent 审核(实际应用中,你需要将图片发回给 Agent进行审核)
best_meta = results[0] # 第一个 batch 的第一个 metadata best_meta = results[0] # 第一个 batch 的第一个 metadata
@@ -209,11 +239,31 @@ class AsyncStylistAgent:
"gpt_subcategory": subcategory, "gpt_subcategory": subcategory,
"image_path": os.path.join(settings.DATA_ROOT, batch_source, 'image_data', f"{item_id}.jpg") "image_path": os.path.join(settings.DATA_ROOT, batch_source, 'image_data', f"{item_id}.jpg")
} }
except Exception as e:
print((f"Internal error in _get_next_item: {str(e)}"))
return None
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: 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) # 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)."
elif occasion == "Evening":
request_summary += (" **EVENING STYLE MANDATE:** Prioritize high textural contrast. Avoid flat/matte monochrome or total black looks. Ensure visual depth through layering "
"(e.g., varying lengths or sheer panels). Use statement accessories to break simple silhouettes.")
elif occasion == "Business / workwear":
request_summary += (" **WORKWEAR STYLE MANDATE:** Focus on 'Power Dressing' silhouettes—prioritize sharp, oversized blazers paired with floor-length wide-leg trousers. "
"Use tonal dressing to create a sophisticated, elongated look. Balance masculine tailoring with polished, feminine textures. "
"Select structured, architectural bags and pointed-toe or sleek loafers. MANDATORY: Include a complete gold or silver jewelry set "
"(earrings, necklaces, bracelets, and rings).")
elif occasion == "Outdoor":
request_summary += " **OUTDOOR STYLE MANDATE:** No jeans allowed. Focus on functional yet polished alternatives like chinos, technical fabrics, or tailored shorts."
elif occasion == "Festival / Concert":
request_summary += " **FESTIVAL STYLE MANDATE:** No maxi dresses or maxi skirts. Prioritize shorter hemlines, sets, or trousers to ensure ease of movement."
elif occasion == "Beach / Swim":
request_summary += " **BEACH STYLE MANDATE:** No denim allowed. Focus on breathable, lightweight fabrics like linen, silk, or crochet."
sys_template = template.format( sys_template = template.format(
gender=self.gender, gender=self.gender,
current_category=current_category.upper(), current_category=current_category.upper(),
@@ -273,7 +323,7 @@ class AsyncStylistAgent:
if status in ['failed']: if status in ['failed']:
# 失败直接打印参数 不发送结果 # 失败直接打印参数 不发送结果
response_data['message'] = message response_data['message'] = message
logger.info(f"request data {json.dumps(response_data, ensure_ascii=False, indent=2)}") logger.error(f"request data {json.dumps(response_data, ensure_ascii=False, indent=2)}")
else: else:
response = post_request(url=callback_url, data=json.dumps(response_data)) response = post_request(url=callback_url, data=json.dumps(response_data))
logger.info(f"request data {json.dumps(response_data, ensure_ascii=False, indent=2)} | JAVA callback info -> status:{response.status_code} | message:{response.text}") logger.info(f"request data {json.dumps(response_data, ensure_ascii=False, indent=2)} | JAVA callback info -> status:{response.status_code} | message:{response.text}")
@@ -281,101 +331,6 @@ class AsyncStylistAgent:
else: else:
return {} return {}
async def _execute_iterative_recommendation(
self,
current_category: str,
system_prompt: str,
schema: Dict,
max_len: int,
occasions: List[str],
batch_sources: List[str],
user_id: str,
url: str
):
recommend_timestep = 0
gemini_data = {'action': 'start'}
existing_subcategories = []
while recommend_timestep < max_len and gemini_data.get('action') != 'stop':
recommend_timestep += 1
# 1. 准备用户输入(上下文)
user_input = self._build_user_input(current_category, ", ".join(existing_subcategories))
# 2. 把图片组装起来供api调用
merged_image_path, image_bytes = await self._merge_images(self.outfit_id, user_id, self.stylist_name)
# 3. 调用 Gemini Agent
gemini_response_text = await self._call_gemini(
user_input,
user_id,
self.outfit_id,
schema,
image_bytes,
system_prompt
)
gemini_data = self._parse_gemini_response(gemini_response_text)
if not gemini_data:
self.post_operation(
status="failed",
message="Agent returned invalid response, terminating process.",
callback_url=url,
img_path=merged_image_path,
)
raise Exception("Agent 返回无效响应,终止流程。")
# 处理推荐单品
if gemini_data.get('action') == 'recommend_item':
subcategory = gemini_data.get('subcategory')
description = gemini_data.get('description')
# 4a. 检查类别是否有效 (重要步骤)
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.",
callback_url=url,
img_path=merged_image_path,
)
continue
# 4b. 在本地 DB 中查询单品
new_item = self._get_next_item(description, current_category, subcategory, occasions, batch_sources, self.gender)
if not new_item:
self.post_operation(
status="continue",
message=f"No matching item is found. Ask Gemini to re-output.",
callback_url=url,
img_path=merged_image_path,
)
continue
elif new_item['subcategory'] in [x['subcategory'] for x in self.outfit_items]:
self.post_operation(
status="continue",
message=f"{new_item['item_id']}'s subcategory {new_item['subcategory']} duplicated. Ask Gemini to re-output.",
callback_url=url,
img_path=merged_image_path,
)
continue
elif new_item['item_id'] in [x['item_id'] for x in self.outfit_items]:
self.post_operation(
status="continue",
message=f"Item {new_item['item_id']} duplicated. Ask Gemini to re-output.",
callback_url=url,
img_path=merged_image_path,
)
continue
else:
self.outfit_items.append(new_item)
existing_subcategories.append(new_item["subcategory"])
self.post_operation(
status="ok",
message=f"Add new item {new_item['item_id']} in category {new_item['category']} successfully.",
callback_url=url,
img_path=merged_image_path,
)
print(f"Stage {current_category.upper()}, Step {recommend_timestep}: {gemini_data}, found item: {new_item['item_id']}")
async def _execute_batch_recommendation( async def _execute_batch_recommendation(
self, self,
current_category: str, # this can be any category or all current_category: str, # this can be any category or all
@@ -426,33 +381,27 @@ class AsyncStylistAgent:
allowed_subcategories = self._get_allowed_subcategories(occasions[0], category) allowed_subcategories = self._get_allowed_subcategories(occasions[0], category)
# 4a. 检查类别是否有效 (重要步骤) # 4a. 检查类别是否有效 (重要步骤)
if subcategory not in allowed_subcategories: 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 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]:
@@ -477,94 +426,6 @@ class AsyncStylistAgent:
return cat return cat
return "unknown" 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()
STAGES = ['clothing', 'shoes', 'bags']
# 深拷贝start_outfit 避免实例之间的参数泄漏 确保每个实例都有自己的 start_outfit 副本
if start_outfit is None:
self.outfit_items = []
else:
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:
allowed_subcategories = self._get_allowed_subcategories(occasions[0], current_category)
max_len = min(4, len(allowed_subcategories)) if current_category == 'clothing' else 1
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
)
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)
# 推荐完成返回
response_data = self.post_operation(
status="stop",
message=reason,
callback_url=url,
img_path=final_image_path,
request_summary=request_summary,
occasions=occasions
)
end_time = time.monotonic()
total_duration = end_time - start_time
if settings.LOCAL == 1:
with open(os.path.join(settings.OUTFIT_OUTPUT_DIR, self.stylist_name, f'{self.outfit_id}.json'), 'w') as f:
json.dump({"request_summary": request_summary, "occasions": occasions, "items": self.outfit_items, "total_duration": total_duration}, f, indent=2)
return response_data, total_duration
async def run_quick_batch_styling(self, request_summary, occasions, start_outfit: Optional[List] = None, batch_sources: List = [], user_id="test", callback_url=""): async def run_quick_batch_styling(self, request_summary, occasions, start_outfit: Optional[List] = None, batch_sources: List = [], user_id="test", callback_url=""):
start_time = time.monotonic() start_time = time.monotonic()
# 深拷贝start_outfit 避免实例之间的参数泄漏 确保每个实例都有自己的 start_outfit 副本 # 深拷贝start_outfit 避免实例之间的参数泄漏 确保每个实例都有自己的 start_outfit 副本
@@ -572,27 +433,31 @@ class AsyncStylistAgent:
self.outfit_items = [] self.outfit_items = []
else: else:
self.outfit_items = deepcopy(start_outfit) self.outfit_items = deepcopy(start_outfit)
stylist_guide, accessories_guide = self._load_style_guide(self.stylist_name) stylist_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()) + '\n' + GENERAL_RULES
allowed_subcategories = self._get_allowed_subcategories(occasions[0], "all") allowed_subcategories = self._get_allowed_subcategories(occasions[0], "all")
system_prompt = self._build_system_prompt( system_prompt = self._build_system_prompt(
all_items_template, all_items_template,
general_rules, general_rules,
request_summary, request_summary,
occasions[0], occasions[0],
stylist_guide + accessories_guide, stylist_guide,
"", "",
allowed_subcategories, allowed_subcategories,
MAX_LEN MAX_LEN
) )
max_retries = 2
for attempt in range(max_retries):
logger.info(f"Batch recommendation attempt {attempt + 1}")
reason = await self._execute_batch_recommendation( reason = await self._execute_batch_recommendation(
'all', # can be 'accessories' or 'all' 'all', # can be 'accessories' or 'all'
system_prompt, system_prompt,
@@ -602,6 +467,17 @@ class AsyncStylistAgent:
user_id, user_id,
url 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]}. Current items: {self.outfit_items}. Subcategories required by this occasion is {allowed_subcategories}",
callback_url=url,
img_path=""
)
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(

View File

@@ -67,16 +67,12 @@ class VectorDatabase():
if brand_strication: if brand_strication:
and_conditions.append({"brand": {"$in": BRAND_WHITELIST}}) and_conditions.append({"brand": {"$in": BRAND_WHITELIST}})
if batch_sources and len(batch_sources) > 0: # 加一条occasion限制婚礼不能穿黑色
if len(batch_sources) == 1: if any(o in ["Bridal / Wedding", "Beach / Swim"] for o in occasions):
and_conditions.append({"batch_source": batch_sources[0]}) and_conditions.append({"color": {"$nin": ["BLACK", "DARK GREY", "DARK BLUE", "NAVY"]}})
else:
source_conditions = []
for source in batch_sources:
source_conditions.append({"batch_source": source})
# 将 Batch Source 的 OR 子句添加到主 AND 条件中 if batch_sources and len(batch_sources) > 0:
and_conditions.append({"$or": source_conditions}) and_conditions.append({"batch_source": {"$in": batch_sources}})
results = self.collection.query( results = self.collection.query(
query_embeddings=[embedding], query_embeddings=[embedding],

View File

@@ -4,6 +4,8 @@ import litserve as ls
import requests import requests
from pydantic import BaseModel from pydantic import BaseModel
from app.config import settings
class PredictRequest(BaseModel): class PredictRequest(BaseModel):
input_image_list: list[str] # 待换脸图片 input_image_list: list[str] # 待换脸图片
@@ -17,7 +19,7 @@ class ReFace(ls.LitAPI):
def predict(self, request): def predict(self, request):
# 服务的 URL # 服务的 URL
url = "http://10.1.1.240:10071/predict" url = f"http://{settings.RE_FACE_MODEL_URL}/predict"
# 请求头 # 请求头
headers = { headers = {

View File

@@ -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"],
@@ -163,7 +163,7 @@ OCCASION_CATEGORY_MAP = {
"accessories": ["earrings", "necklaces", "bracelets", "rings"] "accessories": ["earrings", "necklaces", "bracelets", "rings"]
}, },
"Outdoor": { "Outdoor": {
"clothing": ["jackets", "jeans", "sweaters"], "clothing": ["jackets", "sweaters", "pants", "joggers", "leggings", "shorts"],
"shoes": ["boots"], "shoes": ["boots"],
"bags": ["backpacks", "travel bags"], "bags": ["backpacks", "travel bags"],
"accessories": [] "accessories": []

View File

@@ -1,5 +1,6 @@
services: services:
lc_agent_server: lc_agent_server:
container_name: LC_Agent_Server
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
@@ -9,12 +10,12 @@ services:
DEBUG: 0 DEBUG: 0
volumes: volumes:
- ./app:/app/app - ./app:/app/app
- ./.prod_env:/app/.env - ./.env:/app/.env
- ./data:/data - ./data:/data
- ./google_application_credentials.json:/google_application_credentials.json - ./google_application_credentials.json:/google_application_credentials.json
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
ports: ports:
- "10070:8000" - "${SERVE_PORT}:8000"
deploy: deploy:
resources: resources:
reservations: reservations:
@@ -23,3 +24,9 @@ services:
- driver: nvidia - driver: nvidia
device_ids: [ '0' ] device_ids: [ '0' ]
capabilities: [ gpu ] capabilities: [ gpu ]
networks:
- lc_app_net
networks:
lc_app_net:
external: true
name: lc_app_net

Binary file not shown.

View File

@@ -1,17 +0,0 @@
# Crystal's Accessory Guide: Pure Balance
This guide strictly outlines accessory selection, emphasizing Gold Tones and pure, solid colors to stabilize the outfit's primary focus on bold pattern clashing.
## I. Color, Pattern, and Material Constraints
Color & Tone: All jewelry must be in Gold Tones. Vector-style accessories are prohibited.
Accessory Pattern: Bags and shoes must be Solid Color only. Patterned or printed bags and shoes are strictly prohibited.
## II. Mandatory & Stacking Requirements
The use of accessories is essential to complete the look, focusing on stacking and specific shapes:
Mandatory Jewelry: A Necklace is mandatory (minimum one piece). A Watch must be included as part of the wrist stack.
Earrings: Must be Hoop Earrings.
Stacking: Encourage stacking of Bracelets and Rings alongside the mandatory watch.

View File

@@ -37,3 +37,18 @@ This stylist's style prioritizes visual impact through **clashing prints** rathe
5. **Shoe Principle**: Shoes should provide height (low heel, platform, boots). 5. **Shoe Principle**: Shoes should provide height (low heel, platform, boots).
6. **Overall Balance**: If clothing patterns are complex, shoes must be **simple and pure** to ground the look. Style leans toward **mixed-casual and energetic**, avoiding blandness. 6. **Overall Balance**: If clothing patterns are complex, shoes must be **simple and pure** to ground the look. Style leans toward **mixed-casual and energetic**, avoiding blandness.
8. **Scene Adaptability**: Add **cargo pants/low heels** for casual settings; **yoga sets** for sportier looks; use **denim** to balance out heavy prints. 8. **Scene Adaptability**: Add **cargo pants/low heels** for casual settings; **yoga sets** for sportier looks; use **denim** to balance out heavy prints.
---
## Accessory Guide: Pure Balance
This section strictly outlines accessory selection, emphasizing Gold Tones and pure, solid colors to stabilize the outfit's primary focus on bold pattern clashing.
### I. Color, Pattern, and Material Constraints
Color & Tone: All jewelry must be in Gold Tones. Vector-style accessories are prohibited.
Accessory Pattern: Bags and shoes must be Solid Color only. Patterned or printed bags and shoes are strictly prohibited.
### II. Mandatory & Stacking Requirements
The use of accessories is essential to complete the look, focusing on stacking and specific shapes:
Mandatory Jewelry: A Necklace is mandatory (minimum one piece). A Watch must be included as part of the wrist stack.
Earrings: Must be Hoop Earrings.
Stacking: Encourage stacking of Bracelets and Rings alongside the mandatory watch.

View File

@@ -1,10 +0,0 @@
# Accessory Style Guide
This guide strictly outlines accessory selection, emphasizing a flexible approach to metal tones and highlighting the aesthetic of long-worn items.
# I. Color and Material Constraints
Metal Tones: Both Gold and Silver metals are preferred and should be mixed and matched together.
Aesthetic Preference: Items that show wear, such as Silver items that have changed color over time, are acceptable as they present a unique personal preference.
# II. Mandatory & Stacking Requirements
Mandatory Items: No specific jewelry piece is listed as mandatory, but the style encourages mixing both gold and silver jewelry.

View File

@@ -18,3 +18,13 @@ Color Mixing: All color tones are acceptable for mixing together, including high
## II. Styling Pattern: Functional and Harmonious ## II. Styling Pattern: Functional and Harmonious
This stylist prioritizes practical, comfortable items (functional bag, sneakers) while embracing complex color and print compositions that lean on Black as a foundational element. The style aims for a unique, harmonious look achieved through flexible mixing. This stylist prioritizes practical, comfortable items (functional bag, sneakers) while embracing complex color and print compositions that lean on Black as a foundational element. The style aims for a unique, harmonious look achieved through flexible mixing.
## Accessory Style Guide
This section strictly outlines accessory selection, emphasizing a flexible approach to metal tones and highlighting the aesthetic of long-worn items.
### I. Color and Material Constraints
Metal Tones: Both Gold and Silver metals are preferred and should be mixed and matched together.
Aesthetic Preference: Items that show wear, such as Silver items that have changed color over time, are acceptable as they present a unique personal preference.
### II. Mandatory & Stacking Requirements
Mandatory Items: No specific jewelry piece is listed as mandatory, but the style encourages mixing both gold and silver jewelry.

View File

@@ -1,12 +0,0 @@
Stylist Accessories Guide
I. Gold Jewelry
Preference: Necklace, Bracelet, and Earrings must all be present and layered.
Prohibition: Avoid Large/Bulky Earrings or Vector-style accessories.
II. Watch
Preference: Mandatory item.
Prohibition: N/A.
III. Accent Colors
Preference: Added via accessories (e.g., scarves); Max 2 accent colors in total.
Prohibition: Avoid Bright/Vivid colors dominating the outfit.

View File

@@ -35,3 +35,18 @@ This stylist's outfits emphasize **comfort** and **layering** (creating depth).
4. **Pattern Restriction**: Only **plaid/checkered or stripes** are acceptable as subtle accents; **strictly no florals**, unless an extremely minimal exception is made. 4. **Pattern Restriction**: Only **plaid/checkered or stripes** are acceptable as subtle accents; **strictly no florals**, unless an extremely minimal exception is made.
5. **Shoe/Bag Coordination**: Footwear must be **flat and casual** (white sneakers are preferred). 5. **Shoe/Bag Coordination**: Footwear must be **flat and casual** (white sneakers are preferred).
6. **Overall Balance**: When the upper body is complex (layered), the lower body should remain **simple**; the overall style is **neutral and polished**, avoiding highly feminine heels or boots. 6. **Overall Balance**: When the upper body is complex (layered), the lower body should remain **simple**; the overall style is **neutral and polished**, avoiding highly feminine heels or boots.
---
## Stylist Accessories Guide
### I. Gold Jewelry
Preference: Necklace, Bracelet, and Earrings must all be present and layered.
Prohibition: Avoid Large/Bulky Earrings or Vector-style accessories.
### II. Watch
Preference: Mandatory item.
Prohibition: N/A.
### III. Accent Colors
Preference: Added via accessories (e.g., scarves); Max 2 accent colors in total.
Prohibition: Avoid Bright/Vivid colors dominating the outfit.

View File

@@ -1,9 +0,0 @@
# Accessory Style Guide
This guide outlines accessory selection based on the desired overall aesthetic, emphasizing a balanced approach to metal tones.
# I. Metal Tone and Aesthetic Constraints
Jewelry Tone: Prefers Gold for a vintage and nostalgic feel.
Jewelry Tone: Prefers Silver for grungier looks.
Wear Preference: Wears both Gold and Silver, depending on the outfit's desired aesthetic.

View File

@@ -1,7 +1,7 @@
# Outfit Style Guide # Outfit Style Guide
This guide summarizes the preferred styling logic, colors, patterns, and structure for Vera's outfits, emphasizing harmony in color mixing and the use of statement bags. This guide summarizes the preferred styling logic, colors, patterns, and structure for Vera's outfits, emphasizing harmony in color mixing and the use of statement bags.
# I. Core Preferences and Prohibitions ## I. Core Preferences and Prohibitions
Primary Colors: Most often wears Khakis, Black, Creams, and sometimes Burgundies. Primary Colors: Most often wears Khakis, Black, Creams, and sometimes Burgundies.
Dominant Colors: Maximum of two dominant colors per outfit, handled with care. Dominant Colors: Maximum of two dominant colors per outfit, handled with care.
@@ -20,5 +20,16 @@ Bags: Prefers Bigger bags and bags that make a statement. Specific examples incl
Prohibited Bags: Not a fan of crossbody or micro mini bags. Prohibited Bags: Not a fan of crossbody or micro mini bags.
# II. Styling Pattern: Harmonious Statement ## II. Styling Pattern: Harmonious Statement
This stylist prioritizes a core color palette of neutrals and deep tones (Khakis, Black, Creams, Burgundies) and uses print mixing (e.g., different sized polka dots or florals) only when colors and shapes are harmonized. The overall look is anchored by comfortable shoes (loafers) and a large, functional, statement bag. This stylist prioritizes a core color palette of neutrals and deep tones (Khakis, Black, Creams, Burgundies) and uses print mixing (e.g., different sized polka dots or florals) only when colors and shapes are harmonized. The overall look is anchored by comfortable shoes (loafers) and a large, functional, statement bag.
## Accessory Style Guide
This section outlines accessory selection based on the desired overall aesthetic, emphasizing a balanced approach to metal tones.
### I. Metal Tone and Aesthetic Constraints
Jewelry Tone: Prefers Gold for a vintage and nostalgic feel.
Jewelry Tone: Prefers Silver for grungier looks.
Wear Preference: Wears both Gold and Silver, depending on the outfit's desired aesthetic.