Commit
·
89879a0
1
Parent(s):
7e828dc
修復商品查詢功能,解決貓砂等商品無法找到的問題
Browse files- backend/services/database_service.py +5 -19
- backend/services/enhanced_product_service.py +36 -13
- backend/services/message_router.py +28 -24
- backend/services/pydantic_ai_service.py +9 -8
- simple_db_test.py +115 -0
- test_api_cat_litter.py +181 -0
- test_cat_litter_query.py +165 -0
- test_intent_recognition.py +162 -0
- test_keyword_matching.py +208 -0
backend/services/database_service.py
CHANGED
@@ -400,29 +400,15 @@ class DatabaseService:
|
|
400 |
close_database_session(db)
|
401 |
|
402 |
def save_message(self, user_id: str, message: str, message_type: str = "text") -> bool:
|
403 |
-
"""儲存訊息記錄"""
|
404 |
-
db = None
|
405 |
try:
|
406 |
-
|
407 |
-
|
408 |
-
new_message = LineMessage(
|
409 |
-
user_id=user_id,
|
410 |
-
message=message,
|
411 |
-
message_type=message_type
|
412 |
-
)
|
413 |
-
|
414 |
-
db.add(new_message)
|
415 |
-
db.commit()
|
416 |
return True
|
417 |
-
|
418 |
except Exception as e:
|
419 |
-
|
420 |
-
db.rollback()
|
421 |
-
logger.error(f"儲存訊息錯誤: {str(e)}")
|
422 |
return False
|
423 |
-
finally:
|
424 |
-
if db:
|
425 |
-
close_database_session(db)
|
426 |
|
427 |
def get_user_profile(self, user_id: str) -> Optional[Dict[str, Any]]:
|
428 |
"""取得用戶資料"""
|
|
|
400 |
close_database_session(db)
|
401 |
|
402 |
def save_message(self, user_id: str, message: str, message_type: str = "text") -> bool:
|
403 |
+
"""儲存訊息記錄 - 暫時停用,僅記錄到日誌"""
|
|
|
404 |
try:
|
405 |
+
# 暫時停用資料庫記錄,只記錄到日誌
|
406 |
+
logger.info(f"訊息記錄 - 用戶: {user_id[:10]}..., 類型: {message_type}, 內容: {message[:50]}...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
407 |
return True
|
408 |
+
|
409 |
except Exception as e:
|
410 |
+
logger.error(f"訊息記錄錯誤: {str(e)}")
|
|
|
|
|
411 |
return False
|
|
|
|
|
|
|
412 |
|
413 |
def get_user_profile(self, user_id: str) -> Optional[Dict[str, Any]]:
|
414 |
"""取得用戶資料"""
|
backend/services/enhanced_product_service.py
CHANGED
@@ -218,17 +218,23 @@ class EnhancedProductService:
|
|
218 |
|
219 |
# 分析查詢關鍵字
|
220 |
keywords = self._extract_keywords(query_text)
|
221 |
-
|
222 |
# 建立推薦查詢
|
223 |
query = db.query(Product).filter(Product.is_deleted == False)
|
224 |
-
|
225 |
-
# 多關鍵字匹配
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
|
233 |
# 優先顯示有庫存的商品
|
234 |
query = query.order_by(Product.stock.desc())
|
@@ -290,12 +296,29 @@ class EnhancedProductService:
|
|
290 |
return "低"
|
291 |
|
292 |
def _extract_keywords(self, query_text: str) -> List[str]:
|
293 |
-
"""
|
294 |
# 移除常見的查詢詞彙
|
295 |
stop_words = ['推薦', '有沒有', '是否有', '請問', '想要', '需要', '找', '查詢', '搜尋']
|
296 |
-
|
297 |
# 分割並清理關鍵字
|
298 |
words = query_text.replace('?', '').replace('?', '').split()
|
299 |
keywords = [word for word in words if word not in stop_words and len(word) > 1]
|
300 |
-
|
301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
|
219 |
# 分析查詢關鍵字
|
220 |
keywords = self._extract_keywords(query_text)
|
221 |
+
|
222 |
# 建立推薦查詢
|
223 |
query = db.query(Product).filter(Product.is_deleted == False)
|
224 |
+
|
225 |
+
# 多關鍵字匹配 - 使用 OR 邏輯,任一關鍵字匹配即可
|
226 |
+
if keywords:
|
227 |
+
search_filters = []
|
228 |
+
for keyword in keywords:
|
229 |
+
search_filters.extend([
|
230 |
+
Product.productName.ilike(f"%{keyword}%"),
|
231 |
+
Product.productCode.ilike(f"%{keyword}%"),
|
232 |
+
Product.barcode.ilike(f"%{keyword}%")
|
233 |
+
])
|
234 |
+
|
235 |
+
# 使用 OR 連接所有搜尋條件
|
236 |
+
if search_filters:
|
237 |
+
query = query.filter(or_(*search_filters))
|
238 |
|
239 |
# 優先顯示有庫存的商品
|
240 |
query = query.order_by(Product.stock.desc())
|
|
|
296 |
return "低"
|
297 |
|
298 |
def _extract_keywords(self, query_text: str) -> List[str]:
|
299 |
+
"""從查詢文字中提取關鍵字,並擴展相關詞彙"""
|
300 |
# 移除常見的查詢詞彙
|
301 |
stop_words = ['推薦', '有沒有', '是否有', '請問', '想要', '需要', '找', '查詢', '搜尋']
|
302 |
+
|
303 |
# 分割並清理關鍵字
|
304 |
words = query_text.replace('?', '').replace('?', '').split()
|
305 |
keywords = [word for word in words if word not in stop_words and len(word) > 1]
|
306 |
+
|
307 |
+
# 擴展相關關鍵字
|
308 |
+
expanded_keywords = []
|
309 |
+
for keyword in keywords:
|
310 |
+
expanded_keywords.append(keyword)
|
311 |
+
|
312 |
+
# 貓砂相關擴展
|
313 |
+
if '貓砂' in keyword or '貓' in keyword:
|
314 |
+
expanded_keywords.extend(['礦砂', '豆腐砂', '水晶砂', '木屑砂', 'litter'])
|
315 |
+
|
316 |
+
# 狗糧相關擴展
|
317 |
+
if '狗糧' in keyword or '狗' in keyword:
|
318 |
+
expanded_keywords.extend(['犬糧', '犬種', '狗食', 'dog'])
|
319 |
+
|
320 |
+
# 寵物相關擴展
|
321 |
+
if '寵物' in keyword:
|
322 |
+
expanded_keywords.extend(['貓', '狗', '犬', 'pet', 'cat'])
|
323 |
+
|
324 |
+
return expanded_keywords if expanded_keywords else [query_text.strip()]
|
backend/services/message_router.py
CHANGED
@@ -180,26 +180,30 @@ class MessageRouter:
|
|
180 |
def _handle_smart_routing(self, message: str, user_id: str) -> Dict[str, Any]:
|
181 |
"""智能路由 - 根據內容判斷意圖"""
|
182 |
try:
|
183 |
-
# 1.
|
184 |
if self.product_query_service.is_available():
|
185 |
product_intent = self.product_query_service.analyze_query_intent(message)
|
186 |
|
187 |
-
|
188 |
-
|
|
|
189 |
self.route_stats["product_query"] += 1
|
190 |
return self._handle_product_query_mode(message, user_id)
|
|
|
|
|
|
|
|
|
191 |
|
192 |
-
# 2.
|
193 |
quick_intent = self._quick_intent_check(message)
|
194 |
|
195 |
-
if quick_intent == "
|
196 |
-
# 高信心度的搜尋關鍵字 -> 直接轉到搜尋模式
|
197 |
-
logger.info(f"快速意圖識別: 搜尋模式 - {message[:30]}...")
|
198 |
-
return self._handle_search_mode(message, user_id)
|
199 |
-
|
200 |
-
elif quick_intent == "help":
|
201 |
# 幫助相關 -> 轉到幫助模式
|
202 |
return self._handle_help_mode(user_id)
|
|
|
|
|
|
|
|
|
203 |
|
204 |
# 使用 Groq 進行深度意圖分析
|
205 |
if self.groq_service.is_available():
|
@@ -290,24 +294,24 @@ class MessageRouter:
|
|
290 |
}
|
291 |
|
292 |
def _quick_intent_check(self, message: str) -> str:
|
293 |
-
"""快速意圖檢查 -
|
294 |
message_lower = message.lower()
|
295 |
-
|
296 |
-
#
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
|
|
|
|
301 |
]
|
302 |
-
|
303 |
-
|
304 |
-
help_keywords = ['幫助', 'help', '說明', '怎麼用', '指令', '功能']
|
305 |
-
|
306 |
-
if any(keyword in message_lower for keyword in search_keywords):
|
307 |
-
return "search"
|
308 |
-
elif any(keyword in message_lower for keyword in help_keywords):
|
309 |
return "help"
|
|
|
|
|
310 |
else:
|
|
|
311 |
return "chat"
|
312 |
|
313 |
def _handle_help_mode(self, user_id: str) -> Dict[str, Any]:
|
|
|
180 |
def _handle_smart_routing(self, message: str, user_id: str) -> Dict[str, Any]:
|
181 |
"""智能路由 - 根據內容判斷意圖"""
|
182 |
try:
|
183 |
+
# 1. 優先檢查是否為商品查詢 (使用 Pydantic AI)
|
184 |
if self.product_query_service.is_available():
|
185 |
product_intent = self.product_query_service.analyze_query_intent(message)
|
186 |
|
187 |
+
# 降低信心度閾值,讓更多商品查詢被 Pydantic AI 處理
|
188 |
+
if product_intent["is_product_query"] and product_intent["confidence"] > 0.5:
|
189 |
+
logger.info(f"智能路由 -> Pydantic AI 商品查詢 (信心度: {product_intent['confidence']:.2f})")
|
190 |
self.route_stats["product_query"] += 1
|
191 |
return self._handle_product_query_mode(message, user_id)
|
192 |
+
else:
|
193 |
+
logger.info(f"Pydantic AI 意圖分析: 商品查詢={product_intent['is_product_query']}, 信心度={product_intent['confidence']:.2f}")
|
194 |
+
else:
|
195 |
+
logger.warning("Pydantic AI 服務不可用,使用傳統路由")
|
196 |
|
197 |
+
# 2. 進行簡單的關鍵字預篩選(只處理非商品查詢)
|
198 |
quick_intent = self._quick_intent_check(message)
|
199 |
|
200 |
+
if quick_intent == "help":
|
|
|
|
|
|
|
|
|
|
|
201 |
# 幫助相關 -> 轉到幫助模式
|
202 |
return self._handle_help_mode(user_id)
|
203 |
+
elif quick_intent == "search":
|
204 |
+
# 傳統搜尋關鍵字 -> 轉到搜尋模式
|
205 |
+
logger.info(f"快速意圖識別: 搜尋模式 - {message[:30]}...")
|
206 |
+
return self._handle_search_mode(message, user_id)
|
207 |
|
208 |
# 使用 Groq 進行深度意圖分析
|
209 |
if self.groq_service.is_available():
|
|
|
294 |
}
|
295 |
|
296 |
def _quick_intent_check(self, message: str) -> str:
|
297 |
+
"""快速意圖檢查 - 基於關鍵字(避免與 Pydantic AI 商品查詢衝突)"""
|
298 |
message_lower = message.lower()
|
299 |
+
|
300 |
+
# 幫助相關關鍵字(優先處理)
|
301 |
+
help_keywords = ['幫助', 'help', '說明', '怎麼用', '指令', '功能', '統計', 'stats', '選單', 'menu']
|
302 |
+
|
303 |
+
# 非商品的業務查詢關鍵字(避免與商品查詢衝突)
|
304 |
+
business_keywords = [
|
305 |
+
'訂單狀態', '訂單查詢', '我的訂單', '交易記錄', '購買記錄',
|
306 |
+
'客戶資料', '會員資料', '帳戶資訊', '銷售報表', '財務報表'
|
307 |
]
|
308 |
+
|
309 |
+
if any(keyword in message_lower for keyword in help_keywords):
|
|
|
|
|
|
|
|
|
|
|
310 |
return "help"
|
311 |
+
elif any(keyword in message_lower for keyword in business_keywords):
|
312 |
+
return "search"
|
313 |
else:
|
314 |
+
# 預設返回 "chat",讓 Pydantic AI 有機會處理商品查詢
|
315 |
return "chat"
|
316 |
|
317 |
def _handle_help_mode(self, user_id: str) -> Dict[str, Any]:
|
backend/services/pydantic_ai_service.py
CHANGED
@@ -320,18 +320,19 @@ class ProductQueryService:
|
|
320 |
"""
|
321 |
message_lower = message.lower()
|
322 |
|
323 |
-
#
|
324 |
product_keywords = [
|
325 |
-
'推薦', '有沒有', '是否有', '商品', '產品', '貨品',
|
326 |
-
'查詢', '搜尋', '找', '庫存', '存貨', '價格',
|
327 |
-
'貓砂', '狗糧', '寵物', '食品', '用品'
|
|
|
328 |
]
|
329 |
-
|
330 |
# 推薦查詢關鍵字
|
331 |
-
recommendation_keywords = ['推薦', '建議', '介紹', '有什麼', '哪些']
|
332 |
-
|
333 |
# 庫存查詢關鍵字
|
334 |
-
inventory_keywords = ['庫存', '存貨', '剩餘', '還有', '現貨']
|
335 |
|
336 |
is_product_query = any(keyword in message_lower for keyword in product_keywords)
|
337 |
is_recommendation = any(keyword in message_lower for keyword in recommendation_keywords)
|
|
|
320 |
"""
|
321 |
message_lower = message.lower()
|
322 |
|
323 |
+
# 商品查詢關鍵字(擴展版)
|
324 |
product_keywords = [
|
325 |
+
'推薦', '有沒有', '是否有', '請問有', '商品', '產品', '貨品',
|
326 |
+
'查詢', '搜尋', '找', '庫存', '存貨', '價格', '多少錢',
|
327 |
+
'貓砂', '狗糧', '寵物', '食品', '用品', '貓', '狗', '寵物用品',
|
328 |
+
'cat', 'dog', 'pet', 'litter', 'food' # 英文關鍵字
|
329 |
]
|
330 |
+
|
331 |
# 推薦查詢關鍵字
|
332 |
+
recommendation_keywords = ['推薦', '建議', '介紹', '有什麼', '哪些', '什麼好', '推薦一些']
|
333 |
+
|
334 |
# 庫存查詢關鍵字
|
335 |
+
inventory_keywords = ['庫存', '存貨', '剩餘', '還有', '現貨', '有多少', '剩多少']
|
336 |
|
337 |
is_product_query = any(keyword in message_lower for keyword in product_keywords)
|
338 |
is_recommendation = any(keyword in message_lower for keyword in recommendation_keywords)
|
simple_db_test.py
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
簡化的資料庫測試 - 檢查是否有貓砂相關商品
|
3 |
+
"""
|
4 |
+
|
5 |
+
import sys
|
6 |
+
import os
|
7 |
+
|
8 |
+
# 添加專案根目錄到 Python 路徑
|
9 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
10 |
+
|
11 |
+
def test_database_products():
|
12 |
+
"""測試資料庫中的商品資料"""
|
13 |
+
try:
|
14 |
+
from backend.database.connection import get_database_session, close_database_session
|
15 |
+
from backend.database.models import Product, Category
|
16 |
+
from sqlalchemy.orm import joinedload
|
17 |
+
from sqlalchemy import or_
|
18 |
+
|
19 |
+
print("🗄️ 連接資料庫...")
|
20 |
+
db = get_database_session()
|
21 |
+
|
22 |
+
if not db:
|
23 |
+
print("❌ 無法連接資料庫")
|
24 |
+
return
|
25 |
+
|
26 |
+
print("✅ 資料庫連接成功")
|
27 |
+
|
28 |
+
# 1. 檢查總商品數量
|
29 |
+
total_products = db.query(Product).filter(Product.is_deleted == False).count()
|
30 |
+
print(f"📊 總商品數量: {total_products}")
|
31 |
+
|
32 |
+
# 2. 檢查是否有分類
|
33 |
+
categories = db.query(Category).all()
|
34 |
+
print(f"📂 分類數量: {len(categories)}")
|
35 |
+
if categories:
|
36 |
+
print("分類列表:")
|
37 |
+
for cat in categories[:5]: # 只顯示前5個
|
38 |
+
print(f" - {cat.name}")
|
39 |
+
|
40 |
+
# 3. 搜尋貓砂相關商品
|
41 |
+
print("\n🔍 搜尋貓砂相關商品...")
|
42 |
+
|
43 |
+
search_terms = ["貓砂", "貓", "寵物", "cat", "litter", "pet"]
|
44 |
+
|
45 |
+
for term in search_terms:
|
46 |
+
print(f"\n--- 搜尋關鍵字: '{term}' ---")
|
47 |
+
|
48 |
+
# 搜尋商品名稱、編號、條碼
|
49 |
+
products = db.query(Product).options(
|
50 |
+
joinedload(Product.category)
|
51 |
+
).filter(
|
52 |
+
Product.is_deleted == False
|
53 |
+
).filter(
|
54 |
+
or_(
|
55 |
+
Product.productName.ilike(f"%{term}%"),
|
56 |
+
Product.productCode.ilike(f"%{term}%"),
|
57 |
+
Product.barcode.ilike(f"%{term}%")
|
58 |
+
)
|
59 |
+
).limit(5).all()
|
60 |
+
|
61 |
+
print(f"找到 {len(products)} 個商品:")
|
62 |
+
for product in products:
|
63 |
+
print(f" - {product.productName} (編號: {product.productCode}, 庫存: {product.stock})")
|
64 |
+
|
65 |
+
# 4. 檢查分類中是否有寵物相關
|
66 |
+
print("\n🐾 檢查寵物相關分類...")
|
67 |
+
pet_categories = db.query(Category).filter(
|
68 |
+
or_(
|
69 |
+
Category.name.ilike("%寵物%"),
|
70 |
+
Category.name.ilike("%貓%"),
|
71 |
+
Category.name.ilike("%狗%"),
|
72 |
+
Category.name.ilike("%pet%"),
|
73 |
+
Category.name.ilike("%cat%"),
|
74 |
+
Category.name.ilike("%dog%")
|
75 |
+
)
|
76 |
+
).all()
|
77 |
+
|
78 |
+
print(f"找到 {len(pet_categories)} 個寵物相關分類:")
|
79 |
+
for cat in pet_categories:
|
80 |
+
print(f" - {cat.name}")
|
81 |
+
|
82 |
+
# 檢查該分類下的商品
|
83 |
+
products_in_cat = db.query(Product).filter(
|
84 |
+
Product.category_id == cat.id,
|
85 |
+
Product.is_deleted == False
|
86 |
+
).limit(3).all()
|
87 |
+
|
88 |
+
for product in products_in_cat:
|
89 |
+
print(f" * {product.productName} (庫存: {product.stock})")
|
90 |
+
|
91 |
+
# 5. 隨機顯示一些商品名稱,看看資料庫中有什麼
|
92 |
+
print("\n📋 隨機商品樣本 (前10個):")
|
93 |
+
sample_products = db.query(Product).filter(
|
94 |
+
Product.is_deleted == False
|
95 |
+
).limit(10).all()
|
96 |
+
|
97 |
+
for i, product in enumerate(sample_products, 1):
|
98 |
+
category_name = product.category.name if product.category else "無分類"
|
99 |
+
print(f" {i}. {product.productName} ({category_name}) - 庫存: {product.stock}")
|
100 |
+
|
101 |
+
close_database_session(db)
|
102 |
+
|
103 |
+
except Exception as e:
|
104 |
+
print(f"❌ 測試錯誤: {str(e)}")
|
105 |
+
import traceback
|
106 |
+
traceback.print_exc()
|
107 |
+
|
108 |
+
def main():
|
109 |
+
"""主函數"""
|
110 |
+
print("🚀 開始簡化資料庫測試\n")
|
111 |
+
test_database_products()
|
112 |
+
print("\n✅ 測試完成!")
|
113 |
+
|
114 |
+
if __name__ == "__main__":
|
115 |
+
main()
|
test_api_cat_litter.py
ADDED
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
通過 API 測試貓砂查詢功能
|
3 |
+
"""
|
4 |
+
|
5 |
+
import requests
|
6 |
+
import json
|
7 |
+
|
8 |
+
def test_api_endpoints():
|
9 |
+
"""測試 API 端點"""
|
10 |
+
base_url = "http://localhost:7860"
|
11 |
+
|
12 |
+
print("🚀 開始 API 測試\n")
|
13 |
+
|
14 |
+
# 1. 測試健康檢查
|
15 |
+
print("1. 測試健康檢查...")
|
16 |
+
try:
|
17 |
+
response = requests.get(f"{base_url}/health", timeout=10)
|
18 |
+
print(f" 狀態碼: {response.status_code}")
|
19 |
+
if response.status_code == 200:
|
20 |
+
print(f" 回應: {response.json()}")
|
21 |
+
print(" ✅ 健康檢查通過\n")
|
22 |
+
except Exception as e:
|
23 |
+
print(f" ❌ 健康檢查失敗: {str(e)}\n")
|
24 |
+
return False
|
25 |
+
|
26 |
+
# 2. 測試智能路由 API
|
27 |
+
print("2. 測試智能路由 API...")
|
28 |
+
test_messages = [
|
29 |
+
"你好, 請問有沒有貓砂相關產品?",
|
30 |
+
"是否有推薦貓砂?",
|
31 |
+
"有什麼寵物用品?",
|
32 |
+
"查詢狗糧庫存",
|
33 |
+
"你好!今天天氣如何?" # 非商品查詢
|
34 |
+
]
|
35 |
+
|
36 |
+
for i, message in enumerate(test_messages, 1):
|
37 |
+
print(f"\n 測試 {i}: '{message}'")
|
38 |
+
try:
|
39 |
+
payload = {
|
40 |
+
"message": message,
|
41 |
+
"user_id": "test_user_api"
|
42 |
+
}
|
43 |
+
|
44 |
+
response = requests.post(
|
45 |
+
f"{base_url}/route",
|
46 |
+
json=payload,
|
47 |
+
timeout=30,
|
48 |
+
headers={"Content-Type": "application/json"}
|
49 |
+
)
|
50 |
+
|
51 |
+
print(f" 狀態碼: {response.status_code}")
|
52 |
+
|
53 |
+
if response.status_code == 200:
|
54 |
+
result = response.json()
|
55 |
+
print(f" 模式: {result.get('mode', 'unknown')}")
|
56 |
+
print(f" 成功: {result.get('success', False)}")
|
57 |
+
|
58 |
+
# 顯示回應內容
|
59 |
+
response_text = result.get('text', 'No response')
|
60 |
+
if len(response_text) > 150:
|
61 |
+
print(f" 回應: {response_text[:150]}...")
|
62 |
+
else:
|
63 |
+
print(f" 回應: {response_text}")
|
64 |
+
|
65 |
+
# 如果是商品查詢,顯示額外資訊
|
66 |
+
if result.get('products_found') is not None:
|
67 |
+
print(f" 找到商品: {result['products_found']}")
|
68 |
+
if result.get('has_recommendations'):
|
69 |
+
print(f" 包含推薦: {result['has_recommendations']}")
|
70 |
+
|
71 |
+
else:
|
72 |
+
print(f" ❌ API 錯誤: {response.text}")
|
73 |
+
|
74 |
+
except Exception as e:
|
75 |
+
print(f" ❌ 請求失敗: {str(e)}")
|
76 |
+
|
77 |
+
# 3. 測試專門的商品查詢 API
|
78 |
+
print(f"\n3. 測試專門的商品查詢 API...")
|
79 |
+
|
80 |
+
product_queries = [
|
81 |
+
"是否有推薦貓砂?",
|
82 |
+
"有什麼寵物用品?",
|
83 |
+
"查詢貓相關產品"
|
84 |
+
]
|
85 |
+
|
86 |
+
for i, query in enumerate(product_queries, 1):
|
87 |
+
print(f"\n 商品查詢 {i}: '{query}'")
|
88 |
+
try:
|
89 |
+
payload = {
|
90 |
+
"message": query,
|
91 |
+
"user_id": "test_user_product"
|
92 |
+
}
|
93 |
+
|
94 |
+
response = requests.post(
|
95 |
+
f"{base_url}/product-query",
|
96 |
+
json=payload,
|
97 |
+
timeout=30,
|
98 |
+
headers={"Content-Type": "application/json"}
|
99 |
+
)
|
100 |
+
|
101 |
+
print(f" 狀態碼: {response.status_code}")
|
102 |
+
|
103 |
+
if response.status_code == 200:
|
104 |
+
result = response.json()
|
105 |
+
print(f" 成功: {result.get('success', False)}")
|
106 |
+
|
107 |
+
response_text = result.get('text', 'No response')
|
108 |
+
if len(response_text) > 150:
|
109 |
+
print(f" 回應: {response_text[:150]}...")
|
110 |
+
else:
|
111 |
+
print(f" 回應: {response_text}")
|
112 |
+
|
113 |
+
if result.get('products_found') is not None:
|
114 |
+
print(f" 找到商品: {result['products_found']}")
|
115 |
+
if result.get('error'):
|
116 |
+
print(f" 錯誤: {result['error']}")
|
117 |
+
|
118 |
+
else:
|
119 |
+
print(f" ❌ API 錯誤: {response.text}")
|
120 |
+
|
121 |
+
except Exception as e:
|
122 |
+
print(f" ❌ 請求失敗: {str(e)}")
|
123 |
+
|
124 |
+
# 4. 測試傳統搜尋 API
|
125 |
+
print(f"\n4. 測試傳統搜尋 API...")
|
126 |
+
|
127 |
+
search_queries = [
|
128 |
+
"貓砂",
|
129 |
+
"寵物用品",
|
130 |
+
"貓"
|
131 |
+
]
|
132 |
+
|
133 |
+
for i, query in enumerate(search_queries, 1):
|
134 |
+
print(f"\n 搜尋 {i}: '{query}'")
|
135 |
+
try:
|
136 |
+
payload = {
|
137 |
+
"message": query,
|
138 |
+
"user_id": "test_user_search"
|
139 |
+
}
|
140 |
+
|
141 |
+
response = requests.post(
|
142 |
+
f"{base_url}/search",
|
143 |
+
json=payload,
|
144 |
+
timeout=30,
|
145 |
+
headers={"Content-Type": "application/json"}
|
146 |
+
)
|
147 |
+
|
148 |
+
print(f" 狀態碼: {response.status_code}")
|
149 |
+
|
150 |
+
if response.status_code == 200:
|
151 |
+
result = response.json()
|
152 |
+
print(f" 成功: {result.get('success', False)}")
|
153 |
+
|
154 |
+
response_text = result.get('text', 'No response')
|
155 |
+
if len(response_text) > 150:
|
156 |
+
print(f" 回應: {response_text[:150]}...")
|
157 |
+
else:
|
158 |
+
print(f" 回應: {response_text}")
|
159 |
+
|
160 |
+
else:
|
161 |
+
print(f" ❌ API 錯誤: {response.text}")
|
162 |
+
|
163 |
+
except Exception as e:
|
164 |
+
print(f" ❌ 請求失敗: {str(e)}")
|
165 |
+
|
166 |
+
def main():
|
167 |
+
"""主函數"""
|
168 |
+
print("🧪 貓砂查詢 API 測試")
|
169 |
+
print("=" * 50)
|
170 |
+
|
171 |
+
test_api_endpoints()
|
172 |
+
|
173 |
+
print("\n" + "=" * 50)
|
174 |
+
print("✅ API 測試完成!")
|
175 |
+
print("\n💡 分析建議:")
|
176 |
+
print("1. 檢查智能路由是否正確識別商品查詢意圖")
|
177 |
+
print("2. 比較不同 API 端點的回應差異")
|
178 |
+
print("3. 確認是否真的沒有貓砂相關商品,還是查詢邏輯問題")
|
179 |
+
|
180 |
+
if __name__ == "__main__":
|
181 |
+
main()
|
test_cat_litter_query.py
ADDED
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
測試貓砂查詢功能 - 驗證修復後的智能路由和商品查詢
|
3 |
+
"""
|
4 |
+
|
5 |
+
import sys
|
6 |
+
import os
|
7 |
+
|
8 |
+
# 添加專案根目錄到 Python 路徑
|
9 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
10 |
+
|
11 |
+
from backend.services.message_router import MessageRouter
|
12 |
+
from backend.services.pydantic_ai_service import ProductQueryService
|
13 |
+
from backend.services.enhanced_product_service import EnhancedProductService
|
14 |
+
from backend.database.connection import test_database_connection
|
15 |
+
|
16 |
+
def test_database_connection_first():
|
17 |
+
"""首先測試資料庫連接"""
|
18 |
+
print("🗄️ 測試資料庫連接...")
|
19 |
+
|
20 |
+
if test_database_connection():
|
21 |
+
print("✅ 資料庫連接正常")
|
22 |
+
return True
|
23 |
+
else:
|
24 |
+
print("❌ 資料庫連接失敗")
|
25 |
+
return False
|
26 |
+
|
27 |
+
def test_product_search_directly():
|
28 |
+
"""直接測試商品搜尋功能"""
|
29 |
+
print("\n🔍 直接測試商品搜尋...")
|
30 |
+
|
31 |
+
try:
|
32 |
+
service = EnhancedProductService()
|
33 |
+
|
34 |
+
# 測試不同的搜尋關鍵字
|
35 |
+
test_keywords = ["貓砂", "貓", "寵物", "cat", "litter"]
|
36 |
+
|
37 |
+
for keyword in test_keywords:
|
38 |
+
print(f"\n--- 搜尋關鍵字: '{keyword}' ---")
|
39 |
+
|
40 |
+
# 測試推薦功能
|
41 |
+
result = service.get_product_recommendations(keyword, limit=3)
|
42 |
+
print(f"推薦查詢 - 成功: {result.success}, 數量: {result.count}")
|
43 |
+
|
44 |
+
if result.success and result.data:
|
45 |
+
for i, product in enumerate(result.data, 1):
|
46 |
+
print(f" {i}. {product.get('product_name', 'N/A')} (庫存: {product.get('current_stock', 0)})")
|
47 |
+
elif result.error:
|
48 |
+
print(f" 錯誤: {result.error}")
|
49 |
+
|
50 |
+
# 測試進階搜尋
|
51 |
+
result2 = service.search_products_advanced(keyword, include_stock_info=True, limit=3)
|
52 |
+
print(f"進階搜尋 - 成功: {result2.success}, 數量: {result2.count}")
|
53 |
+
|
54 |
+
if result2.success and result2.data:
|
55 |
+
for i, product in enumerate(result2.data, 1):
|
56 |
+
print(f" {i}. {product.get('product_name', 'N/A')} (庫存: {product.get('current_stock', 0)})")
|
57 |
+
elif result2.error:
|
58 |
+
print(f" 錯誤: {result2.error}")
|
59 |
+
|
60 |
+
except Exception as e:
|
61 |
+
print(f"❌ 直接搜尋測試錯誤: {str(e)}")
|
62 |
+
|
63 |
+
def test_pydantic_ai_intent():
|
64 |
+
"""測試 Pydantic AI 意圖識別"""
|
65 |
+
print("\n🤖 測試 Pydantic AI 意圖識別...")
|
66 |
+
|
67 |
+
try:
|
68 |
+
service = ProductQueryService()
|
69 |
+
|
70 |
+
if not service.is_available():
|
71 |
+
print("❌ Pydantic AI 服務不可用")
|
72 |
+
return
|
73 |
+
|
74 |
+
test_messages = [
|
75 |
+
"你好, 請問有沒有貓砂相關產品?",
|
76 |
+
"是否有推薦貓砂?",
|
77 |
+
"有什麼寵物用品?",
|
78 |
+
"今天天氣如何?" # 非商品查詢
|
79 |
+
]
|
80 |
+
|
81 |
+
for message in test_messages:
|
82 |
+
print(f"\n--- 測試訊息: '{message}' ---")
|
83 |
+
intent = service.analyze_query_intent(message)
|
84 |
+
print(f"商品查詢: {intent['is_product_query']}")
|
85 |
+
print(f"推薦查詢: {intent['is_recommendation']}")
|
86 |
+
print(f"庫存查詢: {intent['is_inventory_check']}")
|
87 |
+
print(f"信心度: {intent['confidence']:.2f}")
|
88 |
+
print(f"意圖: {intent['intent']}")
|
89 |
+
|
90 |
+
except Exception as e:
|
91 |
+
print(f"❌ Pydantic AI 測試錯誤: {str(e)}")
|
92 |
+
|
93 |
+
def test_message_router():
|
94 |
+
"""測試修復後的訊息路由器"""
|
95 |
+
print("\n🔄 測試修復後的訊息路由器...")
|
96 |
+
|
97 |
+
try:
|
98 |
+
router = MessageRouter()
|
99 |
+
|
100 |
+
test_messages = [
|
101 |
+
"你好, 請問有沒有貓砂相關產品?",
|
102 |
+
"是否有推薦貓砂?",
|
103 |
+
"有什麼狗糧推薦?",
|
104 |
+
"查詢寵物用品庫存",
|
105 |
+
"你好!今天天氣如何?", # 應該路由到聊天模式
|
106 |
+
"/help", # 應該顯示幫助
|
107 |
+
]
|
108 |
+
|
109 |
+
for message in test_messages:
|
110 |
+
print(f"\n--- 路由測試: '{message}' ---")
|
111 |
+
try:
|
112 |
+
result = router.route_message(message, "test_user")
|
113 |
+
print(f"模式: {result.get('mode', 'unknown')}")
|
114 |
+
print(f"成功: {result.get('success', False)}")
|
115 |
+
|
116 |
+
# 顯示回應的前100個字符
|
117 |
+
response_text = result.get('text', 'No response')
|
118 |
+
if len(response_text) > 100:
|
119 |
+
print(f"回應: {response_text[:100]}...")
|
120 |
+
else:
|
121 |
+
print(f"回應: {response_text}")
|
122 |
+
|
123 |
+
if result.get('products_found'):
|
124 |
+
print(f"找到商品數量: {result['products_found']}")
|
125 |
+
|
126 |
+
except Exception as e:
|
127 |
+
print(f"❌ 路由錯誤: {str(e)}")
|
128 |
+
|
129 |
+
# 顯示路由統計
|
130 |
+
print(f"\n📊 路由統計:")
|
131 |
+
stats = router.get_route_statistics()
|
132 |
+
for mode, count in stats['stats'].items():
|
133 |
+
if count > 0:
|
134 |
+
print(f" {mode}: {count}")
|
135 |
+
|
136 |
+
except Exception as e:
|
137 |
+
print(f"❌ 路由器測試錯誤: {str(e)}")
|
138 |
+
|
139 |
+
def main():
|
140 |
+
"""主測試函數"""
|
141 |
+
print("🚀 開始貓砂查詢功能測試\n")
|
142 |
+
|
143 |
+
# 1. 測試資料庫連接
|
144 |
+
if not test_database_connection_first():
|
145 |
+
print("❌ 資料庫連接失敗,無法繼續測試")
|
146 |
+
return
|
147 |
+
|
148 |
+
# 2. 直接測試商品搜尋
|
149 |
+
test_product_search_directly()
|
150 |
+
|
151 |
+
# 3. 測試 Pydantic AI 意圖識別
|
152 |
+
test_pydantic_ai_intent()
|
153 |
+
|
154 |
+
# 4. 測試訊息路由器
|
155 |
+
test_message_router()
|
156 |
+
|
157 |
+
print("\n✅ 測試完成!")
|
158 |
+
print("\n💡 如果商品查詢仍然沒有結果,可能的原因:")
|
159 |
+
print(" 1. 資料庫中確實沒有貓砂相關商品")
|
160 |
+
print(" 2. 商品名稱不包含 '貓砂'、'貓'、'寵物' 等關鍵字")
|
161 |
+
print(" 3. 商品被標記為已刪除 (is_deleted=True)")
|
162 |
+
print(" 4. 需要檢查實際的商品資料內容")
|
163 |
+
|
164 |
+
if __name__ == "__main__":
|
165 |
+
main()
|
test_intent_recognition.py
ADDED
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
測試意圖識別邏輯 - 不依賴外部套件
|
3 |
+
"""
|
4 |
+
|
5 |
+
def test_product_intent_logic():
|
6 |
+
"""測試商品意圖識別邏輯"""
|
7 |
+
|
8 |
+
def analyze_query_intent_simple(message: str):
|
9 |
+
"""簡化版的意圖分析"""
|
10 |
+
message_lower = message.lower()
|
11 |
+
|
12 |
+
# 商品查詢關鍵字(擴展版)
|
13 |
+
product_keywords = [
|
14 |
+
'推薦', '有沒有', '是否有', '請問有', '商品', '產品', '貨品',
|
15 |
+
'查詢', '搜尋', '找', '庫存', '存貨', '價格', '多少錢',
|
16 |
+
'貓砂', '狗糧', '寵物', '食品', '用品', '貓', '狗', '寵物用品',
|
17 |
+
'cat', 'dog', 'pet', 'litter', 'food' # 英文關鍵字
|
18 |
+
]
|
19 |
+
|
20 |
+
# 推薦查詢關鍵字
|
21 |
+
recommendation_keywords = ['推薦', '建議', '介紹', '有什麼', '哪些', '什麼好', '推薦一些']
|
22 |
+
|
23 |
+
# 庫存查詢關鍵字
|
24 |
+
inventory_keywords = ['庫存', '存貨', '剩餘', '還有', '現貨', '有多少', '剩多少']
|
25 |
+
|
26 |
+
is_product_query = any(keyword in message_lower for keyword in product_keywords)
|
27 |
+
is_recommendation = any(keyword in message_lower for keyword in recommendation_keywords)
|
28 |
+
is_inventory_check = any(keyword in message_lower for keyword in inventory_keywords)
|
29 |
+
|
30 |
+
confidence = 0.5
|
31 |
+
if is_product_query:
|
32 |
+
confidence += 0.3
|
33 |
+
if is_recommendation:
|
34 |
+
confidence += 0.2
|
35 |
+
if is_inventory_check:
|
36 |
+
confidence += 0.2
|
37 |
+
|
38 |
+
return {
|
39 |
+
"is_product_query": is_product_query,
|
40 |
+
"is_recommendation": is_recommendation,
|
41 |
+
"is_inventory_check": is_inventory_check,
|
42 |
+
"confidence": min(confidence, 1.0),
|
43 |
+
"intent": "product_query" if is_product_query else "unknown"
|
44 |
+
}
|
45 |
+
|
46 |
+
def quick_intent_check_simple(message: str):
|
47 |
+
"""簡化版的快速意圖檢查"""
|
48 |
+
message_lower = message.lower()
|
49 |
+
|
50 |
+
# 幫助相關關鍵字(優先處理)
|
51 |
+
help_keywords = ['幫助', 'help', '說明', '怎麼用', '指令', '功能', '統計', 'stats', '選單', 'menu']
|
52 |
+
|
53 |
+
# 非商品的業務查詢關鍵字(避免與商品查詢衝突)
|
54 |
+
business_keywords = [
|
55 |
+
'訂單狀態', '訂單查詢', '我的訂單', '交易記錄', '購買記錄',
|
56 |
+
'客戶資料', '會員資料', '帳戶資訊', '銷售報表', '財務報表'
|
57 |
+
]
|
58 |
+
|
59 |
+
if any(keyword in message_lower for keyword in help_keywords):
|
60 |
+
return "help"
|
61 |
+
elif any(keyword in message_lower for keyword in business_keywords):
|
62 |
+
return "search"
|
63 |
+
else:
|
64 |
+
# 預設返回 "chat",讓 Pydantic AI 有機會處理商品查詢
|
65 |
+
return "chat"
|
66 |
+
|
67 |
+
# 測試案例
|
68 |
+
test_messages = [
|
69 |
+
"你好, 請問有沒有貓砂相關產品?",
|
70 |
+
"是否有推薦貓砂?",
|
71 |
+
"有什麼寵物用品?",
|
72 |
+
"查詢狗糧庫存",
|
73 |
+
"貓砂還有嗎?",
|
74 |
+
"推薦一些好的貓砂",
|
75 |
+
"你好!今天天氣如何?", # 非商品查詢
|
76 |
+
"/help",
|
77 |
+
"統計",
|
78 |
+
"我的訂單狀態"
|
79 |
+
]
|
80 |
+
|
81 |
+
print("🧪 測試意圖識別邏輯")
|
82 |
+
print("=" * 80)
|
83 |
+
|
84 |
+
for i, message in enumerate(test_messages, 1):
|
85 |
+
print(f"\n{i}. 測試訊息: '{message}'")
|
86 |
+
|
87 |
+
# 1. 快速意圖檢查
|
88 |
+
quick_intent = quick_intent_check_simple(message)
|
89 |
+
print(f" 快速意圖: {quick_intent}")
|
90 |
+
|
91 |
+
# 2. Pydantic AI 意圖分析
|
92 |
+
product_intent = analyze_query_intent_simple(message)
|
93 |
+
print(f" 商品查詢: {product_intent['is_product_query']}")
|
94 |
+
print(f" 推薦查詢: {product_intent['is_recommendation']}")
|
95 |
+
print(f" 庫存查詢: {product_intent['is_inventory_check']}")
|
96 |
+
print(f" 信心度: {product_intent['confidence']:.2f}")
|
97 |
+
|
98 |
+
# 3. 路由決策模擬
|
99 |
+
should_use_pydantic_ai = (
|
100 |
+
product_intent["is_product_query"] and
|
101 |
+
product_intent["confidence"] > 0.5
|
102 |
+
)
|
103 |
+
|
104 |
+
if quick_intent == "help":
|
105 |
+
final_route = "幫助模式"
|
106 |
+
elif should_use_pydantic_ai:
|
107 |
+
final_route = "🛍️ Pydantic AI 商品查詢"
|
108 |
+
elif quick_intent == "search":
|
109 |
+
final_route = "傳統搜尋模式"
|
110 |
+
else:
|
111 |
+
final_route = "聊天模式"
|
112 |
+
|
113 |
+
print(f" 👉 最終路由: {final_route}")
|
114 |
+
|
115 |
+
# 特別標記貓砂相關查詢
|
116 |
+
if "貓砂" in message or "貓" in message:
|
117 |
+
print(f" 🐱 貓砂查詢檢測: {'✅ 應該被 Pydantic AI 處理' if should_use_pydantic_ai else '❌ 可能被錯誤路由'}")
|
118 |
+
|
119 |
+
def test_keyword_extraction():
|
120 |
+
"""測試關鍵字提取邏輯"""
|
121 |
+
|
122 |
+
def extract_keywords_simple(query_text: str):
|
123 |
+
"""簡化版關鍵字提取"""
|
124 |
+
# 移除常見的查詢詞彙
|
125 |
+
stop_words = ['推薦', '有沒有', '是否有', '請問', '想要', '需要', '找', '查詢', '搜尋', '?', '?']
|
126 |
+
|
127 |
+
# 分割並清理關鍵字
|
128 |
+
words = query_text.replace('?', '').replace('?', '').split()
|
129 |
+
keywords = [word for word in words if word not in stop_words and len(word) > 1]
|
130 |
+
|
131 |
+
return keywords if keywords else [query_text.strip()]
|
132 |
+
|
133 |
+
print(f"\n\n🔍 測試關鍵字提取")
|
134 |
+
print("=" * 50)
|
135 |
+
|
136 |
+
test_queries = [
|
137 |
+
"你好, 請問有沒有貓砂相關產品?",
|
138 |
+
"是否有推薦貓砂?",
|
139 |
+
"有什麼寵物用品?",
|
140 |
+
"查詢狗糧庫存"
|
141 |
+
]
|
142 |
+
|
143 |
+
for query in test_queries:
|
144 |
+
keywords = extract_keywords_simple(query)
|
145 |
+
print(f"'{query}' → {keywords}")
|
146 |
+
|
147 |
+
def main():
|
148 |
+
"""主函數"""
|
149 |
+
print("🚀 開始意圖識別測試\n")
|
150 |
+
|
151 |
+
test_product_intent_logic()
|
152 |
+
test_keyword_extraction()
|
153 |
+
|
154 |
+
print("\n" + "=" * 80)
|
155 |
+
print("✅ 測試完成!")
|
156 |
+
print("\n💡 分析結果:")
|
157 |
+
print("1. 檢查貓砂相關查詢是否被正確識別為商品查詢")
|
158 |
+
print("2. 確認信心度是否超過 0.5 閾值")
|
159 |
+
print("3. 驗證路由決策是否正確")
|
160 |
+
|
161 |
+
if __name__ == "__main__":
|
162 |
+
main()
|
test_keyword_matching.py
ADDED
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
測試關鍵字匹配邏輯 - 基於實際商品資料
|
3 |
+
"""
|
4 |
+
|
5 |
+
def test_keyword_expansion():
|
6 |
+
"""測試關鍵字擴展邏輯"""
|
7 |
+
|
8 |
+
def extract_keywords_with_expansion(query_text: str):
|
9 |
+
"""擴展版關鍵字提取"""
|
10 |
+
# 移除常見的查詢詞彙
|
11 |
+
stop_words = ['推薦', '有沒有', '是否有', '請問', '想要', '需要', '找', '查詢', '搜尋']
|
12 |
+
|
13 |
+
# 分割並清理關鍵字
|
14 |
+
words = query_text.replace('?', '').replace('?', '').split()
|
15 |
+
keywords = [word for word in words if word not in stop_words and len(word) > 1]
|
16 |
+
|
17 |
+
# 擴展相關關鍵字
|
18 |
+
expanded_keywords = []
|
19 |
+
for keyword in keywords:
|
20 |
+
expanded_keywords.append(keyword)
|
21 |
+
|
22 |
+
# 貓砂相關擴展
|
23 |
+
if '貓砂' in keyword or '貓' in keyword:
|
24 |
+
expanded_keywords.extend(['礦砂', '豆腐砂', '水晶砂', '木屑砂', 'litter'])
|
25 |
+
|
26 |
+
# 狗糧相關擴展
|
27 |
+
if '狗糧' in keyword or '狗' in keyword:
|
28 |
+
expanded_keywords.extend(['犬糧', '犬種', '狗食', 'dog'])
|
29 |
+
|
30 |
+
# 寵物相關擴展
|
31 |
+
if '寵物' in keyword:
|
32 |
+
expanded_keywords.extend(['貓', '狗', '犬', 'pet', 'cat'])
|
33 |
+
|
34 |
+
return expanded_keywords if expanded_keywords else [query_text.strip()]
|
35 |
+
|
36 |
+
print("🔍 測試關鍵字擴展邏輯")
|
37 |
+
print("=" * 60)
|
38 |
+
|
39 |
+
test_queries = [
|
40 |
+
"你好, 請問有沒有貓砂相關產品?",
|
41 |
+
"是否有推薦貓砂?",
|
42 |
+
"有什麼寵物用品?",
|
43 |
+
"查詢狗糧庫存",
|
44 |
+
"貓砂還有嗎?"
|
45 |
+
]
|
46 |
+
|
47 |
+
for query in test_queries:
|
48 |
+
keywords = extract_keywords_with_expansion(query)
|
49 |
+
print(f"'{query}'")
|
50 |
+
print(f" → 擴展關鍵字: {keywords}")
|
51 |
+
print()
|
52 |
+
|
53 |
+
def test_product_matching():
|
54 |
+
"""測試商品匹配邏輯"""
|
55 |
+
|
56 |
+
# 實際商品資料(來自您的 Supabase)
|
57 |
+
products = [
|
58 |
+
{
|
59 |
+
"id": 1,
|
60 |
+
"productCode": "OL1100-1",
|
61 |
+
"productName": "毆力天然犬種300g 室內成犬無榖小顆粒",
|
62 |
+
"stock": 100,
|
63 |
+
"category_id": 1
|
64 |
+
},
|
65 |
+
{
|
66 |
+
"id": 2,
|
67 |
+
"productCode": "SW-06-01",
|
68 |
+
"productName": "Shovel well豪好鏟 破碎型礦砂",
|
69 |
+
"stock": 50,
|
70 |
+
"category_id": 1
|
71 |
+
},
|
72 |
+
{
|
73 |
+
"id": 3,
|
74 |
+
"productCode": "TL-03",
|
75 |
+
"productName": "美國極冠貓砂 薰衣草12kg",
|
76 |
+
"stock": 48,
|
77 |
+
"category_id": 1
|
78 |
+
},
|
79 |
+
{
|
80 |
+
"id": 4,
|
81 |
+
"productCode": "SL11002",
|
82 |
+
"productName": "首領汪 膠原鴨舌 5入彭湃包",
|
83 |
+
"stock": 100,
|
84 |
+
"category_id": 1
|
85 |
+
}
|
86 |
+
]
|
87 |
+
|
88 |
+
def search_products_simulation(query_text: str):
|
89 |
+
"""模擬商品搜尋"""
|
90 |
+
# 擴展關鍵字
|
91 |
+
def extract_keywords_with_expansion(query_text: str):
|
92 |
+
stop_words = ['推薦', '有沒有', '是否有', '請問', '想要', '需要', '找', '查詢', '搜尋']
|
93 |
+
words = query_text.replace('?', '').replace('?', '').split()
|
94 |
+
keywords = [word for word in words if word not in stop_words and len(word) > 1]
|
95 |
+
|
96 |
+
expanded_keywords = []
|
97 |
+
for keyword in keywords:
|
98 |
+
expanded_keywords.append(keyword)
|
99 |
+
|
100 |
+
if '貓砂' in keyword or '貓' in keyword:
|
101 |
+
expanded_keywords.extend(['礦砂', '豆腐砂', '水晶砂', '木屑砂', 'litter'])
|
102 |
+
|
103 |
+
if '狗糧' in keyword or '狗' in keyword:
|
104 |
+
expanded_keywords.extend(['犬糧', '犬種', '狗食', 'dog'])
|
105 |
+
|
106 |
+
if '寵物' in keyword:
|
107 |
+
expanded_keywords.extend(['貓', '狗', '犬', 'pet', 'cat'])
|
108 |
+
|
109 |
+
return expanded_keywords if expanded_keywords else [query_text.strip()]
|
110 |
+
|
111 |
+
keywords = extract_keywords_with_expansion(query_text)
|
112 |
+
matched_products = []
|
113 |
+
|
114 |
+
for product in products:
|
115 |
+
# 檢查是否有任一關鍵字匹配
|
116 |
+
for keyword in keywords:
|
117 |
+
if (keyword.lower() in product["productName"].lower() or
|
118 |
+
keyword.lower() in product["productCode"].lower()):
|
119 |
+
matched_products.append({
|
120 |
+
"product": product,
|
121 |
+
"matched_keyword": keyword
|
122 |
+
})
|
123 |
+
break # 找到匹配就跳出
|
124 |
+
|
125 |
+
return matched_products, keywords
|
126 |
+
|
127 |
+
print("🛍️ 測試商品匹配邏輯")
|
128 |
+
print("=" * 60)
|
129 |
+
|
130 |
+
test_queries = [
|
131 |
+
"你好, 請問有沒有貓砂相關產品?",
|
132 |
+
"是否有推薦貓砂?",
|
133 |
+
"有什麼寵物用品?",
|
134 |
+
"查詢狗糧庫存",
|
135 |
+
"礦砂還有嗎?",
|
136 |
+
"犬種商品"
|
137 |
+
]
|
138 |
+
|
139 |
+
for query in test_queries:
|
140 |
+
print(f"查詢: '{query}'")
|
141 |
+
matched_products, keywords = search_products_simulation(query)
|
142 |
+
print(f"使用關鍵字: {keywords}")
|
143 |
+
print(f"找到 {len(matched_products)} 個商品:")
|
144 |
+
|
145 |
+
for match in matched_products:
|
146 |
+
product = match["product"]
|
147 |
+
keyword = match["matched_keyword"]
|
148 |
+
print(f" ✅ {product['productName']} (匹配關鍵字: '{keyword}', 庫存: {product['stock']})")
|
149 |
+
|
150 |
+
if not matched_products:
|
151 |
+
print(" ❌ 沒有找到匹配的商品")
|
152 |
+
|
153 |
+
print("-" * 60)
|
154 |
+
|
155 |
+
def test_specific_cat_litter_queries():
|
156 |
+
"""專門測試貓砂查詢"""
|
157 |
+
|
158 |
+
print("\n🐱 專門測試貓砂查詢")
|
159 |
+
print("=" * 60)
|
160 |
+
|
161 |
+
# 貓砂商品
|
162 |
+
cat_litter_products = [
|
163 |
+
"Shovel well豪好鏟 破碎型礦砂",
|
164 |
+
"美國極冠貓砂 薰衣草12kg"
|
165 |
+
]
|
166 |
+
|
167 |
+
# 測試查詢
|
168 |
+
cat_queries = [
|
169 |
+
"貓砂",
|
170 |
+
"礦砂",
|
171 |
+
"貓",
|
172 |
+
"litter",
|
173 |
+
"豪好鏟",
|
174 |
+
"極冠"
|
175 |
+
]
|
176 |
+
|
177 |
+
print("貓砂相關商品:")
|
178 |
+
for product in cat_litter_products:
|
179 |
+
print(f" - {product}")
|
180 |
+
|
181 |
+
print(f"\n測試查詢關鍵字:")
|
182 |
+
for query in cat_queries:
|
183 |
+
matches = []
|
184 |
+
for product in cat_litter_products:
|
185 |
+
if query.lower() in product.lower():
|
186 |
+
matches.append(product)
|
187 |
+
|
188 |
+
print(f" '{query}' → 匹配 {len(matches)} 個商品")
|
189 |
+
for match in matches:
|
190 |
+
print(f" ✅ {match}")
|
191 |
+
|
192 |
+
def main():
|
193 |
+
"""主函數"""
|
194 |
+
print("🚀 開始關鍵字匹配測試\n")
|
195 |
+
|
196 |
+
test_keyword_expansion()
|
197 |
+
test_product_matching()
|
198 |
+
test_specific_cat_litter_queries()
|
199 |
+
|
200 |
+
print("\n" + "=" * 60)
|
201 |
+
print("✅ 測試完成!")
|
202 |
+
print("\n💡 分析結果:")
|
203 |
+
print("1. 關鍵字擴展邏輯應該能找到 '礦砂' 商品")
|
204 |
+
print("2. '貓砂' 查詢應該匹配到兩個商品")
|
205 |
+
print("3. 確認搜尋邏輯是否正確運作")
|
206 |
+
|
207 |
+
if __name__ == "__main__":
|
208 |
+
main()
|