Compare commits
13 Commits
develop
...
507d8a3e12
| Author | SHA1 | Date | |
|---|---|---|---|
| 507d8a3e12 | |||
| feb431e9c1 | |||
| 042e6015f0 | |||
| 8ccf899441 | |||
| 7a1496aeb7 | |||
| d8df6b28ea | |||
| 79ec1c5300 | |||
| 1f6ba5fd09 | |||
|
|
54aac900ad | ||
| 884e7966dd | |||
| 2db36e2c1d | |||
|
|
3910c07c40 | ||
|
|
c0c72a9c87 |
40
.gitea/workflows/prod_build_manual.yaml
Normal file
40
.gitea/workflows/prod_build_manual.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
name: 手动 LC python main 分支构建部署
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
scheduled_deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
REMOTE_DEPLOY_PATH: /workspace/Trinity/Litserve_LC_Prod/lc_stylist_agent
|
||||
|
||||
steps:
|
||||
- name: 1.检出代码
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 'main'
|
||||
|
||||
- name: 2.复制文件到服务器
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
password: ${{ secrets.SERVER_PASSWORD }}
|
||||
source: "."
|
||||
target: ${{ env.REMOTE_DEPLOY_PATH }}
|
||||
|
||||
- name: 3.重启docker-compose
|
||||
uses: appleboy/ssh-action@v0.1.10
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
password: ${{ secrets.SERVER_PASSWORD }}
|
||||
script: |
|
||||
# 进入项目目录
|
||||
cd ${{ env.REMOTE_DEPLOY_PATH }}
|
||||
|
||||
docker-compose down 2>&1
|
||||
docker-compose up -d --build --remove-orphans 2>&1
|
||||
|
||||
# docker image prune -f 2>&1
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ data/
|
||||
.prod_env
|
||||
google_application_credentials.json
|
||||
*.bash
|
||||
test
|
||||
app/google_application_credentials.json
|
||||
63
Dockerfile
63
Dockerfile
@@ -1,46 +1,37 @@
|
||||
# Change CUDA and cuDNN version here
|
||||
FROM ghcr.io/astral-sh/uv:latest AS uv_bin
|
||||
FROM nvidia/cuda:12.4.1-base-ubuntu22.04
|
||||
ARG PYTHON_VERSION=3.10
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
# 1. 基础环境配置
|
||||
ENV UV_LINK_MODE=copy \
|
||||
UV_COMPILE_BYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
UV_PROJECT_ENVIRONMENT=/app/.venv
|
||||
|
||||
COPY --from=uv_bin /uv /uvx /bin/
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
software-properties-common \
|
||||
wget \
|
||||
&& add-apt-repository ppa:deadsnakes/ppa \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||
python$PYTHON_VERSION \
|
||||
python$PYTHON_VERSION-dev \
|
||||
python$PYTHON_VERSION-venv \
|
||||
&& wget https://bootstrap.pypa.io/get-pip.py -O get-pip.py \
|
||||
&& python$PYTHON_VERSION get-pip.py \
|
||||
&& rm get-pip.py \
|
||||
&& ln -sf /usr/bin/python$PYTHON_VERSION /usr/bin/python \
|
||||
&& ln -sf /usr/local/bin/pip$PYTHON_VERSION /usr/local/bin/pip \
|
||||
&& python --version \
|
||||
&& pip --version \
|
||||
&& apt-get purge -y --auto-remove software-properties-common \
|
||||
libcurl4-openssl-dev \
|
||||
build-essential \
|
||||
libgl1 \
|
||||
libglib2.0-0 \
|
||||
ca-certificates \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
####### Add your own installation commands here #######
|
||||
# RUN pip install some-package
|
||||
# RUN wget https://path/to/some/data/or/weights
|
||||
# RUN apt-get update && apt-get install -y <package-name>
|
||||
RUN apt-get update && \
|
||||
apt-get install -y libcurl4-openssl-dev build-essential
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install libgl1
|
||||
RUN apt-get -y install libglib2.0-0
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
# Install litserve and requirements
|
||||
RUN pip install --upgrade pip setuptools wheel
|
||||
RUN pip install --no-cache-dir litserve==0.2.16 -r requirements.txt
|
||||
RUN pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
||||
COPY pyproject.toml uv.lock ./
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=0
|
||||
|
||||
RUN uv sync --frozen --no-dev --no-install-project --python 3.10
|
||||
|
||||
# 4. 拷贝项目文件并安装项目本身
|
||||
COPY . .
|
||||
RUN uv sync --frozen --no-dev --python 3.10
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
EXPOSE 8000
|
||||
CMD ["python", "-m","app.main"]
|
||||
#CMD ["tail", "-f","/dev/null"]
|
||||
CMD ["uv", "run","-m","app.main"]
|
||||
|
||||
@@ -16,6 +16,8 @@ class Settings(BaseSettings):
|
||||
env_file_encoding='utf-8',
|
||||
extra='ignore' # 忽略环境变量中多余的键
|
||||
)
|
||||
# 启动端口
|
||||
SERVE_PROD: int = Field(default=8000, description='')
|
||||
# 调试配饰
|
||||
LOCAL: int = Field(default=0, description="是否在本地运行,1表示本地运行,0表示生产环境运行")
|
||||
|
||||
|
||||
@@ -27,4 +27,4 @@ if __name__ == "__main__":
|
||||
agent_api = LCAgent(enable_async=True, api_path='/api/v1/agent')
|
||||
reface_api = ReFace(api_path='/api/v1/reface')
|
||||
server = ls.LitServer([chat_boot_api, agent_api, reface_api])
|
||||
server.run(port=8000)
|
||||
server.run(port=settings.SERVE_PROD)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from pydantic import Field
|
||||
import time
|
||||
|
||||
@@ -15,6 +16,7 @@ from app.server.ChatbotAgent.core.redis_manager import RedisManager
|
||||
from app.server.ChatbotAgent.core.stylist_agent_server import AsyncStylistAgent
|
||||
from app.server.ChatbotAgent.core.prompt import SUMMARY_PROMPT
|
||||
from app.server.ChatbotAgent.core.vector_database import VectorDatabase
|
||||
from app.server.utils.request_post import post_request
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -56,6 +58,8 @@ class AgentRequestModel(BaseModel):
|
||||
batch_sources: List[str]
|
||||
callback_url: str
|
||||
gender: str
|
||||
occasions: Optional[list] = None
|
||||
request_summary: Optional[str] = None
|
||||
|
||||
|
||||
class LCAgent(ls.LitAPI):
|
||||
@@ -105,8 +109,12 @@ class LCAgent(ls.LitAPI):
|
||||
|
||||
async def background_run(self, request: AgentRequestModel, outfit_ids):
|
||||
# 1. 根据用户ID查询对话历史,总结对话内容
|
||||
if request.request_summary and request.occasions:
|
||||
request_summary = request.request_summary
|
||||
occasions = request.occasions
|
||||
else:
|
||||
request_summary, occasions = await self.get_conversation_summary(request.session_id)
|
||||
logger.info(f"request_summary: {request_summary}")
|
||||
logger.info(f"request_summary: {request_summary},occasions : {occasions}")
|
||||
|
||||
# 2.根据对话总结推荐搭配
|
||||
recommendation_results = await self.recommend_outfit(
|
||||
@@ -138,7 +146,7 @@ class LCAgent(ls.LitAPI):
|
||||
history_messages = self.redis.get_history(session_id)
|
||||
if not history_messages:
|
||||
# 处理无历史记录的情况
|
||||
return {"occasions": [], "summary": "User has no history provided."}
|
||||
return "User has no history provided.", []
|
||||
|
||||
input_message = "\n".join([f"{msg.role.value}: {msg.content}" for msg in history_messages])
|
||||
json_schema = StylistResponse.model_json_schema()
|
||||
@@ -187,32 +195,26 @@ class LCAgent(ls.LitAPI):
|
||||
task_map = {}
|
||||
|
||||
stylist_agent_kwages = self.stylist_agent_kwages.copy()
|
||||
tasks_mapping = {}
|
||||
if num_outfits == 1:
|
||||
# 通过请求数量判断 num == 1 单个outfit刷新
|
||||
stylist_agent_kwages['outfit_id'] = outfit_ids[0]
|
||||
stylist_agent_kwages['stylist_name'] = stylist_name
|
||||
stylist_agent_kwages['gender'] = gender
|
||||
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
||||
task = agent.run_iterative_styling(
|
||||
request_summary=request_summary,
|
||||
occasions=occasions,
|
||||
start_outfit=start_outfit,
|
||||
batch_sources=batch_sources,
|
||||
user_id=user_id,
|
||||
callback_url=callback_url,
|
||||
)
|
||||
tasks.append(task)
|
||||
task_map[task] = {"outfit_id": outfit_ids[0], "retries": 0}
|
||||
elif num_outfits > 1:
|
||||
# 通过请求数量判断 num > 1 四套搭配推荐 (1快 , num-1慢)
|
||||
tasks_mapping[outfit_ids[0]] = "fast"
|
||||
else:
|
||||
for i in range(num_outfits):
|
||||
stylist_agent_kwages['outfit_id'] = outfit_ids[i]
|
||||
if i == 0:
|
||||
tasks_mapping[outfit_ids[i]] = "fast"
|
||||
else:
|
||||
tasks_mapping[outfit_ids[i]] = "slow"
|
||||
|
||||
for k, v in tasks_mapping.items():
|
||||
logger.info(f"fast request outfit_id is : {k}")
|
||||
# 通过请求数量判断 num == 1 单个outfit刷新
|
||||
stylist_agent_kwages['outfit_id'] = k
|
||||
stylist_agent_kwages['stylist_name'] = stylist_name
|
||||
stylist_agent_kwages['gender'] = gender
|
||||
stylist_agent_kwages['callback_url'] = callback_url
|
||||
|
||||
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
||||
if i == 0:
|
||||
# 第一套搭配使用快速方法 一次跑出所有单品
|
||||
logger.info(f"fast request outfit_id is : {outfit_ids[i]}")
|
||||
if v == "fast":
|
||||
task = agent.run_quick_batch_styling(
|
||||
request_summary=request_summary,
|
||||
occasions=occasions,
|
||||
@@ -222,7 +224,6 @@ class LCAgent(ls.LitAPI):
|
||||
callback_url=callback_url,
|
||||
)
|
||||
else:
|
||||
# 后续
|
||||
task = agent.run_iterative_styling(
|
||||
request_summary=request_summary,
|
||||
occasions=occasions,
|
||||
@@ -232,7 +233,7 @@ class LCAgent(ls.LitAPI):
|
||||
callback_url=callback_url,
|
||||
)
|
||||
tasks.append(task)
|
||||
task_map[task] = {"outfit_id": outfit_ids[i], "retries": 0}
|
||||
task_map[task] = {"outfit_id": k, "retries": 0}
|
||||
logger.info(f"--- Starting {num_outfits} concurrent outfit generation tasks. ---")
|
||||
|
||||
# 2. 任务执行与重试循环
|
||||
@@ -243,7 +244,8 @@ class LCAgent(ls.LitAPI):
|
||||
retry_limit = 1 # 允许重试一次
|
||||
while tasks_to_run:
|
||||
try:
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
results = await asyncio.gather(*tasks_to_run, return_exceptions=True)
|
||||
|
||||
next_tasks_to_run = []
|
||||
for task, result in zip(tasks_to_run, results):
|
||||
task_info = task_map[task]
|
||||
@@ -255,7 +257,14 @@ class LCAgent(ls.LitAPI):
|
||||
logger.error(f"Outfit {outfit_id} failed with error: {result}. Current retries: {current_retries}.")
|
||||
|
||||
if current_retries < retry_limit:
|
||||
# 尚未达到重试上限,准备重试
|
||||
# 尚未达到重试上限,准备重试 并通知前端
|
||||
object_data = {
|
||||
'outfit_id': outfit_id,
|
||||
"status": "retrying",
|
||||
"path": "",
|
||||
}
|
||||
post_request(url=f'{callback_url}/api/style/callback', data=json.dumps(object_data))
|
||||
|
||||
task_info["retries"] += 1
|
||||
logger.info(f"--- Retrying outfit {outfit_id} (Attempt {current_retries + 1}/{retry_limit}). ---")
|
||||
|
||||
@@ -263,8 +272,20 @@ class LCAgent(ls.LitAPI):
|
||||
stylist_agent_kwages['outfit_id'] = outfit_id
|
||||
stylist_agent_kwages['stylist_name'] = stylist_name
|
||||
stylist_agent_kwages['gender'] = gender
|
||||
stylist_agent_kwages['callback_url'] = callback_url
|
||||
|
||||
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
||||
if tasks_mapping[outfit_id] == "fast":
|
||||
new_task = agent.run_quick_batch_styling(
|
||||
request_summary=request_summary,
|
||||
occasions=occasions,
|
||||
start_outfit=start_outfit,
|
||||
batch_sources=batch_sources,
|
||||
user_id=user_id,
|
||||
callback_url=callback_url,
|
||||
)
|
||||
else:
|
||||
new_task = agent.run_iterative_styling(
|
||||
request_summary=request_summary,
|
||||
occasions=occasions,
|
||||
start_outfit=start_outfit,
|
||||
@@ -279,8 +300,15 @@ class LCAgent(ls.LitAPI):
|
||||
# 清理旧任务(可选,但推荐,以防内存泄漏或混淆)
|
||||
del task_map[task]
|
||||
else:
|
||||
# 达到重试上限,最终失败
|
||||
# 达到重试上限,最终失败 , 并通知前端
|
||||
object_data = {
|
||||
'outfit_id': outfit_id,
|
||||
"status": "retry_failed",
|
||||
"path": "",
|
||||
}
|
||||
response = post_request(url=f'{callback_url}/api/style/callback', data=json.dumps(object_data))
|
||||
failed_outfits.append(f"Outfit {outfit_id} ultimately failed after {retry_limit} retries: {result}")
|
||||
logger.info(f"request data :{json.dumps(object_data, ensure_ascii=False, indent=2)} | JAVA callback info -> status:{response.status_code} | message:{response.text}")
|
||||
del task_map[task]
|
||||
|
||||
else:
|
||||
@@ -320,14 +348,19 @@ if __name__ == "__main__":
|
||||
# 2. 准备请求数据
|
||||
import json
|
||||
stylist_agent_kwages = agent_api.stylist_agent_kwages.copy()
|
||||
with open("/mnt/data/workspace/Code/lc_stylist_agent/data/2025_q4/request_test.json", "r") as f:
|
||||
if settings.LOCAL == 1:
|
||||
request_file_path = "./data/2025_q4/request_test.json"
|
||||
else:
|
||||
request_file_path = "/mnt/data/workspace/Code/lc_stylist_agent/data/2025_q4/request_test.json"
|
||||
|
||||
with open(request_file_path, "r") as f:
|
||||
request_data = json.load(f)
|
||||
|
||||
tasks_with_metadata = []
|
||||
for test_content in request_data[20:25]:
|
||||
for test_content in request_data[14:20]:
|
||||
occasions = test_content['occasions']
|
||||
request_summary = test_content['request_summary']
|
||||
for stylist_name in ["edi", "vera"]:
|
||||
for stylist_name in ["edi"]:
|
||||
stylist_agent_kwages['outfit_id'] = test_content['test_case_id'] + "_" + "_".join(occasions) + f"_{stylist_name}"
|
||||
stylist_agent_kwages['stylist_name'] = stylist_name
|
||||
stylist_agent_kwages['gender'] = "female"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import litserve as ls
|
||||
|
||||
from typing import AsyncGenerator
|
||||
from google import genai
|
||||
from pydantic import BaseModel
|
||||
from sympy.core.evalf import rnd
|
||||
|
||||
from app.config import settings
|
||||
from google.genai import types
|
||||
|
||||
@@ -95,42 +99,61 @@ class LCChatBot(ls.LitAPI):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.stdout = open('permanent.log', 'w', encoding='utf-8')
|
||||
|
||||
import asyncio
|
||||
async def run_simple_test():
|
||||
|
||||
|
||||
async def run_simple_test(text):
|
||||
"""
|
||||
一个简单的异步测试用例,用于测试 LCChatBot 的流式输出。
|
||||
"""
|
||||
print("\n" + "=" * 50)
|
||||
print("--- 🔬 开始 LCChatBot 简单流式测试 ---")
|
||||
# print("--- 🔬 开始 LCChatBot 简单流式测试 ---")
|
||||
|
||||
# 1. 初始化 LitAPI 和其依赖
|
||||
chatbot_api = LCChatBot()
|
||||
chatbot_api.setup(device="cpu")
|
||||
print("✅ Setup complete. Mock services initialized.")
|
||||
# print("✅ Setup complete. Mock services initialized.")
|
||||
|
||||
# 2. 构造请求数据
|
||||
request_data = PredictRequest(
|
||||
user_id="simple_user",
|
||||
session_id="simple_session",
|
||||
user_message="I want an outfit. I am going to a evening party with friends. Suggest something stylish yet comfortable.",
|
||||
user_message=text,
|
||||
gender="female"
|
||||
)
|
||||
chatbot_api.redis.clear_history(request_data.session_id)
|
||||
|
||||
print(f"-> 正在发送查询: {request_data.user_message}")
|
||||
print(f"user: \n {request_data.user_message}")
|
||||
|
||||
# 3. 调用 predict 方法并处理流
|
||||
response_generator = chatbot_api.predict(request_data)
|
||||
|
||||
print("\n<- 接收流式响应:")
|
||||
print("agent:")
|
||||
|
||||
# 4. 异步迭代生成器,实时打印输出
|
||||
async for chunk in response_generator:
|
||||
print(chunk, end="", flush=True)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
# 启动异步事件循环
|
||||
try:
|
||||
asyncio.run(run_simple_test())
|
||||
except Exception as e:
|
||||
print(f"\n发生致命错误: {e}")
|
||||
|
||||
text_list = [
|
||||
'我要去参加好朋友的婚礼,你能帮我挑一套衣服吗?',
|
||||
'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?',
|
||||
'I’m 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.',
|
||||
'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 feeling really sad today and just want an outfit that matches my mood."
|
||||
]
|
||||
for text in text_list:
|
||||
asyncio.run(run_simple_test(text))
|
||||
# print("\n" + "=" * 50)
|
||||
# # 启动异步事件循环
|
||||
# try:
|
||||
# asyncio.run(run_simple_test())
|
||||
# except Exception as e:
|
||||
# print(f"\n发生致命错误: {e}")
|
||||
#
|
||||
sys.stdout.close()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
BASIC_PROMPT = """You are a professional, friendly, and insightful AI {gender}'s styling assistant.
|
||||
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.
|
||||
|
||||
@@ -21,6 +21,39 @@ 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.
|
||||
|
||||
CONVERSATION GOALS:
|
||||
1. **Occasion:** Determine the specific event (e.g., romantic dinner, summer wedding).
|
||||
2. **Style:** Pinpoint the desired aesthetic (e.g., minimalist, bohemian).
|
||||
3. **Vibe/Details:** Gather mood or constraints (e.g., comfort, specific colors).
|
||||
4. **Item Preference:** Identify preferences for silhouettes or specific items.
|
||||
|
||||
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.
|
||||
- Keep responses extremely SHORT (maximum 2 sentences).
|
||||
- Use clear, modern, and vivid language. Avoid clichés or corporate jargon.
|
||||
- Be positive and encouraging, never patronizing.
|
||||
- When uncertain, state assumptions and invite correction.
|
||||
- Gather sufficient intent before providing recommendations. Ask only 1 to 2 essential questions per turn.
|
||||
|
||||
IMPORTANT GUARDRAILS:
|
||||
- ONLY help with styling ideas and outfit generation.
|
||||
- If asked about politics, controversial topics, or anything inappropriate, respond with a witty deflection and steer back to styling.
|
||||
|
||||
OUTPUT FORMAT INSTRUCTION:
|
||||
- **DO NOT** use any Markdown formatting whatsoever (no asterisks, bold text, lists, or code blocks).
|
||||
- **ONLY** output the plain text response spoken by the AI Assistant.
|
||||
|
||||
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: 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: What do you think about the current election? Response: I prefer to stick to the polls of fashion rather than politics! Tell me, what kind of style aesthetic are we aiming for today?
|
||||
"""
|
||||
|
||||
SUMMARY_PROMPT = """
|
||||
You are an expert fashion request analyzer. Analyze the conversation history provided by the user.
|
||||
Your task is to:
|
||||
@@ -31,24 +64,32 @@ Your task is to:
|
||||
Extract this information accurately from the chat history.
|
||||
"""
|
||||
|
||||
|
||||
from app.taxonomy import FASHION_TAXONOMY, IGNORE_SUBCATEGORY, ALL_SUBCATEGORY_LIST
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
## Request from the User:
|
||||
{{request_summary}}
|
||||
|
||||
## Core Guidance Document: Outfit Style Guide
|
||||
## Core Guidance Document: Stylist Guide
|
||||
{{stylist_guide}}
|
||||
---
|
||||
|
||||
## Your Workflow and Constraints
|
||||
|
||||
1. **Style Adherence**: You must strictly observe all rules in the Style Guide concerning **color palette, fit, layering principles, pattern restrictions , shoe coordination**.
|
||||
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.
|
||||
@@ -88,17 +129,27 @@ You must strictly use the **JSON format** for your output, as follows:
|
||||
Now, please start building an outfit (with strictly unique categories for all items) and output the JSON for the first item.
|
||||
"""
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
## CONTEXT
|
||||
[User Request]: {{request_summary}}
|
||||
|
||||
[Accessories Style Guide]:
|
||||
[Stylist's Accessories Guide]:
|
||||
{{stylist_guide}}
|
||||
|
||||
---
|
||||
@@ -120,6 +171,15 @@ You are a professional fashion stylist Agent, specialized in creating complete,
|
||||
|
||||
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**.
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
## Request from the User:
|
||||
{{request_summary}}
|
||||
@@ -130,29 +190,28 @@ You must create a cohesive look that includes **Clothing, Shoes, Bags, and Acces
|
||||
|
||||
## GENERATION WORKFLOW & RULES
|
||||
|
||||
1. **Holistic Styling**: You are NOT recommending items step-by-step. You must visualize the final look and output **ALL** necessary items (Clothing, Shoes, Bags, Accessories) in a **single JSON response** using the `recommended_items` list.
|
||||
1. **Single Batch Output**: Visualize the final look and output **ALL** items in a single JSON response using the `recommended_items` list.
|
||||
|
||||
2. **Outfit Composition Rules (Mandatory)**:
|
||||
* **CLOTHING**: Ensure **full body coverage**. You must include either [Top + Bottom] OR [One-piece (e.g., Dress/Jumpsuit)]. 'Dresses' and 'Skirts/Pants' are mutually exclusive.
|
||||
* **SHOES**: **Exactly one (1) pair** of shoes is MANDATORY.
|
||||
* **BAGS**: Recommend **0 or 1 bag**. Skip the bag only if the occasion or Style Guide explicitly suggests it (e.g., home wear, yoga).
|
||||
* **ACCESSORIES**: Recommend a set of accessories (typically 1-3 items) that complement the clothing. Follow metal/material constraints in the guide.
|
||||
Number of items in outfit must not exceed {{max_len}}.
|
||||
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. **Uniqueness Mandate**:
|
||||
* Each **subcategory** belonging to CLOTHING (e.g., 't-shirts', 'sweaters', 'jacket') can appear **EXACTLY ONCE** in the entire list.
|
||||
* But **subcategory** belonging to ACCESSORIES can repeat.
|
||||
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. **Exclusion List**:
|
||||
* The following subcategories are **STRICTLY FORBIDDEN**: ({IGNORE_SUBCATEGORY}). Do not include them in your recommendation.
|
||||
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. **Style Adherence**:
|
||||
* Ensure all items coordinate in **color, fit, and material**.
|
||||
* Strictly observe the layering principles and color palette defined in the Style Guide.
|
||||
5. **Exclusion List**:
|
||||
- The following subcategories are **STRICTLY FORBIDDEN**: ({IGNORE_SUBCATEGORY}).
|
||||
|
||||
6. **Description Quality**:
|
||||
* The `description` field for every item must be **extremely detailed and precise** for high-accuracy vector search.
|
||||
* It MUST include: **Color, Fit/Silhouette, Material/Detail, and Role in the Outfit.**
|
||||
- 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.
|
||||
|
||||
@@ -27,7 +27,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AsyncStylistAgent:
|
||||
def __init__(self, local_db: str, gemini_model_name: str, outfit_id: str, stylist_name: str, gender: 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_id = outfit_id
|
||||
self.stylist_name = stylist_name
|
||||
@@ -38,13 +38,6 @@ class AsyncStylistAgent:
|
||||
self.local_db = local_db
|
||||
self.gemini_model_name = gemini_model_name
|
||||
self.stop_reason = ""
|
||||
self.headers = {
|
||||
'Accept': "*/*",
|
||||
'Accept-Encoding': "gzip, deflate, br",
|
||||
'User-Agent': "PostmanRuntime-ApipostRuntime/1.1.0",
|
||||
'Connection': "keep-alive",
|
||||
'Content-Type': "application/json"
|
||||
}
|
||||
|
||||
# 存储桶配置
|
||||
try:
|
||||
@@ -59,6 +52,7 @@ class AsyncStylistAgent:
|
||||
)
|
||||
self.gcs_bucket = "lc_stylist_agent_outfit_items"
|
||||
self.minio_bucket = "lanecarford"
|
||||
self.callback_url = f'{callback_url}/api/style/callback'
|
||||
|
||||
def _load_style_guide(self, stylist_name: str):
|
||||
"""加载 markdown 风格指南内容。"""
|
||||
@@ -178,6 +172,7 @@ class AsyncStylistAgent:
|
||||
results = self.local_db.get_matched_item(
|
||||
query_embedding,
|
||||
category,
|
||||
subcategory,
|
||||
occasions=occasions,
|
||||
batch_sources=batch_sources,
|
||||
gender=gender,
|
||||
@@ -188,8 +183,13 @@ class AsyncStylistAgent:
|
||||
results = []
|
||||
|
||||
if not results:
|
||||
print(f"数据库中未找到符合 '{category}' 和描述的单品。")
|
||||
return None
|
||||
self.post_operation(
|
||||
status="failed",
|
||||
message=f"数据库中未找到符合 '{category}' 和描述的单品。",
|
||||
callback_url=self.callback_url,
|
||||
img_path="",
|
||||
)
|
||||
raise Exception(f"数据库中未找到符合 '{category}' 和描述的单品。")
|
||||
|
||||
# 3. 模拟 Agent 审核(实际应用中,你需要将图片发回给 Agent进行审核)
|
||||
best_meta = results[0] # 第一个 batch 的第一个 metadata
|
||||
@@ -200,6 +200,7 @@ class AsyncStylistAgent:
|
||||
"category": best_meta['category'],
|
||||
'description': best_meta['description'],
|
||||
"subcategory": best_meta['subcategory'],
|
||||
"brand": best_meta['brand'],
|
||||
"gpt_description": item_description,
|
||||
"gpt_subcategory": subcategory,
|
||||
# 假设 'item_path' 存储在 metadata 中,或从 'item_id' 推导
|
||||
@@ -239,7 +240,7 @@ class AsyncStylistAgent:
|
||||
context += "\nRecommend a **complete, full outfit**, including all items (clothing, shoes, bags, and accessories), strictly following the Request Summary and Style Guide. Output the **complete list** of items in a single JSON response."
|
||||
return context
|
||||
|
||||
def post_operation(self, status: str, message: str, callback_url: str, img_path: str):
|
||||
def post_operation(self, status: str, message: str, callback_url: str, img_path: str, request_summary=None, occasions=None):
|
||||
"""处理完成后的回调操作。"""
|
||||
if settings.LOCAL == 0:
|
||||
# 生产回调请求数据处理
|
||||
@@ -256,9 +257,17 @@ class AsyncStylistAgent:
|
||||
'status': status,
|
||||
# 'message': message,
|
||||
'path': img_path,
|
||||
'outfit_id': self.outfit_id
|
||||
'outfit_id': self.outfit_id,
|
||||
"request_summary": request_summary,
|
||||
"occasions": occasions
|
||||
}
|
||||
response = post_request(url=callback_url, data=json.dumps(response_data), headers=self.headers)
|
||||
|
||||
if status in ['failed']:
|
||||
# 失败直接打印参数 不发送结果
|
||||
response_data['message'] = message
|
||||
logger.info(f"request data :{json.dumps(response_data, ensure_ascii=False, indent=2)}")
|
||||
else:
|
||||
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}")
|
||||
return response_data
|
||||
else:
|
||||
@@ -298,14 +307,13 @@ class AsyncStylistAgent:
|
||||
gemini_data = self._parse_gemini_response(gemini_response_text)
|
||||
|
||||
if not gemini_data:
|
||||
print("Agent 返回无效响应,终止流程。")
|
||||
self.post_operation(
|
||||
status="failed",
|
||||
message="Agent returned invalid response, terminating process.",
|
||||
callback_url=url,
|
||||
img_path=merged_image_path,
|
||||
)
|
||||
break
|
||||
raise Exception("Agent 返回无效响应,终止流程。")
|
||||
|
||||
# 处理推荐单品
|
||||
if gemini_data.get('action') == 'recommend_item':
|
||||
@@ -385,15 +393,17 @@ class AsyncStylistAgent:
|
||||
gemini_data = self._parse_gemini_response(gemini_response_text)
|
||||
recommended_items = gemini_data.get('recommended_items', [])
|
||||
reason = gemini_data.get('reason', '')
|
||||
failed_found_item_count = 0
|
||||
|
||||
if not recommended_items or not isinstance(recommended_items, List):
|
||||
print("No recommended item from Gemini, terminating process.")
|
||||
self.post_operation(
|
||||
status="failed",
|
||||
message="Agent returned invalid response, terminating process.",
|
||||
callback_url=url,
|
||||
img_path=merged_image_path
|
||||
)
|
||||
raise Exception("No recommended item from Gemini, terminating process.")
|
||||
|
||||
else:
|
||||
for idx, rec_item in enumerate(recommended_items):
|
||||
subcategory = rec_item.get('subcategory')
|
||||
@@ -416,10 +426,21 @@ class AsyncStylistAgent:
|
||||
|
||||
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
|
||||
continue
|
||||
else:
|
||||
self.outfit_items.append(new_item)
|
||||
print(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
|
||||
|
||||
async def run_iterative_styling(self, request_summary, occasions, start_outfit: Optional[List] = None, batch_sources: List = [], user_id="test", callback_url=""):
|
||||
@@ -464,18 +485,20 @@ class AsyncStylistAgent:
|
||||
)
|
||||
|
||||
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
|
||||
img_path=final_image_path,
|
||||
request_summary=request_summary,
|
||||
occasions=occasions
|
||||
)
|
||||
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}, f, indent=2)
|
||||
|
||||
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
|
||||
|
||||
@@ -502,20 +525,27 @@ class AsyncStylistAgent:
|
||||
user_id,
|
||||
url
|
||||
)
|
||||
|
||||
# 推荐即将完成 回调通知前端
|
||||
self.post_operation(
|
||||
status="almost_done",
|
||||
message="Recommendation has been completed and the outfit is being assembled",
|
||||
callback_url=url,
|
||||
img_path="",
|
||||
)
|
||||
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
|
||||
img_path=final_image_path,
|
||||
request_summary=request_summary,
|
||||
occasions=occasions
|
||||
)
|
||||
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}, f, indent=2)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from PIL import Image
|
||||
from typing import List, Dict, Any
|
||||
from transformers import CLIPProcessor, CLIPModel
|
||||
|
||||
from app.taxonomy import OCCASION, CATEGORY_LIST, IGNORE_SUBCATEGORY
|
||||
from app.taxonomy import OCCASION, CATEGORY_LIST, IGNORE_SUBCATEGORY, BRAND_WHITELIST
|
||||
|
||||
|
||||
class VectorDatabase():
|
||||
@@ -46,7 +46,7 @@ class VectorDatabase():
|
||||
|
||||
return features.cpu().numpy().flatten().tolist()
|
||||
|
||||
def get_matched_item(self, embedding: List[float], category: 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, subcategory: str, occasions: List[str] = [], batch_sources: List[str] = [], gender: str = 'female', n_results: int = 1) -> List[Dict[str, Any]]:
|
||||
if category not in CATEGORY_LIST:
|
||||
raise ValueError(f"Recommended {category} is not valid.")
|
||||
|
||||
@@ -59,6 +59,12 @@ class VectorDatabase():
|
||||
]},
|
||||
{"subcategory": {"$nin": IGNORE_SUBCATEGORY}}
|
||||
]
|
||||
|
||||
# 加了一条限制,但是部署到生产的时候把他设定为False
|
||||
brand_strication = False
|
||||
if brand_strication:
|
||||
and_conditions.append({"brand": {"$in": BRAND_WHITELIST}})
|
||||
|
||||
if batch_sources and len(batch_sources) > 0:
|
||||
if len(batch_sources) == 1:
|
||||
and_conditions.append({"batch_source": batch_sources[0]})
|
||||
@@ -80,10 +86,14 @@ class VectorDatabase():
|
||||
return []
|
||||
|
||||
metadatas = results['metadatas'][0] # List[Dict[str, Any]]
|
||||
final_scores = []
|
||||
candidate_pool = []
|
||||
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_occ = 0.0
|
||||
if occasions:
|
||||
@@ -94,26 +104,42 @@ class VectorDatabase():
|
||||
count += 1
|
||||
status_val = metadata.get(occ, -1)
|
||||
if status_val == 1:
|
||||
score_occ += 1.0
|
||||
score_occ += 5.0
|
||||
elif status_val == 0:
|
||||
score_occ += 0.0
|
||||
else:
|
||||
score_occ -= 100.0
|
||||
score_occ -= 5.0
|
||||
|
||||
score_occ = score_occ / count if count else 0.0
|
||||
|
||||
final_score = 0.6 * score_vec + 0.4 * score_occ
|
||||
final_scores.append(final_score)
|
||||
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
|
||||
})
|
||||
|
||||
scores_arr = np.array(final_scores)
|
||||
temperature = 0.5
|
||||
scores_arr = scores_arr / temperature
|
||||
if not candidate_pool:
|
||||
print(f"All scores are negative: Ignore")
|
||||
return []
|
||||
|
||||
# 采取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(scores_arr - np.max(scores_arr))
|
||||
exp_scores = np.exp(scaled_scores - np.max(scaled_scores))
|
||||
probabilities = exp_scores / np.sum(exp_scores)
|
||||
|
||||
# 采样 (或直接取 Top 1)
|
||||
sampled_index = np.random.choice(a=len(results['ids'][0]), p=probabilities, size=n_results, replace=False) # 不重复采样
|
||||
sampled_items = [metadatas[i] for i in sampled_index]
|
||||
sampled_index = np.random.choice(a=current_k, p=probabilities, size=min(n_results, current_k), replace=False) # 不重复采样
|
||||
sampled_items = [top_candidates[i]['metadata'] for i in sampled_index]
|
||||
|
||||
return sampled_items
|
||||
|
||||
@@ -4,18 +4,24 @@ import time
|
||||
import requests
|
||||
|
||||
|
||||
def post_request(url, data=None, json_data=None, headers=None, auth=None, timeout=5):
|
||||
def post_request(url, data=None, json_data=None, auth=None, timeout=5):
|
||||
"""
|
||||
发送POST请求的封装函数
|
||||
|
||||
:param url: 接口的URL地址
|
||||
:param data: 要发送的数据(字典形式,用于表单数据等,会自动编码)
|
||||
:param json_data: 要发送的JSON数据(字典形式,会自动转换为JSON字符串)
|
||||
:param headers: 请求头字典
|
||||
:param auth: 认证信息(如 ('username', 'password') 形式用于基本认证)
|
||||
:param timeout: 超时时间,单位为秒
|
||||
:return: 返回接口的响应对象
|
||||
"""
|
||||
headers = {
|
||||
'Accept': "*/*",
|
||||
'Accept-Encoding': "gzip, deflate, br",
|
||||
'User-Agent': "PostmanRuntime-ApipostRuntime/1.1.0",
|
||||
'Connection': "keep-alive",
|
||||
'Content-Type': "application/json"
|
||||
}
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
@@ -52,6 +58,6 @@ if __name__ == '__main__':
|
||||
'Content-Type': "application/json"
|
||||
}
|
||||
start_time = time.time()
|
||||
X = post_request(url=url, data=json.dumps(object_data), headers=headers)
|
||||
X = post_request(url=url, data=json.dumps(object_data))
|
||||
print(time.time() - start_time)
|
||||
print(X)
|
||||
|
||||
@@ -91,4 +91,43 @@ FASHION_TAXONOMY = {
|
||||
CATEGORY_LIST = list(FASHION_TAXONOMY.keys())
|
||||
ALL_SUBCATEGORY_LIST = sum(FASHION_TAXONOMY.values(), [])
|
||||
|
||||
IGNORE_SUBCATEGORY = ['socks']
|
||||
IGNORE_SUBCATEGORY = ['socks', 'watches', 'hats']
|
||||
|
||||
BRAND_WHITELIST = [
|
||||
"ALAÏA",
|
||||
"BALENCIAGA",
|
||||
"BALMAIN",
|
||||
"BARBOUR",
|
||||
"BOTTEGA VENETA",
|
||||
"BRUNELLO CUCINELLI",
|
||||
"ALEXANDERWANG",
|
||||
"CHLOÉ",
|
||||
"DIOR",
|
||||
"FEAR OF GOD",
|
||||
"FENDI",
|
||||
"GIA STUDIOS",
|
||||
"GUCCI",
|
||||
"HELMUT LANG",
|
||||
"JACQUEMUS",
|
||||
"JIMMY CHOO",
|
||||
"JW ANDERSON",
|
||||
"KHAITE",
|
||||
"LEMAIRE",
|
||||
"LOEWE",
|
||||
"MAISON MARGIELA",
|
||||
"MIU MIU",
|
||||
"MM6 MAISON MARGIELA",
|
||||
"MONCLER",
|
||||
"PEDDER RED",
|
||||
"PETER DO",
|
||||
"PHOEBE PHILO",
|
||||
"PRADA",
|
||||
"RICK OWENS",
|
||||
"SACAI",
|
||||
"SKIMS",
|
||||
"THE ROW",
|
||||
"THEORY",
|
||||
"THOM BROWNE",
|
||||
"THE FRANKIE SHOP",
|
||||
"TOTEME",
|
||||
]
|
||||
22
pyproject.toml
Executable file
22
pyproject.toml
Executable file
@@ -0,0 +1,22 @@
|
||||
[project]
|
||||
name = "lc-stylist-agent"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
requires-python = "==3.10.*"
|
||||
dependencies = [
|
||||
"chromadb==1.1.1",
|
||||
"google-cloud-storage==2.19.0",
|
||||
"google-genai==1.45.0",
|
||||
"litserve>=0.2.16",
|
||||
"minio==7.2.18",
|
||||
"numpy==1.24.4",
|
||||
"open-clip-torch==2.24.0",
|
||||
"opencv-python==4.9.0.80",
|
||||
"pydantic-settings==2.11.0",
|
||||
"pytorch-fid==0.3.0",
|
||||
"redis==6.4.0",
|
||||
"setuptools==80.9.0",
|
||||
"torch-fidelity==0.3.0",
|
||||
"torchmetrics==1.4.0.post0",
|
||||
"transformers==4.41.1",
|
||||
]
|
||||
Reference in New Issue
Block a user