feat:1.移除所有明文服务密钥,采用环境变量方式读取
2.回调信息简化 \ stylist_agent_server.py中 一部分逻辑更新
This commit is contained in:
@@ -1,2 +0,0 @@
|
|||||||
GEMINI_API_KEY=AIzaSyAO4zXFke1bqyrXd9-RGfKJTLerwLSFKww
|
|
||||||
GOOGLE_APPLICATION_CREDENTIALS="/workspace/lc_stylist_agent/app/request.json"
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -3,4 +3,7 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
data/
|
data/
|
||||||
.idea/
|
.idea/
|
||||||
*.log
|
*.log
|
||||||
|
*.toml
|
||||||
|
.prod_env
|
||||||
|
google_application_credentials.json
|
||||||
@@ -3,8 +3,8 @@ import os
|
|||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
|
|
||||||
# ⚠️ 注意: 您需要安装 pydantic-settings: pip install pydantic-settings
|
# ⚠️ 注意: 您需要安装 pydantic-settings: pip install pydantic-settings
|
||||||
DEBUG = os.environ.get("DEBUG", 1)
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
@@ -35,13 +35,17 @@ class Settings(BaseSettings):
|
|||||||
STYLIST_GUIDE_DIR: str = Field(default="/workspace/lc_stylist_agent/data/stylist_guide", description="风格指南文本目录")
|
STYLIST_GUIDE_DIR: str = Field(default="/workspace/lc_stylist_agent/data/stylist_guide", description="风格指南文本目录")
|
||||||
|
|
||||||
# 向量数据库配置参数
|
# 向量数据库配置参数
|
||||||
if DEBUG == 1:
|
VECTOR_DB_DIR: str = Field(default="/db", description="向量数据库目录")
|
||||||
VECTOR_DB_DIR: str = Field(default="/workspace/lc_stylist_agent/db", description="向量数据库目录")
|
|
||||||
else:
|
|
||||||
VECTOR_DB_DIR: str = Field(default="/db", description="向量数据库目录")
|
|
||||||
COLLECTION_NAME: str = Field(default="lc_clothing_embedding", description="向量数据库集合名称")
|
COLLECTION_NAME: str = Field(default="lc_clothing_embedding", description="向量数据库集合名称")
|
||||||
EMBEDDING_MODEL_NAME: str = Field(default="openai/clip-vit-base-patch32", description="CLIP嵌入模型名称")
|
EMBEDDING_MODEL_NAME: str = Field(default="openai/clip-vit-base-patch32", description="CLIP嵌入模型名称")
|
||||||
|
|
||||||
|
# minio配置
|
||||||
|
MINIO_URL: str = Field(default="", description="URL")
|
||||||
|
MINIO_ACCESS: str = Field(default="", description="ACCESS")
|
||||||
|
MINIO_SECRET: str = Field(default="", description="SECRET")
|
||||||
|
MINIO_SECURE: bool = Field(default=True, description="SECRET")
|
||||||
|
MINIO_LC_DATA_PATH: str = Field(default="", description="图片数据路径")
|
||||||
|
|
||||||
|
|
||||||
# 创建配置实例,供应用其他部分使用
|
# 创建配置实例,供应用其他部分使用
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import logging.config
|
import logging.config
|
||||||
import os
|
import os
|
||||||
import litserve as ls
|
import litserve as ls
|
||||||
from app.config import DEBUG, settings
|
from app.config import settings
|
||||||
from app.server.ChatbotAgent.agent_server import LCAgent
|
from app.server.ChatbotAgent.agent_server import LCAgent
|
||||||
from app.server.ChatbotAgent.chatbot_server import LCChatBot
|
from app.server.ChatbotAgent.chatbot_server import LCChatBot
|
||||||
from app.server.ReFace.server import ReFace
|
from app.server.ReFace.server import ReFace
|
||||||
@@ -21,7 +21,7 @@ logging.config.dictConfig(LOGGER_CONFIG_DICT)
|
|||||||
|
|
||||||
# STEP 2: START THE SERVER
|
# STEP 2: START THE SERVER
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logger.info(f"DEBUG -> :{DEBUG}")
|
logger.info(f"运行环境 1表示本地运行,0表示生产环境运行 : -> :{settings.LOCAL}")
|
||||||
logger.info(f"VECTOR_DB_DIR -> :{settings.VECTOR_DB_DIR}")
|
logger.info(f"VECTOR_DB_DIR -> :{settings.VECTOR_DB_DIR}")
|
||||||
chat_boot_api = LCChatBot(enable_async=True, stream=True, api_path='/api/v1/chatbot')
|
chat_boot_api = LCChatBot(enable_async=True, stream=True, api_path='/api/v1/chatbot')
|
||||||
agent_api = LCAgent(enable_async=True, api_path='/api/v1/agent')
|
agent_api = LCAgent(enable_async=True, api_path='/api/v1/agent')
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "service_account",
|
|
||||||
"project_id": "aida-461108",
|
|
||||||
"private_key_id": "b4afaabebb84da24502b318a5fa175f1dc5c096a",
|
|
||||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmk7LKrp8g9yD1\nWmF+mY2qHCEZ/5aIx6QRh0QoVPBL7Yi7ce009QxaE8fu8+QMgg8l3xMreXvgpt56\noFnVwpFusLjSdjgoFluElM2hYxXlO9q8cbBoU2nehOBLLJzGzkodT7xu/BOjNvKC\n//aTbjtJyk8Kj+ENa0/dPaUZs/PCtQqpAu8ag5nXrordVWfO0K25EjeYyoba35zk\nPp2fBi8KALZZI5Xfd2z9++K0K2mWWIMJic30idHvquj0WxlTRK2Pq8BmJXCQpJIi\nQ5E4egue16BfKjrF0Kxkpqd1RmdlEmaSKbbkZXe2z4jg0qknESRFOmRy8C3LnaB2\nHHJWLYM3AgMBAAECggEACUdroOQJSTTQSS/iWRhZ+S0yoC10nTnsZxg527qfiBs7\nOqB7WNqC+Ew8dDsca6CdvLuoaGDkCFJDTQwRn66u8JOM4sG4bxiPuzBEJBv45EQT\n8zCsuvhVNWgBdoPjAnq19jFdixvPnDqQrRYaY4FdxsaA5f24c57pW/xLGMYawLBt\n9RJZSuWmJdzKG1i5W8a8+4f/seNtuo2MtXU3mPJZPqRWPXTAZeaQPM/57ZQ+kzig\nOkAbQZNRmt1yPCjPCQD8vc8yCBMmjus/rlHXD/L7okYUlVZkob5I3FBrLl+ZyIXS\nqxEsBLBwRW3w8WbX+ZSVciQ72JK68W7LnOHSAENmAQKBgQDgBTCqp87KGLWVPb8w\nK+s1Sfh+nM3M4AlbLdcGBs1JCoddF6pAeY4wpf/ow1Tm4rqEuCYzMClPwxvkue+D\nY7lCQgy2FK3ahUzn8oVmvEPD/YPAojDSY3bH0lquHuS6oVKk834JUykButaAU3XY\nvUGNQuKdLKAeQRT8Q6um4m+EYQKBgQC+Wz6nYESKH6GiNnuFTH8hIkThPlbi4wua\nU1kGnPKe3ouE4zRLfPwQ6RRf1slQ/2hFLOatiTLYUgZWZQeBPSWp2EjYcOSzob+7\n11+KqeIRCD5DKxgf0cjJdihK9AM639OKlH2NvZ2507TksdeTPDzdaOMLwLWKexP5\nlYrdob0ulwKBgD81t7Gvf83Ogw4FSjkRa2Cx6ofvPrKcVIeBu7ZbnPkLG37M+qEO\nq2xWqorG8uHi/7YLL9wprr5u0yQKwuZT8SYc9PE7jIKoMjcQW0vNu2FF2zMzkIsM\nvatMU4Hl/awbcPJSMjH3YQ635WZ4Jjxtyl1NjhvDR7rBqmYzwe9o3QaBAoGANhPB\n1tbYYczepDCKIrI6o3US0FJfaJFLqInpDqHjoxJh3FyXbKKTEVLFwPxJsML+IjjB\nR6dkVGPo/P4yhZqTao7REvvvXMCksX5b3A6q9F+9IGPLtK5qNiFlDPYJPN59QC8z\nA+NMPZBRIW8MaP2B5Px5E8upRy/z2sGK86+RCP0CgYATGs75F97q+Zf8q+Pe3Nsb\ngqmhLoI3PZUSWgBcQgNF4nyCZceUrEl72wKO/NWLgxqQPtlra187ce69g7qARHLb\ntHq80nb0f7lil74B6+OlyNNO1htWA90fmGR2s16Mt0BwJRT+/EFuNqbJIUSLxKiW\nqlXBUbmHHzamo5DPYL8S/w==\n-----END PRIVATE KEY-----\n",
|
|
||||||
"client_email": "aida-239@aida-461108.iam.gserviceaccount.com",
|
|
||||||
"client_id": "103102077955178349079",
|
|
||||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
|
||||||
"token_uri": "https://oauth2.googleapis.com/token",
|
|
||||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
|
||||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/aida-239%40aida-461108.iam.gserviceaccount.com",
|
|
||||||
"universe_domain": "googleapis.com"
|
|
||||||
}
|
|
||||||
@@ -38,6 +38,7 @@ class OccasionEnum(str, Enum):
|
|||||||
SKI_SNOW_MOUNTAIN = "Ski / Snow / Mountain"
|
SKI_SNOW_MOUNTAIN = "Ski / Snow / Mountain"
|
||||||
GARDEN_PARTY_DAYTIME = "Garden Party / Daytime Event"
|
GARDEN_PARTY_DAYTIME = "Garden Party / Daytime Event"
|
||||||
|
|
||||||
|
|
||||||
class StylistResponse(BaseModel):
|
class StylistResponse(BaseModel):
|
||||||
occasions: List[OccasionEnum] = Field(
|
occasions: List[OccasionEnum] = Field(
|
||||||
description="A list of **applicable** occasions that are most strongly implied or explicitly requested by the user's conversation history. These occasions are used later in item retrieval for filtering and must strictly match the predefined OccasionEnum list."
|
description="A list of **applicable** occasions that are most strongly implied or explicitly requested by the user's conversation history. These occasions are used later in item retrieval for filtering and must strictly match the predefined OccasionEnum list."
|
||||||
@@ -55,6 +56,7 @@ class AgentRequestModel(BaseModel):
|
|||||||
batch_sources: List[str]
|
batch_sources: List[str]
|
||||||
callback_url: str
|
callback_url: str
|
||||||
gender: str
|
gender: str
|
||||||
|
is_first_request: bool
|
||||||
|
|
||||||
|
|
||||||
class LCAgent(ls.LitAPI):
|
class LCAgent(ls.LitAPI):
|
||||||
@@ -118,7 +120,8 @@ class LCAgent(ls.LitAPI):
|
|||||||
user_id=request.user_id,
|
user_id=request.user_id,
|
||||||
gender=request.gender,
|
gender=request.gender,
|
||||||
callback_url=request.callback_url,
|
callback_url=request.callback_url,
|
||||||
outfit_ids=outfit_ids
|
outfit_ids=outfit_ids,
|
||||||
|
is_first_request=request.is_first_request
|
||||||
)
|
)
|
||||||
logger.info("--- Final Recommendation Results ---")
|
logger.info("--- Final Recommendation Results ---")
|
||||||
for i, path in enumerate(recommendation_results.get("successful_outfits", [])):
|
for i, path in enumerate(recommendation_results.get("successful_outfits", [])):
|
||||||
@@ -141,37 +144,38 @@ class LCAgent(ls.LitAPI):
|
|||||||
|
|
||||||
input_message = "\n".join([f"{msg.role.value}: {msg.content}" for msg in history_messages])
|
input_message = "\n".join([f"{msg.role.value}: {msg.content}" for msg in history_messages])
|
||||||
json_schema = StylistResponse.model_json_schema()
|
json_schema = StylistResponse.model_json_schema()
|
||||||
|
|
||||||
raw_response = await self.llm.generate_response(
|
raw_response = await self.llm.generate_response(
|
||||||
history=[Message(role=Role.USER, content=input_message)],
|
history=[Message(role=Role.USER, content=input_message)],
|
||||||
system_prompt=SUMMARY_PROMPT,
|
system_prompt=SUMMARY_PROMPT,
|
||||||
json_schema=json_schema
|
json_schema=json_schema
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 验证并解析 JSON
|
# 验证并解析 JSON
|
||||||
parsed_result = StylistResponse.model_validate_json(raw_response)
|
parsed_result = StylistResponse.model_validate_json(raw_response)
|
||||||
|
|
||||||
print(f"Occasions: {[occ.value for occ in parsed_result.occasions]}")
|
print(f"Occasions: {[occ.value for occ in parsed_result.occasions]}")
|
||||||
print(f"Summary: {parsed_result.summary}") # 这是一个 string
|
print(f"Summary: {parsed_result.summary}") # 这是一个 string
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Schema validation failed: {e}")
|
logger.error(f"Schema validation failed: {e}")
|
||||||
|
|
||||||
return str(parsed_result.summary), [occ.value for occ in parsed_result.occasions]
|
return str(parsed_result.summary), [occ.value for occ in parsed_result.occasions]
|
||||||
|
|
||||||
async def recommend_outfit(
|
async def recommend_outfit(
|
||||||
self,
|
self,
|
||||||
request_summary: str,
|
request_summary: str,
|
||||||
occasions: List[str],
|
occasions: List[str],
|
||||||
stylist_name: str,
|
stylist_name: str,
|
||||||
start_outfit: List = [],
|
start_outfit: List = [],
|
||||||
batch_sources: List[str] = [],
|
batch_sources: List[str] = [],
|
||||||
num_outfits: int = 1,
|
num_outfits: int = 1,
|
||||||
user_id: str = "test",
|
user_id: str = "test",
|
||||||
gender: str = "male",
|
gender: str = "male",
|
||||||
callback_url: str = None,
|
callback_url: str = None,
|
||||||
outfit_ids=None
|
outfit_ids=None,
|
||||||
|
is_first_request=False
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
基于用户的对话历史和需求,推荐一套搭配。
|
基于用户的对话历史和需求,推荐一套搭配。
|
||||||
@@ -191,14 +195,26 @@ class LCAgent(ls.LitAPI):
|
|||||||
stylist_agent_kwages['stylist_name'] = stylist_name
|
stylist_agent_kwages['stylist_name'] = stylist_name
|
||||||
stylist_agent_kwages['gender'] = gender
|
stylist_agent_kwages['gender'] = gender
|
||||||
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
||||||
task = agent.run_iterative_styling(
|
if is_first_request:
|
||||||
request_summary=request_summary,
|
# 第一套搭配使用快速方法 一次跑出所有单品
|
||||||
occasions=occasions,
|
task = agent.run_quick_batch_styling(
|
||||||
start_outfit=start_outfit,
|
request_summary=request_summary,
|
||||||
batch_sources=batch_sources,
|
occasions=occasions,
|
||||||
user_id=user_id,
|
start_outfit=start_outfit,
|
||||||
callback_url=callback_url,
|
batch_sources=batch_sources,
|
||||||
)
|
user_id=user_id,
|
||||||
|
callback_url=callback_url,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 后续
|
||||||
|
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)
|
tasks.append(task)
|
||||||
task_map[task] = {"outfit_id": outfit_ids[i], "retries": 0}
|
task_map[task] = {"outfit_id": outfit_ids[i], "retries": 0}
|
||||||
logger.info(f"--- Starting {num_outfits} concurrent outfit generation tasks. ---")
|
logger.info(f"--- Starting {num_outfits} concurrent outfit generation tasks. ---")
|
||||||
@@ -232,7 +248,7 @@ class LCAgent(ls.LitAPI):
|
|||||||
stylist_agent_kwages['stylist_name'] = stylist_name
|
stylist_agent_kwages['stylist_name'] = stylist_name
|
||||||
stylist_agent_kwages['gender'] = gender
|
stylist_agent_kwages['gender'] = gender
|
||||||
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
||||||
new_task = agent.run_iterative_styling(
|
new_task = agent.run_quick_batch_styling(
|
||||||
request_summary=request_summary,
|
request_summary=request_summary,
|
||||||
occasions=occasions,
|
occasions=occasions,
|
||||||
start_outfit=start_outfit,
|
start_outfit=start_outfit,
|
||||||
@@ -283,12 +299,12 @@ if __name__ == "__main__":
|
|||||||
async def test():
|
async def test():
|
||||||
# 1. 准备测试实例
|
# 1. 准备测试实例
|
||||||
agent_api = LCAgent()
|
agent_api = LCAgent()
|
||||||
agent_api.setup(device='cpu')
|
agent_api.setup(device='cpu')
|
||||||
|
|
||||||
# 2. 准备请求数据
|
# 2. 准备请求数据
|
||||||
import json
|
import json
|
||||||
stylist_agent_kwages = agent_api.stylist_agent_kwages.copy()
|
stylist_agent_kwages = agent_api.stylist_agent_kwages.copy()
|
||||||
with open("./data/2025_q4/request_test.json", "r") as f:
|
with open("/mnt/data/workspace/Code/lc_stylist_agent/data/2025_q4/request_test.json", "r") as f:
|
||||||
request_data = json.load(f)
|
request_data = json.load(f)
|
||||||
|
|
||||||
tasks_with_metadata = []
|
tasks_with_metadata = []
|
||||||
@@ -300,18 +316,18 @@ if __name__ == "__main__":
|
|||||||
stylist_agent_kwages['stylist_name'] = stylist_name
|
stylist_agent_kwages['stylist_name'] = stylist_name
|
||||||
stylist_agent_kwages['gender'] = "female"
|
stylist_agent_kwages['gender'] = "female"
|
||||||
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
agent = AsyncStylistAgent(**stylist_agent_kwages)
|
||||||
coro = agent.run_iterative_styling(
|
# coro = agent.run_iterative_styling(
|
||||||
# coro = agent.run_quick_batch_styling(
|
coro = agent.run_quick_batch_styling(
|
||||||
request_summary=request_summary,
|
request_summary=request_summary,
|
||||||
occasions=occasions,
|
occasions=occasions,
|
||||||
start_outfit=[],
|
start_outfit=[],
|
||||||
batch_sources=["2025_q4"],
|
batch_sources=["2025_q4"],
|
||||||
user_id=test_content['test_case_id'],
|
user_id=test_content['test_case_id'],
|
||||||
callback_url="http://mock-callback.com/result",
|
callback_url="http://18.167.251.121:10095",
|
||||||
)
|
)
|
||||||
# 记录任务开始前的单调时间,并将元数据添加到列表中
|
# 记录任务开始前的单调时间,并将元数据添加到列表中
|
||||||
description = f"Batch mode - Case {test_content['test_case_id']} - Stylist {stylist_name}"
|
description = f"Batch mode - Case {test_content['test_case_id']} - Stylist {stylist_name}"
|
||||||
|
|
||||||
tasks_with_metadata.append((coro, description))
|
tasks_with_metadata.append((coro, description))
|
||||||
|
|
||||||
tasks_only = [coro for coro, _ in tasks_with_metadata]
|
tasks_only = [coro for coro, _ in tasks_with_metadata]
|
||||||
@@ -331,8 +347,9 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
print(f"Average time consumption is {sum(time_samples) / len(time_samples)}")
|
print(f"Average time consumption is {sum(time_samples) / len(time_samples)}")
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用 asyncio.run() 来执行顶层异步函数
|
# 使用 asyncio.run() 来执行顶层异步函数
|
||||||
asyncio.run(test())
|
asyncio.run(test())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Test failed due to an unexpected error: {e}")
|
logger.error(f"Test failed due to an unexpected error: {e}")
|
||||||
|
|||||||
@@ -128,10 +128,9 @@ class AsyncStylistAgent:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(存储的路径, 内存图片数据)
|
(存储的路径, 内存图片数据)
|
||||||
"""
|
"""
|
||||||
if not self.outfit_items:
|
if not self.outfit_items:
|
||||||
return "", None
|
return "", None
|
||||||
|
|
||||||
merged_image = merge_images_to_square(self.outfit_items, max_len=9, add_text=False)
|
merged_image = merge_images_to_square(self.outfit_items, max_len=9, add_text=False)
|
||||||
image_bytes_io = io.BytesIO()
|
image_bytes_io = io.BytesIO()
|
||||||
image_format = 'JPEG'
|
image_format = 'JPEG'
|
||||||
@@ -139,14 +138,15 @@ class AsyncStylistAgent:
|
|||||||
image_bytes = image_bytes_io.getvalue()
|
image_bytes = image_bytes_io.getvalue()
|
||||||
if settings.LOCAL == 1:
|
if settings.LOCAL == 1:
|
||||||
local_dir = os.path.join(settings.OUTFIT_OUTPUT_DIR, stylist_name)
|
local_dir = os.path.join(settings.OUTFIT_OUTPUT_DIR, stylist_name)
|
||||||
os.makedirs(local_dir, exist_ok=True)
|
os.makedirs(local_dir, exist_ok=True)
|
||||||
local_file_path = os.path.join(local_dir, f"{file_name}.jpg")
|
local_file_path = os.path.join(local_dir, f"{file_name}.jpg")
|
||||||
|
|
||||||
with open(local_file_path, 'wb') as f:
|
with open(local_file_path, 'wb') as f:
|
||||||
f.write(image_bytes)
|
f.write(image_bytes)
|
||||||
return local_file_path, image_bytes
|
return local_file_path, image_bytes
|
||||||
else:
|
else:
|
||||||
blob_name = f"lc_stylist_agent_outfit_items/{user_id}/{file_name}.jpg"
|
# minio文件地址需保持变动,否则前端缓存导致无法更新图片
|
||||||
|
blob_name = f"lc_stylist_agent_outfit_items/{user_id}/{file_name}-{len(self.outfit_items)}.jpg"
|
||||||
responses = oss_upload_image(oss_client=minio_client, bucket=self.minio_bucket, object_name=blob_name, image_bytes=image_bytes)
|
responses = oss_upload_image(oss_client=minio_client, bucket=self.minio_bucket, object_name=blob_name, image_bytes=image_bytes)
|
||||||
minio_path = f"{responses.bucket_name}/{responses.object_name}"
|
minio_path = f"{responses.bucket_name}/{responses.object_name}"
|
||||||
return minio_path, image_bytes
|
return minio_path, image_bytes
|
||||||
@@ -176,11 +176,11 @@ class AsyncStylistAgent:
|
|||||||
# 2. 执行查询,并过滤类别
|
# 2. 执行查询,并过滤类别
|
||||||
try:
|
try:
|
||||||
results = self.local_db.get_matched_item(
|
results = self.local_db.get_matched_item(
|
||||||
query_embedding,
|
query_embedding,
|
||||||
category,
|
category,
|
||||||
occasions=occasions,
|
occasions=occasions,
|
||||||
batch_sources=batch_sources,
|
batch_sources=batch_sources,
|
||||||
gender=gender,
|
gender=gender,
|
||||||
n_results=1
|
n_results=1
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -206,8 +206,8 @@ class AsyncStylistAgent:
|
|||||||
# 这里假设 item_id 就是文件名的一部分
|
# 这里假设 item_id 就是文件名的一部分
|
||||||
"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")
|
||||||
}
|
}
|
||||||
|
|
||||||
def _build_system_prompt(self, template: str, request_summary: str = "", stylist_guide: str = "", current_category: str = "clothing", max_len: int=4) -> str:
|
def _build_system_prompt(self, template: str, request_summary: str = "", stylist_guide: str = "", current_category: str = "clothing", max_len: int = 4) -> str:
|
||||||
# Insert the style_guide content into the template
|
# Insert the style_guide content into the template
|
||||||
sys_template = template.format(
|
sys_template = template.format(
|
||||||
gender=self.gender,
|
gender=self.gender,
|
||||||
@@ -224,11 +224,11 @@ class AsyncStylistAgent:
|
|||||||
context = ""
|
context = ""
|
||||||
else:
|
else:
|
||||||
context = "Selected fashion items:\n"
|
context = "Selected fashion items:\n"
|
||||||
|
|
||||||
# 将已选单品的信息作为上下文发回给 Agent
|
# 将已选单品的信息作为上下文发回给 Agent
|
||||||
for ii, item in enumerate(self.outfit_items):
|
for ii, item in enumerate(self.outfit_items):
|
||||||
context += f"{ii + 1}. Category: {item['category']}. Subcategory: {item['subcategory']}. Description: {item['description']}\n"
|
context += f"{ii + 1}. Category: {item['category']}. Subcategory: {item['subcategory']}. Description: {item['description']}\n"
|
||||||
|
|
||||||
if current_category == 'clothing':
|
if current_category == 'clothing':
|
||||||
context += f"\nRecommend the next single item based on the selected items, user's request, and style guide. 【CRITICAL CONSTRAINT】You MUST strictly **maintain uniqueness**; do not recommend any clothing whose **Subcategory** is already present in this exclusion list: {existing_subcategories}."
|
context += f"\nRecommend the next single item based on the selected items, user's request, and style guide. 【CRITICAL CONSTRAINT】You MUST strictly **maintain uniqueness**; do not recommend any clothing whose **Subcategory** is already present in this exclusion list: {existing_subcategories}."
|
||||||
elif current_category in ['shoes', 'bags']:
|
elif current_category in ['shoes', 'bags']:
|
||||||
@@ -238,33 +238,42 @@ class AsyncStylistAgent:
|
|||||||
elif current_category == 'all':
|
elif current_category == 'all':
|
||||||
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."
|
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
|
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):
|
||||||
"""处理完成后的回调操作。"""
|
"""处理完成后的回调操作。"""
|
||||||
if settings.LOCAL == 0:
|
if settings.LOCAL == 0:
|
||||||
|
# 生产回调请求数据处理
|
||||||
|
items = []
|
||||||
|
for item in self.outfit_items:
|
||||||
|
items.append(
|
||||||
|
{
|
||||||
|
"item_id": item['item_id'],
|
||||||
|
"category": item['subcategory']
|
||||||
|
}
|
||||||
|
)
|
||||||
response_data = {
|
response_data = {
|
||||||
'items': deepcopy(self.outfit_items),
|
'items': items,
|
||||||
'status': status,
|
'status': status,
|
||||||
'message': message,
|
# 'message': message,
|
||||||
'path': img_path,
|
'path': img_path,
|
||||||
'outfit_id': self.outfit_id
|
'outfit_id': self.outfit_id
|
||||||
}
|
}
|
||||||
response = post_request(url=callback_url, data=json.dumps(response_data), headers=self.headers)
|
response = post_request(url=callback_url, data=json.dumps(response_data), headers=self.headers)
|
||||||
logger.info(f"request data :{response_data} | 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}")
|
||||||
return response_data
|
return response_data
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
async def _execute_iterative_recommendation(
|
async def _execute_iterative_recommendation(
|
||||||
self,
|
self,
|
||||||
current_category: str,
|
current_category: str,
|
||||||
system_prompt: str,
|
system_prompt: str,
|
||||||
schema: Dict,
|
schema: Dict,
|
||||||
max_len: int,
|
max_len: int,
|
||||||
occasions: List[str],
|
occasions: List[str],
|
||||||
batch_sources: List[str],
|
batch_sources: List[str],
|
||||||
user_id: str,
|
user_id: str,
|
||||||
url: str
|
url: str
|
||||||
):
|
):
|
||||||
recommend_timestep = 0
|
recommend_timestep = 0
|
||||||
gemini_data = {'action': 'start'}
|
gemini_data = {'action': 'start'}
|
||||||
@@ -279,11 +288,11 @@ class AsyncStylistAgent:
|
|||||||
|
|
||||||
# 3. 调用 Gemini Agent
|
# 3. 调用 Gemini Agent
|
||||||
gemini_response_text = await self._call_gemini(
|
gemini_response_text = await self._call_gemini(
|
||||||
user_input,
|
user_input,
|
||||||
user_id,
|
user_id,
|
||||||
self.outfit_id,
|
self.outfit_id,
|
||||||
schema,
|
schema,
|
||||||
image_bytes,
|
image_bytes,
|
||||||
system_prompt
|
system_prompt
|
||||||
)
|
)
|
||||||
gemini_data = self._parse_gemini_response(gemini_response_text)
|
gemini_data = self._parse_gemini_response(gemini_response_text)
|
||||||
@@ -291,8 +300,8 @@ class AsyncStylistAgent:
|
|||||||
if not gemini_data:
|
if not gemini_data:
|
||||||
print("Agent 返回无效响应,终止流程。")
|
print("Agent 返回无效响应,终止流程。")
|
||||||
self.post_operation(
|
self.post_operation(
|
||||||
status="failed",
|
status="failed",
|
||||||
message="Agent returned invalid response, terminating process.",
|
message="Agent returned invalid response, terminating process.",
|
||||||
callback_url=url,
|
callback_url=url,
|
||||||
img_path=merged_image_path,
|
img_path=merged_image_path,
|
||||||
)
|
)
|
||||||
@@ -306,8 +315,8 @@ class AsyncStylistAgent:
|
|||||||
# 4a. 检查类别是否有效 (重要步骤)
|
# 4a. 检查类别是否有效 (重要步骤)
|
||||||
if subcategory not in FASHION_TAXONOMY[current_category]:
|
if subcategory not in FASHION_TAXONOMY[current_category]:
|
||||||
self.post_operation(
|
self.post_operation(
|
||||||
status="continue",
|
status="continue",
|
||||||
message=f"Invalid subcategory recommended by Agent: {subcategory}. Requesting Agent to re-output.",
|
message=f"Invalid subcategory recommended by Agent: {subcategory}. Requesting Agent to re-output.",
|
||||||
callback_url=url,
|
callback_url=url,
|
||||||
img_path=merged_image_path,
|
img_path=merged_image_path,
|
||||||
)
|
)
|
||||||
@@ -317,24 +326,24 @@ class AsyncStylistAgent:
|
|||||||
new_item = self._get_next_item(description, current_category, subcategory, occasions, batch_sources, self.gender)
|
new_item = self._get_next_item(description, current_category, subcategory, occasions, batch_sources, self.gender)
|
||||||
if not new_item:
|
if not new_item:
|
||||||
self.post_operation(
|
self.post_operation(
|
||||||
status="continue",
|
status="continue",
|
||||||
message=f"No matching item is found. Ask Gemini to re-output.",
|
message=f"No matching item is found. Ask Gemini to re-output.",
|
||||||
callback_url=url,
|
callback_url=url,
|
||||||
img_path=merged_image_path,
|
img_path=merged_image_path,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
elif new_item['subcategory'] in [x['subcategory'] for x in self.outfit_items]:
|
elif new_item['subcategory'] in [x['subcategory'] for x in self.outfit_items]:
|
||||||
self.post_operation(
|
self.post_operation(
|
||||||
status="continue",
|
status="continue",
|
||||||
message=f"{new_item['item_id']}'s subcategory {new_item['subcategory']} duplicated. Ask Gemini to re-output.",
|
message=f"{new_item['item_id']}'s subcategory {new_item['subcategory']} duplicated. Ask Gemini to re-output.",
|
||||||
callback_url=url,
|
callback_url=url,
|
||||||
img_path=merged_image_path,
|
img_path=merged_image_path,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
elif new_item['item_id'] in [x['item_id'] for x in self.outfit_items]:
|
elif new_item['item_id'] in [x['item_id'] for x in self.outfit_items]:
|
||||||
self.post_operation(
|
self.post_operation(
|
||||||
status="continue",
|
status="continue",
|
||||||
message=f"Item {new_item['item_id']} duplicated. Ask Gemini to re-output.",
|
message=f"Item {new_item['item_id']} duplicated. Ask Gemini to re-output.",
|
||||||
callback_url=url,
|
callback_url=url,
|
||||||
img_path=merged_image_path,
|
img_path=merged_image_path,
|
||||||
)
|
)
|
||||||
@@ -343,43 +352,45 @@ class AsyncStylistAgent:
|
|||||||
self.outfit_items.append(new_item)
|
self.outfit_items.append(new_item)
|
||||||
existing_subcategories.append(new_item["subcategory"])
|
existing_subcategories.append(new_item["subcategory"])
|
||||||
self.post_operation(
|
self.post_operation(
|
||||||
status="ok",
|
status="ok",
|
||||||
message=f"Add new item {new_item['item_id']} in category {new_item['category']} successfully.",
|
message=f"Add new item {new_item['item_id']} in category {new_item['category']} successfully.",
|
||||||
callback_url=url,
|
callback_url=url,
|
||||||
img_path=merged_image_path,
|
img_path=merged_image_path,
|
||||||
)
|
)
|
||||||
print(f"Stage {current_category.upper()}, Step {recommend_timestep}: {gemini_data}, found item: {new_item['item_id']}")
|
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
|
||||||
system_prompt: str,
|
system_prompt: str,
|
||||||
schema: Dict,
|
schema: Dict,
|
||||||
occasions: List[str],
|
occasions: List[str],
|
||||||
batch_sources: List[str],
|
batch_sources: List[str],
|
||||||
user_id: str,
|
user_id: str,
|
||||||
url: str
|
url: str
|
||||||
):
|
):
|
||||||
user_input = self._build_user_input(current_category, existing_subcategories=", ".join([x['subcategory'] for x in self.outfit_items]))
|
user_input = self._build_user_input(current_category, existing_subcategories=", ".join([x['subcategory'] for x in self.outfit_items]))
|
||||||
|
# 合并图片
|
||||||
merged_image_path, image_bytes = await self._merge_images(self.outfit_id, user_id, self.stylist_name)
|
merged_image_path, image_bytes = await self._merge_images(self.outfit_id, user_id, self.stylist_name)
|
||||||
|
# 调用Gemini API
|
||||||
gemini_response_text = await self._call_gemini(
|
gemini_response_text = await self._call_gemini(
|
||||||
user_input,
|
user_input,
|
||||||
user_id,
|
user_id,
|
||||||
self.outfit_id,
|
self.outfit_id,
|
||||||
schema,
|
schema,
|
||||||
image_bytes,
|
image_bytes,
|
||||||
system_prompt
|
system_prompt
|
||||||
)
|
)
|
||||||
|
# 解析响应
|
||||||
gemini_data = self._parse_gemini_response(gemini_response_text)
|
gemini_data = self._parse_gemini_response(gemini_response_text)
|
||||||
recommended_items = gemini_data.get('recommended_items', [])
|
recommended_items = gemini_data.get('recommended_items', [])
|
||||||
reason = gemini_data.get('reason', '')
|
reason = gemini_data.get('reason', '')
|
||||||
|
|
||||||
if not recommended_items or not isinstance(recommended_items, List):
|
if not recommended_items or not isinstance(recommended_items, List):
|
||||||
print("No recommended item from Gemini, terminating process.")
|
print("No recommended item from Gemini, terminating process.")
|
||||||
self.post_operation(
|
self.post_operation(
|
||||||
status="failed",
|
status="failed",
|
||||||
message="Agent returned invalid response, terminating process.",
|
message="Agent returned invalid response, terminating process.",
|
||||||
callback_url=url,
|
callback_url=url,
|
||||||
img_path=merged_image_path
|
img_path=merged_image_path
|
||||||
)
|
)
|
||||||
@@ -410,12 +421,15 @@ class AsyncStylistAgent:
|
|||||||
self.outfit_items.append(new_item)
|
self.outfit_items.append(new_item)
|
||||||
print(f"Item {idx + 1}: ({subcategory}) {rec_item}, found item: {new_item}")
|
print(f"Item {idx + 1}: ({subcategory}) {rec_item}, found item: {new_item}")
|
||||||
return reason
|
return reason
|
||||||
|
|
||||||
|
|
||||||
async def run_iterative_styling(self, request_summary, occasions, start_outfit=[], batch_sources=[], user_id="test", callback_url=""):
|
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()
|
start_time = time.monotonic()
|
||||||
STAGES = ['clothing', 'shoes', 'bags']
|
STAGES = ['clothing', 'shoes', 'bags']
|
||||||
self.outfit_items = start_outfit
|
# 深拷贝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)
|
stylist_guide, accessories_guide = self._load_style_guide(self.stylist_name)
|
||||||
url = f'{callback_url}/api/style/callback'
|
url = f'{callback_url}/api/style/callback'
|
||||||
|
|
||||||
@@ -426,7 +440,7 @@ class AsyncStylistAgent:
|
|||||||
system_prompt = self._build_system_prompt(core_outfit_template, request_summary, stylist_guide, current_category, max_len)
|
system_prompt = self._build_system_prompt(core_outfit_template, request_summary, stylist_guide, current_category, max_len)
|
||||||
|
|
||||||
await self._execute_iterative_recommendation(
|
await self._execute_iterative_recommendation(
|
||||||
current_category,
|
current_category,
|
||||||
system_prompt,
|
system_prompt,
|
||||||
build_iterative_schema(current_category),
|
build_iterative_schema(current_category),
|
||||||
max_len,
|
max_len,
|
||||||
@@ -440,16 +454,16 @@ class AsyncStylistAgent:
|
|||||||
MAX_LEN_ACC = 3
|
MAX_LEN_ACC = 3
|
||||||
acc_system_prompt = self._build_system_prompt(accessories_template, request_summary, accessories_guide, 'accessories', MAX_LEN_ACC)
|
acc_system_prompt = self._build_system_prompt(accessories_template, request_summary, accessories_guide, 'accessories', MAX_LEN_ACC)
|
||||||
reason = await self._execute_batch_recommendation(
|
reason = await self._execute_batch_recommendation(
|
||||||
'accessories', # can be 'accessories' or 'all'
|
'accessories', # can be 'accessories' or 'all'
|
||||||
acc_system_prompt,
|
acc_system_prompt,
|
||||||
build_batch_schema(current_category),
|
build_batch_schema(current_category),
|
||||||
occasions,
|
occasions,
|
||||||
batch_sources,
|
batch_sources,
|
||||||
user_id,
|
user_id,
|
||||||
url
|
url
|
||||||
)
|
)
|
||||||
|
|
||||||
final_image_path = await self._merge_images(self.outfit_id, user_id, self.stylist_name)
|
final_image_path, _ = await self._merge_images(self.outfit_id, user_id, self.stylist_name)
|
||||||
response_data = self.post_operation(
|
response_data = self.post_operation(
|
||||||
status="stop",
|
status="stop",
|
||||||
message=reason,
|
message=reason,
|
||||||
@@ -458,17 +472,20 @@ class AsyncStylistAgent:
|
|||||||
)
|
)
|
||||||
if settings.LOCAL == 1:
|
if settings.LOCAL == 1:
|
||||||
with open(os.path.join(settings.OUTFIT_OUTPUT_DIR, self.stylist_name, f'{self.outfit_id}.json'), 'w') as f:
|
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)
|
json.dump({"request_summary": request_summary, "occasions": occasions, "items": self.outfit_items}, f, indent=2)
|
||||||
|
|
||||||
end_time = time.monotonic()
|
end_time = time.monotonic()
|
||||||
total_duration = end_time - start_time
|
total_duration = end_time - start_time
|
||||||
|
|
||||||
return response_data, total_duration
|
|
||||||
|
|
||||||
async def run_quick_batch_styling(self, request_summary, occasions, start_outfit=[], batch_sources=[], user_id="test", callback_url=""):
|
|
||||||
start_time = time.monotonic()
|
|
||||||
|
|
||||||
self.outfit_items = start_outfit
|
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=""):
|
||||||
|
start_time = time.monotonic()
|
||||||
|
# 深拷贝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)
|
stylist_guide, accessories_guide = self._load_style_guide(self.stylist_name)
|
||||||
url = f'{callback_url}/api/style/callback'
|
url = f'{callback_url}/api/style/callback'
|
||||||
|
|
||||||
@@ -477,7 +494,7 @@ class AsyncStylistAgent:
|
|||||||
MAX_LEN = 9
|
MAX_LEN = 9
|
||||||
system_prompt = self._build_system_prompt(all_items_template, request_summary, stylist_guide + accessories_guide, "", MAX_LEN)
|
system_prompt = self._build_system_prompt(all_items_template, request_summary, stylist_guide + accessories_guide, "", MAX_LEN)
|
||||||
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,
|
||||||
build_batch_schema(),
|
build_batch_schema(),
|
||||||
occasions,
|
occasions,
|
||||||
@@ -486,7 +503,7 @@ class AsyncStylistAgent:
|
|||||||
url
|
url
|
||||||
)
|
)
|
||||||
|
|
||||||
final_image_path = await self._merge_images(self.outfit_id, user_id, self.stylist_name)
|
final_image_path, _ = await self._merge_images(self.outfit_id, user_id, self.stylist_name)
|
||||||
response_data = self.post_operation(
|
response_data = self.post_operation(
|
||||||
status="stop",
|
status="stop",
|
||||||
message=reason,
|
message=reason,
|
||||||
@@ -495,11 +512,11 @@ class AsyncStylistAgent:
|
|||||||
)
|
)
|
||||||
if settings.LOCAL == 1:
|
if settings.LOCAL == 1:
|
||||||
with open(os.path.join(settings.OUTFIT_OUTPUT_DIR, self.stylist_name, f'{self.outfit_id}.json'), 'w') as f:
|
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)
|
json.dump({"request_summary": request_summary, "occasions": occasions, "items": self.outfit_items}, f, indent=2)
|
||||||
|
|
||||||
end_time = time.monotonic()
|
end_time = time.monotonic()
|
||||||
total_duration = end_time - start_time
|
total_duration = end_time - start_time
|
||||||
|
|
||||||
return response_data, total_duration
|
return response_data, total_duration
|
||||||
|
|
||||||
def _upload_to_gcs(self, bucket_name: str, blob_name: str, mime_type, image_bytes) -> str:
|
def _upload_to_gcs(self, bucket_name: str, blob_name: str, mime_type, image_bytes) -> str:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import os
|
|||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
from app.server.utils.minio_client import oss_get_image, minio_client
|
from app.server.utils.minio_client import oss_get_image, minio_client
|
||||||
from app.server.utils.minio_config import MINIO_LC_DATA_PATH
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -79,7 +78,8 @@ def merge_images_to_square(outfit_items: List[Dict[str, str]], max_len=9, add_te
|
|||||||
if settings.LOCAL == 1:
|
if settings.LOCAL == 1:
|
||||||
img = Image.open(path).convert('RGB')
|
img = Image.open(path).convert('RGB')
|
||||||
else:
|
else:
|
||||||
img = oss_get_image(oss_client=minio_client, path=f"{MINIO_LC_DATA_PATH}/{path}", data_type="PIL").convert('RGB')
|
img_name = path.rsplit('/', 1)[-1]
|
||||||
|
img = oss_get_image(oss_client=minio_client, path=f"{settings.MINIO_LC_DATA_PATH}/{img_name}", data_type="PIL").convert('RGB')
|
||||||
# img = Image.open(path).convert('RGB')
|
# img = Image.open(path).convert('RGB')
|
||||||
valid_images.append(img)
|
valid_images.append(img)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import urllib3
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from minio import Minio
|
from minio import Minio
|
||||||
|
|
||||||
from app.server.utils.minio_config import MINIO_ACCESS, MINIO_SECRET, MINIO_URL, MINIO_SECURE
|
from app.config import settings
|
||||||
|
|
||||||
minio_client = Minio(MINIO_URL, access_key=MINIO_ACCESS, secret_key=MINIO_SECRET, secure=MINIO_SECURE)
|
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
|
||||||
|
|
||||||
|
|
||||||
# 自定义 Retry 类
|
# 自定义 Retry 类
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
# minio 配置
|
|
||||||
MINIO_URL = "www.minio-api.aida.com.hk"
|
|
||||||
MINIO_ACCESS = 'vXKFLSJkYeEq2DrSZvkB'
|
|
||||||
MINIO_SECRET = 'uKTZT3x7C43WvPN9QTc99DiRkwddWZrG9Uh3JVlR'
|
|
||||||
MINIO_SECURE = True
|
|
||||||
MINIO_LC_DATA_PATH = "lanecarford/lc_image_data"
|
|
||||||
@@ -5,12 +5,13 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
environment:
|
environment:
|
||||||
GOOGLE_APPLICATION_CREDENTIALS: /app/app/request.json
|
GOOGLE_APPLICATION_CREDENTIALS: /google_application_credentials.json
|
||||||
DEBUG: 0
|
DEBUG: 0
|
||||||
volumes:
|
volumes:
|
||||||
- ./app:/app/app
|
- ./app:/app/app
|
||||||
- ./.env:/app/.env
|
- ./.prod_env:/app/.env
|
||||||
- ./db:/db
|
- ./data:/data
|
||||||
|
- ./google_application_credentials.json:/google_application_credentials.json
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
ports:
|
ports:
|
||||||
- "10070:8000"
|
- "10070:8000"
|
||||||
@@ -20,5 +21,5 @@ services:
|
|||||||
devices:
|
devices:
|
||||||
# 告诉 Docker 使用所有可用的 NVIDIA GPU
|
# 告诉 Docker 使用所有可用的 NVIDIA GPU
|
||||||
- driver: nvidia
|
- driver: nvidia
|
||||||
device_ids: ['0']
|
device_ids: [ '0' ]
|
||||||
capabilities: [ gpu ]
|
capabilities: [ gpu ]
|
||||||
0
docs/Edi.docx
Normal file → Executable file
0
docs/Edi.docx
Normal file → Executable file
0
docs/LC Recommendation Workflow.pdf
Normal file → Executable file
0
docs/LC Recommendation Workflow.pdf
Normal file → Executable file
0
docs/LC Stylist Rules 总结.docx
Normal file → Executable file
0
docs/LC Stylist Rules 总结.docx
Normal file → Executable file
0
docs/vera.docx
Normal file → Executable file
0
docs/vera.docx
Normal file → Executable file
Reference in New Issue
Block a user