Commit
·
3887148
1
Parent(s):
751a827
新增debug訊息
Browse files
backend/services/enhanced_product_service.py
CHANGED
@@ -43,16 +43,20 @@ class EnhancedProductService:
|
|
43 |
"""
|
44 |
db = None
|
45 |
try:
|
|
|
46 |
db = get_database_session()
|
47 |
-
|
48 |
# 建立基本查詢,預載入分類資訊
|
49 |
query = db.query(Product).options(
|
50 |
joinedload(Product.category)
|
51 |
).filter(Product.is_deleted == False)
|
52 |
-
|
53 |
# 關鍵字搜尋 - 支援商品名稱、編號、條碼
|
54 |
if query_text:
|
|
|
55 |
search_terms = query_text.strip().split()
|
|
|
|
|
56 |
for term in search_terms:
|
57 |
search_filter = or_(
|
58 |
Product.productName.ilike(f"%{term}%"),
|
@@ -214,13 +218,22 @@ class EnhancedProductService:
|
|
214 |
"""
|
215 |
db = None
|
216 |
try:
|
|
|
217 |
db = get_database_session()
|
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:
|
@@ -232,17 +245,34 @@ class EnhancedProductService:
|
|
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())
|
241 |
-
|
242 |
products = query.options(joinedload(Product.category)).limit(limit).all()
|
|
|
243 |
|
244 |
data = []
|
245 |
for product in products:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
data.append({
|
247 |
"id": product.id,
|
248 |
"product_code": product.productCode,
|
@@ -251,10 +281,12 @@ class EnhancedProductService:
|
|
251 |
"unit": product.unit,
|
252 |
"category_name": product.category.name if product.category else None,
|
253 |
"warehouse": product.warehouse,
|
254 |
-
"recommendation_reason": f"符合關鍵字: {', '.join(
|
255 |
"availability": "有庫存" if product.stock > 0 else "缺貨"
|
256 |
})
|
257 |
-
|
|
|
|
|
258 |
return DatabaseResult(
|
259 |
success=True,
|
260 |
data=data,
|
@@ -262,7 +294,9 @@ class EnhancedProductService:
|
|
262 |
)
|
263 |
|
264 |
except Exception as e:
|
265 |
-
logger.error(f"商品推薦錯誤: {str(e)}")
|
|
|
|
|
266 |
return DatabaseResult(
|
267 |
success=False,
|
268 |
error=f"商品推薦失敗: {str(e)}"
|
|
|
43 |
"""
|
44 |
db = None
|
45 |
try:
|
46 |
+
logger.info(f"🔍 開始進階商品搜尋: '{query_text}'")
|
47 |
db = get_database_session()
|
48 |
+
|
49 |
# 建立基本查詢,預載入分類資訊
|
50 |
query = db.query(Product).options(
|
51 |
joinedload(Product.category)
|
52 |
).filter(Product.is_deleted == False)
|
53 |
+
|
54 |
# 關鍵字搜尋 - 支援商品名稱、編號、條碼
|
55 |
if query_text:
|
56 |
+
logger.info(f"🔑 使用關鍵字搜尋: '{query_text}'")
|
57 |
search_terms = query_text.strip().split()
|
58 |
+
logger.info(f"📝 分割的搜尋詞: {search_terms}")
|
59 |
+
|
60 |
for term in search_terms:
|
61 |
search_filter = or_(
|
62 |
Product.productName.ilike(f"%{term}%"),
|
|
|
218 |
"""
|
219 |
db = None
|
220 |
try:
|
221 |
+
logger.info(f"🛍️ 開始商品推薦查詢: '{query_text}'")
|
222 |
db = get_database_session()
|
223 |
+
|
224 |
+
if not db:
|
225 |
+
logger.error(f"❌ 無法獲取資料庫連接")
|
226 |
+
return DatabaseResult(success=False, error="資料庫連接失敗")
|
227 |
+
|
228 |
+
logger.info(f"✅ 資料庫連接成功")
|
229 |
+
|
230 |
# 分析查詢關鍵字
|
231 |
keywords = self._extract_keywords(query_text)
|
232 |
+
logger.info(f"🔑 推薦查詢關鍵字: {keywords}")
|
233 |
|
234 |
# 建立推薦查詢
|
235 |
query = db.query(Product).filter(Product.is_deleted == False)
|
236 |
+
logger.info(f"📊 基礎查詢建立完成")
|
237 |
|
238 |
# 多關鍵字匹配 - 使用 OR 邏輯,任一關鍵字匹配即可
|
239 |
if keywords:
|
|
|
245 |
Product.barcode.ilike(f"%{keyword}%")
|
246 |
])
|
247 |
|
248 |
+
logger.info(f"🔍 建立了 {len(search_filters)} 個搜尋條件")
|
249 |
+
|
250 |
# 使用 OR 連接所有搜尋條件
|
251 |
if search_filters:
|
252 |
query = query.filter(or_(*search_filters))
|
253 |
+
logger.info(f"✅ 搜尋條件已應用")
|
254 |
+
else:
|
255 |
+
logger.warning(f"⚠️ 沒有搜尋條件")
|
256 |
+
else:
|
257 |
+
logger.warning(f"⚠️ 沒有關鍵字,將返回所有商品")
|
258 |
+
|
259 |
# 優先顯示有庫存的商品
|
260 |
query = query.order_by(Product.stock.desc())
|
261 |
+
|
262 |
products = query.options(joinedload(Product.category)).limit(limit).all()
|
263 |
+
logger.info(f"📦 查詢到 {len(products)} 個商品")
|
264 |
|
265 |
data = []
|
266 |
for product in products:
|
267 |
+
# 檢查哪個關鍵字匹配了這個商品
|
268 |
+
matched_keywords = []
|
269 |
+
for keyword in keywords:
|
270 |
+
if (keyword.lower() in product.productName.lower() or
|
271 |
+
keyword.lower() in product.productCode.lower()):
|
272 |
+
matched_keywords.append(keyword)
|
273 |
+
|
274 |
+
logger.info(f"📦 商品: {product.productName} - 匹配關鍵字: {matched_keywords}")
|
275 |
+
|
276 |
data.append({
|
277 |
"id": product.id,
|
278 |
"product_code": product.productCode,
|
|
|
281 |
"unit": product.unit,
|
282 |
"category_name": product.category.name if product.category else None,
|
283 |
"warehouse": product.warehouse,
|
284 |
+
"recommendation_reason": f"符合關鍵字: {', '.join(matched_keywords[:3]) if matched_keywords else '一般推薦'}",
|
285 |
"availability": "有庫存" if product.stock > 0 else "缺貨"
|
286 |
})
|
287 |
+
|
288 |
+
logger.info(f"✅ 推薦查詢完成,返回 {len(data)} 個商品")
|
289 |
+
|
290 |
return DatabaseResult(
|
291 |
success=True,
|
292 |
data=data,
|
|
|
294 |
)
|
295 |
|
296 |
except Exception as e:
|
297 |
+
logger.error(f"❌ 商品推薦錯誤: {str(e)}")
|
298 |
+
import traceback
|
299 |
+
logger.error(f"📋 錯誤詳情: {traceback.format_exc()}")
|
300 |
return DatabaseResult(
|
301 |
success=False,
|
302 |
error=f"商品推薦失敗: {str(e)}"
|
backend/services/pydantic_ai_service.py
CHANGED
@@ -193,10 +193,14 @@ class ProductQueryService:
|
|
193 |
查詢結果字典
|
194 |
"""
|
195 |
try:
|
|
|
|
|
196 |
# 分析查詢意圖
|
197 |
intent_analysis = self.analyze_query_intent(user_message)
|
|
|
198 |
|
199 |
if not intent_analysis["is_product_query"]:
|
|
|
200 |
return {
|
201 |
"success": False,
|
202 |
"text": "這似乎不是商品查詢,請嘗試其他功能。",
|
@@ -206,12 +210,14 @@ class ProductQueryService:
|
|
206 |
|
207 |
# 根據意圖類型選擇查詢方法
|
208 |
if intent_analysis["is_recommendation"]:
|
|
|
209 |
# 推薦查詢
|
210 |
result = self.enhanced_product_service.get_product_recommendations(
|
211 |
query_text=user_message,
|
212 |
limit=5
|
213 |
)
|
214 |
else:
|
|
|
215 |
# 一般搜尋
|
216 |
result = self.enhanced_product_service.search_products_advanced(
|
217 |
query_text=user_message,
|
@@ -219,6 +225,8 @@ class ProductQueryService:
|
|
219 |
limit=10
|
220 |
)
|
221 |
|
|
|
|
|
222 |
# 格式化回應
|
223 |
if result.success and result.data:
|
224 |
response_text = self._format_product_response(result.data, intent_analysis)
|
@@ -244,7 +252,9 @@ class ProductQueryService:
|
|
244 |
}
|
245 |
|
246 |
except Exception as e:
|
247 |
-
logger.error(f"同步商品查詢錯誤: {str(e)}")
|
|
|
|
|
248 |
return {
|
249 |
"success": False,
|
250 |
"text": f"抱歉,商品查詢時發生錯誤:{str(e)}",
|
|
|
193 |
查詢結果字典
|
194 |
"""
|
195 |
try:
|
196 |
+
logger.info(f"🔍 Pydantic AI 開始處理商品查詢: '{user_message}'")
|
197 |
+
|
198 |
# 分析查詢意圖
|
199 |
intent_analysis = self.analyze_query_intent(user_message)
|
200 |
+
logger.info(f"📊 意圖分析結果: {intent_analysis}")
|
201 |
|
202 |
if not intent_analysis["is_product_query"]:
|
203 |
+
logger.warning(f"❌ 非商品查詢意圖,拒絕處理")
|
204 |
return {
|
205 |
"success": False,
|
206 |
"text": "這似乎不是商品查詢,請嘗試其他功能。",
|
|
|
210 |
|
211 |
# 根據意圖類型選擇查詢方法
|
212 |
if intent_analysis["is_recommendation"]:
|
213 |
+
logger.info(f"🛍️ 執行推薦查詢")
|
214 |
# 推薦查詢
|
215 |
result = self.enhanced_product_service.get_product_recommendations(
|
216 |
query_text=user_message,
|
217 |
limit=5
|
218 |
)
|
219 |
else:
|
220 |
+
logger.info(f"🔍 執行一般搜尋")
|
221 |
# 一般搜尋
|
222 |
result = self.enhanced_product_service.search_products_advanced(
|
223 |
query_text=user_message,
|
|
|
225 |
limit=10
|
226 |
)
|
227 |
|
228 |
+
logger.info(f"📋 查詢結果: 成功={result.success}, 數量={result.count}, 錯誤={result.error}")
|
229 |
+
|
230 |
# 格式化回應
|
231 |
if result.success and result.data:
|
232 |
response_text = self._format_product_response(result.data, intent_analysis)
|
|
|
252 |
}
|
253 |
|
254 |
except Exception as e:
|
255 |
+
logger.error(f"❌ 同步商品查詢錯誤: {str(e)}")
|
256 |
+
import traceback
|
257 |
+
logger.error(f"📋 錯誤詳情: {traceback.format_exc()}")
|
258 |
return {
|
259 |
"success": False,
|
260 |
"text": f"抱歉,商品查詢時發生錯誤:{str(e)}",
|
test_debug_logs.py
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
測試調試日誌是否正常工作
|
3 |
+
"""
|
4 |
+
|
5 |
+
import logging
|
6 |
+
|
7 |
+
# 設置日誌格式
|
8 |
+
logging.basicConfig(
|
9 |
+
level=logging.INFO,
|
10 |
+
format='%(levelname)s:%(name)s:%(message)s'
|
11 |
+
)
|
12 |
+
|
13 |
+
def test_keyword_extraction_with_logs():
|
14 |
+
"""測試關鍵字提取並顯示日誌"""
|
15 |
+
|
16 |
+
def extract_keywords_with_logs(query_text: str):
|
17 |
+
"""帶日誌的關鍵字提取"""
|
18 |
+
logger = logging.getLogger("test_keyword_extraction")
|
19 |
+
|
20 |
+
# 移除常見的查詢詞彙
|
21 |
+
stop_words = ['推薦', '有沒有', '是否有', '請問', '想要', '需要', '找', '查詢', '搜尋', '還有嗎', '還有', '嗎', '可以']
|
22 |
+
|
23 |
+
# 清理查詢文字
|
24 |
+
cleaned_text = query_text.replace('?', '').replace('?', '').strip()
|
25 |
+
logger.info(f"清理後的文字: '{cleaned_text}'")
|
26 |
+
|
27 |
+
# 先嘗試提取核心商品詞彙
|
28 |
+
core_product_words = ['貓砂', '狗糧', '寵物', '商品', '產品', '貓', '狗', '犬', '礦砂']
|
29 |
+
extracted_core_words = []
|
30 |
+
|
31 |
+
for core_word in core_product_words:
|
32 |
+
if core_word in cleaned_text:
|
33 |
+
extracted_core_words.append(core_word)
|
34 |
+
|
35 |
+
logger.info(f"提取的核心詞彙: {extracted_core_words}")
|
36 |
+
|
37 |
+
# 分割並清理關鍵字
|
38 |
+
words = cleaned_text.split()
|
39 |
+
keywords = []
|
40 |
+
|
41 |
+
for word in words:
|
42 |
+
if word not in stop_words and len(word) > 1:
|
43 |
+
keywords.append(word)
|
44 |
+
|
45 |
+
logger.info(f"分割後的關鍵字: {keywords}")
|
46 |
+
|
47 |
+
# 合併核心詞彙和分割的關鍵字
|
48 |
+
all_keywords = list(set(extracted_core_words + keywords))
|
49 |
+
logger.info(f"合併後的關鍵字: {all_keywords}")
|
50 |
+
|
51 |
+
# 如果沒有有效關鍵字,使用清理後的文字
|
52 |
+
if not all_keywords:
|
53 |
+
all_keywords = [cleaned_text]
|
54 |
+
logger.warning(f"沒有有效關鍵字,使用原始文字: {all_keywords}")
|
55 |
+
|
56 |
+
# 擴展相關關鍵字
|
57 |
+
expanded_keywords = []
|
58 |
+
for keyword in all_keywords:
|
59 |
+
expanded_keywords.append(keyword)
|
60 |
+
|
61 |
+
# 貓砂相關擴展
|
62 |
+
if '貓砂' in keyword or '貓' in keyword:
|
63 |
+
expanded_keywords.extend(['礦砂', '豆腐砂', '水晶砂', '木屑砂', 'litter', '貓砂'])
|
64 |
+
logger.info(f"貓砂相關擴展: {keyword} → 添加貓砂相關詞彙")
|
65 |
+
|
66 |
+
# 狗糧相關擴展
|
67 |
+
if '狗糧' in keyword or '狗' in keyword:
|
68 |
+
expanded_keywords.extend(['犬糧', '犬種', '狗食', 'dog'])
|
69 |
+
logger.info(f"狗糧相關擴展: {keyword} → 添加狗糧相關詞彙")
|
70 |
+
|
71 |
+
# 寵物相關擴展
|
72 |
+
if '寵物' in keyword:
|
73 |
+
expanded_keywords.extend(['貓', '狗', '犬', 'pet', 'cat'])
|
74 |
+
logger.info(f"寵物相關擴展: {keyword} → 添加寵物相關詞彙")
|
75 |
+
|
76 |
+
# 商品相關擴展
|
77 |
+
if '商品' in keyword or '產品' in keyword:
|
78 |
+
expanded_keywords.extend(['貓砂', '狗糧', '寵物', '食品', '用品'])
|
79 |
+
logger.info(f"商品相關擴展: {keyword} → 添加商品相關詞彙")
|
80 |
+
|
81 |
+
# 去除重複並返回
|
82 |
+
unique_keywords = list(set(expanded_keywords))
|
83 |
+
|
84 |
+
# 記錄關鍵字提取結果用於調試
|
85 |
+
logger.info(f"關鍵字提取: '{query_text}' → 核心詞: {extracted_core_words} → 最終: {unique_keywords}")
|
86 |
+
|
87 |
+
return unique_keywords, extracted_core_words
|
88 |
+
|
89 |
+
print("🔍 測試關鍵字提取日誌")
|
90 |
+
print("=" * 50)
|
91 |
+
|
92 |
+
test_queries = [
|
93 |
+
"請問貓砂還有嗎?",
|
94 |
+
"貓砂還有嗎?",
|
95 |
+
"請問有商品可以推薦嗎?"
|
96 |
+
]
|
97 |
+
|
98 |
+
for query in test_queries:
|
99 |
+
print(f"\n測試查詢: '{query}'")
|
100 |
+
print("-" * 30)
|
101 |
+
keywords, core_words = extract_keywords_with_logs(query)
|
102 |
+
print(f"最終結果: {keywords}")
|
103 |
+
|
104 |
+
def test_product_matching_simulation():
|
105 |
+
"""模擬商品匹配過程"""
|
106 |
+
|
107 |
+
logger = logging.getLogger("test_product_matching")
|
108 |
+
|
109 |
+
# 實際商品資料
|
110 |
+
products = [
|
111 |
+
{
|
112 |
+
"id": 2,
|
113 |
+
"productCode": "SW-06-01",
|
114 |
+
"productName": "Shovel well豪好鏟 破碎型礦砂",
|
115 |
+
"stock": 50,
|
116 |
+
"is_deleted": False
|
117 |
+
},
|
118 |
+
{
|
119 |
+
"id": 3,
|
120 |
+
"productCode": "TL-03",
|
121 |
+
"productName": "美國極冠貓砂 薰衣草12kg",
|
122 |
+
"stock": 48,
|
123 |
+
"is_deleted": False
|
124 |
+
}
|
125 |
+
]
|
126 |
+
|
127 |
+
print(f"\n🛍️ 測試商品匹配模擬")
|
128 |
+
print("=" * 50)
|
129 |
+
|
130 |
+
test_query = "貓砂還有嗎?"
|
131 |
+
logger.info(f"開始商品匹配測試: '{test_query}'")
|
132 |
+
|
133 |
+
# 模擬關鍵字提取
|
134 |
+
keywords = ['貓', '水晶砂', '貓砂', '豆腐砂', 'litter', '木屑砂', '礦砂', '貓砂還有嗎']
|
135 |
+
logger.info(f"使用關鍵字: {keywords}")
|
136 |
+
|
137 |
+
# ��擬商品匹配
|
138 |
+
matched_products = []
|
139 |
+
|
140 |
+
for product in products:
|
141 |
+
if product["is_deleted"]:
|
142 |
+
continue
|
143 |
+
|
144 |
+
# 檢查哪個關鍵字匹配了這個商品
|
145 |
+
matched_keywords = []
|
146 |
+
for keyword in keywords:
|
147 |
+
if (keyword.lower() in product["productName"].lower() or
|
148 |
+
keyword.lower() in product["productCode"].lower()):
|
149 |
+
matched_keywords.append(keyword)
|
150 |
+
|
151 |
+
if matched_keywords:
|
152 |
+
matched_products.append(product)
|
153 |
+
logger.info(f"📦 商品: {product['productName']} - 匹配關鍵字: {matched_keywords}")
|
154 |
+
|
155 |
+
logger.info(f"✅ 匹配完成,找到 {len(matched_products)} 個商品")
|
156 |
+
|
157 |
+
print(f"匹配結果: 找到 {len(matched_products)} 個商品")
|
158 |
+
for product in matched_products:
|
159 |
+
print(f" - {product['productName']}")
|
160 |
+
|
161 |
+
def main():
|
162 |
+
"""主函數"""
|
163 |
+
print("🚀 測試調試日誌功能")
|
164 |
+
print("=" * 60)
|
165 |
+
|
166 |
+
test_keyword_extraction_with_logs()
|
167 |
+
test_product_matching_simulation()
|
168 |
+
|
169 |
+
print("\n" + "=" * 60)
|
170 |
+
print("✅ 日誌測試完成!")
|
171 |
+
print("\n💡 現在重啟服務,應該能看到詳細的調試日誌")
|
172 |
+
|
173 |
+
if __name__ == "__main__":
|
174 |
+
main()
|