Commit 37d6b790 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

feat: optimize filter performance and rename query to description -...

feat: optimize filter performance and rename query to description - Pre-compile regex patterns for filter optimization - Rename SearchItem.query to SearchItem.description for semantic search - Add váy đầm, váy bé gái to product_map - Fix price format (remove decimal) - Tune LIMIT values for vector search
parent 40c5eb03
...@@ -147,16 +147,30 @@ def get_graph_manager( ...@@ -147,16 +147,30 @@ def get_graph_manager(
config: AgentConfig | None = None, llm: BaseChatModel | None = None, tools: list | None = None config: AgentConfig | None = None, llm: BaseChatModel | None = None, tools: list | None = None
) -> CANIFAGraph: ) -> CANIFAGraph:
"""Get CANIFAGraph instance (Auto-rebuild if model config changes).""" """Get CANIFAGraph instance (Auto-rebuild if model config changes)."""
from .prompt import get_last_modified
current_prompt_mtime = get_last_modified()
# 1. New Instance if Empty # 1. New Instance if Empty
if _instance[0] is None: if _instance[0] is None:
_instance[0] = CANIFAGraph(config, llm, tools) _instance[0] = CANIFAGraph(config, llm, tools)
_instance[0].prompt_mtime = current_prompt_mtime
logger.info(f"✨ Graph Created: {_instance[0].config.model_name}") logger.info(f"✨ Graph Created: {_instance[0].config.model_name}")
return _instance[0] return _instance[0]
# 2. Check for Config Changes (e.g. Model Switch) # 2. Check for Config Changes (Model Switch OR Prompt Update)
if config and config.model_name != _instance[0].config.model_name: is_model_changed = config and config.model_name != _instance[0].config.model_name
logger.info(f"🔄 Model Switch: {_instance[0].config.model_name} -> {config.model_name}") is_prompt_changed = current_prompt_mtime != getattr(_instance[0], "prompt_mtime", 0.0)
if is_model_changed or is_prompt_changed:
change_reason = []
if is_model_changed: change_reason.append(f"Model ({_instance[0].config.model_name}->{config.model_name})")
if is_prompt_changed: change_reason.append("Prompt File Updated")
logger.info(f"🔄 Rebuilding Graph due to: {', '.join(change_reason)}")
_instance[0] = CANIFAGraph(config, llm, tools) _instance[0] = CANIFAGraph(config, llm, tools)
_instance[0].prompt_mtime = current_prompt_mtime
return _instance[0] return _instance[0]
return _instance[0] return _instance[0]
......
This diff is collapsed.
...@@ -36,4 +36,14 @@ Bạn là CiCi - Chuyên viên tư vấn thời trang CANIFA. ...@@ -36,4 +36,14 @@ Bạn là CiCi - Chuyên viên tư vấn thời trang CANIFA.
Hôm nay: {date_str} Hôm nay: {date_str}
KHÔNG BAO GIỜ BỊA ĐẶT. TRẢ LỜI NGẮN GỌN. KHÔNG BAO GIỜ BỊA ĐẶT. TRẢ LỜI NGẮN GỌN.
""" """
\ No newline at end of file
def get_last_modified() -> float:
"""Trả về timestamp lần sửa cuối cùng của file system_prompt.txt."""
try:
if os.path.exists(PROMPT_FILE_PATH):
return os.path.getmtime(PROMPT_FILE_PATH)
except Exception:
pass
return 0.0
\ No newline at end of file
"""
Structured Output Models for CANIFA Chatbot.
Forces LLM to return strictly structured JSON responses.
Uses OpenAI's native Structured Outputs feature via LangChain.
"""
from pydantic import BaseModel, Field
class UserInsight(BaseModel):
"""6-layer User Insight structure as defined in system prompt."""
USER: str = Field(
default="Chưa rõ.",
description="Thông tin người chat: Giới tính, Người lớn/Trẻ em, Style/Gu"
)
TARGET: str = Field(
default="Chưa rõ.",
description="Đối tượng thụ hưởng: Quan hệ, Giới tính, Người lớn/Trẻ em, Style/Gu"
)
GOAL: str = Field(
default="Chưa rõ.",
description="Mục tiêu mua sắm: Sản phẩm + Dịp sử dụng (Occasion)"
)
CONSTRAINS: str = Field(
default="Chưa có.",
description="Ràng buộc cứng: Budget, Size, Màu, Chất liệu, TRÁNH XA/GHÉT..."
)
LATEST_PRODUCT_INTEREST: str = Field(
default="Chưa có",
description="Sản phẩm vừa mới hỏi/xem gần nhất"
)
NEXT: str = Field(
default="Cần hỏi thêm thông tin.",
description="Chiến lược tiếp theo của bot"
)
SUMMARY_HISTORY: str = Field(
default="",
description="Tóm tắt lịch sử chat quan trọng"
)
class Config:
extra = "forbid" # Required for OpenAI strict mode
class ChatResponse(BaseModel):
"""
Structured response from CANIFA AI Stylist.
This model enforces the exact JSON schema the LLM must follow.
"""
ai_response: str = Field(
description="Câu trả lời cho khách hàng. Phải ngắn gọn, thảo mai, nhắc SKU bằng [SKU]."
)
product_ids: list[str] = Field(
default_factory=list,
description="Danh sách mã SKU sản phẩm được nhắc đến (chỉ string, không object)"
)
user_insight: UserInsight = Field(
default_factory=UserInsight,
description="Insight 6 tầng về khách hàng, cập nhật sau mỗi turn"
)
class Config:
extra = "forbid" # Required for OpenAI strict mode
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -87,6 +87,7 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None) ...@@ -87,6 +87,7 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None)
if magento_code: if magento_code:
logger.info(f"🎯 [CODE SEARCH] Direct search by code: {magento_code}") logger.info(f"🎯 [CODE SEARCH] Direct search by code: {magento_code}")
sql = """ sql = """
SELECT SELECT
internal_ref_code, internal_ref_code,
...@@ -94,9 +95,10 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None) ...@@ -94,9 +95,10 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None)
sale_price, sale_price,
original_price, original_price,
discount_amount, discount_amount,
product_line_vn,
product_line_en,
1.0 as max_score 1.0 as max_score
FROM shared_source.magento_product_dimension_with_text_embedding FROM shared_source.magento_product_dimension_with_text_embedding
WHERE (magento_ref_code = %s OR internal_ref_code = %s)
""" """
return sql, [magento_code, magento_code] return sql, [magento_code, magento_code]
...@@ -105,7 +107,7 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None) ...@@ -105,7 +107,7 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None)
# ============================================================ # ============================================================
logger.info("🚀 [HYDE RETRIEVER] Starting semantic vector search...") logger.info("🚀 [HYDE RETRIEVER] Starting semantic vector search...")
query_text = getattr(params, "query", None) query_text = getattr(params, "description", None)
if query_text and query_vector is None: if query_text and query_vector is None:
emb_start = time.time() emb_start = time.time()
query_vector = await create_embedding_async(query_text) query_vector = await create_embedding_async(query_text)
...@@ -142,7 +144,7 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None) ...@@ -142,7 +144,7 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None)
approx_cosine_similarity(vector, {v_str}) as similarity_score approx_cosine_similarity(vector, {v_str}) as similarity_score
FROM shared_source.magento_product_dimension_with_text_embedding FROM shared_source.magento_product_dimension_with_text_embedding
ORDER BY similarity_score DESC ORDER BY similarity_score DESC
LIMIT 100 LIMIT 200
) )
SELECT SELECT
internal_ref_code, internal_ref_code,
...@@ -155,7 +157,7 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None) ...@@ -155,7 +157,7 @@ async def build_starrocks_query(params, query_vector: list[float] | None = None)
WHERE 1=1 {where_filter} WHERE 1=1 {where_filter}
GROUP BY internal_ref_code GROUP BY internal_ref_code
ORDER BY max_score DESC ORDER BY max_score DESC
LIMIT 20 LIMIT 70
""" """
# Return sql and params (params only contains filter values now, not the vector) # Return sql and params (params only contains filter values now, not the vector)
......
...@@ -111,15 +111,27 @@ async def mock_db_search(req: MockDBRequest): ...@@ -111,15 +111,27 @@ async def mock_db_search(req: MockDBRequest):
logger.info("📍 Data Retrieval Tool called") logger.info("📍 Data Retrieval Tool called")
start_time = time.time() start_time = time.time()
# Xây dựng SearchItem từ request # Xây dựng SearchItem từ request - include all required fields
search_item = SearchItem( search_item = SearchItem(
query=req.query or "sản phẩm", query=req.query or "sản phẩm",
magento_ref_code=req.magento_ref_code, magento_ref_code=req.magento_ref_code,
price_min=req.price_min, price_min=req.price_min,
price_max=req.price_max, price_max=req.price_max,
action="search", action="search",
# Metadata fields - all required with None default
gender_by_product=None,
age_by_product=None,
product_name=None,
style=None,
master_color=None,
season=None,
material_group=None,
fitting=None,
form_neckline=None,
form_sleeve=None,
) )
logger.info(f"🔧 Search params: {search_item.dict(exclude_none=True)}") logger.info(f"🔧 Search params: {search_item.dict(exclude_none=True)}")
# Gọi data_retrieval_tool THẬT với retry # Gọi data_retrieval_tool THẬT với retry
...@@ -161,15 +173,27 @@ async def mock_retriever_db(req: MockRetrieverRequest): ...@@ -161,15 +173,27 @@ async def mock_retriever_db(req: MockRetrieverRequest):
logger.info(f"📍 Retriever DB started: {req.user_query}") logger.info(f"📍 Retriever DB started: {req.user_query}")
start_time = time.time() start_time = time.time()
# Xây dựng SearchItem từ request # Xây dựng SearchItem từ request - include all required fields
search_item = SearchItem( search_item = SearchItem(
query=req.user_query, query=req.user_query,
magento_ref_code=req.magento_ref_code, magento_ref_code=req.magento_ref_code,
price_min=req.price_min, price_min=req.price_min,
price_max=req.price_max, price_max=req.price_max,
action="search", action="search",
# Metadata fields - all required with None default
gender_by_product=None,
age_by_product=None,
product_name=None,
style=None,
master_color=None,
season=None,
material_group=None,
fitting=None,
form_neckline=None,
form_sleeve=None,
) )
logger.info(f"🔧 Retriever params: {search_item.dict(exclude_none=True)}") logger.info(f"🔧 Retriever params: {search_item.dict(exclude_none=True)}")
# Gọi data_retrieval_tool THẬT (embedding + vector search) với retry # Gọi data_retrieval_tool THẬT (embedding + vector search) với retry
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment