mickeywu520 commited on
Commit
4514838
·
1 Parent(s): 184558c

something update...

Browse files
.env.example CHANGED
@@ -13,6 +13,10 @@ DB_PASSWORD=your_database_password_here
13
  OPENROUTER_API_KEY=your_openrouter_api_key_here
14
  OPENROUTER_MODEL=anthropic/claude-3-haiku
15
 
 
 
 
 
16
  # 其他設定
17
  DEBUG=False
18
  LOG_LEVEL=INFO
 
13
  OPENROUTER_API_KEY=your_openrouter_api_key_here
14
  OPENROUTER_MODEL=anthropic/claude-3-haiku
15
 
16
+ # Groq 設定 (主要 AI 服務,推薦)
17
+ GROQ_API_KEY=your_groq_api_key_here
18
+ GROQ_MODEL=qwen/qwen3-32b
19
+
20
  # 其他設定
21
  DEBUG=False
22
  LOG_LEVEL=INFO
GROQ_INTEGRATION_COMPLETE.md ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 Groq 整合完成指南
2
+
3
+ ## 📋 整合概述
4
+
5
+ 已成功將 Groq AI 服務整合到 LINE Bot 系統中,並實現智能訊息路由功能。
6
+
7
+ ### ✅ 完成的功能
8
+
9
+ 1. **Groq 服務整合** (`backend/services/groq_service.py`)
10
+ - 快速 AI 推理服務
11
+ - 意圖分析功能
12
+ - 自然回應生成
13
+
14
+ 2. **智能訊息路由** (`backend/services/message_router.py`)
15
+ - 前綴指令路由
16
+ - 智能意圖識別
17
+ - 統計功能
18
+
19
+ 3. **新增 API 端點**
20
+ - `/chat` - 直接聊天
21
+ - `/search` - 直接搜尋
22
+ - `/route` - 智能路由
23
+ - `/stats` - 使用統計
24
+
25
+ 4. **LINE Bot 優化**
26
+ - 避免所有訊息都觸發 NLP
27
+ - 支援前綴指令
28
+ - 智能判斷聊天 vs 查詢
29
+
30
+ ## 🔧 環境變數設定
31
+
32
+ ### 新增必要變數
33
+ ```bash
34
+ # Groq 設定 (主要 AI 服務)
35
+ GROQ_API_KEY=your_groq_api_key_here
36
+ GROQ_MODEL=qwen/qwen3-32b
37
+ ```
38
+
39
+ ### 完整環境變數
40
+ ```bash
41
+ # LINE Bot 設定
42
+ LINE_CHANNEL_ACCESS_TOKEN=your_line_channel_access_token_here
43
+ LINE_CHANNEL_SECRET=your_line_channel_secret_here
44
+
45
+ # PostgreSQL 資料庫設定
46
+ DB_HOST=your_database_host_here
47
+ DB_PORT=6543
48
+ DB_NAME=your_database_name_here
49
+ DB_USER=your_database_user_here
50
+ DB_PASSWORD=your_database_password_here
51
+
52
+ # Groq 設定 (主要 AI 服務,推薦)
53
+ GROQ_API_KEY=your_groq_api_key_here
54
+ GROQ_MODEL=qwen/qwen3-32b
55
+
56
+ # OpenRouter 設定 (可選,備用)
57
+ OPENROUTER_API_KEY=your_openrouter_api_key_here
58
+ OPENROUTER_MODEL=anthropic/claude-3-haiku
59
+
60
+ # 其他設定
61
+ DEBUG=False
62
+ LOG_LEVEL=INFO
63
+ ```
64
+
65
+ ## 📱 使用方式
66
+
67
+ ### LINE Bot 指令
68
+
69
+ #### 1. 前綴指令模式
70
+ ```
71
+ 💬 聊天模式:
72
+ /chat 你好,今天天氣如何?
73
+
74
+ 🔍 商品查詢:
75
+ /search iPhone 15 Pro
76
+ /search 價格 1000-5000 的商品
77
+
78
+ ❓ 幫助:
79
+ /help 或 幫助
80
+ ```
81
+
82
+ #### 2. 智能模式(無前綴)
83
+ ```
84
+ 🤖 系統會自動判斷:
85
+
86
+ 聊天類型:
87
+ • "你好!"
88
+ • "今天天氣如何?"
89
+ • "推薦一些功能"
90
+
91
+ 查詢類型:
92
+ • "iPhone 還有庫存嗎?"
93
+ • "1000到5000的商品有哪些?"
94
+ • "我的訂單狀態如何?"
95
+ ```
96
+
97
+ ### API 端點使用
98
+
99
+ #### 直接聊天 API
100
+ ```bash
101
+ curl -X POST http://localhost:7860/chat \
102
+ -H "Content-Type: application/json" \
103
+ -d '{"message": "你好!", "user_id": "test_user"}'
104
+ ```
105
+
106
+ #### 直接搜尋 API
107
+ ```bash
108
+ curl -X POST http://localhost:7860/search \
109
+ -H "Content-Type: application/json" \
110
+ -d '{"message": "iPhone 15", "user_id": "test_user"}'
111
+ ```
112
+
113
+ #### 智能路由 API
114
+ ```bash
115
+ curl -X POST http://localhost:7860/route \
116
+ -H "Content-Type: application/json" \
117
+ -d '{"message": "iPhone 還有庫存嗎?", "user_id": "test_user"}'
118
+ ```
119
+
120
+ #### 統計 API
121
+ ```bash
122
+ curl http://localhost:7860/stats
123
+ ```
124
+
125
+ ## 🧪 測試
126
+
127
+ ### 執行整合測試
128
+ ```bash
129
+ python tmp_rovodev_test_groq_integration.py
130
+ ```
131
+
132
+ ### 測試項目
133
+ - ✅ 健康檢查
134
+ - ✅ 聊天 API
135
+ - ✅ 搜尋 API
136
+ - ✅ 智能路由
137
+ - ✅ 統計功能
138
+ - ✅ LINE Webhook 模擬
139
+
140
+ ## 🔄 路由邏輯
141
+
142
+ ### 訊息處理流程
143
+ ```
144
+ 用戶訊息 → MessageRouter
145
+
146
+ 前綴檢查 (/chat, /search, /help)
147
+
148
+ 智能意圖分析 (Groq)
149
+
150
+ 路由到對應服務
151
+
152
+ 生成回應
153
+ ```
154
+
155
+ ### 意圖分析
156
+ - **搜尋意圖**: 商品查詢、庫存、訂單等
157
+ - **聊天意圖**: 問候、閒聊、一般問題
158
+ - **幫助意圖**: 說明、指令、功能介紹
159
+
160
+ ## 📊 優勢
161
+
162
+ ### 相比 OpenRouter
163
+ - ⚡ **速度更快**: Groq 推理速度顯著提升
164
+ - 💰 **成本更低**: 更經濟的 API 定價
165
+ - 🎯 **中文優化**: qwen3-32b 對中文支援更好
166
+
167
+ ### 智能路由優勢
168
+ - 🎯 **精準路由**: 避免誤觸發 NLP 分析
169
+ - 🔀 **靈活切換**: 支援指令和智能模式
170
+ - 📈 **使用統計**: 追蹤路由效果
171
+
172
+ ## 🚨 注意事項
173
+
174
+ ### 1. API Key 安全
175
+ - 請妥善保管 Groq API Key
176
+ - 不要將 API Key 提交到版本控制
177
+
178
+ ### 2. 錯誤處理
179
+ - Groq 服務不可用時會自動降級
180
+ - 保持原有功能的向後相容性
181
+
182
+ ### 3. 效能監控
183
+ - 使用 `/stats` 監控路由統計
184
+ - 關注 Groq API 使用量
185
+
186
+ ## 🔧 故障排除
187
+
188
+ ### Groq 服務不可用
189
+ ```python
190
+ # 檢查 API Key 設定
191
+ print(settings.GROQ_API_KEY)
192
+
193
+ # 檢查服務狀態
194
+ groq_service = GroqService()
195
+ print(groq_service.is_available())
196
+ ```
197
+
198
+ ### 路由統計異常
199
+ ```bash
200
+ # 查看路由統計
201
+ curl http://localhost:7860/stats
202
+ ```
203
+
204
+ ### LINE Bot 無回應
205
+ 1. 檢查 Webhook URL 設定
206
+ 2. 確認環境變數正確
207
+ 3. 查看應用程式日誌
208
+
209
+ ## 📈 後續優化建議
210
+
211
+ 1. **快取機制**: 為常見查詢添加快取
212
+ 2. **A/B 測試**: 比較不同路由策略效果
213
+ 3. **用戶偏好**: 學習用戶使用習慣
214
+ 4. **多語言支援**: 擴展到其他語言
215
+
216
+ ## 🎉 部署
217
+
218
+ ### Hugging Face Spaces
219
+ 1. 更新環境變數(加入 GROQ_API_KEY)
220
+ 2. 推送代碼更新
221
+ 3. 等待自動部署完成
222
+
223
+ ### 本地測試
224
+ ```bash
225
+ # 安裝新依賴
226
+ pip install -r requirements.txt
227
+
228
+ # 設定環境變數
229
+ export GROQ_API_KEY=your_groq_api_key
230
+
231
+ # 啟動服務
232
+ python -m uvicorn backend.main:app --reload --port 7860
233
+ ```
234
+
235
+ ---
236
+
237
+ **🎊 整合完成!** 您的 LINE Bot 現在具備了更快速、更智能的 AI 服務能力!
backend/config.py CHANGED
@@ -25,6 +25,10 @@ class Settings(BaseSettings):
25
  OPENROUTER_API_KEY: Optional[str] = os.getenv("OPENROUTER_API_KEY", "")
26
  OPENROUTER_MODEL: str = os.getenv("OPENROUTER_MODEL", "anthropic/claude-3-haiku")
27
 
 
 
 
 
28
  # 其他設定
29
  DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
30
  LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
 
25
  OPENROUTER_API_KEY: Optional[str] = os.getenv("OPENROUTER_API_KEY", "")
26
  OPENROUTER_MODEL: str = os.getenv("OPENROUTER_MODEL", "anthropic/claude-3-haiku")
27
 
28
+ # Groq 設定 (主要 AI 服務)
29
+ GROQ_API_KEY: Optional[str] = os.getenv("GROQ_API_KEY", "")
30
+ GROQ_MODEL: str = os.getenv("GROQ_MODEL", "qwen/qwen3-32b")
31
+
32
  # 其他設定
33
  DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
34
  LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
backend/main.py CHANGED
@@ -7,6 +7,7 @@ from linebot.models import MessageEvent, TextMessage, TextSendMessage
7
  import logging
8
  from backend.services.nlp_service import NLPService
9
  from backend.services.database_service import DatabaseService
 
10
  from backend.config import settings
11
  from backend.database.connection import test_database_connection
12
 
@@ -32,6 +33,7 @@ handler = WebhookHandler(settings.LINE_CHANNEL_SECRET)
32
  # 服務初始化
33
  nlp_service = NLPService()
34
  db_service = DatabaseService()
 
35
 
36
  @app.get("/")
37
  def greet_json():
@@ -65,26 +67,20 @@ async def callback(request: Request):
65
 
66
  @handler.add(MessageEvent, message=TextMessage)
67
  def handle_message(event):
68
- """處理 LINE 訊息"""
69
  try:
70
  user_message = event.message.text
71
  user_id = event.source.user_id
72
 
73
  logger.info(f"收到訊息: {user_message} from {user_id}")
74
 
75
- # 使用 NLP 服務分析訊息
76
- analysis_result = nlp_service.analyze_message(user_message)
77
-
78
- # 根據分析結果調用對應的資料庫方法
79
- response_data = db_service.execute_query(analysis_result)
80
-
81
- # 格式化回應訊息
82
- reply_message = nlp_service.format_response(response_data, analysis_result, user_message)
83
 
84
  # 回覆訊息
85
  line_bot_api.reply_message(
86
  event.reply_token,
87
- TextSendMessage(text=reply_message)
88
  )
89
 
90
  except Exception as e:
@@ -94,6 +90,98 @@ def handle_message(event):
94
  TextSendMessage(text="抱歉,處理您的訊息時發生錯誤,請稍後再試。")
95
  )
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  if __name__ == "__main__":
98
  import uvicorn
99
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
7
  import logging
8
  from backend.services.nlp_service import NLPService
9
  from backend.services.database_service import DatabaseService
10
+ from backend.services.message_router import MessageRouter
11
  from backend.config import settings
12
  from backend.database.connection import test_database_connection
13
 
 
33
  # 服務初始化
34
  nlp_service = NLPService()
35
  db_service = DatabaseService()
36
+ message_router = MessageRouter()
37
 
38
  @app.get("/")
39
  def greet_json():
 
67
 
68
  @handler.add(MessageEvent, message=TextMessage)
69
  def handle_message(event):
70
+ """處理 LINE 訊息 - 使用新的路由系統"""
71
  try:
72
  user_message = event.message.text
73
  user_id = event.source.user_id
74
 
75
  logger.info(f"收到訊息: {user_message} from {user_id}")
76
 
77
+ # 使用訊息路由器處理
78
+ response_data = message_router.route_message(user_message, user_id)
 
 
 
 
 
 
79
 
80
  # 回覆訊息
81
  line_bot_api.reply_message(
82
  event.reply_token,
83
+ TextSendMessage(text=response_data["text"])
84
  )
85
 
86
  except Exception as e:
 
90
  TextSendMessage(text="抱歉,處理您的訊息時發生錯誤,請稍後再試。")
91
  )
92
 
93
+ # 新增直接 API 端點
94
+ @app.post("/chat")
95
+ async def direct_chat(request: dict):
96
+ """直接聊天 API"""
97
+ try:
98
+ message = request.get("message", "")
99
+ user_id = request.get("user_id", "api_user")
100
+
101
+ if not message:
102
+ return {
103
+ "success": False,
104
+ "error": "訊息內容不能為空"
105
+ }
106
+
107
+ # 強制使用聊天模式
108
+ result = message_router._handle_chat_mode(message, user_id)
109
+ return result
110
+
111
+ except Exception as e:
112
+ logger.error(f"直接聊天 API 錯誤: {str(e)}")
113
+ return {
114
+ "success": False,
115
+ "error": str(e),
116
+ "text": "聊天服務暫時無法使用"
117
+ }
118
+
119
+ @app.post("/search")
120
+ async def direct_search(request: dict):
121
+ """直接搜尋 API"""
122
+ try:
123
+ message = request.get("message", "")
124
+ user_id = request.get("user_id", "api_user")
125
+
126
+ if not message:
127
+ return {
128
+ "success": False,
129
+ "error": "查詢內容不能為空"
130
+ }
131
+
132
+ # 強制使用搜尋模式
133
+ result = message_router._handle_search_mode(message, user_id)
134
+ return result
135
+
136
+ except Exception as e:
137
+ logger.error(f"直接搜尋 API 錯誤: {str(e)}")
138
+ return {
139
+ "success": False,
140
+ "error": str(e),
141
+ "text": "搜尋服務暫時無法使用"
142
+ }
143
+
144
+ @app.post("/route")
145
+ async def smart_route(request: dict):
146
+ """智能路由 API"""
147
+ try:
148
+ message = request.get("message", "")
149
+ user_id = request.get("user_id", "api_user")
150
+
151
+ if not message:
152
+ return {
153
+ "success": False,
154
+ "error": "訊息內容不能為空"
155
+ }
156
+
157
+ # 使用智能路由
158
+ result = message_router.route_message(message, user_id)
159
+ return result
160
+
161
+ except Exception as e:
162
+ logger.error(f"智能路由 API 錯誤: {str(e)}")
163
+ return {
164
+ "success": False,
165
+ "error": str(e),
166
+ "text": "路由服務暫時無法使用"
167
+ }
168
+
169
+ @app.get("/stats")
170
+ async def get_stats():
171
+ """取得路由統計"""
172
+ try:
173
+ stats = message_router.get_route_statistics()
174
+ return {
175
+ "success": True,
176
+ "data": stats
177
+ }
178
+ except Exception as e:
179
+ logger.error(f"統計 API 錯誤: {str(e)}")
180
+ return {
181
+ "success": False,
182
+ "error": str(e)
183
+ }
184
+
185
  if __name__ == "__main__":
186
  import uvicorn
187
  uvicorn.run(app, host="0.0.0.0", port=7860)
backend/services/groq_service.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Groq API 服務
3
+ 提供快速的 AI 推理功能
4
+ """
5
+
6
+ from groq import Groq
7
+ from backend.config import settings
8
+ import logging
9
+ import json
10
+ import re
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class GroqService:
15
+ """Groq AI 服務類"""
16
+
17
+ def __init__(self):
18
+ self.api_key = settings.GROQ_API_KEY
19
+ self.model = getattr(settings, 'GROQ_MODEL', 'qwen/qwen3-32b')
20
+
21
+ if not self.api_key:
22
+ logger.warning("GROQ_API_KEY 未設定,Groq 服務將無法使用")
23
+ self.client = None
24
+ else:
25
+ self.client = Groq(api_key=self.api_key)
26
+
27
+ def is_available(self) -> bool:
28
+ """檢查 Groq 服務是否可用"""
29
+ return self.client is not None
30
+
31
+ def chat_completion(self, message: str, system_prompt: str = None, temperature: float = 0.6) -> str:
32
+ """
33
+ 聊天完成
34
+
35
+ Args:
36
+ message: 用戶訊息
37
+ system_prompt: 系統提示詞
38
+ temperature: 溫度參數 (0.0-1.0)
39
+
40
+ Returns:
41
+ AI 回應內容
42
+ """
43
+ if not self.is_available():
44
+ raise Exception("Groq 服務不可用,請檢查 API Key 設定")
45
+
46
+ try:
47
+ messages = []
48
+ if system_prompt:
49
+ messages.append({"role": "system", "content": system_prompt})
50
+ messages.append({"role": "user", "content": message})
51
+
52
+ completion = self.client.chat.completions.create(
53
+ model=self.model,
54
+ messages=messages,
55
+ temperature=temperature,
56
+ max_completion_tokens=4096,
57
+ top_p=0.95,
58
+ reasoning_effort="default",
59
+ stop=None,
60
+ )
61
+
62
+ return completion.choices[0].message.content
63
+
64
+ except Exception as e:
65
+ logger.error(f"Groq API 調用失敗: {str(e)}")
66
+ raise Exception(f"AI 服務暫時無法使用: {str(e)}")
67
+
68
+ def analyze_intent(self, message: str) -> dict:
69
+ """
70
+ 分析用戶訊息意圖
71
+
72
+ Args:
73
+ message: 用戶訊息
74
+
75
+ Returns:
76
+ 意圖分析結果
77
+ """
78
+ if not self.is_available():
79
+ return {
80
+ "intent": "unknown",
81
+ "confidence": 0.0,
82
+ "entities": {},
83
+ "error": "Groq 服務不可用"
84
+ }
85
+
86
+ system_prompt = """你是一個意圖分析助手,分析用戶訊息的意圖。
87
+
88
+ 請分析用戶訊息並回傳 JSON 格式:
89
+ {
90
+ "intent": "search|chat|help|order|inventory|unknown",
91
+ "confidence": 0.0-1.0,
92
+ "entities": {
93
+ "product": "商品名稱",
94
+ "action": "動作類型",
95
+ "price_range": "價格範圍",
96
+ "category": "商品分類"
97
+ },
98
+ "reasoning": "分析原因"
99
+ }
100
+
101
+ 意圖類型說明:
102
+ - search: 商品查詢、搜尋相關
103
+ - chat: 一般聊天、問候、閒聊
104
+ - help: 求助、說明、指令
105
+ - order: 訂單查詢、訂單相關
106
+ - inventory: 庫存查詢、庫存相關
107
+ - unknown: 無法確定意圖
108
+
109
+ 請用繁體中文回應,並確保回傳有效的 JSON 格式。"""
110
+
111
+ try:
112
+ response = self.chat_completion(message, system_prompt, temperature=0.3)
113
+ return self._parse_intent_response(response)
114
+
115
+ except Exception as e:
116
+ logger.error(f"意圖分析失敗: {str(e)}")
117
+ return {
118
+ "intent": "unknown",
119
+ "confidence": 0.0,
120
+ "entities": {},
121
+ "error": str(e)
122
+ }
123
+
124
+ def _parse_intent_response(self, response: str) -> dict:
125
+ """解析意圖分析回應"""
126
+ try:
127
+ # 嘗試提取 JSON 部分
128
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
129
+ if json_match:
130
+ json_str = json_match.group()
131
+ result = json.loads(json_str)
132
+
133
+ # 驗證必要欄位
134
+ if "intent" not in result:
135
+ result["intent"] = "unknown"
136
+ if "confidence" not in result:
137
+ result["confidence"] = 0.5
138
+ if "entities" not in result:
139
+ result["entities"] = {}
140
+
141
+ # 確保信心度在有效範圍內
142
+ result["confidence"] = max(0.0, min(1.0, float(result["confidence"])))
143
+
144
+ return result
145
+ else:
146
+ # 如果沒有找到 JSON,嘗試簡單解析
147
+ return self._fallback_intent_parsing(response)
148
+
149
+ except json.JSONDecodeError as e:
150
+ logger.warning(f"JSON 解析失敗: {str(e)}, 回應內容: {response}")
151
+ return self._fallback_intent_parsing(response)
152
+ except Exception as e:
153
+ logger.error(f"意圖回應解析錯誤: {str(e)}")
154
+ return {
155
+ "intent": "unknown",
156
+ "confidence": 0.0,
157
+ "entities": {},
158
+ "error": f"解析錯誤: {str(e)}"
159
+ }
160
+
161
+ def _fallback_intent_parsing(self, response: str) -> dict:
162
+ """備用的意圖解析方法"""
163
+ response_lower = response.lower()
164
+
165
+ # 簡單的關鍵字匹配
166
+ if any(keyword in response_lower for keyword in ['查詢', '搜尋', '找', '商品', '產品']):
167
+ return {
168
+ "intent": "search",
169
+ "confidence": 0.6,
170
+ "entities": {},
171
+ "reasoning": "關鍵字匹配: 搜尋相關"
172
+ }
173
+ elif any(keyword in response_lower for keyword in ['訂單', 'order']):
174
+ return {
175
+ "intent": "order",
176
+ "confidence": 0.6,
177
+ "entities": {},
178
+ "reasoning": "關鍵字匹配: 訂單相關"
179
+ }
180
+ elif any(keyword in response_lower for keyword in ['庫存', 'inventory', '存貨']):
181
+ return {
182
+ "intent": "inventory",
183
+ "confidence": 0.6,
184
+ "entities": {},
185
+ "reasoning": "關鍵字匹配: 庫存相關"
186
+ }
187
+ elif any(keyword in response_lower for keyword in ['幫助', 'help', '說明', '指令']):
188
+ return {
189
+ "intent": "help",
190
+ "confidence": 0.8,
191
+ "entities": {},
192
+ "reasoning": "關鍵字匹配: 幫助相關"
193
+ }
194
+ else:
195
+ return {
196
+ "intent": "chat",
197
+ "confidence": 0.4,
198
+ "entities": {},
199
+ "reasoning": "預設為聊天模式"
200
+ }
201
+
202
+ def generate_business_response(self, query_result: dict, original_message: str) -> str:
203
+ """
204
+ 根據業務查詢結果生成自然回應
205
+
206
+ Args:
207
+ query_result: 業務查詢結果
208
+ original_message: 原始用戶訊息
209
+
210
+ Returns:
211
+ 自然語言回應
212
+ """
213
+ if not self.is_available():
214
+ return self._fallback_business_response(query_result)
215
+
216
+ system_prompt = f"""你是一個友善的客服助手,需要根據查詢結果為用戶生成自然的回應。
217
+
218
+ 用戶原始訊息:{original_message}
219
+
220
+ 查詢結果:
221
+ - 成功: {query_result.get('success', False)}
222
+ - 資料筆數: {len(query_result.get('data', []))}
223
+ - 意圖: {query_result.get('intent', 'unknown')}
224
+
225
+ 請用繁體中文生成一個友善、自然的回應,包含查詢結果的摘要。
226
+ 如果有具體資料,請整理成易讀的格式。
227
+ 回應長度控制在 200 字以內。"""
228
+
229
+ try:
230
+ # 準備查詢結果摘要
231
+ data_summary = self._prepare_data_summary(query_result.get('data', []))
232
+ full_prompt = f"{system_prompt}\n\n資料摘要:\n{data_summary}"
233
+
234
+ response = self.chat_completion(original_message, full_prompt, temperature=0.4)
235
+ return response
236
+
237
+ except Exception as e:
238
+ logger.error(f"生成業務回應失敗: {str(e)}")
239
+ return self._fallback_business_response(query_result)
240
+
241
+ def _prepare_data_summary(self, data: list) -> str:
242
+ """準備資料摘要"""
243
+ if not data:
244
+ return "沒有找到相關資料"
245
+
246
+ summary_lines = []
247
+ for i, item in enumerate(data[:5]): # 最多顯示 5 筆
248
+ if isinstance(item, dict):
249
+ # 提取重要欄位
250
+ name = item.get('name', item.get('product_name', ''))
251
+ price = item.get('price', item.get('unit_price', ''))
252
+ stock = item.get('stock', item.get('quantity', ''))
253
+
254
+ line_parts = []
255
+ if name:
256
+ line_parts.append(f"名稱: {name}")
257
+ if price:
258
+ line_parts.append(f"價格: ${price}")
259
+ if stock:
260
+ line_parts.append(f"庫存: {stock}")
261
+
262
+ if line_parts:
263
+ summary_lines.append(f"{i+1}. {', '.join(line_parts)}")
264
+
265
+ if len(data) > 5:
266
+ summary_lines.append(f"... 還有 {len(data) - 5} 筆資料")
267
+
268
+ return '\n'.join(summary_lines) if summary_lines else "資料格式無法解析"
269
+
270
+ def _fallback_business_response(self, query_result: dict) -> str:
271
+ """備用的業務回應生成"""
272
+ if query_result.get('success'):
273
+ data_count = len(query_result.get('data', []))
274
+ if data_count > 0:
275
+ return f"✅ 查詢成功!找到 {data_count} 筆相關資料。"
276
+ else:
277
+ return "✅ 查詢完成,但沒有找到符合條件的資料。"
278
+ else:
279
+ error_msg = query_result.get('error', '未知錯誤')
280
+ return f"❌ 查詢失敗:{error_msg}"
backend/services/message_router.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 訊息路由器 - 根據前綴和內容智能路由訊息
3
+ """
4
+
5
+ import logging
6
+ from typing import Dict, Any
7
+ from backend.services.groq_service import GroqService
8
+ from backend.services.business_query_service import BusinessQueryService
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class MessageRouter:
13
+ """訊息路由器類"""
14
+
15
+ def __init__(self):
16
+ self.groq_service = GroqService()
17
+ self.business_service = BusinessQueryService()
18
+
19
+ # 路由統計
20
+ self.route_stats = {
21
+ "chat": 0,
22
+ "search": 0,
23
+ "help": 0,
24
+ "smart": 0,
25
+ "error": 0
26
+ }
27
+
28
+ def route_message(self, message: str, user_id: str) -> Dict[str, Any]:
29
+ """
30
+ 根據前綴和內容路由訊息
31
+
32
+ Args:
33
+ message: 用戶訊息
34
+ user_id: 用戶ID
35
+
36
+ Returns:
37
+ 處理結果字典
38
+ """
39
+ try:
40
+ # 清理訊息
41
+ clean_message = message.strip()
42
+
43
+ if not clean_message:
44
+ return self._handle_empty_message(user_id)
45
+
46
+ logger.info(f"路由訊息 - 用戶: {user_id}, 內容: {clean_message[:50]}...")
47
+
48
+ # 1. 聊天模式 - /chat 前綴
49
+ if clean_message.startswith('/chat '):
50
+ self.route_stats["chat"] += 1
51
+ return self._handle_chat_mode(clean_message[6:].strip(), user_id)
52
+
53
+ # 2. 商品查詢 - /search 前綴
54
+ elif clean_message.startswith('/search '):
55
+ self.route_stats["search"] += 1
56
+ return self._handle_search_mode(clean_message[8:].strip(), user_id)
57
+
58
+ # 3. 幫助指令
59
+ elif clean_message.lower() in ['/help', '幫助', 'help', '說明', '指令']:
60
+ self.route_stats["help"] += 1
61
+ return self._handle_help_mode(user_id)
62
+
63
+ # 4. 特殊指令
64
+ elif clean_message.lower() in ['選單', 'menu', '功能']:
65
+ return self._handle_menu_mode(user_id)
66
+
67
+ elif clean_message.lower() in ['統計', 'stats', '路由統計']:
68
+ return self._handle_stats_mode(user_id)
69
+
70
+ # 5. 智能路由 - 無前綴時進行意圖分析
71
+ else:
72
+ self.route_stats["smart"] += 1
73
+ return self._handle_smart_routing(clean_message, user_id)
74
+
75
+ except Exception as e:
76
+ self.route_stats["error"] += 1
77
+ logger.error(f"訊息路由錯誤: {str(e)}")
78
+ return {
79
+ "type": "text",
80
+ "text": "❌ 系統處理訊息時發生錯誤,請稍後再試。",
81
+ "mode": "error",
82
+ "success": False,
83
+ "error": str(e)
84
+ }
85
+
86
+ def _handle_chat_mode(self, message: str, user_id: str) -> Dict[str, Any]:
87
+ """處理聊天模式"""
88
+ try:
89
+ if not message:
90
+ return {
91
+ "type": "text",
92
+ "text": "💬 請輸入您想聊的內容!\n\n範例:/chat 今天天氣如何?",
93
+ "mode": "chat",
94
+ "success": True
95
+ }
96
+
97
+ if not self.groq_service.is_available():
98
+ return {
99
+ "type": "text",
100
+ "text": "💬 聊天服務暫時無法使用,請稍後再試。\n\n💡 您可以嘗試使用 /search 進行商品查詢。",
101
+ "mode": "chat",
102
+ "success": False,
103
+ "error": "Groq 服務不可用"
104
+ }
105
+
106
+ system_prompt = """你是一個友善的客服助手,專門協助用戶解答問題。
107
+ 請用繁體中文回應,語氣要親切自然。
108
+ 如果用戶詢問商品或業務相關問題,建議他們使用 /search 指令進行查詢。
109
+ 回應長度控制在 150 字以內。"""
110
+
111
+ response = self.groq_service.chat_completion(message, system_prompt)
112
+
113
+ return {
114
+ "type": "text",
115
+ "text": f"💬 {response}",
116
+ "mode": "chat",
117
+ "success": True,
118
+ "user_id": user_id
119
+ }
120
+
121
+ except Exception as e:
122
+ logger.error(f"聊天模式處理錯誤: {str(e)}")
123
+ return {
124
+ "type": "text",
125
+ "text": "💬 聊天服務暫時無法使用,請稍後再試。",
126
+ "mode": "chat",
127
+ "success": False,
128
+ "error": str(e)
129
+ }
130
+
131
+ def _handle_search_mode(self, message: str, user_id: str) -> Dict[str, Any]:
132
+ """處理搜尋模式"""
133
+ try:
134
+ if not message:
135
+ return {
136
+ "type": "text",
137
+ "text": "🔍 請��入您要查詢的內容!\n\n範例:\n• /search iPhone 15\n• /search 價格 1000-5000\n• /search 庫存不足的商品",
138
+ "mode": "search",
139
+ "success": True
140
+ }
141
+
142
+ # 使用業務查詢服務
143
+ result = self.business_service.process_user_query(message, user_id)
144
+
145
+ # 如果有 Groq 服務,使用它來生成更自然的回應
146
+ if self.groq_service.is_available() and result.get("success"):
147
+ try:
148
+ natural_response = self.groq_service.generate_business_response(result, message)
149
+ response_text = f"🔍 {natural_response}"
150
+ except Exception as e:
151
+ logger.warning(f"Groq 回應生成失敗,使用原始回應: {str(e)}")
152
+ response_text = f"🔍 {result['response_message']}"
153
+ else:
154
+ response_text = f"🔍 {result['response_message']}"
155
+
156
+ return {
157
+ "type": "text",
158
+ "text": response_text,
159
+ "mode": "search",
160
+ "success": result["success"],
161
+ "data": result.get("data", []),
162
+ "intent": result.get("intent", "unknown"),
163
+ "confidence": result.get("confidence", 0.0),
164
+ "user_id": user_id
165
+ }
166
+
167
+ except Exception as e:
168
+ logger.error(f"搜尋模式處理錯誤: {str(e)}")
169
+ return {
170
+ "type": "text",
171
+ "text": "🔍 搜尋服務暫時無法使用,請稍後再試。",
172
+ "mode": "search",
173
+ "success": False,
174
+ "error": str(e)
175
+ }
176
+
177
+ def _handle_smart_routing(self, message: str, user_id: str) -> Dict[str, Any]:
178
+ """智能路由 - 根據內容判斷意圖"""
179
+ try:
180
+ # 先進行簡單的關鍵字預篩選
181
+ quick_intent = self._quick_intent_check(message)
182
+
183
+ if quick_intent == "search":
184
+ # 高信心度的搜尋關鍵字 -> 直接轉到搜尋模式
185
+ logger.info(f"快速意圖識別: 搜尋模式 - {message[:30]}...")
186
+ return self._handle_search_mode(message, user_id)
187
+
188
+ elif quick_intent == "help":
189
+ # 幫助相關 -> 轉到幫助模式
190
+ return self._handle_help_mode(user_id)
191
+
192
+ # 使用 Groq 進行深度意圖分析
193
+ if self.groq_service.is_available():
194
+ try:
195
+ intent_result = self.groq_service.analyze_intent(message)
196
+ logger.info(f"Groq 意圖分析: {intent_result}")
197
+
198
+ # 根據意圖和信心度決定路由
199
+ if intent_result["intent"] in ["search", "order", "inventory"] and intent_result["confidence"] > 0.6:
200
+ logger.info(f"智能路由 -> 搜尋模式 (信心度: {intent_result['confidence']})")
201
+ return self._handle_search_mode(message, user_id)
202
+
203
+ elif intent_result["intent"] == "help" and intent_result["confidence"] > 0.7:
204
+ return self._handle_help_mode(user_id)
205
+
206
+ else:
207
+ # 其他情況或低信心度 -> 轉到聊天模式
208
+ logger.info(f"智能路由 -> 聊天模式 (意圖: {intent_result['intent']}, 信心度: {intent_result['confidence']})")
209
+ return self._handle_chat_mode(message, user_id)
210
+
211
+ except Exception as e:
212
+ logger.warning(f"Groq 意圖分析失敗,使用預設路由: {str(e)}")
213
+ # 錯誤時預設為聊天模式
214
+ return self._handle_chat_mode(message, user_id)
215
+ else:
216
+ # Groq 不可用時,根據簡單規則路由
217
+ if quick_intent == "search":
218
+ return self._handle_search_mode(message, user_id)
219
+ else:
220
+ return self._handle_chat_mode(message, user_id)
221
+
222
+ except Exception as e:
223
+ logger.error(f"智能路由處理錯誤: {str(e)}")
224
+ # 錯誤時預設為聊天模式
225
+ return self._handle_chat_mode(message, user_id)
226
+
227
+ def _quick_intent_check(self, message: str) -> str:
228
+ """快速意圖檢查 - 基於關鍵字"""
229
+ message_lower = message.lower()
230
+
231
+ # 搜尋相關關鍵字
232
+ search_keywords = [
233
+ '查詢', '搜尋', '找', '商品', '產品', '價格', '庫存', '訂單',
234
+ '多少錢', '有沒有', '還有嗎', '剩多少', '存貨', '現貨',
235
+ 'iphone', 'macbook', 'ipad', 'airpods' # 常見商品名
236
+ ]
237
+
238
+ # 幫助相關關鍵字
239
+ help_keywords = ['幫助', 'help', '說明', '���麼用', '指令', '功能']
240
+
241
+ if any(keyword in message_lower for keyword in search_keywords):
242
+ return "search"
243
+ elif any(keyword in message_lower for keyword in help_keywords):
244
+ return "help"
245
+ else:
246
+ return "chat"
247
+
248
+ def _handle_help_mode(self, user_id: str) -> Dict[str, Any]:
249
+ """處理幫助模式"""
250
+ help_text = """🤖 智能客服助手使用指南
251
+
252
+ 📝 指令模式:
253
+ • /chat [訊息] - 聊天模式
254
+ 範例:/chat 今天天氣如何?
255
+
256
+ • /search [查詢] - 商品查詢模式
257
+ 範例:/search iPhone 15 Pro
258
+ 範例:/search 價格 1000-5000
259
+
260
+ • /help - 顯示此說明
261
+
262
+ 🧠 智能模式:
263
+ 直接輸入訊息,系統會自動判斷是聊天還是查詢!
264
+
265
+ 💡 查詢範例:
266
+ • "iPhone 還有庫存嗎?"
267
+ • "1000到5000的商品有哪些?"
268
+ • "我的訂單狀態如何?"
269
+
270
+ 💬 聊天範例:
271
+ • "你好!"
272
+ • "今天天氣如何?"
273
+ • "推薦一些好用的功能"
274
+
275
+ 輸入「選單」查看功能選單
276
+ 輸入「統計」查看使用統計"""
277
+
278
+ return {
279
+ "type": "text",
280
+ "text": help_text,
281
+ "mode": "help",
282
+ "success": True,
283
+ "user_id": user_id
284
+ }
285
+
286
+ def _handle_menu_mode(self, user_id: str) -> Dict[str, Any]:
287
+ """處理選單模式"""
288
+ menu_text = """🏪 功能選單
289
+
290
+ 請選擇您需要的功能:
291
+
292
+ 🔍 商品查詢
293
+ • /search [商品名稱]
294
+ • /search 價格 [範圍]
295
+
296
+ 💬 智能聊天
297
+ • /chat [您的問題]
298
+
299
+ 📊 常用查詢
300
+ • "庫存查詢"
301
+ • "我的訂單"
302
+ • "低庫存商品"
303
+ • "業務統計"
304
+
305
+ ❓ 說明
306
+ • /help - 詳細說明
307
+ • 統計 - 使用統計
308
+
309
+ 💡 小提示:您也可以直接輸入問題,系統會智能判斷處理方式!"""
310
+
311
+ return {
312
+ "type": "text",
313
+ "text": menu_text,
314
+ "mode": "menu",
315
+ "success": True,
316
+ "user_id": user_id
317
+ }
318
+
319
+ def _handle_stats_mode(self, user_id: str) -> Dict[str, Any]:
320
+ """處理統計模式"""
321
+ total_routes = sum(self.route_stats.values())
322
+
323
+ if total_routes == 0:
324
+ stats_text = "📊 路由統計\n\n尚無使用記錄"
325
+ else:
326
+ stats_text = f"""📊 路由統計 (總計: {total_routes})
327
+
328
+ 💬 聊天模式: {self.route_stats['chat']} ({self.route_stats['chat']/total_routes*100:.1f}%)
329
+ 🔍 搜尋模式: {self.route_stats['search']} ({self.route_stats['search']/total_routes*100:.1f}%)
330
+ 🧠 智能路由: {self.route_stats['smart']} ({self.route_stats['smart']/total_routes*100:.1f}%)
331
+ ❓ 幫助模式: {self.route_stats['help']} ({self.route_stats['help']/total_routes*100:.1f}%)
332
+ ❌ 錯誤次數: {self.route_stats['error']} ({self.route_stats['error']/total_routes*100:.1f}%)
333
+
334
+ 🤖 Groq 服務: {'✅ 可用' if self.groq_service.is_available() else '❌ 不可用'}"""
335
+
336
+ return {
337
+ "type": "text",
338
+ "text": stats_text,
339
+ "mode": "stats",
340
+ "success": True,
341
+ "user_id": user_id,
342
+ "data": self.route_stats
343
+ }
344
+
345
+ def _handle_empty_message(self, user_id: str) -> Dict[str, Any]:
346
+ """處理空訊息"""
347
+ return {
348
+ "type": "text",
349
+ "text": "🤔 您似乎沒有輸入任何內容。\n\n輸入 /help 查看使用說明,或直接告訴我您需要什麼幫助!",
350
+ "mode": "empty",
351
+ "success": True,
352
+ "user_id": user_id
353
+ }
354
+
355
+ def get_route_statistics(self) -> Dict[str, Any]:
356
+ """取得路由統計資訊"""
357
+ return {
358
+ "stats": self.route_stats.copy(),
359
+ "total": sum(self.route_stats.values()),
360
+ "groq_available": self.groq_service.is_available()
361
+ }
groq_python_test.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from groq import Groq
2
+
3
+ GROQ_API_KEY = "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"
4
+
5
+ def chat_with_groq(user_message):
6
+ """Send a message to Groq and get the streaming response"""
7
+ client = Groq(api_key=GROQ_API_KEY)
8
+
9
+ try:
10
+ completion = client.chat.completions.create(
11
+ model="qwen/qwen3-32b",
12
+ messages=[
13
+ {"role": "system", "content": "You are a helpful assistant."},
14
+ {"role": "user", "content": user_message}
15
+ ],
16
+ temperature=0.6,
17
+ max_completion_tokens=4096,
18
+ top_p=0.95,
19
+ reasoning_effort="default",
20
+ stream=True,
21
+ stop=None,
22
+ )
23
+
24
+ print("Groq: ", end="", flush=True)
25
+ response_text = ""
26
+
27
+ for chunk in completion:
28
+ content = chunk.choices[0].delta.content or ""
29
+ print(content, end="", flush=True)
30
+ response_text += content
31
+
32
+ print() # New line after response
33
+ return response_text
34
+
35
+ except Exception as e:
36
+ return f"Error: {e}"
37
+
38
+ def main():
39
+ """Main interactive loop"""
40
+ print("=== Groq Chat Bot ===")
41
+ print("Enter your questions, type 'quit' or 'exit' to quit")
42
+ print("=" * 30)
43
+
44
+ while True:
45
+ try:
46
+ user_input = input("\nYour question: ").strip()
47
+
48
+ if user_input.lower() in ['quit', 'exit', 'q']:
49
+ print("Goodbye!")
50
+ break
51
+
52
+ if not user_input:
53
+ print("Please enter a question.")
54
+ continue
55
+
56
+ print("\nThinking...")
57
+ chat_with_groq(user_input)
58
+
59
+ except KeyboardInterrupt:
60
+ print("\n\nProgram interrupted, goodbye!")
61
+ break
62
+ except EOFError:
63
+ print("\n\nInput ended, goodbye!")
64
+ break
65
+
66
+ if __name__ == "__main__":
67
+ main()
requirements.txt CHANGED
@@ -10,4 +10,5 @@ python-multipart
10
  python-dotenv
11
  requests
12
  openai
13
- httpx
 
 
10
  python-dotenv
11
  requests
12
  openai
13
+ httpx
14
+ groq
tmp_rovodev_test_groq_integration.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Groq 整合測試腳本
4
+ 測試新的路由系統和 Groq 服務
5
+ """
6
+
7
+ import requests
8
+ import json
9
+ import os
10
+ from typing import Dict, Any
11
+
12
+ class GroqIntegrationTester:
13
+ def __init__(self, base_url: str = "http://localhost:7860"):
14
+ self.base_url = base_url
15
+ self.test_results = []
16
+
17
+ def test_health_check(self):
18
+ """測試健康檢查"""
19
+ print("🔍 測試健康檢查...")
20
+ try:
21
+ response = requests.get(f"{self.base_url}/health")
22
+ success = response.status_code == 200
23
+ result = {
24
+ "test": "health_check",
25
+ "success": success,
26
+ "status_code": response.status_code,
27
+ "response": response.json() if success else response.text
28
+ }
29
+ self.test_results.append(result)
30
+ print(f"✅ 健康檢查: {response.status_code}")
31
+ return success
32
+ except Exception as e:
33
+ print(f"❌ 健康檢查失敗: {e}")
34
+ self.test_results.append({
35
+ "test": "health_check",
36
+ "success": False,
37
+ "error": str(e)
38
+ })
39
+ return False
40
+
41
+ def test_chat_api(self):
42
+ """測試聊天 API"""
43
+ print("\n💬 測試聊天 API...")
44
+ test_messages = [
45
+ "你好!",
46
+ "今天天氣如何?",
47
+ "請介紹一下你自己",
48
+ "推薦一些好用的功能"
49
+ ]
50
+
51
+ for message in test_messages:
52
+ try:
53
+ response = requests.post(
54
+ f"{self.base_url}/chat",
55
+ json={"message": message, "user_id": "test_user"},
56
+ headers={"Content-Type": "application/json"}
57
+ )
58
+
59
+ success = response.status_code == 200
60
+ result = {
61
+ "test": "chat_api",
62
+ "message": message,
63
+ "success": success,
64
+ "status_code": response.status_code,
65
+ "response": response.json() if success else response.text
66
+ }
67
+ self.test_results.append(result)
68
+
69
+ if success:
70
+ data = response.json()
71
+ print(f"✅ 聊天: {message[:20]}... -> {data.get('text', '')[:50]}...")
72
+ else:
73
+ print(f"❌ 聊天失敗: {message[:20]}... -> {response.status_code}")
74
+
75
+ except Exception as e:
76
+ print(f"❌ 聊天 API 錯誤: {e}")
77
+ self.test_results.append({
78
+ "test": "chat_api",
79
+ "message": message,
80
+ "success": False,
81
+ "error": str(e)
82
+ })
83
+
84
+ def test_search_api(self):
85
+ """測試搜尋 API"""
86
+ print("\n🔍 測試搜尋 API...")
87
+ test_queries = [
88
+ "iPhone 15 Pro",
89
+ "價格 1000-5000",
90
+ "庫存查詢",
91
+ "我的訂單",
92
+ "低庫存商品"
93
+ ]
94
+
95
+ for query in test_queries:
96
+ try:
97
+ response = requests.post(
98
+ f"{self.base_url}/search",
99
+ json={"message": query, "user_id": "test_user"},
100
+ headers={"Content-Type": "application/json"}
101
+ )
102
+
103
+ success = response.status_code == 200
104
+ result = {
105
+ "test": "search_api",
106
+ "query": query,
107
+ "success": success,
108
+ "status_code": response.status_code,
109
+ "response": response.json() if success else response.text
110
+ }
111
+ self.test_results.append(result)
112
+
113
+ if success:
114
+ data = response.json()
115
+ print(f"✅ 搜尋: {query} -> {data.get('text', '')[:50]}...")
116
+ else:
117
+ print(f"❌ 搜尋失敗: {query} -> {response.status_code}")
118
+
119
+ except Exception as e:
120
+ print(f"❌ 搜尋 API 錯誤: {e}")
121
+ self.test_results.append({
122
+ "test": "search_api",
123
+ "query": query,
124
+ "success": False,
125
+ "error": str(e)
126
+ })
127
+
128
+ def test_smart_routing(self):
129
+ """測試智能路由"""
130
+ print("\n🧠 測試智能路由...")
131
+ test_messages = [
132
+ # 應該路由到聊天
133
+ "你好,今天天氣如何?",
134
+ "請介紹一下你的功能",
135
+
136
+ # 應該路由到搜尋
137
+ "iPhone 還有庫存嗎?",
138
+ "查詢價格在 2000 到 5000 的商品",
139
+ "我想看看訂單狀態",
140
+
141
+ # 前綴指令
142
+ "/chat 你好!",
143
+ "/search iPhone 15",
144
+ "/help"
145
+ ]
146
+
147
+ for message in test_messages:
148
+ try:
149
+ response = requests.post(
150
+ f"{self.base_url}/route",
151
+ json={"message": message, "user_id": "test_user"},
152
+ headers={"Content-Type": "application/json"}
153
+ )
154
+
155
+ success = response.status_code == 200
156
+ result = {
157
+ "test": "smart_routing",
158
+ "message": message,
159
+ "success": success,
160
+ "status_code": response.status_code,
161
+ "response": response.json() if success else response.text
162
+ }
163
+ self.test_results.append(result)
164
+
165
+ if success:
166
+ data = response.json()
167
+ mode = data.get('mode', 'unknown')
168
+ print(f"✅ 路由: {message[:30]}... -> {mode}")
169
+ else:
170
+ print(f"❌ 路由失敗: {message[:30]}... -> {response.status_code}")
171
+
172
+ except Exception as e:
173
+ print(f"❌ 智能路由錯誤: {e}")
174
+ self.test_results.append({
175
+ "test": "smart_routing",
176
+ "message": message,
177
+ "success": False,
178
+ "error": str(e)
179
+ })
180
+
181
+ def test_stats_api(self):
182
+ """測試統計 API"""
183
+ print("\n📊 測試統計 API...")
184
+ try:
185
+ response = requests.get(f"{self.base_url}/stats")
186
+ success = response.status_code == 200
187
+ result = {
188
+ "test": "stats_api",
189
+ "success": success,
190
+ "status_code": response.status_code,
191
+ "response": response.json() if success else response.text
192
+ }
193
+ self.test_results.append(result)
194
+
195
+ if success:
196
+ data = response.json()
197
+ stats = data.get('data', {}).get('stats', {})
198
+ total = data.get('data', {}).get('total', 0)
199
+ print(f"✅ 統計: 總計 {total} 次路由")
200
+ for mode, count in stats.items():
201
+ print(f" {mode}: {count}")
202
+ else:
203
+ print(f"❌ 統計失敗: {response.status_code}")
204
+
205
+ except Exception as e:
206
+ print(f"❌ 統計 API 錯誤: {e}")
207
+ self.test_results.append({
208
+ "test": "stats_api",
209
+ "success": False,
210
+ "error": str(e)
211
+ })
212
+
213
+ def test_line_webhook_simulation(self):
214
+ """模擬 LINE Webhook"""
215
+ print("\n📱 測試 LINE Webhook 模擬...")
216
+ test_messages = [
217
+ "/chat 你好!",
218
+ "/search iPhone",
219
+ "iPhone 還有庫存嗎?",
220
+ "幫助"
221
+ ]
222
+
223
+ for message in test_messages:
224
+ webhook_data = {
225
+ "events": [
226
+ {
227
+ "type": "message",
228
+ "message": {
229
+ "type": "text",
230
+ "text": message
231
+ },
232
+ "source": {
233
+ "type": "user",
234
+ "userId": "test_user_webhook"
235
+ },
236
+ "replyToken": "test_reply_token"
237
+ }
238
+ ]
239
+ }
240
+
241
+ try:
242
+ response = requests.post(
243
+ f"{self.base_url}/webhook",
244
+ json=webhook_data,
245
+ headers={"Content-Type": "application/json"}
246
+ )
247
+
248
+ success = response.status_code == 200
249
+ result = {
250
+ "test": "line_webhook",
251
+ "message": message,
252
+ "success": success,
253
+ "status_code": response.status_code
254
+ }
255
+ self.test_results.append(result)
256
+
257
+ print(f"{'✅' if success else '❌'} Webhook: {message} -> {response.status_code}")
258
+
259
+ except Exception as e:
260
+ print(f"❌ Webhook 錯誤: {e}")
261
+ self.test_results.append({
262
+ "test": "line_webhook",
263
+ "message": message,
264
+ "success": False,
265
+ "error": str(e)
266
+ })
267
+
268
+ def generate_report(self):
269
+ """生成測試報告"""
270
+ print("\n" + "="*50)
271
+ print("📋 測試報告")
272
+ print("="*50)
273
+
274
+ total_tests = len(self.test_results)
275
+ successful_tests = sum(1 for result in self.test_results if result.get('success', False))
276
+
277
+ print(f"總測試數: {total_tests}")
278
+ print(f"���功: {successful_tests}")
279
+ print(f"失敗: {total_tests - successful_tests}")
280
+ print(f"成功率: {successful_tests/total_tests*100:.1f}%")
281
+
282
+ # 按測試類型分組
283
+ test_types = {}
284
+ for result in self.test_results:
285
+ test_type = result['test']
286
+ if test_type not in test_types:
287
+ test_types[test_type] = {'total': 0, 'success': 0}
288
+ test_types[test_type]['total'] += 1
289
+ if result.get('success', False):
290
+ test_types[test_type]['success'] += 1
291
+
292
+ print("\n📊 各功能測試結果:")
293
+ for test_type, stats in test_types.items():
294
+ success_rate = stats['success'] / stats['total'] * 100
295
+ print(f" {test_type}: {stats['success']}/{stats['total']} ({success_rate:.1f}%)")
296
+
297
+ # 顯示失敗的測試
298
+ failed_tests = [result for result in self.test_results if not result.get('success', False)]
299
+ if failed_tests:
300
+ print(f"\n❌ 失敗的測試 ({len(failed_tests)}):")
301
+ for result in failed_tests:
302
+ print(f" - {result['test']}: {result.get('error', 'Unknown error')}")
303
+
304
+ return {
305
+ 'total': total_tests,
306
+ 'successful': successful_tests,
307
+ 'failed': total_tests - successful_tests,
308
+ 'success_rate': successful_tests/total_tests*100,
309
+ 'details': test_types,
310
+ 'failed_tests': failed_tests
311
+ }
312
+
313
+ def run_all_tests():
314
+ """執行所有測試"""
315
+ print("🚀 開始 Groq 整合測試")
316
+ print("="*50)
317
+
318
+ tester = GroqIntegrationTester()
319
+
320
+ # 執行所有測試
321
+ tester.test_health_check()
322
+ tester.test_chat_api()
323
+ tester.test_search_api()
324
+ tester.test_smart_routing()
325
+ tester.test_stats_api()
326
+ tester.test_line_webhook_simulation()
327
+
328
+ # 生成報告
329
+ report = tester.generate_report()
330
+
331
+ return report
332
+
333
+ if __name__ == "__main__":
334
+ report = run_all_tests()
335
+
336
+ # 保存詳細結果到文件
337
+ with open("tmp_rovodev_test_results.json", "w", encoding="utf-8") as f:
338
+ json.dump(report, f, ensure_ascii=False, indent=2)
339
+
340
+ print(f"\n📄 詳細測試結果已保存到: tmp_rovodev_test_results.json")