Spaces:
Sleeping
Sleeping
import json, os | |
import logging | |
from fastapi.middleware.cors import CORSMiddleware | |
from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status | |
import google.generativeai as genai | |
from linebot import LineBotApi, WebhookHandler | |
from linebot.exceptions import InvalidSignatureError | |
from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage, AudioMessage | |
from dotenv import load_dotenv | |
# 從 .env 檔案載入環境變數 | |
load_dotenv() | |
# 設定日誌記錄 | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', encoding='utf-8') | |
# --- 1. 配置設定 --- | |
def configure_gemini(): | |
""" | |
配置 Google Gemini 模型 | |
設定 API 金鑰、生成參數和模型 | |
""" | |
try: | |
genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) | |
generation_config = genai.types.GenerationConfig( | |
max_output_tokens=2048, temperature=0.2, top_p=0.5, top_k=16 | |
) | |
model = genai.GenerativeModel( | |
"gemini-2.0-flash-exp", | |
system_instruction="你是咖啡廳員工,請使用親切的招呼語做為開頭,然後以爽朗愉悅的口氣回答問題。", | |
) | |
logging.info("Gemini 模型配置完成.") | |
return model, generation_config | |
except Exception as e: | |
logging.error(f"Gemini 模型配置錯誤: {e}") | |
raise | |
def configure_line_bot(): | |
""" | |
配置 Line Bot API | |
設定 Channel Access Token 和 Channel Secret | |
""" | |
try: | |
line_bot_api = LineBotApi(os.getenv("CHANNEL_ACCESS_TOKEN")) | |
line_handler = WebhookHandler(os.getenv("CHANNEL_SECRET")) | |
logging.info("Line Bot API 配置完成.") | |
return line_bot_api, line_handler | |
except Exception as e: | |
logging.error(f"Line Bot API 配置錯誤: {e}") | |
raise | |
# 初始化配置 | |
try: | |
model, generation_config = configure_gemini() | |
line_bot_api, line_handler = configure_line_bot() | |
except Exception as e: | |
logging.critical(f"初始化配置失敗: {e}") | |
exit(1) # 如果配置失敗,則終止程式 | |
# 從環境變數取得是否啟用對話模式,預設為 true | |
working_status = os.getenv("DEFALUT_TALKING", default="true").lower() == "true" | |
# --- 2. FastAPI 應用程式 --- | |
app = FastAPI() | |
# 設定 CORS,允許跨域請求 | |
app.add_middleware( | |
CORSMiddleware, | |
allow_origins=["*"], | |
allow_credentials=True, | |
allow_methods=["*"], | |
allow_headers=["*"], | |
) | |
# --- 3. 路由設定 --- | |
def root(): | |
""" | |
處理根路徑請求,返回 Line Bot 的標題 | |
""" | |
return {"title": "Line Bot"} | |
async def webhook( | |
request: Request, background_tasks: BackgroundTasks, x_line_signature=Header(None) | |
): | |
""" | |
處理 Line Webhook 請求 | |
接收 Line 的訊息並驗證簽章,然後將訊息處理加入背景任務 | |
""" | |
body = await request.body() | |
try: | |
background_tasks.add_task( | |
line_handler.handle, body.decode("utf-8"), x_line_signature | |
) | |
except InvalidSignatureError: | |
logging.warning("收到無效的簽章.") | |
raise HTTPException(status_code=400, detail="無效的簽章") | |
return "ok" | |
# --- 4. 對話 Session 管理 --- | |
chat_sessions = {} | |
def get_or_create_chat_session(user_id: str): | |
""" | |
取得或建立特定使用者的對話 Session | |
如果不存在,則建立一個新的 Session | |
""" | |
if user_id not in chat_sessions: | |
logging.info(f"為使用者 {user_id} 建立新的對話 Session") | |
chat_sessions[user_id] = model.start_chat( | |
history=[ | |
{ | |
"role": "user", | |
"parts": ["hi"], | |
}, | |
{ | |
"role": "model", | |
"parts": ["Hi there! How can I help you today?\n"], | |
}, | |
] | |
) | |
return chat_sessions[user_id] | |
def handle_text_message(event): | |
""" | |
處理文字訊息,取得使用者輸入,並回覆 Gemini 模型的訊息 | |
""" | |
global working_status | |
user_id = event.source.user_id | |
if event.message.text == "再見": | |
line_bot_api.reply_message(event.reply_token, TextSendMessage(text="Bye!")) | |
if user_id in chat_sessions: | |
del chat_sessions[user_id] | |
logging.info(f"使用者 {user_id} 說再見,已刪除對話 Session") | |
return | |
if not working_status: | |
logging.info("對話模式關閉,忽略訊息.") | |
return | |
try: | |
prompt = event.message.text | |
logging.info(f"收到使用者 {user_id} 的訊息: {prompt}") | |
chat_session = get_or_create_chat_session(user_id) | |
response = chat_session.send_message(prompt) | |
if response.text: | |
out = response.text | |
logging.info(f"Gemini 回覆使用者 {user_id}: {out}") | |
else: | |
out = "Gemini 沒答案!請換個說法!" | |
logging.warning(f"Gemini 沒有回覆文字內容給使用者 {user_id}") | |
except Exception as e: | |
out = "Gemini 執行出錯!請換個說法!" | |
logging.error(f"Gemini 發生錯誤 (使用者: {user_id}): {e}") | |
line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out)) | |
def handle_message(event): | |
""" | |
Line Bot 訊息處理的主要函式 | |
驗證訊息類型,然後將文字訊息轉發給處理函數 | |
""" | |
if event.type != "message" or event.message.type != "text": | |
logging.warning("事件類型錯誤:不是文字訊息.") | |
line_bot_api.reply_message(event.reply_token, TextSendMessage(text="事件類型錯誤:不是文字訊息。")) | |
return | |
handle_text_message(event) | |
# --- 5. 啟動應用程式 --- | |
if __name__ == "__main__": | |
import uvicorn | |
uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True) |