Commit 1d44f574 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

feat: remove description from search, fix polling spam, update prompts for SQL-only search

parent 95fd81fe
Bạn là **Canifa-AI Stylist** - Chuyên viên tư vấn thời trang CANIFA. Bạn là **Canifa-AI Stylist** - Chuyên viên tư vấn thời trang CANIFA.
**Đặc điểm:** **Đặc điểm:**
- Nhiệt tình, thân thiện, chuyên nghiệp như sales thực thụ - Nhiệt tình, thân thiện, chuyên nghiệp như sales thực thụ
...@@ -70,7 +70,7 @@ Khi nhận được ảnh từ khách, BẮT BUỘC thực hiện ĐÚNG quy tr ...@@ -70,7 +70,7 @@ Khi nhận được ảnh từ khách, BẮT BUỘC thực hiện ĐÚNG quy tr
- Tay áo (ngắn, dài, sát nách...) - Tay áo (ngắn, dài, sát nách...)
- Hình in / Họa tiết (trơn, kẻ sọc, hoa, logo, graphic...) - Hình in / Họa tiết (trơn, kẻ sọc, hoa, logo, graphic...)
- Chất liệu (nếu nhận biết được: cotton, nỉ, jean, len...) - Chất liệu (nếu nhận biết được: cotton, nỉ, jean, len...)
2. **GỌI TOOL `data_retrieval_tool`** với description chứa mô tả chi tiết trên 2. **GỌI TOOL `data_retrieval_tool`** với product_name + filters phù hợp
- product_name: loại sản phẩm nhận diện được (VD: "Áo phông") - product_name: loại sản phẩm nhận diện được (VD: "Áo phông")
- master_color: màu sắc chủ đạo - master_color: màu sắc chủ đạo
- Các filter khác nếu nhận biết được (gender, age...) - Các filter khác nếu nhận biết được (gender, age...)
......
...@@ -64,14 +64,12 @@ Nhìn ảnh và liệt kê MỌI sản phẩm quần áo/phụ kiện nhìn th ...@@ -64,14 +64,12 @@ Nhìn ảnh và liệt kê MỌI sản phẩm quần áo/phụ kiện nhìn th
``` ```
data_retrieval_tool(searches=[ data_retrieval_tool(searches=[
{ {
"description": "Áo nỉ/sweatshirt màu xám đậm, form oversize, cổ rộng lệch vai",
"product_name": "Áo nỉ", "product_name": "Áo nỉ",
"master_color": "xám", "master_color": "xám",
"gender_by_product": "women", "gender_by_product": "women",
"age_by_product": "adult" "age_by_product": "adult"
}, },
{ {
"description": "Quần soóc đen viền trắng, dáng sporty ôm nhẹ",
"product_name": "Quần soóc", "product_name": "Quần soóc",
"master_color": "đen", "master_color": "đen",
"gender_by_product": "women", "gender_by_product": "women",
...@@ -128,29 +126,25 @@ Khi user_insight [STAGE] = DECIDE hoặc UPSELL → **TỰ ĐỘNG GỌI** `cani ...@@ -128,29 +126,25 @@ Khi user_insight [STAGE] = DECIDE hoặc UPSELL → **TỰ ĐỘNG GỌI** `cani
### 5.2. QUY TẮC SINH QUERY (BẮT BUỘC) ### 5.2. QUY TẮC SINH QUERY (BẮT BUỘC)
**Cấu trúc query:** **Cấu trúc query — CHỈ CẦN SQL FILTERS (không cần description):**
``` ```
description: [Mô tả ngắn gọn nhu cầu khách, dùng cho fallback search]
product_name: [⚡ BẮT BUỘC lấy từ BẢNG PRODUCT_LINE bên dưới] product_name: [⚡ BẮT BUỘC lấy từ BẢNG PRODUCT_LINE bên dưới]
gender_by_product: [women/men/girl/boy/unisex/null] gender_by_product: [women/men/girl/boy/unisex/null]
age_by_product: [adult/kid/null] age_by_product: [adult/kid/null]
master_color: [Màu nếu có] master_color: [Màu nếu có]
price_min / price_max: [nếu có — KHÔNG đưa giá vào description] price_min / price_max: [nếu có]
discount_min / discount_max: [% giảm giá nếu khách nhắc sale] discount_min / discount_max: [% giảm giá nếu khách nhắc sale]
discovery_mode: [new / best_seller / null] discovery_mode: [new / best_seller / null]
``` ```
⚠️ **KHÔNG CẦN gửi `description`** — tool search bằng SQL filter, KHÔNG dùng semantic search.
**⚡ product_name — QUY TẮC QUAN TRỌNG NHẤT:** **⚡ product_name — QUY TẮC QUAN TRỌNG NHẤT:**
- PHẢI là tên trong **BẢNG PRODUCT_LINE** bên dưới (cột trái) - PHẢI là tên trong **BẢNG PRODUCT_LINE** bên dưới (cột trái)
- Khách nói slang → LLM tự map: "sịp" → `"Quần lót"`, "quần bò" → `"Quần jean"` - Khách nói slang → LLM tự map: "sịp" → `"Quần lót"`, "quần bò" → `"Quần jean"`
- Nhiều SP → dùng "/": `"Áo lót/Áo bra active"` - Nhiều SP → dùng "/": `"Áo lót/Áo bra active"`
- KHÔNG BAO GIỜ bịa tên SP không có trong bảng - KHÔNG BAO GIỜ bịa tên SP không có trong bảng
**description — Vai trò mới (fallback):**
- Mô tả NGẮN nhu cầu khách bằng ngôn ngữ tự nhiên
- Chỉ dùng khi các filter khác không đủ → search LIKE trên description_text
- VD: "Váy đi dự tiệc sang trọng", "Áo đồng điệu cả nhà"
**⚡ BẢNG PRODUCT_LINE — BẮT BUỘC dùng tên DB (cột trái):** **⚡ BẢNG PRODUCT_LINE — BẮT BUỘC dùng tên DB (cột trái):**
| product_line_vn (DÙNG CÁI NÀY) | Khách hay gọi là | | product_line_vn (DÙNG CÁI NÀY) | Khách hay gọi là |
...@@ -222,7 +216,7 @@ discovery_mode: [new / best_seller / null] ...@@ -222,7 +216,7 @@ discovery_mode: [new / best_seller / null]
**Nguyên tắc kế thừa:** Nếu câu hỏi hiện tại thiếu info → lấy từ `[GOAL]` và `[CONSTRAINS]` trong user_insight. Khách không nhắc lại "dài tay" ≠ đổi ý. **Nguyên tắc kế thừa:** Nếu câu hỏi hiện tại thiếu info → lấy từ `[GOAL]` và `[CONSTRAINS]` trong user_insight. Khách không nhắc lại "dài tay" ≠ đổi ý.
**CẤM:** Chèn field names vào description | Đưa giá vào description | Tự suy diễn gender/age **CẤM:** Tự suy diễn gender/age khi user KHÔNG nói rõ | Bịa tên SP không có trong BẢNG PRODUCT_LINE
--- ---
......
...@@ -8,28 +8,23 @@ Siêu công cụ tìm kiếm sản phẩm CANIFA - Hỗ trợ Parallel Multi-Sea ...@@ -8,28 +8,23 @@ Siêu công cụ tìm kiếm sản phẩm CANIFA - Hỗ trợ Parallel Multi-Sea
5. 1 món → 1 Query. Set đồ/phối đồ → 2-3 Queries song song 5. 1 món → 1 Query. Set đồ/phối đồ → 2-3 Queries song song
═══════════════════════ ═══════════════════════
PHÂN BIỆT: description (SEMANTIC SEARCH) vs SQL FILTER CÁCH TÌM KIẾM: SQL FILTER (KHÔNG dùng semantic search)
═══════════════════════ ═══════════════════════
🔍 description — semantic search, format theo DB columns: Tool tìm SP bằng SQL filter trực tiếp. CHỈ CẦN điền đúng fields:
product_name: [tên SP]. description_text: [mô tả chi tiết SP].
material_group: [chất liệu]. season: [mùa]. style: [phong cách].
fitting: [dáng]. form_neckline: [cổ]. form_sleeve: [tay]. product_line_vn: [dòng SP].
⚠️ description_text BẮT BUỘC LUÔN CÓ! 🔒 SQL FILTER FIELDS:
⚠️ KHÔNG đưa gender_by_product, age_by_product, master_color vào description — đó là SQL FILTER! - product_name: ⚡ BẮT BUỘC — tên SP theo BẢNG PRODUCT_LINE bên dưới
🔒 SQL FILTER (tách riêng):
- product_name: Tên SP cụ thể
- gender_by_product — women/men/boy/girl/unisex/newborn. **null nếu user KHÔNG nói rõ!** - gender_by_product — women/men/boy/girl/unisex/newborn. **null nếu user KHÔNG nói rõ!**
- age_by_product — adult/kid/others. **null nếu user KHÔNG nói rõ!** - age_by_product — adult/kid/others. **null nếu user KHÔNG nói rõ!**
- master_color — Gửi CHÍNH XÁC từ khách nói, tool tự match DB - master_color — Gửi CHÍNH XÁC từ khách nói, tool tự match DB
- price_min / price_max — Khoảng giá VND - price_min / price_max — Khoảng giá VND
- discount_min / discount_max — % giảm giá - discount_min / discount_max — % giảm giá
- magento_ref_code — Mã SKU chính xác - magento_ref_code — Mã SKU chính xác
- product_line_vn — Dòng SP RỘNG (dùng LIKE prefix: "Áo" match tất cả loại áo)
- discovery_mode — "new" (hàng mới) hoặc "best_seller" (bán chạy). Chỉ khi khách NÓI RÕ! - discovery_mode — "new" (hàng mới) hoặc "best_seller" (bán chạy). Chỉ khi khách NÓI RÕ!
⚠️ KHÔNG CẦN field `description` — tool KHÔNG dùng nó. Chỉ cần product_name + filters.
═══════════════════════ ═══════════════════════
discovery_mode — HÀNG MỚI / BÁN CHẠY discovery_mode — HÀNG MỚI / BÁN CHẠY
═══════════════════════ ═══════════════════════
...@@ -61,15 +56,6 @@ Chỉ nói rõ thì mới cụ thể: "áo phông" → Áo phông ...@@ -61,15 +56,6 @@ Chỉ nói rõ thì mới cụ thể: "áo phông" → Áo phông
quần suông → Quần dài | đồ ngủ → Bộ mặc nhà | đồ bộ → Bộ quần áo quần suông → Quần dài | đồ ngủ → Bộ mặc nhà | đồ bộ → Bộ quần áo
chân váy/váy maxi → Chân váy | găng tay → Găng tay chống nắng chân váy/váy maxi → Chân váy | găng tay → Găng tay chống nắng
═══════════════════════
product_line_vn — PHÂN LOẠI RỘNG
═══════════════════════
SQL dùng LIKE prefix: "Áo" match "Áo phông", "Áo Polo"...
User nói "áo" chung → "Áo". User nói rõ "áo polo" → "Áo Polo". KHÔNG TỰ THU HẸP!
Bảng: ÁO (Áo phông, Polo, Sơ mi, len, nỉ, khoác gió) | QUẦN (soóc, jean, Khaki, dài, nỉ) | VÁY (Váy liền, Chân váy) | BỘ (quần áo, mặc nhà) | PHỤ KIỆN (Khăn, Mũ, Túi, Tất)
═══════════════════════ ═══════════════════════
GIÁ TRỊ HỢP LỆ CÁC FIELD GIÁ TRỊ HỢP LỆ CÁC FIELD
═══════════════════════ ═══════════════════════
...@@ -85,17 +71,17 @@ season — Fall Winter, Spring Summer, Year ...@@ -85,17 +71,17 @@ season — Fall Winter, Spring Summer, Year
VÍ DỤ VÍ DỤ
═══════════════════════ ═══════════════════════
"Tìm áo đi chơi" → description: "product_name: Áo. description_text: Áo đi chơi thoải mái trẻ trung. style: Casual", product_line_vn: "Áo" "Tìm áo đi chơi" → product_name: "Áo"
"Áo phông nam trắng regular" → description: "product_name: Áo phông. description_text: Áo phông nam regular. fitting: Regular", master_color: "trắng", gender: "men", product_line_vn: "Áo phông" "Áo phông nam trắng" → product_name: "Áo phông", master_color: "trắng", gender: "men"
"Tìm mã 6KS25S005" → magento_ref_code: "6KS25S005" "Tìm mã 6KS25S005" → magento_ref_code: "6KS25S005"
"Áo lót" → description: "product_name: Áo lót/Áo bra active. description_text: Áo lót. Áo bra active thoáng mát", gender: null (CẤM tự điền!) "Áo lót" → product_name: "Áo lót", gender: null (CẤM tự điền!)
"Set đồ công sở nữ" → 2 Queries: Áo công sở nữ + Quần công sở nữ "Set đồ công sở nữ" → 2 searches: {product_name: "Áo Sơ mi", gender: "women"} + {product_name: "Quần Khaki", gender: "women"}
═══════════════════════ ═══════════════════════
DỊP LỄ / SỰ KIỆN DỊP LỄ / SỰ KIỆN
═══════════════════════ ═══════════════════════
Khi user hỏi "mặc gì dịp X" → description_text mô tả: dịp gì + phong cách phù hợp. Khi user hỏi "mặc gì dịp X" → dùng product_name phù hợp + gender/age
VN: Tết (lịch sự, đỏ vàng) | 8/3+20/10 (nữ tính) | 30/4+1/5+2/9 (năng động, thoải mái) | 1/6 (trẻ em, vui nhộn) | 20/11 (lịch sự) VN: Tết (lịch sự, đỏ vàng) | 8/3+20/10 (nữ tính) | 30/4+1/5+2/9 (năng động, thoải mái) | 1/6 (trẻ em, vui nhộn) | 20/11 (lịch sự)
Quốc tế: Valentine (lãng mạn) | Halloween (cá tính, đen cam) | Noel (ấm áp, đỏ xanh trắng) Quốc tế: Valentine (lãng mạn) | Halloween (cá tính, đen cam) | Noel (ấm áp, đỏ xanh trắng)
......
""" """
CANIFA Data Retrieval Tool - Tối giản cho Agentic Workflow. CANIFA Data Retrieval Tool - Tối giản cho Agentic Workflow.
Hỗ trợ Hybrid Search: Semantic (Vector) + Metadata Filter. SQL-based structured search with metadata filters.
Tracing: Tracing:
- LangChain CallbackHandler auto-traces tool call (name, input, output) - LangChain CallbackHandler auto-traces tool call (name, input, output)
...@@ -33,13 +33,12 @@ from agent.prompt_utils import read_tool_prompt ...@@ -33,13 +33,12 @@ from agent.prompt_utils import read_tool_prompt
class SearchItem(BaseModel): class SearchItem(BaseModel):
model_config = {"extra": "ignore"} # Gemini may send extra fields model_config = {"extra": "ignore"} # Gemini may send extra fields
# ====== SEARCH TEXT ====== # ====== SEARCH TEXT (optional fallback) ======
description: str = Field( description: str | None = Field(
default=None,
description=( description=(
"Mô tả ngắn nhu cầu khách bằng ngôn ngữ tự nhiên. " "CHỈ DÙNG KHI không có product_name. "
"Dùng cho fallback search khi các filter khác không đủ. " "Nếu đã có product_name + gender → KHÔNG CẦN description."
"VD: 'Váy đi dự tiệc sang trọng', 'Áo đồng điệu cả nhà'. "
"⚠️ KHÔNG đưa master_color vào description — dùng field master_color riêng! "
) )
) )
product_name: str | None = Field( product_name: str | None = Field(
...@@ -231,7 +230,7 @@ async def _execute_single_search( ...@@ -231,7 +230,7 @@ async def _execute_single_search(
async def data_retrieval_tool(searches: list[SearchItem]) -> str: async def data_retrieval_tool(searches: list[SearchItem]) -> str:
""" """
Công cụ tìm kiếm sản phẩm CANIFA. Công cụ tìm kiếm sản phẩm CANIFA.
Hỗ trợ tìm kiếm Semantic và lọc theo Metadata. SQL-based structured search with metadata filters.
""" """
tool_start = time.time() tool_start = time.time()
logger.info("🔧 data_retrieval_tool called with %d items", len(searches)) logger.info("🔧 data_retrieval_tool called with %d items", len(searches))
...@@ -239,10 +238,9 @@ async def data_retrieval_tool(searches: list[SearchItem]) -> str: ...@@ -239,10 +238,9 @@ async def data_retrieval_tool(searches: list[SearchItem]) -> str:
# Log search params cho debugging # Log search params cho debugging
for i, s in enumerate(searches): for i, s in enumerate(searches):
logger.info( logger.info(
"🔧 Search[%d]: desc=%r, name=%r, gender=%s, age=%s, color=%s, " "🔧 Search[%d]: name=%r, gender=%s, age=%s, color=%s, "
"price=%s-%s, code=%s, mode=%s", "price=%s-%s, code=%s, mode=%s, desc=%s",
i, i,
(s.description[:80] + "...") if s.description and len(s.description) > 80 else s.description,
s.product_name, s.product_name,
s.gender_by_product, s.gender_by_product,
s.age_by_product, s.age_by_product,
...@@ -250,6 +248,7 @@ async def data_retrieval_tool(searches: list[SearchItem]) -> str: ...@@ -250,6 +248,7 @@ async def data_retrieval_tool(searches: list[SearchItem]) -> str:
s.price_min, s.price_max, s.price_min, s.price_max,
s.magento_ref_code, s.magento_ref_code,
s.discovery_mode, s.discovery_mode,
(s.description[:40] + "...") if s.description else None,
) )
# Get DB Connection # Get DB Connection
...@@ -282,9 +281,10 @@ async def data_retrieval_tool(searches: list[SearchItem]) -> str: ...@@ -282,9 +281,10 @@ async def data_retrieval_tool(searches: list[SearchItem]) -> str:
# Include search input so LLM knows its own reasoning # Include search input so LLM knows its own reasoning
search_inputs = [ search_inputs = [
{ {
"description": item.description, "product_name": item.product_name,
"gender_by_product": item.gender_by_product, "gender_by_product": item.gender_by_product,
"age_by_product": item.age_by_product, "age_by_product": item.age_by_product,
"color": item.master_color,
"discovery_mode": item.discovery_mode, "discovery_mode": item.discovery_mode,
} }
for item in searches for item in searches
......
...@@ -170,8 +170,8 @@ class StarRocksConnection: ...@@ -170,8 +170,8 @@ class StarRocksConnection:
minsize=minsize, # Giảm minsize để đỡ tốn tài nguyên idle minsize=minsize, # Giảm minsize để đỡ tốn tài nguyên idle
maxsize=maxsize, maxsize=maxsize,
connect_timeout=10, connect_timeout=10,
# --- CHỈNH SỬA QUAN TRỌNG Ở ĐÂY ---
pool_recycle=280, # Recycle sau 4 phút rưỡi (tránh timeout 5 phút của Windows/Firewall) pool_recycle=3600, # Recycle sau 4 phút rưỡi (tránh timeout 5 phút của Windows/Firewall)
# ---------------------------------- # ----------------------------------
autocommit=True, autocommit=True,
) )
...@@ -189,7 +189,7 @@ class StarRocksConnection: ...@@ -189,7 +189,7 @@ class StarRocksConnection:
conn = None conn = None
try: try:
pool = await self.get_pool() pool = await self.get_pool()
conn = await asyncio.wait_for(pool.acquire(), timeout=90) conn = await asyncio.wait_for(pool.acquire(), timeout=15)
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(query, params) await cursor.execute(query, params)
......
/* ═══ iframe detection + auto-redirect ═══
When a page is loaded inside main.html's iframe,
hide its own sidebar + adjust main margin.
When accessed directly (not in iframe), redirect to main.html. */
(function() {
if (window.self !== window.top) {
// Inside iframe - just mark it
document.documentElement.classList.add('in-iframe');
} else {
// Accessed directly - redirect to main.html unless already on main.html
var path = window.location.pathname;
var filename = path.split('/').pop();
// Don't redirect main.html itself, chatbot pages, or test pages
var noRedirect = ['main.html', 'index.html', 'chatbot.html', 'test_sp.html', 'test_db.html'];
if (filename && filename.endsWith('.html') && noRedirect.indexOf(filename) === -1) {
// Preserve any query string
var qs = window.location.search;
window.location.replace('/static/main.html?page=' + filename + (qs ? '&' + qs.substring(1) : ''));
}
}
})();
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canifa DEV Experimental</title> <title>Canifa DEV Experimental</title>
<link rel="stylesheet" href="/static/warm-override.css">
<style> <style>
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
...@@ -2377,8 +2378,8 @@ ...@@ -2377,8 +2378,8 @@
}; };
const pollUserInsight = async () => { const pollUserInsight = async () => {
const maxAttempts = 60; // ~12s at 200ms const maxAttempts = 15; // ~15s at 1000ms
const intervalMs = 200; const intervalMs = 1000;
let attempts = 0; let attempts = 0;
const headers = { const headers = {
......
This diff is collapsed.
This diff is collapsed.
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