gemiline / main.py
motaer0206's picture
Update main.py
1c3bc77 verified
import os
import io
import traceback
import json
# 解決 Matplotlib/Fontconfig 在無頭環境中的問題
os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
os.environ["XDG_CACHE_HOME"] = "/tmp"
os.environ["USER_AGENT"] = "my-huggingface-space"
import gradio as gr
from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status
import requests # 確保導入 requests
import requests_handler # 確保導入 requests_handler
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage, ImageMessage, AudioMessage, AudioSendMessage
import google.generativeai as genai
from chatbot import ChatBot
from line_bot_app import LineBotApp
from system_instruction import system_instruction
# --- 將 app 實例化移到頂層 ---
app = FastAPI()
# --- Gradio 的處理函數 ---
def greet(name):
"""Gradio UI 的簡單測試函數"""
return "Hello " + name + "!! 橘橘管理員介面測試中..."
# --- FastAPI 路由定義 ---
@app.post("/callback")
async def callback(request: Request, background_tasks: BackgroundTasks, x_line_signature: str = Header(None)):
"""
Line Webhook 回調路由。
"""
body = await request.body()
# 從 app.state 獲取 bot_app 實例
bot_app = getattr(app.state, 'bot_app', None)
if not bot_app:
print("Error: bot_app not initialized in app state.")
raise HTTPException(status_code=500, detail="Bot application not ready")
try:
# 使用背景任務處理 webhook,避免阻塞請求
background_tasks.add_task(
bot_app.handle_webhook, body.decode("utf-8"), x_line_signature
)
return {"status": "ok"}
except InvalidSignatureError:
print("Invalid signature error.")
raise HTTPException(status_code=400, detail="Invalid signature")
except Exception as e:
print(f"處理 webhook 時發生未預期錯誤: {e}")
print(traceback.format_exc())
# 即使內部處理出錯,也盡量回覆 Line "ok",避免 Line 不斷重試
return {"status": "ok"} # 或者返回 500 錯誤,取決於你的策略
@app.get("/")
async def read_root():
"""
根路由,用於確認服務正常運行。
"""
return {"status": "ok", "message": "Line Bot Service is running"}
@app.get("/healthcheck")
async def health_check():
"""
健康檢查路由。
"""
health_status = {
"status": "ok",
"details": {}
}
# 檢查 ChatBot 是否初始化 (通過 LineBotApp)
bot_app = getattr(app.state, 'bot_app', None)
if bot_app and hasattr(bot_app, 'chatbot') and bot_app.chatbot:
health_status["details"]["chatbot_initialized"] = "ok"
# 檢查 Gemini 模型是否初始化
if bot_app.chatbot.model:
health_status["details"]["gemini_chat_model"] = "ok"
# 可以加一個簡單的 API 測試,但避免重複配置
try:
# 假設 chatbot 實例已配置好 API Key
_ = bot_app.chatbot.model.generate_content("health check", generation_config={"max_output_tokens": 5}) # 限制輸出
except Exception as e:
health_status["details"]["gemini_chat_model"] = f"error: {str(e)}"
health_status["status"] = "error"
else:
health_status["details"]["gemini_chat_model"] = "error: model not loaded"
health_status["status"] = "error"
# 檢查圖片生成模型是否初始化 (如果需要)
if hasattr(bot_app.chatbot, 'image_generator') and bot_app.chatbot.image_generator and hasattr(bot_app.chatbot.image_generator, 'image_model') and bot_app.chatbot.image_generator.image_model:
health_status["details"]["gemini_image_model"] = "ok"
else:
health_status["details"]["gemini_image_model"] = "warning: model not loaded or check not implemented"
# 可以考慮不將此設為 error
else:
health_status["details"]["chatbot_initialized"] = "error"
health_status["status"] = "error"
# 檢查 Line Bot API (簡單檢查實例是否存在)
if bot_app and hasattr(bot_app, 'line_bot_api'):
health_status["details"]["line_bot_api"] = "ok (instance exists)"
else:
health_status["details"]["line_bot_api"] = "error: instance not found"
health_status["status"] = "error"
return health_status
# --- FastAPI 啟動事件 ---
@app.on_event("startup")
async def startup_event():
"""
FastAPI 啟動事件。初始化 ChatBot、Line Bot 應用和 Gradio UI。
"""
print("===== Application Startup =====")
try:
# 從環境變數獲取配置
google_api_key = os.getenv("GOOGLE_API_KEY")
line_channel_access_token = os.getenv("CHANNEL_ACCESS_TOKEN")
line_channel_secret = os.getenv("CHANNEL_SECRET")
google_search_api_key = os.getenv("GOOGLE_CUSTOM_SEARCH_API_KEY")
google_search_cse_id = os.getenv("GOOGLE_CUSTOM_SEARCH_CSE_ID")
print("Checking required environment variables...")
if not google_api_key: print("Warning: GOOGLE_API_KEY not set.")
if not line_channel_access_token: print("Warning: CHANNEL_ACCESS_TOKEN not set.")
if not line_channel_secret: print("Warning: CHANNEL_SECRET not set.")
# 根據你的應用是否嚴格需要這些來決定是否 raise ValueError
# if not all([google_api_key, line_channel_access_token, line_channel_secret]):
# raise ValueError("缺少必要的環境變數設定 (Google API Key, Line Channel Access Token, Line Channel Secret)")
print("Initializing ChatBot...")
# 初始化 ChatBot
chatbot = ChatBot(
google_api_key=google_api_key, # 傳遞 API Key
system_instruction=system_instruction,
google_search_api_key=google_search_api_key,
google_search_cse_id=google_search_cse_id
)
print("ChatBot initialized.")
print("Initializing LineBotApp...")
# 初始化 Line Bot
bot_app = LineBotApp(
channel_access_token=line_channel_access_token,
channel_secret=line_channel_secret,
chatbot=chatbot
)
print("LineBotApp initialized.")
# 將 bot_app 保存到應用狀態中 (使用頂層 app)
app.state.bot_app = bot_app
print("LineBotApp stored in app state.")
print("Setting up Gradio UI...")
# 設置 Gradio UI
gradio_ui = gr.Interface(
fn=greet,
inputs="text",
outputs="text",
flagging_options=None, # 使用 flagging_options
title="Line Bot Admin",
description="管理員介面 - 用於測試和監控 Line Bot"
)
# 使用頂層 app 掛載 Gradio
# mount_gradio_app 通常會修改傳入的 app 實例
gr.mount_gradio_app(app, gradio_ui, path="/ui")
print("Gradio UI mounted at /ui.")
print("===== Application Startup Complete =====")
except Exception as e:
print(f"啟動服務時發生錯誤: {e}")
print(traceback.format_exc())
# 啟動失敗時,可以考慮讓應用程式退出或進入錯誤狀態
# raise e # 重新拋出異常,可能會讓 uvicorn 退出
# --- 主程式入口點 ---
if __name__ == "__main__":
import uvicorn
# 從環境變數讀取端口,預設為 7860 (Hugging Face 通常用這個)
port = int(os.getenv("PORT", 7860))
print(f"Starting Uvicorn on host 0.0.0.0, port {port}")
# 使用頂層 app 啟動 uvicorn
uvicorn.run(app, host="0.0.0.0", port=port)