mickeywu520's picture
something update...
4514838
"""
Groq API 服務
提供快速的 AI 推理功能
"""
from groq import Groq
from backend.config import settings
import logging
import json
import re
logger = logging.getLogger(__name__)
class GroqService:
"""Groq AI 服務類"""
def __init__(self):
self.api_key = settings.GROQ_API_KEY
self.model = getattr(settings, 'GROQ_MODEL', 'qwen/qwen3-32b')
if not self.api_key:
logger.warning("GROQ_API_KEY 未設定,Groq 服務將無法使用")
self.client = None
else:
self.client = Groq(api_key=self.api_key)
def is_available(self) -> bool:
"""檢查 Groq 服務是否可用"""
return self.client is not None
def chat_completion(self, message: str, system_prompt: str = None, temperature: float = 0.6) -> str:
"""
聊天完成
Args:
message: 用戶訊息
system_prompt: 系統提示詞
temperature: 溫度參數 (0.0-1.0)
Returns:
AI 回應內容
"""
if not self.is_available():
raise Exception("Groq 服務不可用,請檢查 API Key 設定")
try:
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": message})
completion = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
max_completion_tokens=4096,
top_p=0.95,
reasoning_effort="default",
stop=None,
)
return completion.choices[0].message.content
except Exception as e:
logger.error(f"Groq API 調用失敗: {str(e)}")
raise Exception(f"AI 服務暫時無法使用: {str(e)}")
def analyze_intent(self, message: str) -> dict:
"""
分析用戶訊息意圖
Args:
message: 用戶訊息
Returns:
意圖分析結果
"""
if not self.is_available():
return {
"intent": "unknown",
"confidence": 0.0,
"entities": {},
"error": "Groq 服務不可用"
}
system_prompt = """你是一個意圖分析助手,分析用戶訊息的意圖。
請分析用戶訊息並回傳 JSON 格式:
{
"intent": "search|chat|help|order|inventory|unknown",
"confidence": 0.0-1.0,
"entities": {
"product": "商品名稱",
"action": "動作類型",
"price_range": "價格範圍",
"category": "商品分類"
},
"reasoning": "分析原因"
}
意圖類型說明:
- search: 商品查詢、搜尋相關
- chat: 一般聊天、問候、閒聊
- help: 求助、說明、指令
- order: 訂單查詢、訂單相關
- inventory: 庫存查詢、庫存相關
- unknown: 無法確定意圖
請用繁體中文回應,並確保回傳有效的 JSON 格式。"""
try:
response = self.chat_completion(message, system_prompt, temperature=0.3)
return self._parse_intent_response(response)
except Exception as e:
logger.error(f"意圖分析失敗: {str(e)}")
return {
"intent": "unknown",
"confidence": 0.0,
"entities": {},
"error": str(e)
}
def _parse_intent_response(self, response: str) -> dict:
"""解析意圖分析回應"""
try:
# 嘗試提取 JSON 部分
json_match = re.search(r'\{.*\}', response, re.DOTALL)
if json_match:
json_str = json_match.group()
result = json.loads(json_str)
# 驗證必要欄位
if "intent" not in result:
result["intent"] = "unknown"
if "confidence" not in result:
result["confidence"] = 0.5
if "entities" not in result:
result["entities"] = {}
# 確保信心度在有效範圍內
result["confidence"] = max(0.0, min(1.0, float(result["confidence"])))
return result
else:
# 如果沒有找到 JSON,嘗試簡單解析
return self._fallback_intent_parsing(response)
except json.JSONDecodeError as e:
logger.warning(f"JSON 解析失敗: {str(e)}, 回應內容: {response}")
return self._fallback_intent_parsing(response)
except Exception as e:
logger.error(f"意圖回應解析錯誤: {str(e)}")
return {
"intent": "unknown",
"confidence": 0.0,
"entities": {},
"error": f"解析錯誤: {str(e)}"
}
def _fallback_intent_parsing(self, response: str) -> dict:
"""備用的意圖解析方法"""
response_lower = response.lower()
# 簡單的關鍵字匹配
if any(keyword in response_lower for keyword in ['查詢', '搜尋', '找', '商品', '產品']):
return {
"intent": "search",
"confidence": 0.6,
"entities": {},
"reasoning": "關鍵字匹配: 搜尋相關"
}
elif any(keyword in response_lower for keyword in ['訂單', 'order']):
return {
"intent": "order",
"confidence": 0.6,
"entities": {},
"reasoning": "關鍵字匹配: 訂單相關"
}
elif any(keyword in response_lower for keyword in ['庫存', 'inventory', '存貨']):
return {
"intent": "inventory",
"confidence": 0.6,
"entities": {},
"reasoning": "關鍵字匹配: 庫存相關"
}
elif any(keyword in response_lower for keyword in ['幫助', 'help', '說明', '指令']):
return {
"intent": "help",
"confidence": 0.8,
"entities": {},
"reasoning": "關鍵字匹配: 幫助相關"
}
else:
return {
"intent": "chat",
"confidence": 0.4,
"entities": {},
"reasoning": "預設為聊天模式"
}
def generate_business_response(self, query_result: dict, original_message: str) -> str:
"""
根據業務查詢結果生成自然回應
Args:
query_result: 業務查詢結果
original_message: 原始用戶訊息
Returns:
自然語言回應
"""
if not self.is_available():
return self._fallback_business_response(query_result)
system_prompt = f"""你是一個友善的客服助手,需要根據查詢結果為用戶生成自然的回應。
用戶原始訊息:{original_message}
查詢結果:
- 成功: {query_result.get('success', False)}
- 資料筆數: {len(query_result.get('data', []))}
- 意圖: {query_result.get('intent', 'unknown')}
請用繁體中文生成一個友善、自然的回應,包含查詢結果的摘要。
如果有具體資料,請整理成易讀的格式。
回應長度控制在 200 字以內。"""
try:
# 準備查詢結果摘要
data_summary = self._prepare_data_summary(query_result.get('data', []))
full_prompt = f"{system_prompt}\n\n資料摘要:\n{data_summary}"
response = self.chat_completion(original_message, full_prompt, temperature=0.4)
return response
except Exception as e:
logger.error(f"生成業務回應失敗: {str(e)}")
return self._fallback_business_response(query_result)
def _prepare_data_summary(self, data: list) -> str:
"""準備資料摘要"""
if not data:
return "沒有找到相關資料"
summary_lines = []
for i, item in enumerate(data[:5]): # 最多顯示 5 筆
if isinstance(item, dict):
# 提取重要欄位
name = item.get('name', item.get('product_name', ''))
price = item.get('price', item.get('unit_price', ''))
stock = item.get('stock', item.get('quantity', ''))
line_parts = []
if name:
line_parts.append(f"名稱: {name}")
if price:
line_parts.append(f"價格: ${price}")
if stock:
line_parts.append(f"庫存: {stock}")
if line_parts:
summary_lines.append(f"{i+1}. {', '.join(line_parts)}")
if len(data) > 5:
summary_lines.append(f"... 還有 {len(data) - 5} 筆資料")
return '\n'.join(summary_lines) if summary_lines else "資料格式無法解析"
def _fallback_business_response(self, query_result: dict) -> str:
"""備用的業務回應生成"""
if query_result.get('success'):
data_count = len(query_result.get('data', []))
if data_count > 0:
return f"✅ 查詢成功!找到 {data_count} 筆相關資料。"
else:
return "✅ 查詢完成,但沒有找到符合條件的資料。"
else:
error_msg = query_result.get('error', '未知錯誤')
return f"❌ 查詢失敗:{error_msg}"