Spaces:
Sleeping
Sleeping
Update main.py
Browse files
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 |
-
#
|
13 |
load_dotenv()
|
14 |
|
15 |
-
#
|
16 |
-
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s'
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
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 |
-
|
104 |
-
|
105 |
except Exception as e:
|
106 |
-
logging.critical(f"
|
107 |
-
exit(1) #
|
108 |
|
109 |
-
#
|
110 |
working_status = os.getenv("DEFALUT_TALKING", default="true").lower() == "true"
|
111 |
|
112 |
-
# --- 2. FastAPI
|
113 |
app = FastAPI()
|
114 |
|
115 |
-
#
|
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 |
-
|
145 |
)
|
146 |
except InvalidSignatureError:
|
147 |
-
logging.warning("
|
148 |
-
raise HTTPException(status_code=400, detail="
|
149 |
return "ok"
|
150 |
|
151 |
-
#
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
logging.info(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
|
185 |
-
|
186 |
-
|
187 |
-
|
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(
|
236 |
-
|
237 |
return
|
238 |
|
239 |
-
|
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 |
|