AlanRex commited on
Commit
a9eda70
·
verified ·
1 Parent(s): 95098ec

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +107 -92
main.py CHANGED
@@ -1,78 +1,79 @@
1
- import json, os, glob, pathlib
2
  from fastapi.middleware.cors import CORSMiddleware
3
- from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status
4
  from google import genai
5
  from linebot import LineBotApi, WebhookHandler
6
  from linebot.exceptions import InvalidSignatureError
7
- from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage, AudioMessage
8
  import PyPDF2
9
  import logging
10
 
11
- # 設定日誌
12
  logging.basicConfig(level=logging.INFO)
13
  logger = logging.getLogger(__name__)
14
 
15
- # 檢查並設定 Google AI API 金鑰
16
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
17
  if not GOOGLE_API_KEY:
18
- logger.error("錯誤:未設定 GOOGLE_API_KEY")
19
- raise ValueError("請在環境變數中設定 GOOGLE_API_KEY")
20
 
21
  client = genai.Client(api_key=GOOGLE_API_KEY)
22
 
23
- # 讀取 PDF 內容
24
  files = glob.glob('docs/*.pdf')
25
  pdf_content = ''
26
 
27
- if not files:
28
- logger.warning("警告:docs/ 資料夾中找不到 PDF 檔案")
29
- else:
30
- logger.info(f"找到 {len(files)} 個 PDF 檔案")
31
  for filename in files:
32
  try:
33
  with open(filename, 'rb') as pdf_file:
34
  pdf_reader = PyPDF2.PdfReader(pdf_file)
35
- for i in range(len(pdf_reader.pages)):
36
- page = pdf_reader.pages[i]
37
  pdf_content += page.extract_text()
38
- logger.info(f"成功讀取: {filename}")
39
  except Exception as e:
40
- logger.error(f"讀取 {filename} 失敗: {str(e)}")
41
-
42
- # 檢查 PDF 內容長度
43
- logger.info(f"PDF 內容總長度: {len(pdf_content)} 字元")
44
- if len(pdf_content) > 100000: # 如果內容過長
45
- logger.warning("PDF 內容過長,可能影響 API 效能")
46
-
47
- # 設定生成文字的參數 + 角色扮演
48
- system_instruction = pdf_content + "\n" + "您是一位問答助手。請僅限使用以上提供的內容來回答問題,如果字數太多,請用列表方式呈現,如果您不知道答案,請說聯繫服務專員,不要捏造答案。"
49
- thinking_config = genai.types.ThinkingConfig(thinking_budget=0)
50
- generation_config = genai.types.GenerateContentConfig(
51
- max_output_tokens=3000,
52
- temperature=0.1,
53
- top_p=0.2,
54
- thinking_config=thinking_config,
55
- system_instruction=system_instruction
56
- )
57
-
58
- # 設定 Line Bot 的 API 金鑰和秘密金鑰
59
- CHANNEL_ACCESS_TOKEN = os.getenv("CHANNEL_ACCESS_TOKEN")
60
- CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
61
-
62
- if not CHANNEL_ACCESS_TOKEN or not CHANNEL_SECRET:
63
- logger.error("錯誤:未設定 LINE Bot 憑證")
64
- raise ValueError("請設定 CHANNEL_ACCESS_TOKEN 和 CHANNEL_SECRET")
65
-
66
- line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
67
- line_handler = WebhookHandler(CHANNEL_SECRET)
68
-
69
- # 設定是否正在與使用者交談
70
- working_status = os.getenv("DEFALUT_TALKING", default="true").lower() == "true"
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- # 建立 FastAPI 應用程式
73
  app = FastAPI()
74
-
75
- # 設定 CORS
76
  app.add_middleware(
77
  CORSMiddleware,
78
  allow_origins=["*"],
@@ -83,79 +84,93 @@ app.add_middleware(
83
 
84
  @app.get("/")
85
  def root():
86
- return {"title": "Line Bot", "status": "running"}
 
 
 
 
87
 
88
  @app.post("/webhook")
89
- async def webhook(
90
- request: Request,
91
- background_tasks: BackgroundTasks,
92
- x_line_signature=Header(None),
93
- ):
94
  body = await request.body()
95
  try:
96
- background_tasks.add_task(
97
- line_handler.handle, body.decode("utf-8"), x_line_signature
98
- )
99
  except InvalidSignatureError:
100
- logger.error("無效的簽章")
101
  raise HTTPException(status_code=400, detail="Invalid signature")
102
  return "ok"
103
 
104
  @line_handler.add(MessageEvent, message=TextMessage)
105
  def handle_message(event):
106
- global working_status
107
-
108
  if event.type != "message" or event.message.type != "text":
109
- line_bot_api.reply_message(
110
- event.reply_token,
111
- TextSendMessage(text="錯誤:訊息類型不支援")
112
- )
113
  return
114
 
115
  if event.message.text == "再見":
116
- line_bot_api.reply_message(
117
- event.reply_token,
118
- TextSendMessage(text="Bye!")
119
- )
120
  return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  if working_status:
123
  try:
124
  prompt = event.message.text
125
- logger.info(f"收到訊息: {prompt}")
126
 
127
- # 呼叫 Gemini API
128
- response = client.models.generate_content(
129
- model="gemini-2.0-flash", # 使用最新的模型
130
- contents=prompt,
131
- config=generation_config
132
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- # 檢查回應
135
  if response and response.text:
136
  out = response.text
137
- logger.info("Gemini 回應成功")
138
  else:
139
- out = "Gemini沒答案!將由專員服務!"
140
- logger.warning("Gemini 回應為空")
141
 
142
  except Exception as e:
143
- # 詳細記錄錯誤
144
  error_msg = str(e)
145
- logger.error(f"Gemini API 錯誤: {error_msg}")
146
 
147
- # 根據錯誤類型給予不同提示
148
- if "quota" in error_msg.lower() or "limit" in error_msg.lower():
149
- out = "API 使用額度已達上限,請稍後再試!"
150
- elif "key" in error_msg.lower() or "auth" in error_msg.lower():
151
- out = "API 金鑰驗證失敗,請聯繫服務專員!"
152
  else:
153
- out = f"Gemini執行出錯!請聯繫服務專員。錯誤:{error_msg[:50]}"
154
 
155
- line_bot_api.reply_message(
156
- event.reply_token,
157
- TextSendMessage(text=out)
158
- )
159
 
160
  if __name__ == "__main__":
161
  import uvicorn
 
1
+ import json, os, glob, time
2
  from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException
4
  from google import genai
5
  from linebot import LineBotApi, WebhookHandler
6
  from linebot.exceptions import InvalidSignatureError
7
+ from linebot.models import MessageEvent, TextMessage, TextSendMessage
8
  import PyPDF2
9
  import logging
10
 
 
11
  logging.basicConfig(level=logging.INFO)
12
  logger = logging.getLogger(__name__)
13
 
 
14
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
15
  if not GOOGLE_API_KEY:
16
+ raise ValueError("請設定 GOOGLE_API_KEY")
 
17
 
18
  client = genai.Client(api_key=GOOGLE_API_KEY)
19
 
20
+ # 讀取 PDF
21
  files = glob.glob('docs/*.pdf')
22
  pdf_content = ''
23
 
24
+ if files:
25
+ logger.info(f"找到 {len(files)} PDF")
 
 
26
  for filename in files:
27
  try:
28
  with open(filename, 'rb') as pdf_file:
29
  pdf_reader = PyPDF2.PdfReader(pdf_file)
30
+ for page in pdf_reader.pages:
 
31
  pdf_content += page.extract_text()
 
32
  except Exception as e:
33
+ logger.error(f"讀取失敗: {e}")
34
+
35
+ logger.info(f"PDF 長度: {len(pdf_content)} 字元")
36
+
37
+ # 限制長度
38
+ if len(pdf_content) > 50000:
39
+ pdf_content = pdf_content[:50000]
40
+
41
+ # 使用 Context Caching 儲存 PDF 內容
42
+ cache_name = None
43
+ try:
44
+ # 建立 cached content
45
+ cached_content = client.caches.create(
46
+ model="gemini-1.5-flash-001",
47
+ contents=[
48
+ genai.types.Content(
49
+ role="user",
50
+ parts=[genai.types.Part(text=f"""以下是參考資料,請記住這些內容:
51
+
52
+ {pdf_content}
53
+
54
+ 你是問答助手,使用以上資料回答問題。規則:
55
+ 1. 只用提供的資料回答
56
+ 2. 不知道就說「請聯繫服務專員」
57
+ 3. 答案簡潔明瞭""")]
58
+ )
59
+ ],
60
+ ttl="300s", # 快取 5 分鐘
61
+ )
62
+ cache_name = cached_content.name
63
+ logger.info(f"成功建立 Cache: {cache_name}")
64
+ except Exception as e:
65
+ logger.error(f"無法建立 Cache: {e}")
66
+ logger.info("將使用一般模式(不使用 Cache)")
67
+
68
+ # LINE Bot 設定
69
+ line_bot_api = LineBotApi(os.getenv("CHANNEL_ACCESS_TOKEN"))
70
+ line_handler = WebhookHandler(os.getenv("CHANNEL_SECRET"))
71
+ working_status = True
72
+
73
+ last_request_time = {}
74
+ REQUEST_COOLDOWN = 3
75
 
 
76
  app = FastAPI()
 
 
77
  app.add_middleware(
78
  CORSMiddleware,
79
  allow_origins=["*"],
 
84
 
85
  @app.get("/")
86
  def root():
87
+ return {
88
+ "status": "running",
89
+ "cache_enabled": cache_name is not None,
90
+ "pdf_length": len(pdf_content)
91
+ }
92
 
93
  @app.post("/webhook")
94
+ async def webhook(request: Request, background_tasks: BackgroundTasks, x_line_signature=Header(None)):
 
 
 
 
95
  body = await request.body()
96
  try:
97
+ background_tasks.add_task(line_handler.handle, body.decode("utf-8"), x_line_signature)
 
 
98
  except InvalidSignatureError:
 
99
  raise HTTPException(status_code=400, detail="Invalid signature")
100
  return "ok"
101
 
102
  @line_handler.add(MessageEvent, message=TextMessage)
103
  def handle_message(event):
 
 
104
  if event.type != "message" or event.message.type != "text":
 
 
 
 
105
  return
106
 
107
  if event.message.text == "再見":
108
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text="Bye!"))
 
 
 
109
  return
110
+
111
+ # 速率限制
112
+ user_id = event.source.user_id
113
+ current_time = time.time()
114
+
115
+ if user_id in last_request_time:
116
+ elapsed = current_time - last_request_time[user_id]
117
+ if elapsed < REQUEST_COOLDOWN:
118
+ line_bot_api.reply_message(
119
+ event.reply_token,
120
+ TextSendMessage(text=f"請稍等 {REQUEST_COOLDOWN - int(elapsed)} 秒")
121
+ )
122
+ return
123
+
124
+ last_request_time[user_id] = current_time
125
 
126
  if working_status:
127
  try:
128
  prompt = event.message.text
129
+ logger.info(f"提問: {prompt[:50]}")
130
 
131
+ # 如果有 Cache,使用 Cache
132
+ if cache_name:
133
+ response = client.models.generate_content(
134
+ model="gemini-1.5-flash-001",
135
+ contents=prompt,
136
+ config=genai.types.GenerateContentConfig(
137
+ max_output_tokens=1000,
138
+ temperature=0.1,
139
+ cached_content=cache_name
140
+ )
141
+ )
142
+ else:
143
+ # 沒有 Cache,使用一般模式
144
+ response = client.models.generate_content(
145
+ model="gemini-1.5-flash",
146
+ contents=prompt,
147
+ config=genai.types.GenerateContentConfig(
148
+ max_output_tokens=1000,
149
+ temperature=0.1,
150
+ system_instruction=f"""你是問答助手。參考資料:
151
+ {pdf_content[:10000]}
152
+ 規則:只用資料回答,不知道就說聯繫專員。"""
153
+ )
154
+ )
155
 
 
156
  if response and response.text:
157
  out = response.text
158
+ logger.info("回應成功")
159
  else:
160
+ out = "無法回答,請聯繫服務專員。"
 
161
 
162
  except Exception as e:
 
163
  error_msg = str(e)
164
+ logger.error(f"錯誤: {error_msg}")
165
 
166
+ if "quota" in error_msg.lower() or "resource_exhausted" in error_msg.lower():
167
+ out = "系統忙碌,請稍後再試。"
168
+ elif "429" in error_msg:
169
+ out = "請求過於頻繁,請稍候。"
 
170
  else:
171
+ out = "系統錯誤,請聯繫服務專員。"
172
 
173
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))
 
 
 
174
 
175
  if __name__ == "__main__":
176
  import uvicorn