""" OpenRouter API 服務 用於進階自然語言處理功能 """ import httpx import json from typing import Dict, Any, Optional from backend.config import settings import logging logger = logging.getLogger(__name__) class OpenRouterService: """OpenRouter API 服務類""" def __init__(self): self.api_key = settings.OPENROUTER_API_KEY self.model = settings.OPENROUTER_MODEL self.base_url = "https://openrouter.ai/api/v1" self.headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", "HTTP-Referer": "https://huggingface.co/spaces", "X-Title": "LINE Bot NLP Service" } async def analyze_intent_advanced(self, message: str, context: Dict[str, Any] = None) -> Dict[str, Any]: """ 使用 OpenRouter 進行進階意圖分析 Args: message: 用戶訊息 context: 上下文資訊 Returns: 分析結果字典 """ if not self.api_key: logger.warning("OpenRouter API Key 未設定,使用基礎 NLP 服務") return self._fallback_analysis(message) try: prompt = self._build_analysis_prompt(message, context) async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{self.base_url}/chat/completions", headers=self.headers, json={ "model": self.model, "messages": [ { "role": "system", "content": "你是一個專業的中文自然語言處理助手,專門分析用戶查詢意圖並提取相關實體。請以 JSON 格式回應。" }, { "role": "user", "content": prompt } ], "temperature": 0.1, "max_tokens": 500 } ) if response.status_code == 200: result = response.json() content = result["choices"][0]["message"]["content"] try: # 嘗試解析 JSON 回應 analysis = json.loads(content) return self._validate_analysis_result(analysis) except json.JSONDecodeError: logger.error(f"無法解析 OpenRouter 回應: {content}") return self._fallback_analysis(message) else: logger.error(f"OpenRouter API 錯誤: {response.status_code}") return self._fallback_analysis(message) except Exception as e: logger.error(f"OpenRouter 服務錯誤: {str(e)}") return self._fallback_analysis(message) def _build_analysis_prompt(self, message: str, context: Dict[str, Any] = None) -> str: """建構分析提示詞""" context_info = "" if context: context_info = f"\n上下文資訊: {json.dumps(context, ensure_ascii=False)}" prompt = f""" 請分析以下中文訊息的意圖和實體: 訊息: "{message}"{context_info} 請以以下 JSON 格式回應: {{ "intent": "查詢意圖類型 (search_user/search_product/search_order/create_order/update_profile/analytics/unknown)", "confidence": 0.0-1.0之間的信心度, "entities": {{ "user_id": "提取的用戶ID", "user_name": "提取的用戶名稱", "product_name": "提取的商品名稱", "order_id": "提取的訂單ID", "min_price": 最低價格數字, "max_price": 最高價格數字, "category": "商品類別", "number": 數量 }}, "query_type": "search/create/update/delete/analytics", "parameters": {{ "table": "目標資料表名稱", "conditions": "查詢條件", "limit": 查詢限制數量 }} }} 範例: - "查詢用戶張三" → intent: "search_user", entities: {{"user_name": "張三"}} - "找價格1000到5000的手機" → intent: "search_product", entities: {{"min_price": 1000, "max_price": 5000, "category": "手機"}} - "統計訂單數量" → intent: "analytics", query_type: "analytics" """ return prompt def _validate_analysis_result(self, analysis: Dict[str, Any]) -> Dict[str, Any]: """驗證分析結果""" # 確保必要欄位存在 required_fields = ["intent", "confidence", "entities", "query_type"] for field in required_fields: if field not in analysis: analysis[field] = self._get_default_value(field) # 驗證信心度範圍 if not 0 <= analysis.get("confidence", 0) <= 1: analysis["confidence"] = 0.5 # 確保實體是字典 if not isinstance(analysis.get("entities"), dict): analysis["entities"] = {} return analysis def _get_default_value(self, field: str) -> Any: """取得欄位預設值""" defaults = { "intent": "unknown", "confidence": 0.1, "entities": {}, "query_type": "unknown", "parameters": {} } return defaults.get(field, None) def _fallback_analysis(self, message: str) -> Dict[str, Any]: """備用分析方法""" return { "intent": "unknown", "confidence": 0.1, "entities": {}, "query_type": "unknown", "parameters": {}, "fallback": True } async def generate_response(self, query_result: Dict[str, Any], user_message: str) -> str: """ 使用 OpenRouter 生成更自然的回應 Args: query_result: 資料庫查詢結果 user_message: 用戶原始訊息 Returns: 生成的回應文字 """ if not self.api_key: return self._generate_simple_response(query_result) try: prompt = f""" 請根據以下資訊生成一個友善、自然的中文回應: 用戶訊息: "{user_message}" 查詢結果: {json.dumps(query_result, ensure_ascii=False)} 要求: 1. 回應要簡潔明瞭 2. 使用友善的語調 3. 如果有資料,要清楚呈現重要資訊 4. 如果沒有資料,要給出建議 5. 回應長度控制在 200 字以內 範例格式: - 找到資料時:「找到 X 筆資料:[列出重要資訊]」 - 沒有資料時:「很抱歉,沒有找到相關資料,您可以嘗試...」 """ async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{self.base_url}/chat/completions", headers=self.headers, json={ "model": self.model, "messages": [ { "role": "system", "content": "你是一個友善的客服助手,專門幫助用戶理解查詢結果。" }, { "role": "user", "content": prompt } ], "temperature": 0.3, "max_tokens": 300 } ) if response.status_code == 200: result = response.json() generated_response = result["choices"][0]["message"]["content"].strip() return generated_response else: logger.error(f"OpenRouter 回應生成錯誤: {response.status_code}") return self._generate_simple_response(query_result) except Exception as e: logger.error(f"OpenRouter 回應生成失敗: {str(e)}") return self._generate_simple_response(query_result) def _generate_simple_response(self, query_result: Dict[str, Any]) -> str: """簡單回應生成""" if query_result.get("success"): data_count = len(query_result.get("data", [])) if data_count > 0: return f"找到 {data_count} 筆相關資料。" else: return "沒有找到相關資料。" else: return "查詢時發生錯誤,請稍後再試。"