mickeywu520 commited on
Commit
7e828dc
·
1 Parent(s): 0503b45

整合 Pydantic AI 框架並優化專案結構

Browse files
.gitignore ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ # IDE
132
+ .vscode/
133
+ .idea/
134
+ *.swp
135
+ *.swo
136
+ *~
137
+
138
+ # OS
139
+ .DS_Store
140
+ .DS_Store?
141
+ ._*
142
+ .Spotlight-V100
143
+ .Trashes
144
+ ehthumbs.db
145
+ Thumbs.db
146
+
147
+ # Project specific
148
+ *.log
149
+ .env.local
150
+ .env.production
151
+ .env.development
152
+
153
+ # Database
154
+ *.db
155
+ *.sqlite
156
+ *.sqlite3
157
+
158
+ # Temporary files
159
+ *.tmp
160
+ *.temp
161
+ temp/
162
+ tmp/
API_KEYS_GUIDE.md DELETED
@@ -1,215 +0,0 @@
1
- # 🔑 API Keys 取得指南
2
-
3
- 本文件詳細說明如何取得所有必要的 API Keys 來運行 LINE Bot 系統。
4
-
5
- ## 📋 必要的 API Keys
6
-
7
- ### 1. LINE Bot API Keys
8
-
9
- #### 步驟 1: 建立 LINE Developers 帳號
10
- 1. 前往 [LINE Developers Console](https://developers.line.biz/)
11
- 2. 使用 LINE 帳號登入
12
- 3. 同意開發者條款
13
-
14
- #### 步驟 2: 建立 Provider
15
- 1. 點擊 "Create a new provider"
16
- 2. 輸入 Provider 名稱 (例如: "我的公司")
17
- 3. 點擊 "Create"
18
-
19
- #### 步驟 3: 建立 Messaging API Channel
20
- 1. 在 Provider 頁面點擊 "Create a Messaging API channel"
21
- 2. 填寫以下資訊:
22
- - **Channel name**: 您的 Bot 名稱
23
- - **Channel description**: Bot 描述
24
- - **Category**: 選擇適合的類別
25
- - **Subcategory**: 選擇子類別
26
- 3. 上傳 Channel icon (可選)
27
- 4. 同意條款並點擊 "Create"
28
-
29
- #### 步驟 4: 取得 API Keys
30
- 1. 進入剛建立的 Channel
31
- 2. 前往 "Basic settings" 頁籤:
32
- - 複製 **Channel secret** → 這是您的 `LINE_CHANNEL_SECRET`
33
- 3. 前往 "Messaging API" 頁籤:
34
- - 點擊 "Issue" 按鈕產生 Channel access token
35
- - 複製 **Channel access token** → 這是您的 `LINE_CHANNEL_ACCESS_TOKEN`
36
-
37
- #### 步驟 5: 設定 Webhook (部署後執行)
38
- 1. 在 "Messaging API" 頁籤中
39
- 2. 設定 **Webhook URL**: `https://你的用戶名-你的空間名稱.hf.space/webhook`
40
- 3. 啟用 "Use webhook"
41
- 4. 關閉 "Auto-reply messages" (可選)
42
-
43
- ### 2. Supabase API Keys
44
-
45
- #### 步驟 1: 建立 Supabase 帳號
46
- 1. 前往 [Supabase](https://supabase.com/)
47
- 2. 點擊 "Start your project"
48
- 3. 使用 GitHub 或 Google 帳號註冊
49
-
50
- #### 步驟 2: 建立新專案
51
- 1. 點擊 "New project"
52
- 2. 選擇組織 (或建立新組織)
53
- 3. 填寫專案資訊:
54
- - **Name**: 專案名稱
55
- - **Database Password**: 設定強密碼
56
- - **Region**: 選擇最近的區域 (建議: Southeast Asia)
57
- 4. 點擊 "Create new project"
58
- 5. 等待專案建立完成 (約 2-3 分鐘)
59
-
60
- #### 步驟 3: 取得 API Keys
61
- 1. 在專案 Dashboard 中
62
- 2. 前往左側選單的 "Settings" → "API"
63
- 3. 複製以下資訊:
64
- - **URL** → 這是您的 `SUPABASE_URL`
65
- - **anon public** key → 這是您的 `SUPABASE_KEY`
66
-
67
- #### 步驟 4: 建立資料表
68
- 1. 前往左側選單的 "SQL Editor"
69
- 2. 複製 `setup_guide.md` 中的 SQL 語句
70
- 3. 點擊 "RUN" 執行
71
-
72
- ## 🚀 可選的 API Keys
73
-
74
- ### 3. OpenRouter API Keys (進階 NLP 功能)
75
-
76
- #### 步驟 1: 建立 OpenRouter 帳號
77
- 1. 前往 [OpenRouter](https://openrouter.ai/)
78
- 2. 點擊 "Sign Up"
79
- 3. 使用 Google 或 GitHub 帳號註冊
80
-
81
- #### 步驟 2: 充值帳戶
82
- 1. 前往 [Credits 頁面](https://openrouter.ai/credits)
83
- 2. 點擊 "Add Credits"
84
- 3. 選擇充值金額 (建議先充值 $5-10 測試)
85
- 4. 完成付款
86
-
87
- #### 步驟 3: 建立 API Key
88
- 1. 前往 [API Keys 頁面](https://openrouter.ai/keys)
89
- 2. 點擊 "Create Key"
90
- 3. 輸入 Key 名稱 (例如: "LINE Bot")
91
- 4. 複製產生的 API Key → 這是您的 `OPENROUTER_API_KEY`
92
-
93
- #### 步驟 4: 選擇模型
94
- 根據您的需求和預算選擇模型:
95
-
96
- | 模型 | 成本 | 適用場景 | 設定值 |
97
- |------|------|----------|--------|
98
- | Claude 3 Haiku | 最低 | 日常查詢 | `anthropic/claude-3-haiku` |
99
- | GPT-3.5 Turbo | 中等 | 平衡使用 | `openai/gpt-3.5-turbo` |
100
- | Claude 3 Sonnet | 較高 | 複雜查詢 | `anthropic/claude-3-sonnet` |
101
-
102
- ## 🔧 環境變數設定
103
-
104
- ### Hugging Face Spaces 設定
105
-
106
- 1. 前往您的 Hugging Face Spaces 專案
107
- 2. 點擊 "Settings" 頁籤
108
- 3. 在 "Repository secrets" 區域新增以下變數:
109
-
110
- ```bash
111
- # 必要設定
112
- LINE_CHANNEL_ACCESS_TOKEN=你的_LINE_Channel_Access_Token
113
- LINE_CHANNEL_SECRET=你的_LINE_Channel_Secret
114
- SUPABASE_URL=你的_Supabase_專案_URL
115
- SUPABASE_KEY=你的_Supabase_Anon_Key
116
-
117
- # 可選設定 (進階 NLP)
118
- OPENROUTER_API_KEY=你的_OpenRouter_API_Key
119
- OPENROUTER_MODEL=anthropic/claude-3-haiku
120
-
121
- # 其他設定
122
- DEBUG=False
123
- LOG_LEVEL=INFO
124
- ```
125
-
126
- ### 本地開發設定
127
-
128
- 建立 `.env` 檔案:
129
-
130
- ```bash
131
- cp .env.example .env
132
- ```
133
-
134
- 編輯 `.env` 檔案並填入您的 API Keys。
135
-
136
- ## ✅ 驗證設定
137
-
138
- ### 1. 測試 LINE Bot
139
- ```bash
140
- # 檢查 LINE Channel 設定
141
- curl -H "Authorization: Bearer YOUR_CHANNEL_ACCESS_TOKEN" \
142
- https://api.line.me/v2/bot/info
143
- ```
144
-
145
- ### 2. 測試 Supabase
146
- ```bash
147
- # 檢查 Supabase 連線
148
- curl "YOUR_SUPABASE_URL/rest/v1/" \
149
- -H "apikey: YOUR_SUPABASE_KEY"
150
- ```
151
-
152
- ### 3. 測試 OpenRouter
153
- ```bash
154
- # 檢查 OpenRouter API
155
- curl https://openrouter.ai/api/v1/models \
156
- -H "Authorization: Bearer YOUR_OPENROUTER_KEY"
157
- ```
158
-
159
- ## 🔒 安全性注意事項
160
-
161
- 1. **絕不在程式碼中硬編碼 API Keys**
162
- 2. **使用環境變數儲存敏感資訊**
163
- 3. **定期輪換 API Keys**
164
- 4. **監控 API 使用量和成本**
165
- 5. **設定適當的權限和限制**
166
-
167
- ## 💰 成本估算
168
-
169
- ### LINE Bot
170
- - **免費額度**: 每月 1,000 則訊息
171
- - **付費方案**: 超過免費額度後按量計費
172
-
173
- ### Supabase
174
- - **免費方案**:
175
- - 500MB 資料庫
176
- - 50MB 檔案儲存
177
- - 2GB 頻寬
178
- - **Pro 方案**: $25/月起
179
-
180
- ### OpenRouter
181
- - **按使用量計費**
182
- - **Claude 3 Haiku**: ~$0.25/1M input tokens
183
- - **建議預算**: $5-20/月 (中小型應用)
184
-
185
- ## 🆘 常見問題
186
-
187
- ### Q: LINE Bot 無法回應訊息
188
- A: 檢查以下項目:
189
- 1. Channel Access Token 是否正確
190
- 2. Webhook URL 是否設定正確
191
- 3. 應用程式是否正常運行
192
-
193
- ### Q: Supabase 連線失敗
194
- A: 檢查以下項目:
195
- 1. URL 和 API Key 是否正確
196
- 2. 資料表是否已建立
197
- 3. 網路連線是否正常
198
-
199
- ### Q: OpenRouter 回應錯誤
200
- A: 檢查以下項目:
201
- 1. API Key 是否有效
202
- 2. 帳戶餘額是否充足
203
- 3. 模型名稱是否正確
204
-
205
- ## 📞 技術支援
206
-
207
- 如果您在設定過程中遇到問題:
208
-
209
- 1. **LINE Bot**: [LINE Developers 文件](https://developers.line.biz/en/docs/)
210
- 2. **Supabase**: [Supabase 文件](https://supabase.com/docs)
211
- 3. **OpenRouter**: [OpenRouter 文件](https://openrouter.ai/docs)
212
-
213
- ---
214
-
215
- **提醒**: 請妥善保管您的 API Keys,避免洩露給他人使用。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
BUSINESS_QUERY_GUIDE.md DELETED
@@ -1,225 +0,0 @@
1
- # 業務查詢系統使用指南
2
-
3
- ## 概述
4
-
5
- 本系統為您的 LINE 官方帳號提供了智能業務查詢功能,用戶可以透過自然語言詢問商品、庫存、訂單等業務資訊。系統整合了 Pydantic AI 進行語意分析,並提供友善的中文回應。
6
-
7
- ## 系統架構
8
-
9
- ```
10
- LINE 用戶訊息 → LINE Bot Service → Business Query Service → NLP Service + Database Service → 回應用戶
11
- ```
12
-
13
- ### 核心組件
14
-
15
- 1. **NLP Service** (`backend/services/nlp_service.py`)
16
- - 自然語言處理和意圖識別
17
- - 實體提取和信心度計算
18
- - 支援業務相關的查詢模式
19
-
20
- 2. **Database Service** (`backend/services/database_service.py`)
21
- - 資料庫查詢操作
22
- - 業務專用查詢方法
23
- - 支援商品、庫存、訂單等查詢
24
-
25
- 3. **Business Query Service** (`backend/services/business_query_service.py`)
26
- - 整合 NLP 和資料庫服務
27
- - 統一的查詢處理入口
28
- - 格式化回應訊息
29
-
30
- 4. **LINE Bot Service** (`backend/services/line_bot_service.py`)
31
- - 處理 LINE 平台的訊息
32
- - 用戶管理和對話流程
33
- - 快速回覆按鈕生成
34
-
35
- ## 支援的查詢類型
36
-
37
- ### 1. 商品查詢
38
- **用戶可以這樣問:**
39
- - "查詢商品 iPhone"
40
- - "有什麼筆記型電腦"
41
- - "商品價格查詢"
42
- - "找找看有沒有滑鼠"
43
-
44
- **系統回應範例:**
45
- ```
46
- 找到商品:
47
- 名稱:iPhone 14 Pro
48
- 描述:最新款智慧型手機
49
- 價格:$35,900
50
- 類別:手機
51
- ```
52
-
53
- ### 2. 庫存查詢
54
- **用戶可以這樣問:**
55
- - "庫存查詢 iPhone"
56
- - "筆記型電腦還有多少"
57
- - "查詢存貨狀況"
58
- - "iPhone 的庫存"
59
-
60
- **系統回應範例:**
61
- ```
62
- 庫存資訊:
63
- 商品:iPhone 14 Pro
64
- 目前庫存:25 件
65
- 類別:手機
66
- 價格:$35,900
67
- ```
68
-
69
- ### 3. 訂單查詢
70
- **用戶可以這樣問:**
71
- - "我的訂單"
72
- - "查詢訂單狀態"
73
- - "訂單編號 ORD001"
74
- - "購買記錄"
75
-
76
- **系統回應範例:**
77
- ```
78
- 找到 3 筆訂單:
79
- 1. ORD001 - 已出貨 - $15,000
80
- 2. ORD002 - 處理中 - $8,500
81
- 3. ORD003 - 已完成 - $12,300
82
- ```
83
-
84
- ### 4. 低庫存警告
85
- **用戶可以這樣問:**
86
- - "低庫存商品"
87
- - "缺貨商品查詢"
88
- - "庫存不足的商品"
89
-
90
- **系統回應範例:**
91
- ```
92
- ⚠️ 發現 3 個低庫存商品:
93
- 1. iPhone 13 - 剩餘:5 件
94
- 2. MacBook Air - 剩餘:2 件
95
- 3. AirPods Pro - 剩餘:8 件
96
- ```
97
-
98
- ### 5. 業務統計
99
- **用戶可以這樣問:**
100
- - "業務摘要"
101
- - "統計報表"
102
- - "總計資料"
103
- - "業務狀況"
104
-
105
- **系統回應範例:**
106
- ```
107
- 📊 業務摘要:
108
- 商品總數:156 個
109
- 訂單總數:89 筆
110
- 用戶總數:45 人
111
- 低庫存商品:3 個
112
- 統計時間:2024-01-15T10:30:00
113
- ```
114
-
115
- ## 特殊指令
116
-
117
- ### 幫助指令
118
- - `help`, `幫助`, `說明`, `指令`
119
-
120
- ### 選單指令
121
- - `menu`, `選單`, `功能`
122
-
123
- ## 快速開始
124
-
125
- ### 1. 測試業務查詢功能
126
- ```bash
127
- python tmp_rovodev_test_business_query.py
128
- ```
129
-
130
- ### 2. 測試 LINE Bot 整合
131
- ```bash
132
- python tmp_rovodev_test_line_integration.py
133
- ```
134
-
135
- ### 3. 在您的應用中使用
136
-
137
- ```python
138
- from backend.services.line_bot_service import LineBotService
139
-
140
- # 初始化服務
141
- line_service = LineBotService()
142
-
143
- # 處理用戶訊息
144
- response = line_service.handle_text_message(
145
- user_id="U1234567890",
146
- message_text="查詢商品 iPhone",
147
- display_name="張小明"
148
- )
149
-
150
- print(response['text']) # 系統回應
151
- ```
152
-
153
- ## 自訂擴展
154
-
155
- ### 1. 新增查詢類型
156
-
157
- 在 `nlp_service.py` 中新增意圖模式:
158
-
159
- ```python
160
- self.business_intent_patterns = {
161
- # 現有模式...
162
- "new_query_type": [
163
- r"新查詢.*模式",
164
- r"特殊.*查詢"
165
- ]
166
- }
167
- ```
168
-
169
- 在 `database_service.py` 中新增對應方法:
170
-
171
- ```python
172
- def new_query_method(self, param1: str = None) -> DatabaseResult:
173
- """新的查詢方法"""
174
- # 實作查詢邏輯
175
- pass
176
- ```
177
-
178
- ### 2. 自訂回應格式
179
-
180
- 在 `nlp_service.py` 中修改格式化方法:
181
-
182
- ```python
183
- def _format_custom_response(self, data: List[Dict[str, Any]]) -> str:
184
- """自訂回應格式"""
185
- # 實作自訂格式
186
- pass
187
- ```
188
-
189
- ### 3. 整合其他資料源
190
-
191
- 您可以修改 `database_service.py` 來連接其他資料庫或 API:
192
-
193
- ```python
194
- def search_external_data(self, query: str) -> DatabaseResult:
195
- """查詢外部資料源"""
196
- # 連接外部 API 或資料庫
197
- pass
198
- ```
199
-
200
- ## 注意事項
201
-
202
- 1. **資料庫結構**: 目前的實作基於現有的 `Product`, `Order`, `User` 模型。如果您的資料庫結構不同,請相應調整查詢方法。
203
-
204
- 2. **權限控制**: 系統目前允許所有查詢。在生產環境中,建議實作適當的權限控制。
205
-
206
- 3. **效能考量**: 對於大量資料,建議加入分頁和快取機制。
207
-
208
- 4. **錯誤處理**: 系統已包含基本的錯誤處理,但建議根據實際需求進一步完善。
209
-
210
- ## 參考資料
211
-
212
- - `another_proj_schemas.py`: 包含完整的業務模型定義
213
- - `backend/database/models.py`: 資料庫模型定義
214
- - `backend/models/schemas.py`: API 回應模型定義
215
-
216
- ## 技術支援
217
-
218
- 如需��術支援或功能擴展,請參考:
219
- 1. 測試腳本中的使用範例
220
- 2. 各服務類別的文檔字串
221
- 3. 錯誤日誌輸出
222
-
223
- ---
224
-
225
- **注意**: 這是一個基礎實作,您可以根據實際業務需求進行擴展和優化。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
GROQ_INTEGRATION_COMPLETE.md DELETED
@@ -1,237 +0,0 @@
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 服務能力!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LINEBOT_OPERATION_GUIDE.md ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LineBot 操作指南
2
+
3
+ ## 🤖 系統概述
4
+
5
+ 本 LineBot 系統整合了 Pydantic AI 框架,提供智能商品查詢、聊天和業務查詢功能。系統設計考慮了 LINE 群組使用場景,避免不必要的 AI token 消耗。
6
+
7
+ ## 📱 用戶操作指南
8
+
9
+ ### 基本指令
10
+
11
+ #### 1. 聊天模式
12
+ ```
13
+ /chat 你好!今天天氣如何?
14
+ /chat 推薦一些好用的功能
15
+ ```
16
+ - **用途**:一般對話、問候、非業務相關問題
17
+ - **適用場景**:群組聊天、客戶服務
18
+ - **AI 服務**:使用 Groq 進行自然對話
19
+
20
+ #### 2. 商品查詢模式
21
+ ```
22
+ /search iPhone 15 Pro
23
+ /search 價格 1000-5000
24
+ /search 庫存不足的商品
25
+ ```
26
+ - **用途**:傳統的商品搜尋和業務查詢
27
+ - **適用場景**:明確的商品查詢需求
28
+ - **查詢範圍**:商品、訂單、庫存、客戶資料
29
+
30
+ #### 3. 幫助指令
31
+ ```
32
+ /help
33
+ 幫助
34
+ 說明
35
+ ```
36
+ - **用途**:顯示系統使用說明
37
+ - **回應**:完整的功能介紹和使用範例
38
+
39
+ ### 智能模式(無前綴)
40
+
41
+ #### 商品推薦查詢
42
+ ```
43
+ 是否有推薦貓砂?
44
+ 有什麼狗糧推薦?
45
+ 推薦一些寵物用品
46
+ ```
47
+ - **特點**:自動識別推薦意圖
48
+ - **回應**:智能推薦相關商品,包含庫存資訊
49
+ - **優勢**:更自然的對話方式
50
+
51
+ #### 庫存查詢
52
+ ```
53
+ 查詢iPhone庫存
54
+ 寵物用品還有多少?
55
+ 低庫存商品有哪些?
56
+ ```
57
+ - **特點**:自動識別庫存查詢意圖
58
+ - **回應**:詳細的庫存狀態和警告提醒
59
+
60
+ #### 一般對話
61
+ ```
62
+ 你好!
63
+ 今天天氣如何?
64
+ 謝謝你的幫助
65
+ ```
66
+ - **特點**:自動識別為聊天意圖
67
+ - **回應**:友善的對話回應
68
+
69
+ ## 🏢 管理員操作指南
70
+
71
+ ### 系統監控
72
+
73
+ #### 1. 健康檢查
74
+ ```bash
75
+ curl http://localhost:7860/health
76
+ ```
77
+ **回應內容**:
78
+ - 系統狀態
79
+ - 資料庫連線狀態
80
+ - 資料庫連線資訊
81
+
82
+ #### 2. 路由統計
83
+ 在 LINE 中輸入:`統計` 或 `stats`
84
+
85
+ **統計內容**:
86
+ - 各模式使用次數和百分比
87
+ - Groq 服務狀態
88
+ - Pydantic AI 服務狀態
89
+
90
+ ### API 端點管理
91
+
92
+ #### 1. 直接聊天 API
93
+ ```bash
94
+ curl -X POST "http://localhost:7860/chat" \
95
+ -H "Content-Type: application/json" \
96
+ -d '{
97
+ "message": "今天天氣如何?",
98
+ "user_id": "admin_user"
99
+ }'
100
+ ```
101
+
102
+ #### 2. 直接搜尋 API
103
+ ```bash
104
+ curl -X POST "http://localhost:7860/search" \
105
+ -H "Content-Type: application/json" \
106
+ -d '{
107
+ "message": "iPhone 15",
108
+ "user_id": "admin_user"
109
+ }'
110
+ ```
111
+
112
+ #### 3. 商品查詢 API
113
+ ```bash
114
+ curl -X POST "http://localhost:7860/product-query" \
115
+ -H "Content-Type: application/json" \
116
+ -d '{
117
+ "message": "是否有推薦貓砂?",
118
+ "user_id": "admin_user"
119
+ }'
120
+ ```
121
+
122
+ #### 4. 智能路由 API
123
+ ```bash
124
+ curl -X POST "http://localhost:7860/route" \
125
+ -H "Content-Type: application/json" \
126
+ -d '{
127
+ "message": "有什麼狗糧推薦?",
128
+ "user_id": "admin_user"
129
+ }'
130
+ ```
131
+
132
+ ### 系統配置
133
+
134
+ #### 環境變數檢查
135
+ ```bash
136
+ # 檢查必要的環境變數
137
+ echo $GROQ_API_KEY
138
+ echo $DB_HOST
139
+ echo $LINE_CHANNEL_ACCESS_TOKEN
140
+ ```
141
+
142
+ #### 服務狀態檢查
143
+ ```python
144
+ # 在 Python 中檢查服務狀態
145
+ from backend.services.pydantic_ai_service import ProductQueryService
146
+ from backend.services.groq_service import GroqService
147
+
148
+ product_service = ProductQueryService()
149
+ groq_service = GroqService()
150
+
151
+ print(f"Pydantic AI 可用: {product_service.is_available()}")
152
+ print(f"Groq 服務可用: {groq_service.is_available()}")
153
+ ```
154
+
155
+ ## 🔧 故障排除
156
+
157
+ ### 常見問題
158
+
159
+ #### 1. 商品查詢沒有結果
160
+ **可能原因**:
161
+ - 資料庫中沒有相關商品
162
+ - 關鍵字匹配不準確
163
+ - 商品被標記為已刪除
164
+
165
+ **解決方案**:
166
+ - 檢查資料庫商品資料
167
+ - 嘗試使用不同的關鍵字
168
+ - 使用 `/search` 進行傳統查詢
169
+
170
+ #### 2. Pydantic AI 服務不可用
171
+ **錯誤訊息**:「商品查詢服務暫時無法使用」
172
+
173
+ **解決方案**:
174
+ - 檢查 `GROQ_API_KEY` 環境變數
175
+ - 確認 Groq API 配額
176
+ - 系統會自動降級到傳統搜尋
177
+
178
+ #### 3. 資料庫連線失敗
179
+ **錯誤訊息**:「資料庫連線測試失敗」
180
+
181
+ **解決方案**:
182
+ - 檢查資料庫連線參數
183
+ - 確認資料庫服務運行狀態
184
+ - 檢查網路連線
185
+
186
+ ### 日誌查看
187
+
188
+ #### 應用程式日誌
189
+ ```bash
190
+ # 查看即時日誌
191
+ tail -f app.log
192
+
193
+ # 搜尋特定錯誤
194
+ grep "ERROR" app.log
195
+ grep "商品查詢錯誤" app.log
196
+ ```
197
+
198
+ #### 特定功能日誌
199
+ ```bash
200
+ # Pydantic AI 相關
201
+ grep "Pydantic AI" app.log
202
+
203
+ # 路由統計
204
+ grep "智能路由" app.log
205
+
206
+ # 資料庫查詢
207
+ grep "資料庫" app.log
208
+ ```
209
+
210
+ ## 📊 效能監控
211
+
212
+ ### API 回應時間
213
+ - **聊天模式**:通常 1-3 秒
214
+ - **商品查詢**:通常 2-5 秒
215
+ - **智能路由**:通常 1-4 秒
216
+
217
+ ### 資源使用
218
+ - **記憶體**:約 200-500MB
219
+ - **CPU**:低負載時 < 10%
220
+ - **網路**:主要是 API 調用
221
+
222
+ ### Token 使用監控
223
+ - 監控 Groq API 使用量
224
+ - 設定使用量警告
225
+ - 考慮實施使用限制
226
+
227
+ ## 🚀 部署指南
228
+
229
+ ### 開發環境
230
+ ```bash
231
+ # 1. 安裝依賴
232
+ pip install -r requirements.txt
233
+
234
+ # 2. 設定環境變數
235
+ cp .env.example .env
236
+ # 編輯 .env 文件
237
+
238
+ # 3. 運行測試
239
+ python test_pydantic_ai_integration.py
240
+
241
+ # 4. 啟動服務
242
+ uvicorn backend.main:app --host 0.0.0.0 --port 7860
243
+ ```
244
+
245
+ ### 生產環境
246
+ ```bash
247
+ # 使用 Docker
248
+ docker build -t linebot-pydantic .
249
+ docker run -p 7860:7860 --env-file .env linebot-pydantic
250
+
251
+ # 或使用 Docker Compose
252
+ docker-compose up -d
253
+ ```
254
+
255
+ ### LINE Webhook 設定
256
+ 1. 在 LINE Developers Console 設定 Webhook URL
257
+ 2. URL 格式:`https://your-domain.com/webhook`
258
+ 3. 確認 SSL 憑證有效
259
+
260
+ ## 📋 維護檢查清單
261
+
262
+ ### 每日檢查
263
+ - [ ] 系統健康狀態
264
+ - [ ] API 回應時間
265
+ - [ ] 錯誤日誌
266
+ - [ ] Groq API 使用量
267
+
268
+ ### 每週檢查
269
+ - [ ] 路由統計分析
270
+ - [ ] 資料庫效能
271
+ - [ ] 商品資料更新
272
+ - [ ] 用戶回饋收集
273
+
274
+ ### 每月檢查
275
+ - [ ] 系統效能評估
276
+ - [ ] 功能使用分析
277
+ - [ ] 安全性檢查
278
+ - [ ] 備份驗證
279
+
280
+ ## 📞 支援聯絡
281
+
282
+ ### 技術問題
283
+ - 檢查日誌文件
284
+ - 參考故障排除指南
285
+ - 聯絡系統管理員
286
+
287
+ ### 功能建議
288
+ - 記錄用戶需求
289
+ - 評估實施可行性
290
+ - 規劃功能更新
291
+
292
+ ---
293
+
294
+ **版本**:v1.0
295
+ **更新日期**:2025-01-12
296
+ **維護團隊**:開發團隊
OPENROUTER_INTEGRATION.md DELETED
@@ -1,273 +0,0 @@
1
- # 🚀 OpenRouter 整合文件
2
-
3
- ## 📋 概述
4
-
5
- 本專案已整合 OpenRouter API 來提供進階的自然語言處理功能,包括:
6
- - 更精確的意圖識別
7
- - 智能實體提取
8
- - 自然的回應生成
9
-
10
- ## 🔧 設定方式
11
-
12
- ### 1. 取得 OpenRouter API Key
13
-
14
- 1. 前往 [OpenRouter 官網](https://openrouter.ai/)
15
- 2. 註冊帳號並登入
16
- 3. 前往 [API Keys 頁面](https://openrouter.ai/keys)
17
- 4. 建立新的 API Key
18
- 5. 複製 API Key 備用
19
-
20
- ### 2. 環境變數設定
21
-
22
- 在 Hugging Face Spaces 的 Settings 中新增以下環境變數:
23
-
24
- ```bash
25
- # 必要設定
26
- OPENROUTER_API_KEY=your_openrouter_api_key_here
27
-
28
- # 可選設定 (預設值已設定)
29
- OPENROUTER_MODEL=anthropic/claude-3-haiku
30
- ```
31
-
32
- ### 3. 支援的模型
33
-
34
- OpenRouter 支援多種 AI 模型,您可以根據需求選擇:
35
-
36
- #### 推薦模型 (成本效益佳)
37
- - `anthropic/claude-3-haiku` (預設) - 快速、便宜
38
- - `openai/gpt-3.5-turbo` - 平衡性能與成本
39
- - `meta-llama/llama-2-70b-chat` - 開源選項
40
-
41
- #### 高性能模型
42
- - `anthropic/claude-3-sonnet` - 更好的理解能力
43
- - `openai/gpt-4` - 最佳性能
44
- - `anthropic/claude-3-opus` - 最高品質
45
-
46
- #### 設定範例
47
- ```bash
48
- # 使用 GPT-3.5 Turbo
49
- OPENROUTER_MODEL=openai/gpt-3.5-turbo
50
-
51
- # 使用 Claude 3 Sonnet
52
- OPENROUTER_MODEL=anthropic/claude-3-sonnet
53
- ```
54
-
55
- ## 🎯 功能特色
56
-
57
- ### 1. 進階意圖識別
58
-
59
- OpenRouter 整合後,系統能更準確地識別用戶意圖:
60
-
61
- ```python
62
- # 範例輸入
63
- "幫我找一下價格在一千到五千之間的手機"
64
-
65
- # 基礎 NLP (規則引擎)
66
- {
67
- "intent": "search_product",
68
- "confidence": 0.6,
69
- "entities": {"min_price": 1000, "max_price": 5000}
70
- }
71
-
72
- # 進階 NLP (OpenRouter)
73
- {
74
- "intent": "search_product",
75
- "confidence": 0.9,
76
- "entities": {
77
- "min_price": 1000,
78
- "max_price": 5000,
79
- "category": "手機",
80
- "product_type": "電子產品"
81
- }
82
- }
83
- ```
84
-
85
- ### 2. 智能回應生成
86
-
87
- 系統會根據查詢結果生成更自然的回應:
88
-
89
- ```python
90
- # 基礎回應
91
- "找到 3 筆商品資料。"
92
-
93
- # 進階回應 (OpenRouter)
94
- "我為您找到了 3 款符合條件的手機:
95
- 1. iPhone 15 Pro - NT$ 35,900
96
- 2. Samsung Galaxy S24 - NT$ 28,900
97
- 3. Google Pixel 8 - NT$ 24,900
98
-
99
- 這些都在您的預算範圍內,您想了解哪一款的詳細資訊呢?"
100
- ```
101
-
102
- ## 🔄 降級機制
103
-
104
- 系統具有完整的降級機制:
105
-
106
- 1. **優先使用 OpenRouter**: 如果 API Key 可用且正常運作
107
- 2. **自動降級**: 如果 OpenRouter 失敗,自動使用基礎規則引擎
108
- 3. **錯誤處理**: 完整的錯誤日誌和異常處理
109
-
110
- ```python
111
- # 在程式碼中的實作
112
- def analyze_message(self, message: str, use_advanced: bool = True):
113
- if use_advanced and self.openrouter_service.api_key:
114
- try:
115
- # 嘗試使用 OpenRouter
116
- return advanced_analysis
117
- except Exception as e:
118
- logger.warning(f"進階 NLP 分析失敗,使用基礎分析: {str(e)}")
119
-
120
- # 降級到基礎規則引擎
121
- return basic_analysis
122
- ```
123
-
124
- ## 💰 成本考量
125
-
126
- ### 模型成本比較 (每 1M tokens)
127
-
128
- | 模型 | 輸入成本 | 輸出成本 | 適用場景 |
129
- |------|----------|----------|----------|
130
- | claude-3-haiku | $0.25 | $1.25 | 日常查詢 |
131
- | gpt-3.5-turbo | $0.50 | $1.50 | 平衡使用 |
132
- | claude-3-sonnet | $3.00 | $15.00 | 複雜查詢 |
133
- | gpt-4 | $10.00 | $30.00 | 高精度需求 |
134
-
135
- ### 成本優化建議
136
-
137
- 1. **使用 claude-3-haiku** 作為預設模型 (成本最低)
138
- 2. **設定合理的 token 限制** (max_tokens: 300-500)
139
- 3. **監控使用量** 透過 OpenRouter Dashboard
140
- 4. **考慮快取機制** 對常見查詢進行快取
141
-
142
- ## 🛠️ 開發與測試
143
-
144
- ### 本地測試
145
-
146
- ```bash
147
- # 設定環境變數
148
- export OPENROUTER_API_KEY="your_key_here"
149
- export OPENROUTER_MODEL="anthropic/claude-3-haiku"
150
-
151
- # 執行測試
152
- python test_api.py
153
- ```
154
-
155
- ### API 測試範例
156
-
157
- ```python
158
- # 測試進階 NLP
159
- from backend.services.openrouter_service import OpenRouterService
160
-
161
- service = OpenRouterService()
162
- result = await service.analyze_intent_advanced("查詢用戶張三的訂單")
163
- print(result)
164
- ```
165
-
166
- ## 🔍 監控與除錯
167
-
168
- ### 日誌監控
169
-
170
- 系統會記錄以下資訊:
171
-
172
- ```python
173
- # 成功使用 OpenRouter
174
- logger.info("使用 OpenRouter 進行進階分析")
175
-
176
- # 降級到基礎引擎
177
- logger.warning("進階 NLP 分析失敗,使用基礎分析")
178
-
179
- # API 錯誤
180
- logger.error("OpenRouter API 錯誤: 401 Unauthorized")
181
- ```
182
-
183
- ### 常見問題
184
-
185
- 1. **API Key 無效**
186
- - 檢查 API Key 是否正確
187
- - 確認 OpenRouter 帳戶餘額
188
-
189
- 2. **模型不存在**
190
- - 檢查模型名稱是否正確
191
- - 參考 OpenRouter 模型列表
192
-
193
- 3. **請求超時**
194
- - 檢查網路連線
195
- - 考慮增加 timeout 設定
196
-
197
- ## 📈 效能優化
198
-
199
- ### 1. 異步處理
200
-
201
- ```python
202
- # 使用 asyncio 避免阻塞
203
- async def analyze_intent_advanced(self, message: str):
204
- async with httpx.AsyncClient(timeout=30.0) as client:
205
- response = await client.post(...)
206
- ```
207
-
208
- ### 2. 錯誤重試
209
-
210
- ```python
211
- # 實作重試機制
212
- for attempt in range(3):
213
- try:
214
- result = await openrouter_request()
215
- break
216
- except Exception as e:
217
- if attempt == 2:
218
- raise e
219
- await asyncio.sleep(1)
220
- ```
221
-
222
- ### 3. 快取策略
223
-
224
- ```python
225
- # 快取常見查詢結果
226
- @lru_cache(maxsize=100)
227
- def cached_analysis(message_hash: str):
228
- return analysis_result
229
- ```
230
-
231
- ## 🚀 進階功能
232
-
233
- ### 1. 自訂提示詞
234
-
235
- 您可以修改 `openrouter_service.py` 中的提示詞來優化特定領域的表現:
236
-
237
- ```python
238
- def _build_analysis_prompt(self, message: str):
239
- # 自訂您的提示詞
240
- prompt = f"""
241
- 您是一個專業的電商客服助手...
242
- """
243
- ```
244
-
245
- ### 2. 多語言支援
246
-
247
- ```python
248
- # 支援多語言分析
249
- def analyze_message_multilingual(self, message: str, language: str = "zh"):
250
- prompt = self._build_multilingual_prompt(message, language)
251
- ```
252
-
253
- ### 3. 上下文記憶
254
-
255
- ```python
256
- # 維護對話上下文
257
- def analyze_with_context(self, message: str, conversation_history: List[str]):
258
- context = {"history": conversation_history}
259
- return await self.analyze_intent_advanced(message, context)
260
- ```
261
-
262
- ## 📞 技術支援
263
-
264
- 如果您在整合 OpenRouter 時遇到問題:
265
-
266
- 1. 檢查 [OpenRouter 文件](https://openrouter.ai/docs)
267
- 2. 查看系統日誌檔案
268
- 3. 參考本專案的 `test_api.py` 進行測試
269
- 4. 確認環境變數設定正確
270
-
271
- ---
272
-
273
- **注意**: OpenRouter 是付費服務,請根據您的使用量選擇適合的模型和方案。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
POSTGRESQL_MIGRATION_GUIDE.md DELETED
@@ -1,342 +0,0 @@
1
- # 🔄 PostgreSQL 直連遷移指南
2
-
3
- 本文件說明如何從 Supabase SDK 遷移到 PostgreSQL 直連的詳細步驟和注意事項。
4
-
5
- ## 📋 遷移概述
6
-
7
- ### 變更內容
8
- - ✅ 移除 Supabase Python SDK 依賴
9
- - ✅ 新增 SQLAlchemy + psycopg 直連
10
- - ✅ 重構資料庫服務層
11
- - ✅ 新增 ORM 模型定義
12
- - ✅ 支援 Alembic 資料庫遷移
13
-
14
- ### 優勢
15
- - 🚀 **更好的效能**: 直連減少中間層開銷
16
- - 🔧 **更多控制**: 完整的 SQL 查詢控制
17
- - 📊 **ORM 支援**: SQLAlchemy 提供強大的 ORM 功能
18
- - 🔄 **遷移管理**: Alembic 提供版本化的資料庫遷移
19
- - 🛡️ **連線池**: 自動連線池管理
20
-
21
- ## 🔑 環境變數更新
22
-
23
- ### 舊的 Supabase 設定
24
- ```bash
25
- SUPABASE_URL=https://your-project.supabase.co
26
- SUPABASE_KEY=your-anon-key
27
- ```
28
-
29
- ### 新的 PostgreSQL 設定
30
- ```bash
31
- DB_HOST=db.your-project.supabase.co
32
- DB_PORT=6543
33
- DB_NAME=postgres
34
- DB_USER=postgres
35
- DB_PASSWORD=your-database-password
36
- ```
37
-
38
- ## 🔍 如何取得 PostgreSQL 連線資訊
39
-
40
- ### 從 Supabase Dashboard 取得
41
-
42
- 1. **登入 Supabase Dashboard**
43
- - 前往 [Supabase Dashboard](https://supabase.com/dashboard)
44
- - 選擇您的專案
45
-
46
- 2. **取得連線資訊**
47
- - 前往 "Settings" → "Database"
48
- - 在 "Connection info" 區域找到:
49
- - **Host**: `db.your-project-ref.supabase.co`
50
- - **Port**: `6543` (PostgreSQL 直連埠)
51
- - **Database**: `postgres`
52
- - **User**: `postgres`
53
- - **Password**: 您設定的資料庫密碼
54
-
55
- 3. **連線字串範例**
56
- ```
57
- postgresql://postgres:your-password@db.your-project-ref.supabase.co:6543/postgres
58
- ```
59
-
60
- ### 從其他 PostgreSQL 提供商取得
61
-
62
- 如果您使用其他 PostgreSQL 服務:
63
- - **AWS RDS**: 在 RDS Console 中查看端點資訊
64
- - **Google Cloud SQL**: 在 Cloud Console 中查看連線詳情
65
- - **Azure Database**: 在 Azure Portal 中查看連線字串
66
- - **Railway/Render**: 在專案設定中查看資料庫資訊
67
-
68
- ## 🛠️ 部署步驟
69
-
70
- ### 1. 更新環境變數
71
-
72
- 在 Hugging Face Spaces 的 Settings 中更新:
73
-
74
- ```bash
75
- # 移除舊的 Supabase 設定
76
- # SUPABASE_URL=...
77
- # SUPABASE_KEY=...
78
-
79
- # 新增 PostgreSQL 設定
80
- DB_HOST=db.your-project-ref.supabase.co
81
- DB_PORT=6543
82
- DB_NAME=postgres
83
- DB_USER=postgres
84
- DB_PASSWORD=your-database-password
85
-
86
- # 其他設定保持不變
87
- LINE_CHANNEL_ACCESS_TOKEN=your-line-token
88
- LINE_CHANNEL_SECRET=your-line-secret
89
- OPENROUTER_API_KEY=your-openrouter-key
90
- OPENROUTER_MODEL=anthropic/claude-3-haiku
91
- ```
92
-
93
- ### 2. 初始化資料庫
94
-
95
- 部署後,執行資料庫初始化:
96
-
97
- ```bash
98
- # 在容器中執行
99
- python -m backend.database.init_db
100
- ```
101
-
102
- 或者透過 API 端點觸發初始化(如果實作了相關端點)。
103
-
104
- ### 3. 驗證連線
105
-
106
- 檢查健康檢查端點:
107
- ```bash
108
- curl https://your-space-url.hf.space/health
109
- ```
110
-
111
- 應該會看到:
112
- ```json
113
- {
114
- "status": "healthy",
115
- "message": "LINE Bot API is operational",
116
- "database": "connected",
117
- "database_url": "postgresql://your-host:6543/postgres"
118
- }
119
- ```
120
-
121
- ## 📊 資料庫模型對應
122
-
123
- ### 新的 SQLAlchemy 模型
124
-
125
- ```python
126
- # backend/database/models.py
127
-
128
- class User(Base):
129
- __tablename__ = "users"
130
- user_id = Column(String(255), primary_key=True)
131
- name = Column(String(255))
132
- email = Column(String(255))
133
- # ... 其他欄位
134
-
135
- class Product(Base):
136
- __tablename__ = "products"
137
- product_id = Column(Integer, primary_key=True)
138
- name = Column(String(255), nullable=False)
139
- price = Column(DECIMAL(10, 2), nullable=False)
140
- # ... 其他欄位
141
- ```
142
-
143
- ### 資料表關聯
144
-
145
- ```python
146
- # 一對多關聯
147
- class User(Base):
148
- orders = relationship("Order", back_populates="user")
149
-
150
- class Order(Base):
151
- user = relationship("User", back_populates="orders")
152
- order_items = relationship("OrderItem", back_populates="order")
153
- ```
154
-
155
- ## 🔄 API 變更
156
-
157
- ### 查詢方式變更
158
-
159
- #### 舊的 Supabase 方式
160
- ```python
161
- result = supabase.table("users").select("*").eq("name", "張三").execute()
162
- ```
163
-
164
- #### 新的 SQLAlchemy 方式
165
- ```python
166
- db = get_database_session()
167
- users = db.query(User).filter(User.name == "張三").all()
168
- ```
169
-
170
- ### 錯誤處理改進
171
-
172
- ```python
173
- def database_operation():
174
- db = None
175
- try:
176
- db = get_database_session()
177
- # 資料庫操作
178
- result = db.query(User).all()
179
- db.commit()
180
- return result
181
- except Exception as e:
182
- if db:
183
- db.rollback()
184
- logger.error(f"資料庫操作失敗: {str(e)}")
185
- raise
186
- finally:
187
- if db:
188
- close_database_session(db)
189
- ```
190
-
191
- ## 🚀 效能優化
192
-
193
- ### 連線池設定
194
-
195
- ```python
196
- # backend/database/connection.py
197
- engine = create_engine(
198
- settings.DATABASE_URL,
199
- pool_size=10, # 連線池大小
200
- max_overflow=20, # 最大溢出連線
201
- pool_pre_ping=True, # 連線前檢查
202
- pool_recycle=3600, # 1小時後回收連線
203
- )
204
- ```
205
-
206
- ### 查詢優化
207
-
208
- ```python
209
- # 使用 eager loading 避免 N+1 查詢
210
- from sqlalchemy.orm import joinedload
211
-
212
- users_with_orders = db.query(User).options(
213
- joinedload(User.orders)
214
- ).all()
215
-
216
- # 使用索引優化查詢
217
- # 在模型中定義索引
218
- class User(Base):
219
- __tablename__ = "users"
220
- name = Column(String(255), index=True) # 建立索引
221
- ```
222
-
223
- ## 🔧 開發工具
224
-
225
- ### Alembic 遷移
226
-
227
- ```bash
228
- # 初始化 Alembic
229
- alembic init alembic
230
-
231
- # 建立遷移檔案
232
- alembic revision --autogenerate -m "Initial migration"
233
-
234
- # 執行遷移
235
- alembic upgrade head
236
-
237
- # 回滾遷移
238
- alembic downgrade -1
239
- ```
240
-
241
- ### 本地開發
242
-
243
- ```bash
244
- # 安裝依賴
245
- pip install -r requirements.txt
246
-
247
- # 設定環境變數
248
- export DB_HOST=localhost
249
- export DB_PORT=5432
250
- export DB_NAME=linebot_dev
251
- export DB_USER=postgres
252
- export DB_PASSWORD=password
253
-
254
- # 初始化資料庫
255
- python -m backend.database.init_db
256
-
257
- # 執行應用程式
258
- uvicorn backend.main:app --reload --port 7860
259
- ```
260
-
261
- ## 🛡️ 安全性考量
262
-
263
- ### 連線安全
264
-
265
- 1. **使用 SSL 連線**
266
- ```python
267
- DATABASE_URL = f"postgresql+psycopg://{user}:{password}@{host}:{port}/{db}?sslmode=require"
268
- ```
269
-
270
- 2. **限制資料庫權限**
271
- - 建立專用的應用程式使用者
272
- - 只授予必要的資料表權限
273
- - 定期輪換密碼
274
-
275
- 3. **連線字串保護**
276
- - 使用環境變數儲存敏感資訊
277
- - 避免在日誌中記錄連線字串
278
- - 使用密碼管理工具
279
-
280
- ## 📈 監控與除錯
281
-
282
- ### 日誌設定
283
-
284
- ```python
285
- # 啟用 SQLAlchemy 日誌
286
- engine = create_engine(
287
- settings.DATABASE_URL,
288
- echo=settings.DEBUG, # 在 DEBUG 模式下顯示 SQL
289
- )
290
- ```
291
-
292
- ### 效能監控
293
-
294
- ```python
295
- import time
296
- from functools import wraps
297
-
298
- def monitor_db_performance(func):
299
- @wraps(func)
300
- def wrapper(*args, **kwargs):
301
- start_time = time.time()
302
- result = func(*args, **kwargs)
303
- execution_time = time.time() - start_time
304
- logger.info(f"{func.__name__} 執行時間: {execution_time:.2f}秒")
305
- return result
306
- return wrapper
307
- ```
308
-
309
- ## 🚨 常見問題
310
-
311
- ### Q: 連線失敗怎麼辦?
312
- A: 檢查以下項目:
313
- 1. 主機名稱和埠號是否正確
314
- 2. 使用者名稱和密碼是否正確
315
- 3. 資料庫是否允許外部連線
316
- 4. 防火牆設定是否正確
317
-
318
- ### Q: 效能比 Supabase SDK 慢?
319
- A: 可能原因:
320
- 1. 連線池設定不當
321
- 2. 查詢未優化
322
- 3. 缺少適當的索引
323
- 4. 網路延遲問題
324
-
325
- ### Q: 如何處理資料庫遷移?
326
- A: 使用 Alembic:
327
- 1. 建立遷移腳本
328
- 2. 在部署前測試遷移
329
- 3. 備份資料庫
330
- 4. 執行遷移
331
-
332
- ## 📞 技術支援
333
-
334
- 如果遇到問題:
335
- 1. 檢查應用程式日誌
336
- 2. 驗證環境變數設定
337
- 3. 測試資料庫連線
338
- 4. 參考 SQLAlchemy 官方文件
339
-
340
- ---
341
-
342
- **注意**: 遷移前請務必備份您的資料庫,並在測試環境中驗證所有功能正常運作。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
PYDANTIC_AI_INTEGRATION.md ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pydantic AI 整合說明文件
2
+
3
+ ## 📋 概述
4
+
5
+ 本次更新整合了 Pydantic AI 框架到 LineBot 專案中,主要解決商品查詢準確性問題,特別是像 "是否有推薦貓砂?" 這類自然語言查詢。
6
+
7
+ ## 🎯 解決的問題
8
+
9
+ ### 原有問題
10
+ - 用戶詢問 "是否有推薦貓砂?" 時,系統常常回答沒有相關資訊
11
+ - 商品查詢意圖識別不準確
12
+ - 缺乏智能推薦功能
13
+
14
+ ### 解決方案
15
+ - 整合 Pydantic AI 框架,提供更精確的意圖識別
16
+ - 移植 inventory 專案的查詢功能,增強商品搜尋能力
17
+ - 新增智能商品推薦系統
18
+
19
+ ## 🔧 技術架構變更
20
+
21
+ ### 新增組件
22
+
23
+ 1. **Pydantic AI 服務** (`backend/services/pydantic_ai_service.py`)
24
+ - 商品查詢意圖分析
25
+ - 智能推薦系統
26
+ - 結構化輸出驗證
27
+
28
+ 2. **增強商品服務** (`backend/services/enhanced_product_service.py`)
29
+ - 進階商品搜尋
30
+ - 商品推薦功能
31
+ - 庫存狀態分析
32
+
33
+ 3. **新增依賴**
34
+ - `pydantic-ai[groq]`: Pydantic AI 框架
35
+
36
+ ### 修改組件
37
+
38
+ 1. **訊息路由器** (`backend/services/message_router.py`)
39
+ - 新增商品查詢路由邏輯
40
+ - 整合 Pydantic AI 意圖分析
41
+ - 保留原有 `/chat` 和 `/search` 功能
42
+
43
+ 2. **主應用** (`backend/main.py`)
44
+ - 新增 `/product-query` API 端點
45
+
46
+ ## 🚀 功能特性
47
+
48
+ ### 1. 智能意圖識別
49
+ ```python
50
+ # 自動識別商品查詢意圖
51
+ "是否有推薦貓砂?" → 商品推薦查詢 (信心度: 0.8)
52
+ "有什麼狗糧推薦?" → 商品推薦查詢 (信心度: 0.9)
53
+ "查詢iPhone庫存" → 庫存查詢 (信心度: 0.7)
54
+ ```
55
+
56
+ ### 2. 智能商品推薦
57
+ - 基於關鍵字的商品匹配
58
+ - 優先顯示有庫存的商品
59
+ - 提供推薦原因說明
60
+
61
+ ### 3. 增強的庫存資訊
62
+ - 實時庫存狀態顯示
63
+ - 庫存警告提醒
64
+ - 緊急程度分級
65
+
66
+ ## 📱 使用指南
67
+
68
+ ### LINE Bot 使用方式
69
+
70
+ #### 1. 指令模式(原有功能保留)
71
+ ```
72
+ /chat 今天天氣如何? # 聊天模式
73
+ /search iPhone 15 Pro # 商品查詢模式
74
+ /help # 顯示幫助
75
+ ```
76
+
77
+ #### 2. 智能模式(新增功能)
78
+ 直接輸入訊息,系統自動判斷意圖:
79
+ ```
80
+ 是否有推薦貓砂? # → 智能商品推薦
81
+ 有什麼狗糧推薦? # → 智能商品推薦
82
+ 查詢寵物用品庫存 # → 庫存查詢
83
+ iPhone 還有庫存嗎? # → 庫存查詢
84
+ 你好! # → 聊天模式
85
+ ```
86
+
87
+ ### API 端點
88
+
89
+ #### 1. 原有端點(保留)
90
+ - `POST /chat`: 直接聊天
91
+ - `POST /search`: 直接搜尋
92
+ - `POST /route`: 智能路由
93
+
94
+ #### 2. 新增端點
95
+ - `POST /product-query`: 專門的商品查詢
96
+
97
+ ### API 使用範例
98
+
99
+ ```bash
100
+ # 商品查詢 API
101
+ curl -X POST "http://localhost:7860/product-query" \
102
+ -H "Content-Type: application/json" \
103
+ -d '{
104
+ "message": "是否有推薦貓砂?",
105
+ "user_id": "test_user"
106
+ }'
107
+
108
+ # 智能路由 API
109
+ curl -X POST "http://localhost:7860/route" \
110
+ -H "Content-Type: application/json" \
111
+ -d '{
112
+ "message": "有什麼狗糧推薦?",
113
+ "user_id": "test_user"
114
+ }'
115
+ ```
116
+
117
+ ## ⚙️ 配置要求
118
+
119
+ ### 環境變數
120
+ ```env
121
+ # 必需
122
+ GROQ_API_KEY=your_groq_api_key_here
123
+ DB_HOST=your_database_host
124
+ DB_NAME=your_database_name
125
+ DB_USER=your_database_user
126
+ DB_PASSWORD=your_database_password
127
+
128
+ # LINE Bot (如果使用)
129
+ LINE_CHANNEL_ACCESS_TOKEN=your_line_token
130
+ LINE_CHANNEL_SECRET=your_line_secret
131
+ ```
132
+
133
+ ### 依賴安裝
134
+ ```bash
135
+ pip install -r requirements.txt
136
+ ```
137
+
138
+ ## 🧪 測試
139
+
140
+ ### 運行測試腳本
141
+ ```bash
142
+ python test_pydantic_ai_integration.py
143
+ ```
144
+
145
+ ### 測試案例
146
+ 1. 商品推薦查詢
147
+ 2. 庫存查詢
148
+ 3. 意圖分析準確性
149
+ 4. 路由統計
150
+
151
+ ## 📊 路由統計
152
+
153
+ 系統會追蹤各種路由模式的使用情況:
154
+ - 💬 聊天模式
155
+ - 🔍 搜尋模式
156
+ - 🛍️ 商品查詢(新增)
157
+ - 🧠 智能路由
158
+ - ❓ 幫助模式
159
+ - ❌ 錯誤次數
160
+
161
+ 查看統計:輸入 `統計` 或 `stats`
162
+
163
+ ## 🔄 路由邏輯
164
+
165
+ ```
166
+ 用戶訊息
167
+
168
+ 是否有前綴?
169
+ ├─ /chat → 聊天模式
170
+ ├─ /search → 搜尋模式
171
+ ├─ /help → 幫助模式
172
+ └─ 無前綴 → 智能路由
173
+
174
+ Pydantic AI 意圖分析
175
+
176
+ 商品查詢?(信心度>0.6)
177
+ ├─ 是 → 商品查詢模式
178
+ └─ 否 → 關鍵字分析
179
+
180
+ 搜尋關鍵字?
181
+ ├─ 是 → 搜尋模式
182
+ └─ 否 → 聊天模式
183
+ ```
184
+
185
+ ## 🚨 注意事項
186
+
187
+ ### LINE 群組使用
188
+ - **保留前綴設計**:在群組中使用 `/chat` 和 `/search` 前綴,避免所有訊息都觸發 AI
189
+ - **智能路由**:只在私聊或明確需要時使用無前綴訊息
190
+
191
+ ### 效能考量
192
+ - Pydantic AI 查詢會消耗 Groq API token
193
+ - 建議在生產環境中監控 API 使用量
194
+ - 可以調整意圖識別的信心度閾值
195
+
196
+ ### 錯誤處理
197
+ - 當 Pydantic AI 不可用���,會自動降級到原有的搜尋功能
198
+ - 完整的錯誤日誌記錄
199
+
200
+ ## 📈 預期效果
201
+
202
+ ### 查詢準確性提升
203
+ - **之前**:「是否有推薦貓砂?」→ 沒有相關資訊
204
+ - **現在**:「是否有推薦貓砂?」→ 顯示貓砂商品列表,包含庫存狀態
205
+
206
+ ### 用戶體驗改善
207
+ - 更自然的對話方式
208
+ - 智能推薦功能
209
+ - 詳細的庫存資訊
210
+
211
+ ### 系統可維護性
212
+ - 模組化設計
213
+ - 完整的測試覆蓋
214
+ - 清晰的錯誤處理
215
+
216
+ ## 🔮 未來擴展
217
+
218
+ 1. **多語言支援**:擴展到英文查詢
219
+ 2. **個人化推薦**:基於用戶歷史的推薦
220
+ 3. **語音查詢**:整合語音識別
221
+ 4. **圖片查詢**:支援商品圖片搜尋
222
+
223
+ ---
224
+
225
+ **版本**:v1.0
226
+ **更新日期**:2025-01-12
227
+ **作者**:Augment Agent
another_proj_schemas.py DELETED
@@ -1,492 +0,0 @@
1
- from pydantic import BaseModel, EmailStr, Field
2
- from typing import List, Optional, Union
3
- from datetime import datetime, date
4
- import enum
5
-
6
- # Import Enum types from models.py to be used in Pydantic schemas
7
- # This avoids redefining them and ensures consistency.
8
- # We might need to adjust models.py if enums are defined in a way that's hard to import directly
9
- # For now, let's assume we can import them or we'll redefine for Pydantic if necessary.
10
- # For simplicity in this step, I'll redefine them here. If issues arise, we can refactor.
11
-
12
- class UserRole(str, enum.Enum):
13
- ADMIN = "ADMIN"
14
- USER = "USER"
15
-
16
- class TransactionType(str, enum.Enum):
17
- PURCHASE = "PURCHASE"
18
- SELL = "SELL"
19
-
20
- class TransactionStatus(str, enum.Enum):
21
- PENDING = "PENDING"
22
- PROCESSING = "PROCESSING" # Added PROCESSING status
23
- COMPLETED = "COMPLETED"
24
- CANCELLED = "CANCELLED"
25
-
26
- # CustomerType 和 PaymentCategory 改為字串類型,支援動態值
27
- # PaymentMethod 改為支援動態字串,如 "月結30天", "下收" 等
28
-
29
- # Base and Read schemas for Category
30
- class CategoryBase(BaseModel):
31
- name: str
32
-
33
- class CategoryCreate(CategoryBase):
34
- pass
35
-
36
- class Category(CategoryBase): # For Read operations
37
- id: int
38
-
39
- class Config:
40
- from_attributes = True
41
-
42
- # Base and Read schemas for Product - 根據客戶 Excel 欄位重新設計
43
- class ProductBase(BaseModel):
44
- productCode: str = Field(..., description="貨品編號,必須唯一")
45
- productName: str = Field(..., description="貨品名稱")
46
- unit: str = Field(..., description="單位(箱、盒等)")
47
- warehouse: Optional[str] = None # 倉別
48
- unitWeight: Optional[float] = None # 單位重量(KG)
49
- barcode: Optional[str] = None # 條碼編號
50
- category_id: int = Field(..., description="類別ID")
51
-
52
- class ProductCreate(ProductBase):
53
- pass
54
-
55
- # For Read operations
56
- class Product(ProductBase):
57
- id: int
58
- stock: int = Field(default=0, description="庫存數量") # 添加庫存欄位
59
- is_deleted: bool = Field(default=False, description="是否已刪除")
60
- deleted_at: Optional[datetime] = None
61
- deleted_by: Optional[int] = None
62
- createdAt: datetime
63
- updatedAt: Optional[datetime] = None
64
- category: Optional[Category] = None # 包含類別詳細資訊
65
-
66
- class Config:
67
- from_attributes = True
68
-
69
- # Schemas for updating a product (all fields optional)
70
- class ProductUpdate(BaseModel):
71
- productCode: Optional[str] = None
72
- productName: Optional[str] = None
73
- unit: Optional[str] = None
74
- warehouse: Optional[str] = None
75
- unitWeight: Optional[float] = None
76
- barcode: Optional[str] = None
77
- category_id: Optional[int] = None
78
- stock: Optional[int] = Field(None, ge=0, description="庫存數量,必須大於等於0") # 添加庫存更新
79
-
80
-
81
- # Base and Read schemas for Supplier
82
- class SupplierBase(BaseModel):
83
- name: str
84
- contactInfo: Optional[str] = None
85
- address: Optional[str] = None
86
-
87
- class SupplierCreate(SupplierBase):
88
- pass
89
-
90
- class Supplier(SupplierBase): # For Read operations
91
- id: int
92
-
93
- class Config:
94
- from_attributes = True
95
-
96
- # Base and Read schemas for Customer
97
- class CustomerBase(BaseModel):
98
- customerType: str
99
- salesPersonId: Optional[str] = None
100
- salesPersonName: Optional[str] = None
101
- customerCode: str = Field(..., description="客戶編號,必須唯一")
102
- customerName: str = Field(..., description="客戶名稱")
103
- contactPerson: Optional[str] = None
104
- invoiceTitle: Optional[str] = None
105
- taxId: Optional[str] = None
106
- phoneNumber: Optional[str] = None
107
- faxNumber: Optional[str] = None
108
- deliveryAddress: Optional[str] = None
109
- businessHours: Optional[str] = None
110
- paymentMethod: Optional[str] = None
111
- paymentCategory: Optional[str] = None
112
- creditLimit: Optional[float] = 0.0
113
-
114
- class CustomerCreate(CustomerBase):
115
- pass
116
-
117
- class CustomerUpdate(BaseModel):
118
- customerType: Optional[str] = None
119
- salesPersonId: Optional[str] = None
120
- salesPersonName: Optional[str] = None
121
- customerCode: Optional[str] = None
122
- customerName: Optional[str] = None
123
- contactPerson: Optional[str] = None
124
- invoiceTitle: Optional[str] = None
125
- taxId: Optional[str] = None
126
- phoneNumber: Optional[str] = None
127
- faxNumber: Optional[str] = None
128
- deliveryAddress: Optional[str] = None
129
- businessHours: Optional[str] = None
130
- paymentMethod: Optional[str] = None
131
- paymentCategory: Optional[str] = None
132
- creditLimit: Optional[float] = None
133
-
134
- class Customer(CustomerBase): # For Read operations
135
- id: int
136
- createdDate: datetime
137
- updatedAt: Optional[datetime] = None
138
-
139
- class Config:
140
- from_attributes = True
141
-
142
- # Base and Read schemas for User
143
- class UserBase(BaseModel):
144
- name: Optional[str] = None
145
- email: EmailStr
146
- phoneNumber: Optional[str] = None
147
- role: Optional[UserRole] = UserRole.USER
148
-
149
- class UserCreate(UserBase):
150
- password: str = Field(..., min_length=4) # Example: make password required on create
151
-
152
- class UserUpdate(BaseModel): # For updating user profile
153
- name: Optional[str] = None
154
- email: Optional[EmailStr] = None
155
- phoneNumber: Optional[str] = None
156
- role: Optional[UserRole] = None
157
- # Role and password updates might be handled by separate, more secure endpoints or admin functions
158
-
159
- class User(UserBase): # For Read operations (e.g., /users/current)
160
- id: int
161
- createdAt: datetime
162
- # Do not include password in responses
163
-
164
- class Config:
165
- from_attributes = True
166
-
167
-
168
- # Schemas for TransactionProductAssociation (enhanced with pricing information)
169
- class TransactionProductAssociationBase(BaseModel):
170
- product_id: int
171
- quantity: Optional[int] = 1
172
- unit_price: Optional[float] = None
173
- line_total: Optional[float] = None
174
- notes: Optional[str] = None
175
-
176
- class TransactionProductAssociationCreate(TransactionProductAssociationBase):
177
- pass
178
-
179
- class TransactionProductAssociation(TransactionProductAssociationBase): # For Read
180
- # Include product details for complete information
181
- product: Optional[Product] = None
182
-
183
- class Config:
184
- from_attributes = True
185
-
186
-
187
- # Base and Read schemas for Transaction
188
- class TransactionBase(BaseModel):
189
- totalProducts: int
190
- totalPrice: float
191
- transactionType: TransactionType
192
- transactionStatus: Optional[TransactionStatus] = TransactionStatus.PENDING
193
- description: Optional[str] = None
194
- note: Optional[str] = None
195
- user_id: Optional[int] = None # Assuming user_id is set based on authenticated user
196
- supplier_id: Optional[int] = None
197
- customer_id: Optional[int] = None # For sell transactions
198
- # For creating transactions, the frontend might send a list of products involved
199
- # This needs to align with how api.service.ts sends data for purchase/sell
200
- # For example: products_involved: List[TransactionProductAssociationCreate]
201
-
202
- class TransactionCreate(TransactionBase):
203
- # The frontend's purchaseProduct/sellProduct takes a 'body'. We need to match that structure.
204
- # If 'body' contains a list of product IDs and quantities:
205
- products_involved: List[TransactionProductAssociationCreate]
206
-
207
-
208
- class Transaction(TransactionBase): # For Read operations
209
- id: int
210
- createdAt: datetime
211
- updatedAt: Optional[datetime] = None
212
- user: Optional[User] = None # Nested user details
213
- supplier: Optional[Supplier] = None # Nested supplier details
214
- customer: Optional[Customer] = None # Nested customer details for sell transactions
215
- products: List[TransactionProductAssociation] # List of products involved in the transaction
216
-
217
- class Config:
218
- from_attributes = True
219
-
220
- # Schema for updating transaction status (as per frontend api.service.ts)
221
- class TransactionStatusUpdate(BaseModel):
222
- status: TransactionStatus # Frontend sends JSON.stringify(status) - need to ensure this matches
223
-
224
- # Schemas for Authentication
225
- class Token(BaseModel):
226
- access_token: str
227
- token_type: str
228
-
229
- class TokenData(BaseModel):
230
- email: Optional[str] = None
231
-
232
- class UserLogin(BaseModel):
233
- email: EmailStr
234
- password: str
235
-
236
-
237
- # 採購單相關的枚舉類型
238
- class PurchaseOrderStatus(str, enum.Enum):
239
- DRAFT = "DRAFT" # 草稿
240
- PENDING = "PENDING" # 待處理
241
- CONFIRMED = "CONFIRMED" # 已確認
242
- RECEIVED = "RECEIVED" # 已收貨
243
- CANCELLED = "CANCELLED" # 已取消
244
-
245
- class PaymentStatus(str, enum.Enum):
246
- UNPAID = "UNPAID" # 未付款
247
- PARTIAL = "PARTIAL" # 部分付款
248
- PAID = "PAID" # 已付款
249
-
250
- class TaxType(str, enum.Enum):
251
- INCLUSIVE = "INCLUSIVE" # 含稅
252
- EXCLUSIVE = "EXCLUSIVE" # 未稅
253
- ADDITIONAL = "ADDITIONAL" # 外加稅
254
-
255
- # 入庫單相關的枚舉類型
256
- class GoodsReceiptStatus(str, enum.Enum):
257
- DRAFT = "DRAFT" # 草稿
258
- PENDING = "PENDING" # 待處理
259
- COMPLETED = "COMPLETED" # 已完成
260
- CANCELLED = "CANCELLED" # 已取消
261
-
262
- class WarehouseType(str, enum.Enum):
263
- MAIN = "MAIN" # 主倉
264
- RAW_MATERIAL = "RAW_MATERIAL" # 原料倉
265
- FINISHED_GOODS = "FINISHED_GOODS" # 成品倉
266
- QUARANTINE = "QUARANTINE" # 檢疫倉
267
- DAMAGED = "DAMAGED" # 損壞品倉
268
-
269
- # 銷售單相關的枚舉類型
270
- class SalesOrderStatus(str, enum.Enum):
271
- DRAFT = "DRAFT" # 草稿
272
- CONFIRMED = "CONFIRMED" # 已確認
273
- SHIPPED = "SHIPPED" # 已出貨
274
- DELIVERED = "DELIVERED" # 已送達
275
- CANCELLED = "CANCELLED" # 已取消
276
-
277
- class PaymentTerm(str, enum.Enum):
278
- CASH = "CASH" # 現金
279
- MONTHLY = "MONTHLY" # 月結
280
- TRANSFER = "TRANSFER" # 轉帳
281
- CREDIT_CARD = "CREDIT_CARD" # 信用卡
282
- CHECK = "CHECK" # 支票
283
-
284
-
285
- # 採購明細項目 schemas
286
- class PurchaseOrderItemBase(BaseModel):
287
- product_id: int = Field(..., description="產品ID")
288
- quantity: int = Field(..., gt=0, description="數量,必須大於0")
289
- unit_price: float = Field(..., ge=0, description="單價,必須大於等於0")
290
- notes: Optional[str] = None
291
-
292
- class PurchaseOrderItemCreate(PurchaseOrderItemBase):
293
- pass
294
-
295
- class PurchaseOrderItemUpdate(BaseModel):
296
- product_id: Optional[int] = None
297
- quantity: Optional[int] = Field(None, gt=0)
298
- unit_price: Optional[float] = Field(None, ge=0)
299
- notes: Optional[str] = None
300
-
301
- class PurchaseOrderItem(PurchaseOrderItemBase):
302
- id: int
303
- line_total: float # 小計 (數量 × 單價)
304
- product: Optional[Product] = None # 包含產品詳細資訊
305
- created_at: datetime
306
- updated_at: Optional[datetime] = None
307
-
308
- class Config:
309
- from_attributes = True
310
-
311
-
312
- # 採購單主檔 schemas
313
- class PurchaseOrderBase(BaseModel):
314
- purchase_date: date = Field(..., description="採購日期")
315
- expected_delivery_date: Optional[date] = None
316
- supplier_id: int = Field(..., description="供應商ID")
317
- notes: Optional[str] = None
318
- tax_type: TaxType = TaxType.INCLUSIVE
319
- tax_rate: float = Field(0.05, ge=0, le=1, description="稅率,0-1之間")
320
- payment_method: Optional[str] = None
321
-
322
- class PurchaseOrderCreate(PurchaseOrderBase):
323
- items: List[PurchaseOrderItemCreate] = Field(..., min_items=1, description="採購明細,至少要有一項")
324
-
325
- class PurchaseOrderUpdate(BaseModel):
326
- purchase_date: Optional[date] = None
327
- expected_delivery_date: Optional[date] = None
328
- supplier_id: Optional[int] = None
329
- status: Optional[PurchaseOrderStatus] = None
330
- notes: Optional[str] = None
331
- tax_type: Optional[TaxType] = None
332
- tax_rate: Optional[float] = Field(None, ge=0, le=1)
333
- payment_method: Optional[str] = None
334
- payment_status: Optional[PaymentStatus] = None
335
-
336
- class PurchaseOrder(PurchaseOrderBase):
337
- id: int
338
- po_number: str # 採購單號
339
- purchaser_id: int
340
- status: PurchaseOrderStatus
341
- subtotal: float # 小計
342
- tax_amount: float # 稅額
343
- total_amount: float # 含稅總額
344
- payment_status: PaymentStatus
345
- created_at: datetime
346
- updated_at: Optional[datetime] = None
347
-
348
- # 關聯資料
349
- purchaser: Optional[User] = None
350
- supplier: Optional[Supplier] = None
351
- items: List[PurchaseOrderItem] = []
352
-
353
- class Config:
354
- from_attributes = True
355
-
356
-
357
- # 入庫明細項目 schemas
358
- class GoodsReceiptItemBase(BaseModel):
359
- purchase_order_item_id: int = Field(..., description="採購明細ID")
360
- product_id: int = Field(..., description="產品ID")
361
- ordered_quantity: int = Field(..., gt=0, description="採購數量")
362
- received_quantity: int = Field(..., ge=0, description="實到數量,必須大於等於0")
363
- storage_location: Optional[str] = None
364
- notes: Optional[str] = None
365
-
366
- class GoodsReceiptItemCreate(GoodsReceiptItemBase):
367
- pass
368
-
369
- class GoodsReceiptItemUpdate(BaseModel):
370
- purchase_order_item_id: Optional[int] = None # 需要用來識別明細項目
371
- product_id: Optional[int] = None # 需要用來識別產品
372
- received_quantity: Optional[int] = Field(None, ge=0)
373
- storage_location: Optional[str] = None
374
- notes: Optional[str] = None
375
-
376
- class GoodsReceiptItem(GoodsReceiptItemBase):
377
- id: int
378
- product: Optional[Product] = None # 包含產品詳細資訊
379
- purchase_order_item: Optional[PurchaseOrderItem] = None # 包含採購明細資訊
380
- created_at: datetime
381
- updated_at: Optional[datetime] = None
382
-
383
- class Config:
384
- from_attributes = True
385
-
386
-
387
- # 入庫單主檔 schemas
388
- class GoodsReceiptBase(BaseModel):
389
- receipt_date: date = Field(..., description="入庫日期")
390
- purchase_order_id: int = Field(..., description="採購單ID")
391
- warehouse_type: WarehouseType = WarehouseType.MAIN
392
- warehouse_location: Optional[str] = None
393
- notes: Optional[str] = None
394
-
395
- class GoodsReceiptCreate(GoodsReceiptBase):
396
- items: List[GoodsReceiptItemCreate] = Field(..., min_items=1, description="入庫明細,至少要有一項")
397
-
398
- class GoodsReceiptUpdate(BaseModel):
399
- receipt_date: Optional[date] = None
400
- warehouse_type: Optional[WarehouseType] = None
401
- warehouse_location: Optional[str] = None
402
- status: Optional[GoodsReceiptStatus] = None
403
- notes: Optional[str] = None
404
- items: Optional[List[GoodsReceiptItemUpdate]] = None # 新增入庫明細更新
405
-
406
- class GoodsReceipt(GoodsReceiptBase):
407
- id: int
408
- gr_number: str # 入庫單號
409
- warehouse_staff_id: int
410
- status: GoodsReceiptStatus
411
- created_at: datetime
412
- updated_at: Optional[datetime] = None
413
-
414
- # 關聯資料
415
- warehouse_staff: Optional[User] = None
416
- purchase_order: Optional[PurchaseOrder] = None
417
- items: List[GoodsReceiptItem] = []
418
-
419
- class Config:
420
- from_attributes = True
421
-
422
-
423
- # 銷售明細項目 schemas
424
- class SalesOrderItemBase(BaseModel):
425
- product_id: int = Field(..., description="產品ID")
426
- quantity: int = Field(..., gt=0, description="數量,必須大於0")
427
- unit_price: float = Field(..., ge=0, description="單價,必須大於等於0")
428
- notes: Optional[str] = None
429
-
430
- class SalesOrderItemCreate(SalesOrderItemBase):
431
- pass
432
-
433
- class SalesOrderItemUpdate(BaseModel):
434
- product_id: Optional[int] = None
435
- quantity: Optional[int] = Field(None, gt=0)
436
- unit_price: Optional[float] = Field(None, ge=0)
437
- notes: Optional[str] = None
438
-
439
- class SalesOrderItem(SalesOrderItemBase):
440
- id: int
441
- line_total: float # 小計 (數量 × 單價)
442
- product: Optional[Product] = None # 包含產品詳細資訊
443
- created_at: datetime
444
- updated_at: Optional[datetime] = None
445
-
446
- class Config:
447
- from_attributes = True
448
-
449
-
450
- # 銷售單主檔 schemas
451
- class SalesOrderBase(BaseModel):
452
- sales_date: date = Field(..., description="銷售日期")
453
- customer_id: int = Field(..., description="客戶ID")
454
- payment_term: PaymentTerm = PaymentTerm.CASH
455
- notes: Optional[str] = None
456
- tax_type: TaxType = TaxType.INCLUSIVE
457
- tax_rate: float = Field(0.05, ge=0, le=1, description="稅率,0-1之間")
458
- discount_rate: float = Field(0.0, ge=0, le=1, description="折扣率,0-1之間")
459
-
460
- class SalesOrderCreate(SalesOrderBase):
461
- items: List[SalesOrderItemCreate] = Field(..., min_items=1, description="銷售明細,至少要有一項")
462
-
463
- class SalesOrderUpdate(BaseModel):
464
- sales_date: Optional[date] = None
465
- customer_id: Optional[int] = None
466
- payment_term: Optional[PaymentTerm] = None
467
- status: Optional[SalesOrderStatus] = None
468
- notes: Optional[str] = None
469
- tax_type: Optional[TaxType] = None
470
- tax_rate: Optional[float] = Field(None, ge=0, le=1)
471
- discount_rate: Optional[float] = Field(None, ge=0, le=1)
472
- items: Optional[List[SalesOrderItemUpdate]] = None # 銷售明細更新
473
-
474
- class SalesOrder(SalesOrderBase):
475
- id: int
476
- so_number: str # 銷售單號
477
- salesperson_id: int
478
- status: SalesOrderStatus
479
- subtotal: float # 小計
480
- tax_amount: float # 稅額
481
- discount_amount: float # 折扣金額
482
- total_amount: float # 實收總額
483
- created_at: datetime
484
- updated_at: Optional[datetime] = None
485
-
486
- # 關聯資料
487
- salesperson: Optional[User] = None
488
- customer: Optional[Customer] = None
489
- items: List[SalesOrderItem] = []
490
-
491
- class Config:
492
- from_attributes = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py DELETED
@@ -1,7 +0,0 @@
1
- from fastapi import FastAPI
2
-
3
- app = FastAPI()
4
-
5
- @app.get("/")
6
- def greet_json():
7
- return {"Hello": "World!"}
 
 
 
 
 
 
 
 
backend/main.py CHANGED
@@ -147,17 +147,17 @@ async def smart_route(request: dict):
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 {
@@ -166,6 +166,41 @@ async def smart_route(request: dict):
166
  "text": "路由服務暫時無法使用"
167
  }
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  @app.get("/stats")
170
  async def get_stats():
171
  """取得路由統計"""
 
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 {
 
166
  "text": "路由服務暫時無法使用"
167
  }
168
 
169
+ @app.post("/product-query")
170
+ async def product_query(request: dict):
171
+ """專門的商品查詢 API - 使用 Pydantic AI"""
172
+ try:
173
+ message = request.get("message", "")
174
+ user_id = request.get("user_id", "api_user")
175
+
176
+ if not message:
177
+ return {
178
+ "success": False,
179
+ "error": "查詢內容不能為空"
180
+ }
181
+
182
+ # 直接使用商品查詢服務
183
+ from backend.services.pydantic_ai_service import ProductQueryService
184
+ product_service = ProductQueryService()
185
+
186
+ if not product_service.is_available():
187
+ return {
188
+ "success": False,
189
+ "error": "商品查詢服務不可用,請檢查 GROQ_API_KEY 設定",
190
+ "text": "商品查詢服務暫時無法使用"
191
+ }
192
+
193
+ result = product_service.process_product_query_sync(message, user_id)
194
+ return result
195
+
196
+ except Exception as e:
197
+ logger.error(f"商品查詢 API 錯誤: {str(e)}")
198
+ return {
199
+ "success": False,
200
+ "error": str(e),
201
+ "text": "商品查詢服務暫時無法使用"
202
+ }
203
+
204
  @app.get("/stats")
205
  async def get_stats():
206
  """取得路由統計"""
backend/services/enhanced_product_service.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 增強的商品查詢服務 - 移植自 inventory_proj_routers
3
+ 提供更精確的商品搜尋和庫存查詢功能
4
+ """
5
+
6
+ import logging
7
+ from typing import List, Optional, Dict, Any
8
+ from sqlalchemy.orm import Session, joinedload
9
+ from sqlalchemy import or_, and_, func
10
+ from backend.database.connection import get_database_session, close_database_session
11
+ from backend.database.models import Product, Category, PurchaseOrder, SalesOrder
12
+ from backend.models.schemas import DatabaseResult
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class EnhancedProductService:
17
+ """增強的商品查詢服務"""
18
+
19
+ def __init__(self):
20
+ pass
21
+
22
+ def search_products_advanced(
23
+ self,
24
+ query_text: str = None,
25
+ category_name: str = None,
26
+ warehouse: str = None,
27
+ include_stock_info: bool = True,
28
+ min_stock: int = None,
29
+ max_stock: int = None,
30
+ limit: int = 20
31
+ ) -> DatabaseResult:
32
+ """
33
+ 進階商品搜尋功能
34
+
35
+ Args:
36
+ query_text: 搜尋關鍵字(商品名稱、編號、條碼)
37
+ category_name: 分類名稱
38
+ warehouse: 倉庫名稱
39
+ include_stock_info: 是否包含庫存資訊
40
+ min_stock: 最小庫存量
41
+ max_stock: 最大庫存量
42
+ limit: 查詢限制數量
43
+ """
44
+ db = None
45
+ try:
46
+ db = get_database_session()
47
+
48
+ # 建立基本查詢,預載入分類資訊
49
+ query = db.query(Product).options(
50
+ joinedload(Product.category)
51
+ ).filter(Product.is_deleted == False)
52
+
53
+ # 關鍵字搜尋 - 支援商品名稱、編號、條碼
54
+ if query_text:
55
+ search_terms = query_text.strip().split()
56
+ for term in search_terms:
57
+ search_filter = or_(
58
+ Product.productName.ilike(f"%{term}%"),
59
+ Product.productCode.ilike(f"%{term}%"),
60
+ Product.barcode.ilike(f"%{term}%")
61
+ )
62
+ query = query.filter(search_filter)
63
+
64
+ # 分類篩選
65
+ if category_name:
66
+ query = query.join(Category).filter(
67
+ Category.name.ilike(f"%{category_name}%")
68
+ )
69
+
70
+ # 倉庫篩選
71
+ if warehouse:
72
+ query = query.filter(Product.warehouse.ilike(f"%{warehouse}%"))
73
+
74
+ # 庫存範圍篩選
75
+ if min_stock is not None:
76
+ query = query.filter(Product.stock >= min_stock)
77
+ if max_stock is not None:
78
+ query = query.filter(Product.stock <= max_stock)
79
+
80
+ # 執行查詢
81
+ products = query.limit(limit).all()
82
+
83
+ # 轉換為字典格式
84
+ data = []
85
+ for product in products:
86
+ product_data = {
87
+ "id": product.id,
88
+ "product_code": product.productCode,
89
+ "product_name": product.productName,
90
+ "unit": product.unit,
91
+ "warehouse": product.warehouse,
92
+ "unit_weight": product.unitWeight,
93
+ "barcode": product.barcode,
94
+ "category_id": product.category_id,
95
+ "category_name": product.category.name if product.category else None,
96
+ "created_at": product.createdAt.isoformat() if product.createdAt else None,
97
+ "updated_at": product.updatedAt.isoformat() if product.updatedAt else None
98
+ }
99
+
100
+ # 包含庫存資訊
101
+ if include_stock_info:
102
+ product_data.update({
103
+ "current_stock": product.stock,
104
+ "stock_status": self._get_stock_status(product.stock),
105
+ "is_low_stock": product.stock <= 10
106
+ })
107
+
108
+ data.append(product_data)
109
+
110
+ return DatabaseResult(
111
+ success=True,
112
+ data=data,
113
+ count=len(data)
114
+ )
115
+
116
+ except Exception as e:
117
+ logger.error(f"進階商品搜尋錯誤: {str(e)}")
118
+ return DatabaseResult(
119
+ success=False,
120
+ error=f"商品搜尋失敗: {str(e)}"
121
+ )
122
+ finally:
123
+ if db:
124
+ close_database_session(db)
125
+
126
+ def get_products_by_category(self, category_name: str, limit: int = 20) -> DatabaseResult:
127
+ """根據分類獲取商品"""
128
+ db = None
129
+ try:
130
+ db = get_database_session()
131
+
132
+ products = db.query(Product).join(Category).filter(
133
+ and_(
134
+ Category.name.ilike(f"%{category_name}%"),
135
+ Product.is_deleted == False
136
+ )
137
+ ).options(joinedload(Product.category)).limit(limit).all()
138
+
139
+ data = []
140
+ for product in products:
141
+ data.append({
142
+ "id": product.id,
143
+ "product_code": product.productCode,
144
+ "product_name": product.productName,
145
+ "current_stock": product.stock,
146
+ "unit": product.unit,
147
+ "category_name": product.category.name if product.category else None,
148
+ "warehouse": product.warehouse
149
+ })
150
+
151
+ return DatabaseResult(
152
+ success=True,
153
+ data=data,
154
+ count=len(data)
155
+ )
156
+
157
+ except Exception as e:
158
+ logger.error(f"分類商品查詢錯誤: {str(e)}")
159
+ return DatabaseResult(
160
+ success=False,
161
+ error=f"分類商品查詢失敗: {str(e)}"
162
+ )
163
+ finally:
164
+ if db:
165
+ close_database_session(db)
166
+
167
+ def get_low_stock_products(self, threshold: int = 10) -> DatabaseResult:
168
+ """獲取低庫存商品"""
169
+ db = None
170
+ try:
171
+ db = get_database_session()
172
+
173
+ products = db.query(Product).filter(
174
+ and_(
175
+ Product.stock <= threshold,
176
+ Product.is_deleted == False
177
+ )
178
+ ).options(joinedload(Product.category)).all()
179
+
180
+ data = []
181
+ for product in products:
182
+ data.append({
183
+ "id": product.id,
184
+ "product_code": product.productCode,
185
+ "product_name": product.productName,
186
+ "current_stock": product.stock,
187
+ "threshold": threshold,
188
+ "unit": product.unit,
189
+ "category_name": product.category.name if product.category else None,
190
+ "warehouse": product.warehouse,
191
+ "urgency_level": self._get_urgency_level(product.stock)
192
+ })
193
+
194
+ return DatabaseResult(
195
+ success=True,
196
+ data=data,
197
+ count=len(data)
198
+ )
199
+
200
+ except Exception as e:
201
+ logger.error(f"低庫存查詢錯誤: {str(e)}")
202
+ return DatabaseResult(
203
+ success=False,
204
+ error=f"低庫存查詢失敗: {str(e)}"
205
+ )
206
+ finally:
207
+ if db:
208
+ close_database_session(db)
209
+
210
+ def get_product_recommendations(self, query_text: str, limit: int = 5) -> DatabaseResult:
211
+ """
212
+ 商品推薦功能 - 基於關鍵字的智能推薦
213
+ 特別針對像 "推薦貓砂" 這樣的查詢
214
+ """
215
+ db = None
216
+ try:
217
+ db = get_database_session()
218
+
219
+ # 分析查詢關鍵字
220
+ keywords = self._extract_keywords(query_text)
221
+
222
+ # 建立推薦查詢
223
+ query = db.query(Product).filter(Product.is_deleted == False)
224
+
225
+ # 多關鍵字匹配
226
+ for keyword in keywords:
227
+ search_filter = or_(
228
+ Product.productName.ilike(f"%{keyword}%"),
229
+ Product.productCode.ilike(f"%{keyword}%")
230
+ )
231
+ query = query.filter(search_filter)
232
+
233
+ # 優先顯示有庫存的商品
234
+ query = query.order_by(Product.stock.desc())
235
+
236
+ products = query.options(joinedload(Product.category)).limit(limit).all()
237
+
238
+ data = []
239
+ for product in products:
240
+ data.append({
241
+ "id": product.id,
242
+ "product_code": product.productCode,
243
+ "product_name": product.productName,
244
+ "current_stock": product.stock,
245
+ "unit": product.unit,
246
+ "category_name": product.category.name if product.category else None,
247
+ "warehouse": product.warehouse,
248
+ "recommendation_reason": f"符合關鍵字: {', '.join(keywords)}",
249
+ "availability": "有庫存" if product.stock > 0 else "缺貨"
250
+ })
251
+
252
+ return DatabaseResult(
253
+ success=True,
254
+ data=data,
255
+ count=len(data)
256
+ )
257
+
258
+ except Exception as e:
259
+ logger.error(f"商品推薦錯誤: {str(e)}")
260
+ return DatabaseResult(
261
+ success=False,
262
+ error=f"商品推薦失敗: {str(e)}"
263
+ )
264
+ finally:
265
+ if db:
266
+ close_database_session(db)
267
+
268
+ def _get_stock_status(self, stock: int) -> str:
269
+ """獲取庫存狀態"""
270
+ if stock <= 0:
271
+ return "缺��"
272
+ elif stock <= 5:
273
+ return "庫存極低"
274
+ elif stock <= 10:
275
+ return "庫存偏低"
276
+ elif stock <= 50:
277
+ return "庫存正常"
278
+ else:
279
+ return "庫存充足"
280
+
281
+ def _get_urgency_level(self, stock: int) -> str:
282
+ """獲取緊急程度"""
283
+ if stock <= 0:
284
+ return "緊急"
285
+ elif stock <= 3:
286
+ return "高"
287
+ elif stock <= 10:
288
+ return "中"
289
+ else:
290
+ return "低"
291
+
292
+ def _extract_keywords(self, query_text: str) -> List[str]:
293
+ """從查詢文字中提取關鍵字"""
294
+ # 移除常見的查詢詞彙
295
+ stop_words = ['推薦', '有沒有', '是否有', '請問', '想要', '需要', '找', '查詢', '搜尋']
296
+
297
+ # 分割並清理關鍵字
298
+ words = query_text.replace('?', '').replace('?', '').split()
299
+ keywords = [word for word in words if word not in stop_words and len(word) > 1]
300
+
301
+ return keywords if keywords else [query_text.strip()]
backend/services/message_router.py CHANGED
@@ -6,6 +6,7 @@ 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
 
@@ -15,11 +16,13 @@ class MessageRouter:
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
@@ -177,14 +180,23 @@ class MessageRouter:
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)
@@ -223,7 +235,60 @@ class MessageRouter:
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()
@@ -253,7 +318,7 @@ class MessageRouter:
253
  • /chat [訊息] - 聊天模式
254
  範例:/chat 今天天氣如何?
255
 
256
- • /search [查詢] - 商品查詢模式
257
  範例:/search iPhone 15 Pro
258
  ��例:/search 價格 1000-5000
259
 
@@ -262,16 +327,22 @@ class MessageRouter:
262
  🧠 智能模式:
263
  直接輸入訊息,系統會自動判斷是聊天還是查詢!
264
 
265
- 💡 查詢範例:
 
 
 
266
  • "iPhone 還有庫存嗎?"
267
  • "1000到5000的商品有哪些?"
268
- • "我的訂單狀態如何?"
269
 
270
  💬 聊天範例:
271
  • "你好!"
272
  • "今天天氣如何?"
273
  • "推薦一些好用的功能"
274
 
 
 
 
 
275
  輸入「選單」查看功能選單
276
  輸入「統計」查看使用統計"""
277
 
@@ -327,11 +398,13 @@ class MessageRouter:
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",
 
6
  from typing import Dict, Any
7
  from backend.services.groq_service import GroqService
8
  from backend.services.business_query_service import BusinessQueryService
9
+ from backend.services.pydantic_ai_service import ProductQueryService
10
 
11
  logger = logging.getLogger(__name__)
12
 
 
16
  def __init__(self):
17
  self.groq_service = GroqService()
18
  self.business_service = BusinessQueryService()
19
+ self.product_query_service = ProductQueryService()
20
+
21
  # 路由統計
22
  self.route_stats = {
23
  "chat": 0,
24
+ "search": 0,
25
+ "product_query": 0,
26
  "help": 0,
27
  "smart": 0,
28
  "error": 0
 
180
  def _handle_smart_routing(self, message: str, user_id: str) -> Dict[str, Any]:
181
  """智能路由 - 根據內容判斷意圖"""
182
  try:
183
+ # 1. 先檢查是否為商品查詢 (使用 Pydantic AI)
184
+ if self.product_query_service.is_available():
185
+ product_intent = self.product_query_service.analyze_query_intent(message)
186
+
187
+ if product_intent["is_product_query"] and product_intent["confidence"] > 0.6:
188
+ logger.info(f"智能路由 -> Pydantic AI 商品查詢 (信心度: {product_intent['confidence']})")
189
+ self.route_stats["product_query"] += 1
190
+ return self._handle_product_query_mode(message, user_id)
191
+
192
+ # 2. 進行簡單的關鍵字預篩選
193
  quick_intent = self._quick_intent_check(message)
194
+
195
  if quick_intent == "search":
196
  # 高信心度的搜尋關鍵字 -> 直接轉到搜尋模式
197
  logger.info(f"快速意圖識別: 搜尋模式 - {message[:30]}...")
198
  return self._handle_search_mode(message, user_id)
199
+
200
  elif quick_intent == "help":
201
  # 幫助相關 -> 轉到幫助模式
202
  return self._handle_help_mode(user_id)
 
235
  logger.error(f"智能路由處理錯誤: {str(e)}")
236
  # 錯誤時預設為聊天模式
237
  return self._handle_chat_mode(message, user_id)
238
+
239
+ def _handle_product_query_mode(self, message: str, user_id: str) -> Dict[str, Any]:
240
+ """處理 Pydantic AI 商品查詢模式"""
241
+ try:
242
+ if not message:
243
+ return {
244
+ "type": "text",
245
+ "text": "🛍️ 請告訴我您想查詢什麼商品!\n\n範例:\n• 是否有推薦貓砂?\n• 查詢狗糧庫存\n• 有什麼寵物用品?",
246
+ "mode": "product_query",
247
+ "success": True
248
+ }
249
+
250
+ if not self.product_query_service.is_available():
251
+ return {
252
+ "type": "text",
253
+ "text": "🛍️ 商品查詢服務暫時無法使用,請稍後再試。\n\n💡 您可以嘗試使用 /search 進行基本查詢。",
254
+ "mode": "product_query",
255
+ "success": False,
256
+ "error": "Pydantic AI 服務不可用"
257
+ }
258
+
259
+ # 使用同步版本避免異步複雜性
260
+ result = self.product_query_service.process_product_query_sync(message, user_id)
261
+
262
+ if result["success"]:
263
+ return {
264
+ "type": "text",
265
+ "text": f"🛍️ {result['text']}",
266
+ "mode": "product_query",
267
+ "success": True,
268
+ "products_found": result.get("products_found", 0),
269
+ "has_recommendations": result.get("has_recommendations", False),
270
+ "user_id": user_id
271
+ }
272
+ else:
273
+ return {
274
+ "type": "text",
275
+ "text": f"🛍️ {result.get('text', '商品查詢失敗')}",
276
+ "mode": "product_query",
277
+ "success": False,
278
+ "error": result.get("error"),
279
+ "user_id": user_id
280
+ }
281
+
282
+ except Exception as e:
283
+ logger.error(f"商品查詢模式處理錯誤: {str(e)}")
284
+ return {
285
+ "type": "text",
286
+ "text": "🛍️ 商品查詢服務暫時無法使用,請稍後再試。",
287
+ "mode": "product_query",
288
+ "success": False,
289
+ "error": str(e)
290
+ }
291
+
292
  def _quick_intent_check(self, message: str) -> str:
293
  """快速意圖檢查 - 基於關鍵字"""
294
  message_lower = message.lower()
 
318
  • /chat [訊息] - 聊天模式
319
  範例:/chat 今天天氣如何?
320
 
321
+ • /search [查詢] - 商品查詢模式
322
  範例:/search iPhone 15 Pro
323
  ��例:/search 價格 1000-5000
324
 
 
327
  🧠 智能模式:
328
  直接輸入訊息,系統會自動判斷是聊天還是查詢!
329
 
330
+ 🛍️ 商品查詢範例(AI 智能識別):
331
+ • "是否有推薦貓砂?"
332
+ • "有什麼狗糧推薦?"
333
+ • "查詢寵物用品庫存"
334
  • "iPhone 還有庫存嗎?"
335
  • "1000到5000的商品有哪些?"
 
336
 
337
  💬 聊天範例:
338
  • "你好!"
339
  • "今天天氣如何?"
340
  • "推薦一些好用的功能"
341
 
342
+ 📋 其他查詢:
343
+ • "我的訂單狀態如何?"
344
+ • "低庫存商品有哪些?"
345
+
346
  輸入「選單」查看功能選單
347
  輸入「統計」查看使用統計"""
348
 
 
398
 
399
  💬 聊天模式: {self.route_stats['chat']} ({self.route_stats['chat']/total_routes*100:.1f}%)
400
  🔍 搜尋模式: {self.route_stats['search']} ({self.route_stats['search']/total_routes*100:.1f}%)
401
+ 🛍️ 商品查詢: {self.route_stats['product_query']} ({self.route_stats['product_query']/total_routes*100:.1f}%)
402
  🧠 智能路由: {self.route_stats['smart']} ({self.route_stats['smart']/total_routes*100:.1f}%)
403
  ❓ 幫助模式: {self.route_stats['help']} ({self.route_stats['help']/total_routes*100:.1f}%)
404
  ❌ 錯誤次數: {self.route_stats['error']} ({self.route_stats['error']/total_routes*100:.1f}%)
405
 
406
+ 🤖 Groq 服務: {'✅ 可用' if self.groq_service.is_available() else '❌ 不可用'}
407
+ 🛍️ Pydantic AI: {'✅ 可用' if self.product_query_service.is_available() else '❌ 不可用'}"""
408
 
409
  return {
410
  "type": "text",
backend/services/pydantic_ai_service.py ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pydantic AI 服務 - 使用 Pydantic AI 框架整合 Groq 和商品查詢功能
3
+ 解決商品查詢準確性問題,特別是像 "是否有推薦貓砂?" 這類查詢
4
+ """
5
+
6
+ import logging
7
+ from typing import Dict, Any, List, Optional
8
+ from dataclasses import dataclass
9
+ from pydantic import BaseModel, Field
10
+ from pydantic_ai import Agent, RunContext
11
+
12
+ from backend.services.enhanced_product_service import EnhancedProductService
13
+ from backend.services.database_service import DatabaseService
14
+ from backend.config import settings
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # 依賴注入類型
19
+ @dataclass
20
+ class ProductQueryDependencies:
21
+ """商品查詢依賴"""
22
+ enhanced_product_service: EnhancedProductService
23
+ database_service: DatabaseService
24
+ user_id: Optional[str] = None
25
+
26
+ # 輸出模型
27
+ class ProductQueryResult(BaseModel):
28
+ """商品查詢結果"""
29
+ intent: str = Field(description="查詢意圖")
30
+ response_text: str = Field(description="回應文字")
31
+ products_found: int = Field(description="找到的商品數量")
32
+ has_recommendations: bool = Field(description="是否包含推薦")
33
+ stock_info_included: bool = Field(description="是否包含庫存資訊")
34
+ search_keywords: List[str] = Field(default_factory=list, description="搜尋關鍵字")
35
+
36
+ class ProductQueryService:
37
+ """Pydantic AI 商品查詢服務"""
38
+
39
+ def __init__(self):
40
+ self.enhanced_product_service = EnhancedProductService()
41
+ self.database_service = DatabaseService()
42
+
43
+ # 創建 Pydantic AI Agent
44
+ self.product_agent = Agent(
45
+ f'groq:{settings.GROQ_MODEL}',
46
+ deps_type=ProductQueryDependencies,
47
+ output_type=ProductQueryResult,
48
+ system_prompt=self._get_system_prompt()
49
+ )
50
+
51
+ # 註冊工具
52
+ self._register_tools()
53
+
54
+ def _get_system_prompt(self) -> str:
55
+ """系統提示詞"""
56
+ return """你是一個專業的商品查詢助手,專門協助用戶查詢商品資訊。
57
+
58
+ 你的主要任務:
59
+ 1. 理解用戶的商品查詢意圖,包括推薦、搜尋、庫存查詢等
60
+ 2. 使用適當的工具查詢商品資料庫
61
+ 3. 提供準確、有用的商品資訊回應
62
+
63
+ 特別注意:
64
+ - 當用戶詢問"推薦"、"有沒有"、"是否有"時,要積極查詢相關商品
65
+ - 優先顯示有庫存的商品
66
+ - 提供具體的商品名稱、庫存狀況和分類資訊
67
+ - 如果沒有找到完全匹配的商品,嘗試提供相似或相關的商品
68
+
69
+ 回應要求:
70
+ - 使用繁體中文
71
+ - 語氣友善專業
72
+ - 資訊準確完整
73
+ - 如果有多個商品,按庫存量排序推薦"""
74
+
75
+ def _register_tools(self):
76
+ """註冊 AI Agent 工具"""
77
+
78
+ @self.product_agent.tool
79
+ async def search_products(
80
+ ctx: RunContext[ProductQueryDependencies],
81
+ query_text: str,
82
+ include_recommendations: bool = False
83
+ ) -> Dict[str, Any]:
84
+ """
85
+ 搜尋商品
86
+
87
+ Args:
88
+ query_text: 搜尋關鍵字
89
+ include_recommendations: 是否包含推薦功能
90
+ """
91
+ try:
92
+ if include_recommendations:
93
+ # 使用推薦功能
94
+ result = ctx.deps.enhanced_product_service.get_product_recommendations(
95
+ query_text=query_text,
96
+ limit=10
97
+ )
98
+ else:
99
+ # 使用一般搜尋
100
+ result = ctx.deps.enhanced_product_service.search_products_advanced(
101
+ query_text=query_text,
102
+ include_stock_info=True,
103
+ limit=10
104
+ )
105
+
106
+ return {
107
+ "success": result.success,
108
+ "products": result.data,
109
+ "count": result.count,
110
+ "error": result.error
111
+ }
112
+ except Exception as e:
113
+ logger.error(f"商品搜尋工具錯誤: {str(e)}")
114
+ return {
115
+ "success": False,
116
+ "products": [],
117
+ "count": 0,
118
+ "error": str(e)
119
+ }
120
+
121
+ @self.product_agent.tool
122
+ async def get_products_by_category(
123
+ ctx: RunContext[ProductQueryDependencies],
124
+ category_name: str
125
+ ) -> Dict[str, Any]:
126
+ """
127
+ 根據分類獲取商品
128
+
129
+ Args:
130
+ category_name: 分類名稱
131
+ """
132
+ try:
133
+ result = ctx.deps.enhanced_product_service.get_products_by_category(
134
+ category_name=category_name,
135
+ limit=10
136
+ )
137
+
138
+ return {
139
+ "success": result.success,
140
+ "products": result.data,
141
+ "count": result.count,
142
+ "error": result.error
143
+ }
144
+ except Exception as e:
145
+ logger.error(f"分類查詢工具錯誤: {str(e)}")
146
+ return {
147
+ "success": False,
148
+ "products": [],
149
+ "count": 0,
150
+ "error": str(e)
151
+ }
152
+
153
+ @self.product_agent.tool
154
+ async def check_low_stock_products(
155
+ ctx: RunContext[ProductQueryDependencies],
156
+ threshold: int = 10
157
+ ) -> Dict[str, Any]:
158
+ """
159
+ 檢查低庫存商品
160
+
161
+ Args:
162
+ threshold: 庫存閾值
163
+ """
164
+ try:
165
+ result = ctx.deps.enhanced_product_service.get_low_stock_products(
166
+ threshold=threshold
167
+ )
168
+
169
+ return {
170
+ "success": result.success,
171
+ "products": result.data,
172
+ "count": result.count,
173
+ "error": result.error
174
+ }
175
+ except Exception as e:
176
+ logger.error(f"低庫存查詢工具錯誤: {str(e)}")
177
+ return {
178
+ "success": False,
179
+ "products": [],
180
+ "count": 0,
181
+ "error": str(e)
182
+ }
183
+
184
+ def process_product_query_sync(self, user_message: str, user_id: str = None) -> Dict[str, Any]:
185
+ """
186
+ 同步處理商品查詢 - 為了避免異步複雜性
187
+
188
+ Args:
189
+ user_message: 用戶訊息
190
+ user_id: 用戶ID
191
+
192
+ Returns:
193
+ 查詢結果字典
194
+ """
195
+ try:
196
+ # 分析查詢意圖
197
+ intent_analysis = self.analyze_query_intent(user_message)
198
+
199
+ if not intent_analysis["is_product_query"]:
200
+ return {
201
+ "success": False,
202
+ "text": "這似乎不是商品查詢,請嘗試其他功能。",
203
+ "mode": "product_query",
204
+ "error": "非商品查詢意圖"
205
+ }
206
+
207
+ # 根據意圖類型選擇查詢方法
208
+ if intent_analysis["is_recommendation"]:
209
+ # 推薦查詢
210
+ result = self.enhanced_product_service.get_product_recommendations(
211
+ query_text=user_message,
212
+ limit=5
213
+ )
214
+ else:
215
+ # 一般搜尋
216
+ result = self.enhanced_product_service.search_products_advanced(
217
+ query_text=user_message,
218
+ include_stock_info=True,
219
+ limit=10
220
+ )
221
+
222
+ # 格式化回應
223
+ if result.success and result.data:
224
+ response_text = self._format_product_response(result.data, intent_analysis)
225
+
226
+ return {
227
+ "success": True,
228
+ "intent": "product_query",
229
+ "text": response_text,
230
+ "products_found": result.count,
231
+ "has_recommendations": intent_analysis["is_recommendation"],
232
+ "stock_info_included": True,
233
+ "search_keywords": self._extract_keywords_from_message(user_message),
234
+ "mode": "product_query",
235
+ "user_id": user_id
236
+ }
237
+ else:
238
+ return {
239
+ "success": False,
240
+ "text": f"抱歉,沒有找到相關商品。{result.error if result.error else ''}",
241
+ "mode": "product_query",
242
+ "error": result.error,
243
+ "user_id": user_id
244
+ }
245
+
246
+ except Exception as e:
247
+ logger.error(f"同步商品查詢錯誤: {str(e)}")
248
+ return {
249
+ "success": False,
250
+ "text": f"抱歉,商品查詢時發生錯誤:{str(e)}",
251
+ "mode": "product_query",
252
+ "error": str(e),
253
+ "user_id": user_id
254
+ }
255
+
256
+ async def process_product_query(self, user_message: str, user_id: str = None) -> Dict[str, Any]:
257
+ """
258
+ 處理商品查詢
259
+
260
+ Args:
261
+ user_message: 用戶訊息
262
+ user_id: 用戶ID
263
+
264
+ Returns:
265
+ 查詢結果字典
266
+ """
267
+ try:
268
+ # 準備依賴
269
+ deps = ProductQueryDependencies(
270
+ enhanced_product_service=self.enhanced_product_service,
271
+ database_service=self.database_service,
272
+ user_id=user_id
273
+ )
274
+
275
+ # 執行 AI Agent
276
+ result = await self.product_agent.run(user_message, deps=deps)
277
+
278
+ # 記錄查詢
279
+ if user_id:
280
+ try:
281
+ self.database_service.save_message(user_id, user_message, "product_query")
282
+ except Exception as e:
283
+ logger.warning(f"記錄查詢失敗: {str(e)}")
284
+
285
+ return {
286
+ "success": True,
287
+ "intent": result.output.intent,
288
+ "text": result.output.response_text,
289
+ "products_found": result.output.products_found,
290
+ "has_recommendations": result.output.has_recommendations,
291
+ "stock_info_included": result.output.stock_info_included,
292
+ "search_keywords": result.output.search_keywords,
293
+ "mode": "product_query",
294
+ "user_id": user_id
295
+ }
296
+
297
+ except Exception as e:
298
+ logger.error(f"Pydantic AI 商品查詢錯誤: {str(e)}")
299
+ return {
300
+ "success": False,
301
+ "text": f"抱歉,商品查詢時發生錯誤:{str(e)}",
302
+ "mode": "product_query",
303
+ "error": str(e),
304
+ "user_id": user_id
305
+ }
306
+
307
+ def is_available(self) -> bool:
308
+ """檢查服務是否可用"""
309
+ return bool(settings.GROQ_API_KEY)
310
+
311
+ def analyze_query_intent(self, message: str) -> Dict[str, Any]:
312
+ """
313
+ 分析查詢意圖 - 判斷是否為商品查詢
314
+
315
+ Args:
316
+ message: 用戶訊息
317
+
318
+ Returns:
319
+ 意圖分析結果
320
+ """
321
+ message_lower = message.lower()
322
+
323
+ # 商品查詢關鍵字
324
+ product_keywords = [
325
+ '推薦', '有沒有', '是否有', '商品', '產品', '貨品',
326
+ '查詢', '搜尋', '找', '庫存', '存貨', '價格',
327
+ '貓砂', '狗糧', '寵物', '食品', '用品' # 常見商品類別
328
+ ]
329
+
330
+ # 推薦查詢關鍵字
331
+ recommendation_keywords = ['推薦', '建議', '介紹', '有什麼', '哪些']
332
+
333
+ # 庫存查詢關鍵字
334
+ inventory_keywords = ['庫存', '存貨', '剩餘', '還有', '現貨']
335
+
336
+ is_product_query = any(keyword in message_lower for keyword in product_keywords)
337
+ is_recommendation = any(keyword in message_lower for keyword in recommendation_keywords)
338
+ is_inventory_check = any(keyword in message_lower for keyword in inventory_keywords)
339
+
340
+ confidence = 0.5
341
+ if is_product_query:
342
+ confidence += 0.3
343
+ if is_recommendation:
344
+ confidence += 0.2
345
+ if is_inventory_check:
346
+ confidence += 0.2
347
+
348
+ return {
349
+ "is_product_query": is_product_query,
350
+ "is_recommendation": is_recommendation,
351
+ "is_inventory_check": is_inventory_check,
352
+ "confidence": min(confidence, 1.0),
353
+ "intent": "product_query" if is_product_query else "unknown"
354
+ }
355
+
356
+ def _format_product_response(self, products: List[Dict[str, Any]], intent_analysis: Dict[str, Any]) -> str:
357
+ """格式化商品查詢回應"""
358
+ if not products:
359
+ return "沒有找到相關商品。"
360
+
361
+ # 根據意圖類型調整回應風格
362
+ if intent_analysis["is_recommendation"]:
363
+ header = f"為您推薦 {len(products)} 個商品:"
364
+ elif intent_analysis["is_inventory_check"]:
365
+ header = f"庫存查詢結果,找到 {len(products)} 個商品:"
366
+ else:
367
+ header = f"商品搜尋結果,找到 {len(products)} 個商品:"
368
+
369
+ response_lines = [header, ""]
370
+
371
+ for i, product in enumerate(products[:5], 1): # 最多顯示5個
372
+ name = product.get('product_name', 'N/A')
373
+ stock = product.get('current_stock', 0)
374
+ category = product.get('category_name', '')
375
+ warehouse = product.get('warehouse', '')
376
+
377
+ # 庫存狀態
378
+ stock_status = product.get('stock_status', self._get_stock_status_text(stock))
379
+
380
+ line = f"{i}. {name}"
381
+ if category:
382
+ line += f" ({category})"
383
+
384
+ line += f"\n 庫存: {stock} - {stock_status}"
385
+
386
+ if warehouse:
387
+ line += f"\n 倉庫: {warehouse}"
388
+
389
+ if product.get('recommendation_reason'):
390
+ line += f"\n 推薦原因: {product['recommendation_reason']}"
391
+
392
+ response_lines.append(line)
393
+
394
+ if len(products) > 5:
395
+ response_lines.append(f"\n... 還有 {len(products) - 5} 個商品")
396
+
397
+ # 添加庫存提醒
398
+ low_stock_count = sum(1 for p in products if p.get('current_stock', 0) <= 10)
399
+ if low_stock_count > 0:
400
+ response_lines.append(f"\n⚠️ 其中 {low_stock_count} 個商品庫存偏低,建議盡快補貨。")
401
+
402
+ return "\n".join(response_lines)
403
+
404
+ def _get_stock_status_text(self, stock: int) -> str:
405
+ """獲取庫存狀態文字"""
406
+ if stock <= 0:
407
+ return "缺貨 ❌"
408
+ elif stock <= 5:
409
+ return "庫存極低 🔴"
410
+ elif stock <= 10:
411
+ return "庫存偏低 🟡"
412
+ elif stock <= 50:
413
+ return "庫存正常 🟢"
414
+ else:
415
+ return "庫存充足 ✅"
416
+
417
+ def _extract_keywords_from_message(self, message: str) -> List[str]:
418
+ """從訊息中提取關鍵字"""
419
+ # 移除常見的查詢詞彙
420
+ stop_words = ['推薦', '有沒有', '是否有', '請問', '想要', '需要', '找', '查詢', '搜尋', '?', '?']
421
+
422
+ # 分割並清理關鍵字
423
+ words = message.replace('?', '').replace('?', '').split()
424
+ keywords = [word for word in words if word not in stop_words and len(word) > 1]
425
+
426
+ return keywords if keywords else [message.strip()]
groq_python_test.py DELETED
@@ -1,67 +0,0 @@
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
@@ -6,6 +6,7 @@ psycopg[binary]==3.1.18
6
  alembic
7
  pydantic
8
  pydantic-settings
 
9
  python-multipart
10
  python-dotenv
11
  requests
 
6
  alembic
7
  pydantic
8
  pydantic-settings
9
+ pydantic-ai[groq]
10
  python-multipart
11
  python-dotenv
12
  requests
setup_guide.md DELETED
@@ -1,178 +0,0 @@
1
- # LINE Bot + FastAPI + Supabase 設定指南
2
-
3
- ## 🚀 快速開始
4
-
5
- ### 1. 必要的 API Keys 和設定
6
-
7
- 您需要準備以下 API Keys 和設定:
8
-
9
- #### LINE Developers Console
10
- 1. 前往 [LINE Developers Console](https://developers.line.biz/)
11
- 2. 建立新的 Provider 和 Messaging API Channel
12
- 3. 取得以下資訊:
13
- - `Channel Access Token` (長期)
14
- - `Channel Secret`
15
-
16
- #### Supabase
17
- 1. 前往 [Supabase](https://supabase.com/)
18
- 2. 建立新專案
19
- 3. 取得以下資訊:
20
- - `Project URL`
21
- - `Anon/Public Key`
22
-
23
- #### OpenRouter (可選)
24
- 1. 前往 [OpenRouter](https://openrouter.ai/)
25
- 2. 註冊帳號並取得 `API Key` (用於進階 NLP 功能)
26
- 3. 選擇適合的模型 (預設: anthropic/claude-3-haiku)
27
-
28
- ### 2. Hugging Face Spaces 環境變數設定
29
-
30
- 在 Hugging Face Spaces 的 Settings 中設定以下環境變數:
31
-
32
- ```
33
- LINE_CHANNEL_ACCESS_TOKEN=你的_LINE_Channel_Access_Token
34
- LINE_CHANNEL_SECRET=你的_LINE_Channel_Secret
35
- SUPABASE_URL=你的_Supabase_專案_URL
36
- SUPABASE_KEY=你的_Supabase_Anon_Key
37
- OPENROUTER_API_KEY=你的_OpenRouter_API_Key (可選)
38
- OPENROUTER_MODEL=anthropic/claude-3-haiku (可選)
39
- DEBUG=False
40
- LOG_LEVEL=INFO
41
- ```
42
-
43
- ### 3. Supabase 資料庫設定
44
-
45
- 在 Supabase Dashboard 的 SQL Editor 中執行以下 SQL:
46
-
47
- ```sql
48
- -- 建立用戶表
49
- CREATE TABLE IF NOT EXISTS users (
50
- user_id VARCHAR(255) PRIMARY KEY,
51
- name VARCHAR(255),
52
- email VARCHAR(255),
53
- display_name VARCHAR(255),
54
- picture_url TEXT,
55
- status_message TEXT,
56
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
57
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
58
- );
59
-
60
- -- 建立商品表
61
- CREATE TABLE IF NOT EXISTS products (
62
- product_id SERIAL PRIMARY KEY,
63
- name VARCHAR(255) NOT NULL,
64
- description TEXT,
65
- price DECIMAL(10,2) NOT NULL,
66
- stock INTEGER DEFAULT 0,
67
- category VARCHAR(100),
68
- image_url TEXT,
69
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
70
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
71
- );
72
-
73
- -- 建立訂單表
74
- CREATE TABLE IF NOT EXISTS orders (
75
- order_id VARCHAR(255) PRIMARY KEY,
76
- user_id VARCHAR(255) REFERENCES users(user_id),
77
- total_amount DECIMAL(10,2) NOT NULL,
78
- status VARCHAR(50) DEFAULT 'pending',
79
- shipping_address TEXT,
80
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
81
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
82
- );
83
-
84
- -- 建立訂單項目表
85
- CREATE TABLE IF NOT EXISTS order_items (
86
- id SERIAL PRIMARY KEY,
87
- order_id VARCHAR(255) REFERENCES orders(order_id),
88
- product_id INTEGER REFERENCES products(product_id),
89
- quantity INTEGER NOT NULL,
90
- unit_price DECIMAL(10,2) NOT NULL,
91
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
92
- );
93
-
94
- -- 建立訊息記錄表
95
- CREATE TABLE IF NOT EXISTS line_messages (
96
- id SERIAL PRIMARY KEY,
97
- user_id VARCHAR(255),
98
- message TEXT NOT NULL,
99
- message_type VARCHAR(50) DEFAULT 'text',
100
- timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
101
- processed BOOLEAN DEFAULT FALSE
102
- );
103
-
104
- -- 建立用戶會話表
105
- CREATE TABLE IF NOT EXISTS user_sessions (
106
- id SERIAL PRIMARY KEY,
107
- user_id VARCHAR(255) REFERENCES users(user_id),
108
- session_data JSONB,
109
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
110
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
111
- );
112
-
113
- -- 插入範例商品資料
114
- INSERT INTO products (name, description, price, stock, category) VALUES
115
- ('iPhone 15 Pro', '最新款 iPhone,配備 A17 Pro 晶片', 35900, 50, '手機'),
116
- ('MacBook Air M2', '輕薄筆記型電腦,搭載 M2 晶片', 37900, 30, '筆電'),
117
- ('AirPods Pro', '主動降噪無線耳機', 7490, 100, '耳機');
118
- ```
119
-
120
- ### 4. LINE Bot Webhook 設定
121
-
122
- 1. 部署到 Hugging Face Spaces 後,取得您的應用程式 URL
123
- 2. 在 LINE Developers Console 中設定 Webhook URL:
124
- ```
125
- https://你的用戶名-你的空間名稱.hf.space/webhook
126
- ```
127
- 3. 啟用 Webhook 和 Auto-reply messages
128
-
129
- ### 5. 測試功能
130
-
131
- 部署完成後,您可以測試以下功能:
132
-
133
- #### 用戶查詢
134
- - "查詢用戶 張三"
135
- - "找用戶名叫王小明的資料"
136
-
137
- #### 商品查詢
138
- - "查詢商品 iPhone"
139
- - "有什麼手機商品"
140
- - "價格 1000 到 5000 的商品"
141
-
142
- #### 訂單查詢
143
- - "查詢訂單 ORD001"
144
- - "我的訂單狀態如何"
145
-
146
- #### 統計分析
147
- - "統計用戶數量"
148
- - "總共有多少筆訂單"
149
-
150
- #### 說明功能
151
- - "幫助"
152
- - "說明"
153
-
154
- ## 🔧 本地開發
155
-
156
- 如果要在本地開發:
157
-
158
- 1. 複製專案
159
- 2. 建立 `.env` 檔案(參考 `.env.example`)
160
- 3. 安裝依賴:`pip install -r requirements.txt`
161
- 4. 執行:`python -m uvicorn backend.main:app --reload --port 7860`
162
- 5. 使用 ngrok 建立公開 URL 用於 LINE Webhook 測試
163
-
164
- ## 📝 架構說明
165
-
166
- - **FastAPI**: Web 框架和 API 端點
167
- - **Pydantic**: 資料驗證和序列化
168
- - **LINE Bot SDK**: 處理 LINE 訊息
169
- - **Supabase**: PostgreSQL 資料庫
170
- - **NLP Service**: 自然語言處理和意圖識別
171
- - **Database Service**: 資料庫操作抽象層
172
-
173
- ## 🚨 注意事項
174
-
175
- 1. 確保所有環境變數都��確設定
176
- 2. Supabase 資料表必須先建立
177
- 3. LINE Bot Webhook URL 必須是 HTTPS
178
- 4. 測試時注意 LINE 訊息的回應時間限制(30秒)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_openrouter_connection.py DELETED
@@ -1,223 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- OpenRouter API 連線測試腳本
4
- 測試 DeepSeek V3 是否能正常連接和回應
5
- """
6
-
7
- import requests
8
- import json
9
- import os
10
- from datetime import datetime
11
-
12
- # API 設定
13
- OPENROUTER_API_KEY = "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"
14
- OPENROUTER_MODEL = "deepseek/deepseek-chat-v3-0324:free"
15
- OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
16
-
17
- def test_openrouter_connection():
18
- """測試 OpenRouter API 連線"""
19
-
20
- print("🔧 OpenRouter API 連線測試")
21
- print("=" * 50)
22
- print(f"⏰ 測試時間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
23
- print(f"🔑 API Key: {OPENROUTER_API_KEY[:20]}...")
24
- print(f"🤖 模型: {OPENROUTER_MODEL}")
25
- print(f"🌐 API URL: {OPENROUTER_URL}")
26
- print("=" * 50)
27
-
28
- # 設定請求標頭
29
- headers = {
30
- "Authorization": f"Bearer {OPENROUTER_API_KEY}",
31
- "Content-Type": "application/json",
32
- "HTTP-Referer": "https://huggingface.co/spaces",
33
- "X-Title": "OpenRouter Connection Test"
34
- }
35
-
36
- # 測試案例
37
- test_cases = [
38
- {
39
- "name": "基本連線測試",
40
- "message": "你好,請介紹一下自己"
41
- },
42
- {
43
- "name": "中文理解測試",
44
- "message": "請分析這句話的意圖:查詢iPhone庫存"
45
- },
46
- {
47
- "name": "業務查詢測試",
48
- "message": "如果用戶說「商品查詢」,這應該被歸類為什麼意圖?請用JSON格式回答"
49
- }
50
- ]
51
-
52
- for i, test_case in enumerate(test_cases, 1):
53
- print(f"\n📝 測試 {i}: {test_case['name']}")
54
- print("-" * 30)
55
-
56
- # 準備請求資料
57
- data = {
58
- "model": OPENROUTER_MODEL,
59
- "messages": [
60
- {
61
- "role": "system",
62
- "content": "你是 DeepSeek V3,一個強大的中文 AI 助手。請用繁體中文回應。"
63
- },
64
- {
65
- "role": "user",
66
- "content": test_case['message']
67
- }
68
- ],
69
- "temperature": 0.7,
70
- "max_tokens": 300
71
- }
72
-
73
- try:
74
- print(f"📤 發送請求: {test_case['message']}")
75
-
76
- # 發送 API 請求
77
- response = requests.post(
78
- OPENROUTER_URL,
79
- headers=headers,
80
- json=data,
81
- timeout=30
82
- )
83
-
84
- print(f"📊 狀態碼: {response.status_code}")
85
-
86
- if response.status_code == 200:
87
- # 解析回應
88
- result = response.json()
89
- content = result["choices"][0]["message"]["content"]
90
-
91
- print(f"✅ 請求成功!")
92
- print(f"🤖 DeepSeek V3 回應:")
93
- print(f" {content[:200]}{'...' if len(content) > 200 else ''}")
94
-
95
- # 顯示使用統計
96
- if "usage" in result:
97
- usage = result["usage"]
98
- print(f"📈 Token 使用: {usage.get('total_tokens', 'N/A')} tokens")
99
-
100
- else:
101
- print(f"❌ 請求失敗!")
102
- print(f"錯誤回應: {response.text}")
103
-
104
- except requests.exceptions.Timeout:
105
- print("❌ 請求超時 (30秒)")
106
-
107
- except requests.exceptions.ConnectionError:
108
- print("❌ 網路連線錯誤")
109
-
110
- except json.JSONDecodeError:
111
- print("❌ JSON 解析錯誤")
112
- print(f"原始回應: {response.text}")
113
-
114
- except Exception as e:
115
- print(f"❌ 未知錯誤: {str(e)}")
116
-
117
- def test_api_key_validity():
118
- """測試 API Key 有效性"""
119
-
120
- print("\n🔐 API Key 有效性測試")
121
- print("-" * 30)
122
-
123
- headers = {
124
- "Authorization": f"Bearer {OPENROUTER_API_KEY}",
125
- "Content-Type": "application/json"
126
- }
127
-
128
- # 簡單的測試請求
129
- data = {
130
- "model": OPENROUTER_MODEL,
131
- "messages": [{"role": "user", "content": "test"}],
132
- "max_tokens": 1
133
- }
134
-
135
- try:
136
- response = requests.post(OPENROUTER_URL, headers=headers, json=data, timeout=10)
137
-
138
- if response.status_code == 200:
139
- print("✅ API Key 有效")
140
- return True
141
- elif response.status_code == 401:
142
- print("❌ API Key 無效或已過期")
143
- return False
144
- elif response.status_code == 402:
145
- print("⚠️ 餘額不足")
146
- return False
147
- else:
148
- print(f"⚠️ 未知狀態: {response.status_code}")
149
- print(f"回應: {response.text}")
150
- return False
151
-
152
- except Exception as e:
153
- print(f"❌ 測試失敗: {str(e)}")
154
- return False
155
-
156
- def check_model_availability():
157
- """檢查模型可用性"""
158
-
159
- print("\n🤖 模型可用性檢查")
160
- print("-" * 30)
161
-
162
- # 嘗試獲取可用模型列表
163
- try:
164
- headers = {
165
- "Authorization": f"Bearer {OPENROUTER_API_KEY}",
166
- }
167
-
168
- response = requests.get("https://openrouter.ai/api/v1/models", headers=headers, timeout=10)
169
-
170
- if response.status_code == 200:
171
- models = response.json()
172
-
173
- # 檢查 DeepSeek 模型
174
- deepseek_models = [model for model in models.get('data', [])
175
- if 'deepseek' in model.get('id', '').lower()]
176
-
177
- print(f"✅ 找到 {len(deepseek_models)} 個 DeepSeek 模型:")
178
- for model in deepseek_models[:5]: # 只顯示前5個
179
- print(f" - {model.get('id', 'N/A')}")
180
-
181
- # 檢查目標模型是否存在
182
- target_model_exists = any(model.get('id') == OPENROUTER_MODEL
183
- for model in models.get('data', []))
184
-
185
- if target_model_exists:
186
- print(f"✅ 目標模型 {OPENROUTER_MODEL} 可用")
187
- else:
188
- print(f"⚠️ 目標模型 {OPENROUTER_MODEL} 可能不可用")
189
-
190
- else:
191
- print(f"⚠️ 無法獲取模型列表: {response.status_code}")
192
-
193
- except Exception as e:
194
- print(f"❌ 檢查失敗: {str(e)}")
195
-
196
- def main():
197
- """主函數"""
198
-
199
- print("🚀 開始 OpenRouter API 完整測試")
200
- print("=" * 60)
201
-
202
- # 1. 檢查 API Key 有效性
203
- if not test_api_key_validity():
204
- print("\n❌ API Key 測試失敗,停止後續測試")
205
- return
206
-
207
- # 2. 檢查模型可用性
208
- check_model_availability()
209
-
210
- # 3. 進行完整連線測試
211
- test_openrouter_connection()
212
-
213
- print("\n" + "=" * 60)
214
- print("🎉 測試完成!")
215
- print("\n💡 如果所有測試都成功,表示:")
216
- print(" ✅ OpenRouter API Key 有效")
217
- print(" ✅ DeepSeek V3 模型可用")
218
- print(" ✅ 網路連線正常")
219
- print(" ✅ API 回應正常")
220
- print("\n🔧 如果業務查詢仍有問題,則確定是事件循環衝突導致的技術問題")
221
-
222
- if __name__ == "__main__":
223
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_pydantic_ai_integration.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 測試 Pydantic AI 整合 - 驗證商品查詢功能
3
+ 特別測試 "是否有推薦貓砂?" 這類查詢的準確性
4
+ """
5
+
6
+ import asyncio
7
+ import sys
8
+ import os
9
+
10
+ # 添加專案根目錄到 Python 路徑
11
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
12
+
13
+ from backend.services.message_router import MessageRouter
14
+ from backend.services.pydantic_ai_service import ProductQueryService
15
+ from backend.services.enhanced_product_service import EnhancedProductService
16
+
17
+ def test_message_router():
18
+ """測試訊息路由器"""
19
+ print("🧪 測試訊息路由器...")
20
+
21
+ router = MessageRouter()
22
+
23
+ # 測試案例
24
+ test_cases = [
25
+ "是否有推薦貓砂?",
26
+ "有什麼狗糧推薦?",
27
+ "查詢寵物用品庫存",
28
+ "iPhone 還有庫存嗎?",
29
+ "推薦一些好用的商品",
30
+ "你好!今天天氣如何?", # 應該路由到聊天模式
31
+ "/help", # 應該顯示幫助
32
+ ]
33
+
34
+ for i, message in enumerate(test_cases, 1):
35
+ print(f"\n--- 測試案例 {i}: {message} ---")
36
+ try:
37
+ result = router.route_message(message, "test_user")
38
+ print(f"模式: {result.get('mode', 'unknown')}")
39
+ print(f"成功: {result.get('success', False)}")
40
+ print(f"回應: {result.get('text', 'No response')[:100]}...")
41
+ if result.get('products_found'):
42
+ print(f"找到商品數量: {result['products_found']}")
43
+ except Exception as e:
44
+ print(f"❌ 錯誤: {str(e)}")
45
+
46
+ # 顯示統計
47
+ print(f"\n📊 路由統計:")
48
+ stats = router.get_route_statistics()
49
+ for mode, count in stats['stats'].items():
50
+ print(f" {mode}: {count}")
51
+
52
+ def test_product_query_service():
53
+ """測試 Pydantic AI 商品查詢服務"""
54
+ print("\n🛍️ 測試 Pydantic AI 商品查詢服務...")
55
+
56
+ try:
57
+ service = ProductQueryService()
58
+
59
+ if not service.is_available():
60
+ print("❌ Pydantic AI 服務不可用 (請檢查 GROQ_API_KEY)")
61
+ return
62
+
63
+ # 測試意圖分析
64
+ test_messages = [
65
+ "是否有推薦貓砂?",
66
+ "有什麼狗糧推薦?",
67
+ "查詢iPhone庫存",
68
+ "今天天氣如何?", # 非商品查詢
69
+ ]
70
+
71
+ print("\n🔍 意圖分析測試:")
72
+ for message in test_messages:
73
+ intent = service.analyze_query_intent(message)
74
+ print(f" '{message}' -> 商品查詢: {intent['is_product_query']}, 信心度: {intent['confidence']:.2f}")
75
+
76
+ # 測試實際查詢
77
+ print("\n🛍️ 實際查詢測試:")
78
+ query_result = service.process_product_query_sync("是否有推薦貓砂?", "test_user")
79
+
80
+ print(f"成功: {query_result['success']}")
81
+ print(f"意圖: {query_result.get('intent', 'unknown')}")
82
+ print(f"回應: {query_result.get('text', 'No response')}")
83
+ if query_result.get('products_found'):
84
+ print(f"找到商品: {query_result['products_found']}")
85
+
86
+ except Exception as e:
87
+ print(f"❌ 測試錯誤: {str(e)}")
88
+
89
+ def test_enhanced_product_service():
90
+ """測試增強商品服務"""
91
+ print("\n🔧 測試增強商品服務...")
92
+
93
+ try:
94
+ service = EnhancedProductService()
95
+
96
+ # 測試商品推薦
97
+ print("🎯 測試商品推薦功能:")
98
+ result = service.get_product_recommendations("貓砂", limit=5)
99
+
100
+ print(f"成功: {result.success}")
101
+ print(f"找到商品數量: {result.count}")
102
+
103
+ if result.success and result.data:
104
+ print("推薦商品:")
105
+ for i, product in enumerate(result.data[:3], 1):
106
+ print(f" {i}. {product.get('product_name', 'N/A')} - 庫存: {product.get('current_stock', 0)}")
107
+ elif result.error:
108
+ print(f"錯誤: {result.error}")
109
+
110
+ # 測試進階搜尋
111
+ print("\n🔍 測試進階搜尋功能:")
112
+ result = service.search_products_advanced("寵物", include_stock_info=True, limit=3)
113
+
114
+ print(f"成功: {result.success}")
115
+ print(f"找到商品數量: {result.count}")
116
+
117
+ if result.success and result.data:
118
+ print("搜尋結果:")
119
+ for i, product in enumerate(result.data, 1):
120
+ print(f" {i}. {product.get('product_name', 'N/A')} - {product.get('stock_status', 'N/A')}")
121
+
122
+ except Exception as e:
123
+ print(f"❌ 測試錯誤: {str(e)}")
124
+
125
+ def test_database_connection():
126
+ """測試資料庫連接"""
127
+ print("\n🗄️ 測試資料庫連接...")
128
+
129
+ try:
130
+ from backend.database.connection import test_database_connection
131
+
132
+ if test_database_connection():
133
+ print("✅ 資料庫連接正常")
134
+ else:
135
+ print("❌ 資料庫連接失敗")
136
+
137
+ except Exception as e:
138
+ print(f"❌ 資��庫測試錯誤: {str(e)}")
139
+
140
+ def main():
141
+ """主測試函數"""
142
+ print("🚀 開始 Pydantic AI 整合測試\n")
143
+
144
+ # 1. 測試資料庫連接
145
+ test_database_connection()
146
+
147
+ # 2. 測試增強商品服務
148
+ test_enhanced_product_service()
149
+
150
+ # 3. 測試 Pydantic AI 服務
151
+ test_product_query_service()
152
+
153
+ # 4. 測試訊息路由器
154
+ test_message_router()
155
+
156
+ print("\n✅ 測試完成!")
157
+
158
+ if __name__ == "__main__":
159
+ # 檢查環境變數
160
+ from backend.config import settings
161
+
162
+ print("🔧 環境檢查:")
163
+ print(f" GROQ_API_KEY: {'✅ 已設定' if settings.GROQ_API_KEY else '❌ 未設定'}")
164
+ print(f" DATABASE_URL: {'✅ 已設定' if settings.DATABASE_URL else '❌ 未設定'}")
165
+ print()
166
+
167
+ # 執行測試
168
+ main()