mickeywu520's picture
修復商品查詢功能,解決貓砂等商品無法找到的問題
89879a0
"""
訊息路由器 - 根據前綴和內容智能路由訊息
"""
import logging
from typing import Dict, Any
from backend.services.groq_service import GroqService
from backend.services.business_query_service import BusinessQueryService
from backend.services.pydantic_ai_service import ProductQueryService
logger = logging.getLogger(__name__)
class MessageRouter:
"""訊息路由器類"""
def __init__(self):
self.groq_service = GroqService()
self.business_service = BusinessQueryService()
self.product_query_service = ProductQueryService()
# 路由統計
self.route_stats = {
"chat": 0,
"search": 0,
"product_query": 0,
"help": 0,
"smart": 0,
"error": 0
}
def route_message(self, message: str, user_id: str) -> Dict[str, Any]:
"""
根據前綴和內容路由訊息
Args:
message: 用戶訊息
user_id: 用戶ID
Returns:
處理結果字典
"""
try:
# 清理訊息
clean_message = message.strip()
if not clean_message:
return self._handle_empty_message(user_id)
logger.info(f"路由訊息 - 用戶: {user_id}, 內容: {clean_message[:50]}...")
# 1. 聊天模式 - /chat 前綴
if clean_message.startswith('/chat '):
self.route_stats["chat"] += 1
return self._handle_chat_mode(clean_message[6:].strip(), user_id)
# 2. 商品查詢 - /search 前綴
elif clean_message.startswith('/search '):
self.route_stats["search"] += 1
return self._handle_search_mode(clean_message[8:].strip(), user_id)
# 3. 幫助指令
elif clean_message.lower() in ['/help', '幫助', 'help', '說明', '指令']:
self.route_stats["help"] += 1
return self._handle_help_mode(user_id)
# 4. 特殊指令
elif clean_message.lower() in ['選單', 'menu', '功能']:
return self._handle_menu_mode(user_id)
elif clean_message.lower() in ['統計', 'stats', '路由統計']:
return self._handle_stats_mode(user_id)
# 5. 智能路由 - 無前綴時進行意圖分析
else:
self.route_stats["smart"] += 1
return self._handle_smart_routing(clean_message, user_id)
except Exception as e:
self.route_stats["error"] += 1
logger.error(f"訊息路由錯誤: {str(e)}")
return {
"type": "text",
"text": "❌ 系統處理訊息時發生錯誤,請稍後再試。",
"mode": "error",
"success": False,
"error": str(e)
}
def _handle_chat_mode(self, message: str, user_id: str) -> Dict[str, Any]:
"""處理聊天模式"""
try:
if not message:
return {
"type": "text",
"text": "💬 請輸入您想聊的內容!\n\n範例:/chat 今天天氣如何?",
"mode": "chat",
"success": True
}
if not self.groq_service.is_available():
return {
"type": "text",
"text": "💬 聊天服務暫時無法使用,請稍後再試。\n\n💡 您可以嘗試使用 /search 進行商品查詢。",
"mode": "chat",
"success": False,
"error": "Groq 服務不可用"
}
system_prompt = """你是一個友善的客服助手,專門協助用戶解答問題。
請用繁體中文回應,語氣要親切自然。
如果用戶詢問商品或業務相關問題,建議他們使用 /search 指令進行查詢。
回應長度控制在 150 字以內。"""
response = self.groq_service.chat_completion(message, system_prompt)
return {
"type": "text",
"text": f"💬 {response}",
"mode": "chat",
"success": True,
"user_id": user_id
}
except Exception as e:
logger.error(f"聊天模式處理錯誤: {str(e)}")
return {
"type": "text",
"text": "💬 聊天服務暫時無法使用,請稍後再試。",
"mode": "chat",
"success": False,
"error": str(e)
}
def _handle_search_mode(self, message: str, user_id: str) -> Dict[str, Any]:
"""處理搜尋模式"""
try:
if not message:
return {
"type": "text",
"text": "🔍 請輸入您要查詢的內容!\n\n範例:\n• /search iPhone 15\n• /search 價格 1000-5000\n• /search 庫存不足的商品",
"mode": "search",
"success": True
}
# 使用業務查詢服務
result = self.business_service.process_user_query(message, user_id)
# 如果有 Groq 服務,使用它來生成更自然的回應
if self.groq_service.is_available() and result.get("success"):
try:
natural_response = self.groq_service.generate_business_response(result, message)
response_text = f"🔍 {natural_response}"
except Exception as e:
logger.warning(f"Groq 回應生成失敗,使用原始回應: {str(e)}")
response_text = f"🔍 {result['response_message']}"
else:
response_text = f"🔍 {result['response_message']}"
return {
"type": "text",
"text": response_text,
"mode": "search",
"success": result["success"],
"data": result.get("data", []),
"intent": result.get("intent", "unknown"),
"confidence": result.get("confidence", 0.0),
"user_id": user_id
}
except Exception as e:
logger.error(f"搜尋模式處理錯誤: {str(e)}")
return {
"type": "text",
"text": "🔍 搜尋服務暫時無法使用,請稍後再試。",
"mode": "search",
"success": False,
"error": str(e)
}
def _handle_smart_routing(self, message: str, user_id: str) -> Dict[str, Any]:
"""智能路由 - 根據內容判斷意圖"""
try:
# 1. 優先檢查是否為商品查詢 (使用 Pydantic AI)
if self.product_query_service.is_available():
product_intent = self.product_query_service.analyze_query_intent(message)
# 降低信心度閾值,讓更多商品查詢被 Pydantic AI 處理
if product_intent["is_product_query"] and product_intent["confidence"] > 0.5:
logger.info(f"智能路由 -> Pydantic AI 商品查詢 (信心度: {product_intent['confidence']:.2f})")
self.route_stats["product_query"] += 1
return self._handle_product_query_mode(message, user_id)
else:
logger.info(f"Pydantic AI 意圖分析: 商品查詢={product_intent['is_product_query']}, 信心度={product_intent['confidence']:.2f}")
else:
logger.warning("Pydantic AI 服務不可用,使用傳統路由")
# 2. 進行簡單的關鍵字預篩選(只處理非商品查詢)
quick_intent = self._quick_intent_check(message)
if quick_intent == "help":
# 幫助相關 -> 轉到幫助模式
return self._handle_help_mode(user_id)
elif quick_intent == "search":
# 傳統搜尋關鍵字 -> 轉到搜尋模式
logger.info(f"快速意圖識別: 搜尋模式 - {message[:30]}...")
return self._handle_search_mode(message, user_id)
# 使用 Groq 進行深度意圖分析
if self.groq_service.is_available():
try:
intent_result = self.groq_service.analyze_intent(message)
logger.info(f"Groq 意圖分析: {intent_result}")
# 根據意圖和信心度決定路由
if intent_result["intent"] in ["search", "order", "inventory"] and intent_result["confidence"] > 0.6:
logger.info(f"智能路由 -> 搜尋模式 (信心度: {intent_result['confidence']})")
return self._handle_search_mode(message, user_id)
elif intent_result["intent"] == "help" and intent_result["confidence"] > 0.7:
return self._handle_help_mode(user_id)
else:
# 其他情況或低信心度 -> 轉到聊天模式
logger.info(f"智能路由 -> 聊天模式 (意圖: {intent_result['intent']}, 信心度: {intent_result['confidence']})")
return self._handle_chat_mode(message, user_id)
except Exception as e:
logger.warning(f"Groq 意圖分析失敗,使用預設路由: {str(e)}")
# 錯誤時預設為聊天模式
return self._handle_chat_mode(message, user_id)
else:
# Groq 不可用時,根據簡單規則路由
if quick_intent == "search":
return self._handle_search_mode(message, user_id)
else:
return self._handle_chat_mode(message, user_id)
except Exception as e:
logger.error(f"智能路由處理錯誤: {str(e)}")
# 錯誤時預設為聊天模式
return self._handle_chat_mode(message, user_id)
def _handle_product_query_mode(self, message: str, user_id: str) -> Dict[str, Any]:
"""處理 Pydantic AI 商品查詢模式"""
try:
if not message:
return {
"type": "text",
"text": "🛍️ 請告訴我您想查詢什麼商品!\n\n範例:\n• 是否有推薦貓砂?\n• 查詢狗糧庫存\n• 有什麼寵物用品?",
"mode": "product_query",
"success": True
}
if not self.product_query_service.is_available():
return {
"type": "text",
"text": "🛍️ 商品查詢服務暫時無法使用,請稍後再試。\n\n💡 您可以嘗試使用 /search 進行基本查詢。",
"mode": "product_query",
"success": False,
"error": "Pydantic AI 服務不可用"
}
# 使用同步版本避免異步複雜性
result = self.product_query_service.process_product_query_sync(message, user_id)
if result["success"]:
return {
"type": "text",
"text": f"🛍️ {result['text']}",
"mode": "product_query",
"success": True,
"products_found": result.get("products_found", 0),
"has_recommendations": result.get("has_recommendations", False),
"user_id": user_id
}
else:
return {
"type": "text",
"text": f"🛍️ {result.get('text', '商品查詢失敗')}",
"mode": "product_query",
"success": False,
"error": result.get("error"),
"user_id": user_id
}
except Exception as e:
logger.error(f"商品查詢模式處理錯誤: {str(e)}")
return {
"type": "text",
"text": "🛍️ 商品查詢服務暫時無法使用,請稍後再試。",
"mode": "product_query",
"success": False,
"error": str(e)
}
def _quick_intent_check(self, message: str) -> str:
"""快速意圖檢查 - 基於關鍵字(避免與 Pydantic AI 商品查詢衝突)"""
message_lower = message.lower()
# 幫助相關關鍵字(優先處理)
help_keywords = ['幫助', 'help', '說明', '怎麼用', '指令', '功能', '統計', 'stats', '選單', 'menu']
# 非商品的業務查詢關鍵字(避免與商品查詢衝突)
business_keywords = [
'訂單狀態', '訂單查詢', '我的訂單', '交易記錄', '購買記錄',
'客戶資料', '會員資料', '帳戶資訊', '銷售報表', '財務報表'
]
if any(keyword in message_lower for keyword in help_keywords):
return "help"
elif any(keyword in message_lower for keyword in business_keywords):
return "search"
else:
# 預設返回 "chat",讓 Pydantic AI 有機會處理商品查詢
return "chat"
def _handle_help_mode(self, user_id: str) -> Dict[str, Any]:
"""處理幫助模式"""
help_text = """🤖 智能客服助手使用指南
📝 指令模式:
• /chat [訊息] - 聊天模式
範例:/chat 今天天氣如何?
• /search [查詢] - 商品查詢模式
範例:/search iPhone 15 Pro
範例:/search 價格 1000-5000
• /help - 顯示此說明
🧠 智能模式:
直接輸入訊息,系統會自動判斷是聊天還是查詢!
🛍️ 商品查詢範例(AI 智能識別):
• "是否有推薦貓砂?"
• "有什麼狗糧推薦?"
• "查詢寵物用品庫存"
• "iPhone 還有庫存嗎?"
• "1000到5000的商品有哪些?"
💬 聊天範例:
• "你好!"
• "今天天氣如何?"
• "推薦一些好用的功能"
📋 其他查詢:
• "我的訂單狀態如何?"
• "低庫存商品有哪些?"
輸入「選單」查看功能選單
輸入「統計」查看使用統計"""
return {
"type": "text",
"text": help_text,
"mode": "help",
"success": True,
"user_id": user_id
}
def _handle_menu_mode(self, user_id: str) -> Dict[str, Any]:
"""處理選單模式"""
menu_text = """🏪 功能選單
請選擇您需要的功能:
🔍 商品查詢
• /search [商品名稱]
• /search 價格 [範圍]
💬 智能聊天
• /chat [您的問題]
📊 常用查詢
• "庫存查詢"
• "我的訂單"
• "低庫存商品"
• "業務統計"
❓ 說明
• /help - 詳細說明
• 統計 - 使用統計
💡 小提示:您也可以直接輸入問題,系統會智能判斷處理方式!"""
return {
"type": "text",
"text": menu_text,
"mode": "menu",
"success": True,
"user_id": user_id
}
def _handle_stats_mode(self, user_id: str) -> Dict[str, Any]:
"""處理統計模式"""
total_routes = sum(self.route_stats.values())
if total_routes == 0:
stats_text = "📊 路由統計\n\n尚無使用記錄"
else:
stats_text = f"""📊 路由統計 (總計: {total_routes})
💬 聊天模式: {self.route_stats['chat']} ({self.route_stats['chat']/total_routes*100:.1f}%)
🔍 搜尋模式: {self.route_stats['search']} ({self.route_stats['search']/total_routes*100:.1f}%)
🛍️ 商品查詢: {self.route_stats['product_query']} ({self.route_stats['product_query']/total_routes*100:.1f}%)
🧠 智能路由: {self.route_stats['smart']} ({self.route_stats['smart']/total_routes*100:.1f}%)
❓ 幫助模式: {self.route_stats['help']} ({self.route_stats['help']/total_routes*100:.1f}%)
❌ 錯誤次數: {self.route_stats['error']} ({self.route_stats['error']/total_routes*100:.1f}%)
🤖 Groq 服務: {'✅ 可用' if self.groq_service.is_available() else '❌ 不可用'}
🛍️ Pydantic AI: {'✅ 可用' if self.product_query_service.is_available() else '❌ 不可用'}"""
return {
"type": "text",
"text": stats_text,
"mode": "stats",
"success": True,
"user_id": user_id,
"data": self.route_stats
}
def _handle_empty_message(self, user_id: str) -> Dict[str, Any]:
"""處理空訊息"""
return {
"type": "text",
"text": "🤔 您似乎沒有輸入任何內容。\n\n輸入 /help 查看使用說明,或直接告訴我您需要什麼幫助!",
"mode": "empty",
"success": True,
"user_id": user_id
}
def get_route_statistics(self) -> Dict[str, Any]:
"""取得路由統計資訊"""
return {
"stats": self.route_stats.copy(),
"total": sum(self.route_stats.values()),
"groq_available": self.groq_service.is_available()
}