File size: 8,855 Bytes
cd9bca9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
"""
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 "查詢時發生錯誤,請稍後再試。" |