push gitignore

This commit is contained in:
zhh
2025-10-24 15:33:00 +08:00
parent 62dec64a0a
commit ba39a387fd
14 changed files with 55 additions and 4185 deletions

View File

@@ -1,8 +1,11 @@
import os
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field
# ⚠️ 注意: 您需要安装 pydantic-settings: pip install pydantic-settings
DEBUG = os.environ.get("DEBUG", True)
class Settings(BaseSettings):
"""
@@ -31,7 +34,10 @@ class Settings(BaseSettings):
STYLIST_GUIDE_DIR: str = Field(default="/workspace/lc_stylist_agent/app/core/data/stylist_guide", description="风格指南文本目录")
# 向量数据库配置参数
VECTOR_DB_DIR: str = Field(default="./app/db", description="向量数据库目录")
if DEBUG:
VECTOR_DB_DIR: str = Field(default="./db", description="向量数据库目录")
else:
VECTOR_DB_DIR: str = Field(default="./app/db", description="向量数据库目录")
COLLECTION_NAME: str = Field(default="lc_clothing_embedding", description="向量数据库集合名称")
EMBEDDING_MODEL_NAME: str = Field(default="openai/clip-vit-base-patch32", description="CLIP嵌入模型名称")

View File

@@ -21,9 +21,9 @@ logger = logging.getLogger(__name__)
class AsyncStylistAgent:
CATEGORY_SET = {'Activewear', 'Watches', 'Shopping Totes', 'Underwear', 'Sunglasses', 'Dresses', 'Outerwear', 'Handbags', 'Backpacks', 'Belts', 'Hats', 'Skirts', 'Swimwear', 'Jewelry', 'Briefcases', 'Socks', 'Neckties', 'Pants', 'Suits', 'Shoes', 'Shirts & Tops', 'Scarves & Shawls'}
def __init__(self, local_db, max_len: int, gemini_model_name: str):
def __init__(self, local_db, max_len: int, gemini_model_name: str, outfit_id=str):
# self.outfit_items: List[Dict[str, str]] = []
self.outfit_id = str(uuid.uuid4())
self.outfit_id = outfit_id
self.gemini_client = genai.Client(
vertexai=True, project='aida-461108', location='us-central1'
)

View File

@@ -1,4 +1,27 @@
BASIC_PROMPT = """You are a professional, friendly, and insightful AI Styling Assistant.
BASIC_PROMPT = """"""
WOMEN_BASIC_PROMPT = """You are a professional, friendly, and insightful AI women's styling assistant.
Your primary mission is to engage in a multi-turn conversation with the user to fully understand their dressing intent. You must adopt a professional yet approachable tone.
CONVERSATION GOALS:
1. **Occasion:** Determine the specific event (e.g., romantic dinner, summer wedding, business meeting).
2. **Style:** Pinpoint the desired aesthetic (e.g., classic elegance, edgy, minimalist, bohemian).
3. **Vibe/Details:** Gather any mood or specific constraints (e.g., needs to be comfortable, requires light colors, no bare shoulders).
4. **Item Preference:** Ask the user if they have any specific preferences for an item type or silhouette (e.g., preference for a dress, skirt, tailored pants, or a particular neckline/length).
GUIDANCE FOR RESPONSE GENERATION:
- After the user's initial request (e.g., "I want a chic outfit for dinner."), immediately reply with a friendly, targeted follow-up question to elicit the most crucial missing information (usually a combination of **Occasion** and **Style**).
- Be concise. Ask only 1 to 2 essential questions per turn.
- You must gather sufficient, clear intent before proceeding to actual clothing recommendations.
OUTPUT FORMAT INSTRUCTION:
- **DO NOT** use any Markdown formatting whatsoever (e.g., do not use asterisks (*), bold text (**), lists, or code blocks).
- **ONLY** output the plain text response spoken by the AI Assistant.
Example Follow-up (mimicking a conversational flow):
User: I want a chic outfit for dinner.
Your Response: Hey there! A chic dinner outfit, I love that! To give you the perfect recommendations, tell me: is this a romantic date, business dinner, or celebration with friends? And what's your go-to style vibe: classic elegance or something with more edge?"""
MEN_BASIC_PROMPT = """You are a professional, friendly, and insightful AI men'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.
@@ -30,4 +53,4 @@ JSON FIELD REQUIREMENTS:
- **style (string):** The overall aesthetic description (e.g., "Classic elegance", "Modern minimalist", "Bohemian vibe", "Edgy and contemporary").
- **color_preference (string or list):** User's preferred or excluded colors/tones (e.g., "Light colors only", "Avoid deep shades", "['Cream', 'Pale Blue']", "No preference").
- **clothing_type (string):** User's preference for specific garment types, material, or silhouette (e.g., "Lightweight maxi dress", "Skirt with silk blouse", "Tailored wide-leg pants", "Floral print").
- **vibe_or_details (string):** Any other details, mood requirements, or specific constraints (e.g., "Needs to be comfortable and breathable", "Accent on accessories", "Must cover shoulders")."""
- **vibe_or_details (string):** Any other details, mood requirements, or specific constraints (e.g., "Needs to be comfortable and breathable", "Accent on accessories", "Must cover shoulders")."""

File diff suppressed because it is too large Load Diff

View File

@@ -1,124 +0,0 @@
2025-10-23 21:43:04,108 base_events.py [line:1758] ERROR Task exception was never retrieved
future: <Task finished name='Task-3' coro=<LCAgent.background_run() done, defined at /workspace/lc_stylist_agent/app/server/ChatbotAgent/agent_server.py:57> exception=DefaultCredentialsError('Gemini API call failed: File /app/request.json was not found.')>
Traceback (most recent call last):
File "/workspace/lc_stylist_agent/app/core/llm_interface.py", line 45, in generate_response
response = await self.gemini_client.aio.models.generate_content(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/models.py", line 6757, in generate_content
response = await self._generate_content(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/models.py", line 5592, in _generate_content
response = await self._api_client.async_request(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 1341, in async_request
result = await self._async_request(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 1286, in _async_request
return await self._async_retry( # type: ignore[no-any-return]
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/_asyncio.py", line 58, in __call__
do = await self.iter(retry_state=retry_state)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/_asyncio.py", line 110, in iter
result = await action(retry_state)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/_asyncio.py", line 78, in inner
return fn(*args, **kwargs)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/__init__.py", line 410, in exc_check
raise retry_exc.reraise()
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/__init__.py", line 183, in reraise
raise self.last_attempt.result()
File "/home/user/miniconda3/envs/test/lib/python3.10/concurrent/futures/_base.py", line 451, in result
return self.__get_result()
File "/home/user/miniconda3/envs/test/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/_asyncio.py", line 61, in __call__
result = await fn(*args, **kwargs)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 1153, in _async_request_once
f'Bearer {await self._async_access_token()}'
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 970, in _async_access_token
self._credentials, project = await asyncio.to_thread(
File "/home/user/miniconda3/envs/test/lib/python3.10/asyncio/threads.py", line 25, in to_thread
return await loop.run_in_executor(None, func_call)
File "/home/user/miniconda3/envs/test/lib/python3.10/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 184, in load_auth
credentials, loaded_project_id = google.auth.default( # type: ignore[no-untyped-call]
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/auth/_default.py", line 705, in default
credentials, project_id = checker()
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/auth/_default.py", line 698, in <lambda>
lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id),
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/auth/_default.py", line 346, in _get_explicit_environ_credentials
credentials, project_id = load_credentials_from_file(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/auth/_default.py", line 173, in load_credentials_from_file
raise exceptions.DefaultCredentialsError(
google.auth.exceptions.DefaultCredentialsError: File /app/request.json was not found.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/workspace/lc_stylist_agent/app/server/ChatbotAgent/agent_server.py", line 59, in background_run
request_summary = await self.get_conversation_summary(request.user_id)
File "/workspace/lc_stylist_agent/app/server/ChatbotAgent/agent_server.py", line 84, in get_conversation_summary
summary = await self.llm.generate_response(history=[Message(role=Role.USER, content=input_message)], system_prompt=SUMMARY_PROMPT)
File "/workspace/lc_stylist_agent/app/core/llm_interface.py", line 55, in generate_response
raise type(e)(f"Gemini API call failed: {e}")
google.auth.exceptions.DefaultCredentialsError: Gemini API call failed: File /app/request.json was not found.
2025-10-23 21:43:39,697 base_events.py [line:1758] ERROR Task exception was never retrieved
future: <Task finished name='Task-5' coro=<LCAgent.background_run() done, defined at /workspace/lc_stylist_agent/app/server/ChatbotAgent/agent_server.py:57> exception=DefaultCredentialsError('Gemini API call failed: File /app/request.json was not found.')>
Traceback (most recent call last):
File "/workspace/lc_stylist_agent/app/core/llm_interface.py", line 45, in generate_response
response = await self.gemini_client.aio.models.generate_content(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/models.py", line 6757, in generate_content
response = await self._generate_content(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/models.py", line 5592, in _generate_content
response = await self._api_client.async_request(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 1341, in async_request
result = await self._async_request(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 1286, in _async_request
return await self._async_retry( # type: ignore[no-any-return]
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/_asyncio.py", line 58, in __call__
do = await self.iter(retry_state=retry_state)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/_asyncio.py", line 110, in iter
result = await action(retry_state)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/_asyncio.py", line 78, in inner
return fn(*args, **kwargs)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/__init__.py", line 410, in exc_check
raise retry_exc.reraise()
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/__init__.py", line 183, in reraise
raise self.last_attempt.result()
File "/home/user/miniconda3/envs/test/lib/python3.10/concurrent/futures/_base.py", line 451, in result
return self.__get_result()
File "/home/user/miniconda3/envs/test/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/tenacity/_asyncio.py", line 61, in __call__
result = await fn(*args, **kwargs)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 1153, in _async_request_once
f'Bearer {await self._async_access_token()}'
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 970, in _async_access_token
self._credentials, project = await asyncio.to_thread(
File "/home/user/miniconda3/envs/test/lib/python3.10/asyncio/threads.py", line 25, in to_thread
return await loop.run_in_executor(None, func_call)
File "/home/user/miniconda3/envs/test/lib/python3.10/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/genai/_api_client.py", line 184, in load_auth
credentials, loaded_project_id = google.auth.default( # type: ignore[no-untyped-call]
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/auth/_default.py", line 705, in default
credentials, project_id = checker()
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/auth/_default.py", line 698, in <lambda>
lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id),
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/auth/_default.py", line 346, in _get_explicit_environ_credentials
credentials, project_id = load_credentials_from_file(
File "/home/user/miniconda3/envs/test/lib/python3.10/site-packages/google/auth/_default.py", line 173, in load_credentials_from_file
raise exceptions.DefaultCredentialsError(
google.auth.exceptions.DefaultCredentialsError: File /app/request.json was not found.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/workspace/lc_stylist_agent/app/server/ChatbotAgent/agent_server.py", line 59, in background_run
request_summary = await self.get_conversation_summary(request.user_id)
File "/workspace/lc_stylist_agent/app/server/ChatbotAgent/agent_server.py", line 84, in get_conversation_summary
summary = await self.llm.generate_response(history=[Message(role=Role.USER, content=input_message)], system_prompt=SUMMARY_PROMPT)
File "/workspace/lc_stylist_agent/app/core/llm_interface.py", line 55, in generate_response
raise type(e)(f"Gemini API call failed: {e}")
google.auth.exceptions.DefaultCredentialsError: Gemini API call failed: File /app/request.json was not found.
2025-10-23 21:44:09,080 agent_server.py [line:73] ERROR ❌ Failed: 'NoneType' object has no attribute 'get'
2025-10-24 09:59:19,870 agent_server.py [line:73] ERROR ❌ Failed: 'NoneType' object has no attribute 'get'
2025-10-24 09:59:51,131 agent_server.py [line:73] ERROR ❌ Failed: 'NoneType' object has no attribute 'get'
2025-10-24 10:15:04,724 agent_server.py [line:73] ERROR ❌ Failed: 'NoneType' object has no attribute 'get'
2025-10-24 10:15:11,944 base_events.py [line:1758] ERROR Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7c7cc2989ff0>

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,7 @@ class LCAgent(ls.LitAPI):
key_prefix=settings.REDIS_HISTORY_KEY_PREFIX
)
self.vector_db = VectorDatabase(
vector_db_dir=os.getenv('VECTOR_DB_DIR', '/app/app/db'),
vector_db_dir=settings.VECTOR_DB_DIR,
collection_name=settings.COLLECTION_NAME,
embedding_model_name=settings.EMBEDDING_MODEL_NAME
)
@@ -43,6 +43,7 @@ class LCAgent(ls.LitAPI):
'max_len': 5,
'gemini_model_name': settings.LLM_MODEL_NAME
}
self.outfit_ids = []
async def decode_request(self, request: AgentRequestModel):
"""
@@ -59,8 +60,11 @@ class LCAgent(ls.LitAPI):
return request
async def predict(self, request):
self.outfit_ids = [str(uuid.uuid4()) for _ in range(request.num_outfits)]
asyncio.create_task(self.background_run(request))
return {"status": "Task initiated in background."}
return {"status": "Task initiated in background.", "outfit_ids": self.outfit_ids}
async def encode_response(self, output):
return output
@@ -108,7 +112,8 @@ class LCAgent(ls.LitAPI):
if start_outfit is None:
start_outfit = []
tasks = []
for _ in range(num_outfits):
for i in range(num_outfits):
self.stylist_agent_kwages['outfit_id'] = self.outfit_ids[i]
agent = AsyncStylistAgent(**self.stylist_agent_kwages)
task = agent.run_styling_process(
request_summary=request_summary,

View File

@@ -12,7 +12,7 @@ from app.core.data_structure import Role, Message
from app.core.llm_interface import AsyncGeminiLLM
from app.core.redis_manager import RedisManager
from app.core.stylist_agent import AsyncStylistAgent
from app.core.system_prompt import BASIC_PROMPT, SUMMARY_PROMPT
from app.core.system_prompt import BASIC_PROMPT, SUMMARY_PROMPT, MEN_BASIC_PROMPT, WOMEN_BASIC_PROMPT
from app.core.vector_database import VectorDatabase
from google.genai import types
@@ -22,6 +22,7 @@ logger = logging.getLogger(__name__)
class PredictRequest(BaseModel):
user_id: str # 用戶ID
user_message: str # 用戶輸入
gender: str # 服装类型
class LCChatBot(ls.LitAPI):
@@ -60,6 +61,10 @@ class LCChatBot(ls.LitAPI):
user_msg = Message(role=Role.USER, content=user_message)
chat_history = self.redis.get_history(user_id)
chat_history.append(user_msg)
if request.gender == 'male':
BASIC_PROMPT = MEN_BASIC_PROMPT
else:
BASIC_PROMPT = WOMEN_BASIC_PROMPT
contents = []
@@ -103,86 +108,3 @@ class LCChatBot(ls.LitAPI):
# The for-loop must have async keyword here since output is an AsyncGenerator
async for out in output:
yield {"output": out}
async def process_query(self, user_id: str, user_message: str) -> str:
"""
处理用户的最新输入,调用 LLM, 并更新历史记录。
"""
# 添加用户消息到历史
user_msg = Message(role=Role.USER, content=user_message)
chat_history = self.redis.get_history(user_id)
chat_history.append(user_msg)
# 生成 LLM 回复
try:
response_text = await self.llm.generate_response(chat_history, system_prompt=BASIC_PROMPT)
except Exception as e:
logger("\n--- Final Recommendation Results ---")
logger.error(f"LLM 调用失败: {e}")
response_text = "抱歉,系统暂时无法响应,请稍后再试。"
# 添加助手消息到历史
if response_text:
assistant_msg = Message(role=Role.ASSISTANT, content=response_text)
else:
assistant_msg = Message(role=Role.ASSISTANT, content="No response generated. Try again later.")
self.redis.save_message(user_id, user_msg)
self.redis.save_message(user_id, assistant_msg)
return response_text
async def get_conversation_summary(self, user_id: str) -> str:
"""
分析用户的完整会话历史,并打包成一个简洁的需求总结。
这个总结可以直接作为输入 Prompt 传递给 Stylist Agent。`
"""
history_messages = self.redis.get_history(user_id)
input_message = "\n".join([f"{msg.role.value}: {msg.content}" for msg in history_messages])
# 临时调用 LLM 或使用本地逻辑生成总结
summary = await self.llm.generate_response(history=[Message(role=Role.USER, content=input_message)], system_prompt=SUMMARY_PROMPT)
return summary
async def recommend_outfit(self, request_summary: str, stylist_name: str, start_outfit=None, num_outfits: int = 1):
"""
基于用户的对话历史和需求,推荐一套搭配。
Args:
request_summary: 用户的request
start_outfit: 可选的初始搭配列表,每个元素包含 'item_id''category'
"""
if start_outfit is None:
start_outfit = []
tasks = []
for _ in range(num_outfits):
agent = AsyncStylistAgent(**self.stylist_agent_kwages)
task = agent.run_styling_process(request_summary, stylist_name, start_outfit)
tasks.append(task)
logger.info(f"--- Starting {num_outfits} concurrent outfit generation tasks. ---")
try:
results = await asyncio.gather(*tasks, return_exceptions=True)
successful_outfits = []
failed_outfits = []
for result in results:
if isinstance(result, Exception):
# 任务执行中发生异常
failed_outfits.append(f"Failed: {result}")
else:
# 任务成功result 是 run_styling_process 返回的图片路径
successful_outfits.append(result)
return {
"successful_outfits": successful_outfits,
"failed_outfits": failed_outfits
}
except Exception as e:
logger.error(f"An unexpected error occurred during concurrent recommendation: {e}")
return {"error": str(e)}