Enzo886 commited on
Commit
2c540e2
·
verified ·
1 Parent(s): 8146db8

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +104 -196
main.py CHANGED
@@ -1,118 +1,63 @@
1
  import json, os
2
  import logging
3
- from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status
4
  from fastapi.middleware.cors import CORSMiddleware
 
5
  import google.generativeai as genai
6
  from linebot import LineBotApi, WebhookHandler
7
  from linebot.exceptions import InvalidSignatureError
8
- from linebot.models import MessageEvent, TextMessage, TextSendMessage, AudioMessage
9
  from dotenv import load_dotenv
10
- import asyncio
11
 
12
- # .env 檔案載入環境變數
13
  load_dotenv()
14
 
15
- # 設定日誌記錄
16
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', encoding='utf-8')
17
-
18
- # --- 1. 配置設定 ---
19
- class GeminiModelManager:
20
- """
21
- 負責管理 Gemini 模型相關操作的類別
22
- 包括初始化模型、建立對話 session 以及傳送訊息
23
- """
24
- def __init__(self):
25
- """
26
- 初始化 Gemini 模型管理器
27
- 載入 Google AI API 金鑰,設定文字生成參數,並建立 Gemini 模型
28
- """
29
- try:
30
- genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
31
- self.generation_config = genai.types.GenerationConfig(
32
- max_output_tokens=2048, temperature=0.2, top_p=0.5, top_k=16
33
- )
34
- self.model = genai.GenerativeModel(
35
- "gemini-2.0-flash-exp",
36
- system_instruction="你是咖啡廳員工,請使用親切的招呼語做為開頭,然後以爽朗愉悅的口氣回答問題。",
37
- )
38
- logging.info("Gemini 模型配置完成")
39
- except Exception as e:
40
- logging.error(f"Gemini 模型配置錯誤: {e}")
41
- raise
42
-
43
- def create_chat_session(self, history=None):
44
- """
45
- 建立一個新的對話 session
46
- 若沒有提供歷史紀錄,則使用預設的歷史紀錄
47
- """
48
- if history is None:
49
- history = [
50
- {
51
- "role": "user",
52
- "parts": ["hi"],
53
- },
54
- {
55
- "role": "model",
56
- "parts": ["Hi there! How can I help you today?\n"],
57
- },
58
- ]
59
- return self.model.start_chat(history=history)
60
-
61
- async def send_message(self, chat_session, prompt):
62
- """
63
- 非同步傳送訊息到 Gemini 模型
64
- """
65
- try:
66
- response = await asyncio.to_thread(chat_session.send_message, prompt)
67
- return response
68
- except Exception as e:
69
- logging.error(f"傳送訊息到 Gemini 時發生錯誤: {e}")
70
- raise
71
-
72
- class LineBotManager:
73
- """
74
- 負責管理 Line Bot API 相關操作的類別
75
- 包括初始化 Line Bot API 和回覆訊息
76
- """
77
- def __init__(self):
78
- """
79
- 初始化 Line Bot API 管理器
80
- 載入 Line Bot API 金鑰和秘密金鑰
81
- """
82
- try:
83
- self.line_bot_api = LineBotApi(os.getenv("CHANNEL_ACCESS_TOKEN"))
84
- self.line_handler = WebhookHandler(os.getenv("CHANNEL_SECRET"))
85
- logging.info("Line Bot API 配置完成")
86
- except Exception as e:
87
- logging.error(f"Line Bot API 配置錯誤: {e}")
88
- raise
89
-
90
- async def reply_message(self, event, message):
91
- """
92
- 非同步回覆訊息到 Line
93
- """
94
- try:
95
- await asyncio.to_thread(self.line_bot_api.reply_message, event.reply_token, message)
96
- logging.info(f"成功回覆 Line 訊息: {message}")
97
- except Exception as e:
98
- logging.error(f"回覆 Line 訊息時發生錯誤: {e}")
99
- raise
100
-
101
- # 初始化配置
102
  try:
103
- gemini_manager = GeminiModelManager()
104
- line_bot_manager = LineBotManager()
105
  except Exception as e:
106
- logging.critical(f"初始化配置失敗: {e}")
107
- exit(1) # 如果配置失敗,則終止程式
108
 
109
- # 從環境變數取得是否啟用對話模式,預設為 true
110
  working_status = os.getenv("DEFALUT_TALKING", default="true").lower() == "true"
111
 
112
- # --- 2. FastAPI 應用程式 ---
113
  app = FastAPI()
114
 
115
- # 設定 CORS,允許跨域請求
116
  app.add_middleware(
117
  CORSMiddleware,
118
  allow_origins=["*"],
@@ -121,12 +66,9 @@ app.add_middleware(
121
  allow_headers=["*"],
122
  )
123
 
124
- # --- 3. 路由設定 ---
125
  @app.get("/")
126
  def root():
127
- """
128
- 處理根路徑請求,返回 Line Bot 的標題
129
- """
130
  return {"title": "Line Bot"}
131
 
132
 
@@ -134,112 +76,78 @@ def root():
134
  async def webhook(
135
  request: Request, background_tasks: BackgroundTasks, x_line_signature=Header(None)
136
  ):
137
- """
138
- 處理 Line Webhook 請求
139
- 接收 Line 的訊息並驗證簽章,然後將訊息處理加入背景任務
140
- """
141
  body = await request.body()
142
  try:
143
  background_tasks.add_task(
144
- self.run_sync_handler, line_bot_manager.line_handler, body.decode("utf-8"), x_line_signature # 修改這裡
145
  )
146
  except InvalidSignatureError:
147
- logging.warning("收到無效的簽章")
148
- raise HTTPException(status_code=400, detail="無效的簽章")
149
  return "ok"
150
 
151
- # 定義一個方法讓 line_handler 可以正確處理非同步事件
152
- async def run_sync_handler(handler, body, signature):
153
- await asyncio.to_thread(handler.handle, body, signature)
154
-
155
- # --- 4. 對話 Session 管理 ---
156
- class ChatSessionManager:
157
- """
158
- 負責管理對話 Session 的類別
159
- 包括建立、取得、刪除對話 session,以及處理訊息
160
- """
161
- def __init__(self):
162
- """
163
- 初始化對話 Session 管理器,建立一個儲存對話 Session 的字典
164
- """
165
- self.chat_sessions = {}
166
-
167
- def get_or_create_session(self, user_id):
168
- """
169
- 取得或建立特定使用者的對話 Session
170
- 如果不存在,則建立一個新的 Session
171
- """
172
- if user_id not in self.chat_sessions:
173
- logging.info(f"為使用者 {user_id} 建立新的對話 Session")
174
- self.chat_sessions[user_id] = gemini_manager.create_chat_session()
175
- return self.chat_sessions[user_id]
176
-
177
- def remove_session(self, user_id):
178
- """
179
- 刪除特定使用者的對話 Session
180
- """
181
- if user_id in self.chat_sessions:
182
- del self.chat_sessions[user_id]
183
- logging.info(f"刪除使用者 {user_id} 的對話 Session")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
- async def handle_message(self, event):
186
- """
187
- 處理 Line 訊息,取得使用者輸入,並回覆 Gemini 模型的訊息
188
- """
189
- user_id = event.source.user_id
190
- logging.info(f"開始處理來自使用者 {user_id} 的訊息")
191
- if event.message.text == "再見":
192
- await line_bot_manager.reply_message(event, TextSendMessage(text="Bye!"))
193
- self.remove_session(user_id)
194
- logging.info(f"使用者 {user_id} 說再見,已刪除對話 Session")
195
- return
196
-
197
- if not working_status:
198
- logging.info(f"對話模式關閉,忽略來自使用者 {user_id} 的訊息")
199
- return
200
-
201
- try:
202
- prompt = event.message.text
203
- logging.info(f"收到使用者 {user_id} 的訊息: {prompt}")
204
- chat_session = self.get_or_create_session(user_id)
205
- response = await gemini_manager.send_message(chat_session, prompt)
206
- if response.text:
207
- out = response.text
208
- logging.info(f"Gemini 回覆使用者 {user_id}: {out}")
209
- else:
210
- out = "Gemini 沒答案!請換個說法!"
211
- logging.warning(f"Gemini 沒有回覆文字內容給使用者 {user_id}")
212
-
213
- await line_bot_manager.reply_message(event, TextSendMessage(text=out))
214
-
215
- except Exception as e:
216
- out = "Gemini 執行出錯!請換個說法!"
217
- logging.error(f"Gemini 發生錯誤 (使用者: {user_id}): {e}")
218
- try:
219
- await line_bot_manager.reply_message(event, TextSendMessage(text=out))
220
- except Exception as e:
221
- logging.error(f"回覆使用者時發生錯誤: {e}")
222
- logging.info(f"訊息處理完成 (使用者: {user_id})")
223
-
224
-
225
- chat_session_manager = ChatSessionManager()
226
-
227
- @line_bot_manager.line_handler.add(MessageEvent, message=TextMessage)
228
- async def handle_message(event):
229
- """
230
- Line Bot 訊息處理的主要函式
231
- 驗證訊息類型,然後將文字訊息轉發給對話管理員處理
232
- """
233
- logging.info(f"收到來自 {event.source.user_id} 的訊息,訊息類型:{event.message.type}")
234
  if event.type != "message" or event.message.type != "text":
235
- logging.warning(f"事件類型錯誤:不是文字訊息 (使用者: {event.source.user_id})")
236
- await line_bot_manager.reply_message(event, TextSendMessage(text="事件類型錯誤:不是文字訊息。"))
237
  return
238
 
239
- await chat_session_manager.handle_message(event)
240
- logging.info(f"完成來自 {event.source.user_id} 的訊息處理")
241
 
242
- # --- 5. 啟動應用程式 ---
243
  if __name__ == "__main__":
244
  import uvicorn
245
 
 
1
  import json, os
2
  import logging
 
3
  from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status
5
  import google.generativeai as genai
6
  from linebot import LineBotApi, WebhookHandler
7
  from linebot.exceptions import InvalidSignatureError
8
+ from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage, AudioMessage
9
  from dotenv import load_dotenv
 
10
 
11
+ # Load environment variables from .env file
12
  load_dotenv()
13
 
14
+ # Logging configuration
15
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16
+
17
+
18
+ # --- 1. Configuration ---
19
+ def configure_gemini():
20
+ try:
21
+ genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
22
+ generation_config = genai.types.GenerationConfig(
23
+ max_output_tokens=2048, temperature=0.2, top_p=0.5, top_k=16
24
+ )
25
+ model = genai.GenerativeModel(
26
+ "gemini-2.0-flash-exp",
27
+ system_instruction="你是咖啡廳員工,請使用親切的招呼語做為開頭,然後以爽朗愉悅的口氣回答問題。",
28
+ )
29
+ logging.info("Gemini model configured successfully.")
30
+ return model, generation_config
31
+ except Exception as e:
32
+ logging.error(f"Error configuring Gemini: {e}")
33
+ raise
34
+
35
+
36
+ def configure_line_bot():
37
+ try:
38
+ line_bot_api = LineBotApi(os.getenv("CHANNEL_ACCESS_TOKEN"))
39
+ line_handler = WebhookHandler(os.getenv("CHANNEL_SECRET"))
40
+ logging.info("Line Bot API configured successfully.")
41
+ return line_bot_api, line_handler
42
+ except Exception as e:
43
+ logging.error(f"Error configuring Line Bot API: {e}")
44
+ raise
45
+
46
+ # Initialize configurations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  try:
48
+ model, generation_config = configure_gemini()
49
+ line_bot_api, line_handler = configure_line_bot()
50
  except Exception as e:
51
+ logging.critical(f"Failed to initialize configurations: {e}")
52
+ exit(1) # Exit if critical configuration fails
53
 
54
+ # Default talking state from environment variable
55
  working_status = os.getenv("DEFALUT_TALKING", default="true").lower() == "true"
56
 
57
+ # --- 2. FastAPI App ---
58
  app = FastAPI()
59
 
60
+ # Setup CORS
61
  app.add_middleware(
62
  CORSMiddleware,
63
  allow_origins=["*"],
 
66
  allow_headers=["*"],
67
  )
68
 
69
+ # --- 3. Routes ---
70
  @app.get("/")
71
  def root():
 
 
 
72
  return {"title": "Line Bot"}
73
 
74
 
 
76
  async def webhook(
77
  request: Request, background_tasks: BackgroundTasks, x_line_signature=Header(None)
78
  ):
 
 
 
 
79
  body = await request.body()
80
  try:
81
  background_tasks.add_task(
82
+ line_handler.handle, body.decode("utf-8"), x_line_signature
83
  )
84
  except InvalidSignatureError:
85
+ logging.warning("Invalid signature received.")
86
+ raise HTTPException(status_code=400, detail="Invalid signature")
87
  return "ok"
88
 
89
+ # --- 4. Chat Session Management ---
90
+ chat_sessions = {}
91
+
92
+ def get_or_create_chat_session(user_id: str):
93
+ if user_id not in chat_sessions:
94
+ logging.info(f"Creating new chat session for user: {user_id}")
95
+ chat_sessions[user_id] = model.start_chat(
96
+ history=[
97
+ {
98
+ "role": "user",
99
+ "parts": ["hi"],
100
+ },
101
+ {
102
+ "role": "model",
103
+ "parts": ["Hi there! How can I help you today?\n"],
104
+ },
105
+ ]
106
+ )
107
+ return chat_sessions[user_id]
108
+
109
+ def handle_text_message(event):
110
+ global working_status
111
+
112
+ user_id = event.source.user_id
113
+ if event.message.text == "再見":
114
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text="Bye!"))
115
+ if user_id in chat_sessions:
116
+ del chat_sessions[user_id]
117
+ logging.info(f"Chat session deleted for user: {user_id}")
118
+ return
119
+
120
+ if not working_status:
121
+ logging.info("Bot is not in working status, ignoring message.")
122
+ return
123
+
124
+ try:
125
+ prompt = event.message.text
126
+ logging.info(f"Received user prompt: {prompt}, user: {user_id}")
127
+ chat_session = get_or_create_chat_session(user_id)
128
+ response = chat_session.send_message(prompt)
129
+ if response.text:
130
+ out = response.text
131
+ logging.info(f"Gemini response: {out}, user: {user_id}")
132
+ else:
133
+ out = "Gemini 沒答案!請換個說法!"
134
+ logging.warning(f"Gemini returned no text response for user: {user_id}")
135
+ except Exception as e:
136
+ out = "Gemini 執行出錯!請換個說法!"
137
+ logging.error(f"Gemini error: {e}, user: {user_id}")
138
 
139
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))
140
+
141
+ @line_handler.add(MessageEvent, message=TextMessage)
142
+ def handle_message(event):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  if event.type != "message" or event.message.type != "text":
144
+ logging.warning("Event type error: not a text message.")
145
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text="Event type error: not a text message."))
146
  return
147
 
148
+ handle_text_message(event)
 
149
 
150
+ # --- 5. Start App ---
151
  if __name__ == "__main__":
152
  import uvicorn
153