3v324v23 commited on
Commit
622d516
·
1 Parent(s): e93d820
Files changed (7) hide show
  1. gemini.py +63 -55
  2. multiturn.py +88 -0
  3. replybot.py +84 -0
  4. requirements.txt +8 -8
  5. system_prompt.py +88 -0
  6. with_logs.py +94 -0
  7. with_search.py +100 -0
gemini.py CHANGED
@@ -8,8 +8,11 @@ from io import BytesIO
8
  import markdown
9
  from bs4 import BeautifulSoup
10
  from flask import Flask, abort, request, send_from_directory
 
11
  from google import genai
12
  from google.genai import types
 
 
13
  from linebot.v3 import WebhookHandler
14
  from linebot.v3.exceptions import InvalidSignatureError
15
  from linebot.v3.messaging import (
@@ -21,14 +24,31 @@ from linebot.v3.messaging import (
21
  ReplyMessageRequest,
22
  TextMessage,
23
  )
24
- from linebot.v3.webhooks import ImageMessageContent, MessageEvent, TextMessageContent
 
 
 
 
 
25
  from PIL import Image
 
26
 
27
  # === 初始化 Google Gemini ===
28
  GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
29
- google_client = genai.Client(api_key=GOOGLE_API_KEY)
30
- text_system_prompt = "你是一個中文的AI助手,請用繁體中文回答"
31
- chat = google_client.chats.create(model="gemini-2.0-flash")
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  # === 初始設定 ===
34
  static_tmp_path = tempfile.gettempdir()
@@ -38,7 +58,8 @@ base_url = os.getenv("SPACE_HOST") # e.g., "your-space-name.hf.space"
38
  # === Flask 應用初始化 ===
39
  app = Flask(__name__)
40
  logging.basicConfig(
41
- level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
 
42
  )
43
  app.logger.setLevel(logging.INFO)
44
 
@@ -51,9 +72,10 @@ handler = WebhookHandler(channel_secret)
51
 
52
  # === AI Query 包裝 ===
53
  def query(payload):
54
- response = chat.send_message(f"{text_system_prompt}:{payload}")
55
  return response.text
56
 
 
57
  # === 靜態圖檔路由 ===
58
  @app.route("/images/<filename>")
59
  def serve_image(filename):
@@ -89,7 +111,7 @@ def handle_text_message(event):
89
  prompt = user_input[3:].strip()
90
  try:
91
  # 使用 Gemini 生成圖片
92
- response = google_client.models.generate_content(
93
  model="gemini-2.0-flash-exp-image-generation",
94
  contents=prompt,
95
  config=types.GenerateContentConfig(
@@ -123,7 +145,7 @@ def handle_text_message(event):
123
  ],
124
  )
125
  )
126
-
127
  except Exception as e:
128
  app.logger.error(f"Gemini API error: {e}")
129
  with ApiClient(configuration) as api_client:
@@ -170,14 +192,18 @@ def handle_image_message(event):
170
 
171
  # === 以下是解釋圖片 === #
172
  image = Image.open(tf.name)
173
- response = google_client.models.generate_content(
174
  model="gemini-2.0-flash",
 
 
 
 
 
175
  contents=[image, "用繁體中文描述這張圖片"],
176
  )
177
  app.logger.info(response.text)
178
 
179
  # === 以下是回傳圖片部分 === #
180
-
181
  with ApiClient(configuration) as api_client:
182
  line_bot_api = MessagingApi(api_client)
183
  line_bot_api.reply_message(
@@ -191,69 +217,51 @@ def handle_image_message(event):
191
  ],
192
  )
193
  )
194
- '''
195
- @handler.add(MessageEvent, message=ImageMessageContent)
196
- def handle_image_message(event):
197
 
198
- # === 以下是處理圖片回傳部分 === #
199
 
 
 
 
200
  with ApiClient(configuration) as api_client:
201
  blob_api = MessagingApiBlob(api_client)
202
  content = blob_api.get_message_content(message_id=event.message.id)
203
- image_bytes = content
204
-
205
- # Step 2:轉成 base64 字串
206
- base64_string = base64.b64encode(image_bytes).decode("utf-8")
207
 
208
- # Step 3:組成 OpenAI 的 data URI 格式
209
- data_uri = f"data:image/png;base64,{base64_string}"
210
- app.logger.info(f"Data URI: {data_uri}")
211
-
212
- # Step 4:將圖片存到本地端
213
  with tempfile.NamedTemporaryFile(
214
- dir=static_tmp_path, suffix=".jpg", delete=False
215
  ) as tf:
216
  tf.write(content)
217
  filename = os.path.basename(tf.name)
218
 
219
- image_url = f"https://{base_url}/images/{filename}"
220
-
221
- app.logger.info(f"Image URL: {image_url}")
222
-
223
- # === 以下是處理解釋圖片部分 === #
224
- response = client.responses.create(
225
- model="gpt-4.1-nano",
226
- input=[
227
- {
228
- "role": "user",
229
- "content": [
230
- {
231
- "type": "input_text",
232
- "text": "describe the image in traditional chinese",
233
- },
234
- {
235
- "type": "input_image",
236
- "image_url": data_uri,
237
- },
238
- ],
239
- }
240
- ],
241
- )
242
- app.logger.info(response.output_text)
243
 
244
- # === 以下是回傳圖片部分 === #
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
 
246
  with ApiClient(configuration) as api_client:
247
  line_bot_api = MessagingApi(api_client)
248
  line_bot_api.reply_message(
249
  ReplyMessageRequest(
250
  reply_token=event.reply_token,
251
  messages=[
252
- ImageMessage(
253
- original_content_url=image_url, preview_image_url=image_url
254
- ),
255
- TextMessage(text=response.output_text),
256
  ],
257
  )
258
- )
259
- '''
 
8
  import markdown
9
  from bs4 import BeautifulSoup
10
  from flask import Flask, abort, request, send_from_directory
11
+
12
  from google import genai
13
  from google.genai import types
14
+ from google.genai.types import Tool, GenerateContentConfig, GoogleSearch
15
+
16
  from linebot.v3 import WebhookHandler
17
  from linebot.v3.exceptions import InvalidSignatureError
18
  from linebot.v3.messaging import (
 
24
  ReplyMessageRequest,
25
  TextMessage,
26
  )
27
+ from linebot.v3.webhooks import (
28
+ ImageMessageContent,
29
+ MessageEvent,
30
+ TextMessageContent,
31
+ )
32
+
33
  from PIL import Image
34
+ from linebot.v3.webhooks import VideoMessageContent
35
 
36
  # === 初始化 Google Gemini ===
37
  GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
38
+ client = genai.Client(api_key=GOOGLE_API_KEY)
39
+
40
+ google_search_tool = Tool(
41
+ google_search=GoogleSearch()
42
+ )
43
+
44
+ chat = client.chats.create(
45
+ model="gemini-2.5-pro-preview-05-06",
46
+ config=GenerateContentConfig(
47
+ system_instruction="你是一個中文的AI助手,請用繁體中文回答",
48
+ tools=[google_search_tool],
49
+ response_modalities=["TEXT"],
50
+ )
51
+ )
52
 
53
  # === 初始設定 ===
54
  static_tmp_path = tempfile.gettempdir()
 
58
  # === Flask 應用初始化 ===
59
  app = Flask(__name__)
60
  logging.basicConfig(
61
+ level=logging.INFO,
62
+ format="%(asctime)s - %(levelname)s - %(message)s"
63
  )
64
  app.logger.setLevel(logging.INFO)
65
 
 
72
 
73
  # === AI Query 包裝 ===
74
  def query(payload):
75
+ response = chat.send_message(message=payload)
76
  return response.text
77
 
78
+
79
  # === 靜態圖檔路由 ===
80
  @app.route("/images/<filename>")
81
  def serve_image(filename):
 
111
  prompt = user_input[3:].strip()
112
  try:
113
  # 使用 Gemini 生成圖片
114
+ response = client.models.generate_content(
115
  model="gemini-2.0-flash-exp-image-generation",
116
  contents=prompt,
117
  config=types.GenerateContentConfig(
 
145
  ],
146
  )
147
  )
148
+
149
  except Exception as e:
150
  app.logger.error(f"Gemini API error: {e}")
151
  with ApiClient(configuration) as api_client:
 
192
 
193
  # === 以下是解釋圖片 === #
194
  image = Image.open(tf.name)
195
+ response = client.models.generate_content(
196
  model="gemini-2.0-flash",
197
+ config=types.GenerateContentConfig(
198
+ system_instruction="你是一個資深的面相命理師,如果有人上手掌的照片,就幫他解釋手相,如果上傳正面臉部的照片,就幫他解釋面相,照片要先去背,如果是一般的照片,就正常說明照片不用算命,請用繁體中文回答",
199
+ response_modalities=["TEXT"],
200
+ tools=[google_search_tool],
201
+ ),
202
  contents=[image, "用繁體中文描述這張圖片"],
203
  )
204
  app.logger.info(response.text)
205
 
206
  # === 以下是回傳圖片部分 === #
 
207
  with ApiClient(configuration) as api_client:
208
  line_bot_api = MessagingApi(api_client)
209
  line_bot_api.reply_message(
 
217
  ],
218
  )
219
  )
 
 
 
220
 
221
+ # === 處理影片訊息 ===
222
 
223
+ @handler.add(MessageEvent, message=VideoMessageContent)
224
+ def handle_video_message(event):
225
+ # 下載影片內容
226
  with ApiClient(configuration) as api_client:
227
  blob_api = MessagingApiBlob(api_client)
228
  content = blob_api.get_message_content(message_id=event.message.id)
 
 
 
 
229
 
230
+ # 儲存影片到本地
 
 
 
 
231
  with tempfile.NamedTemporaryFile(
232
+ dir=static_tmp_path, suffix=".mp4", delete=False
233
  ) as tf:
234
  tf.write(content)
235
  filename = os.path.basename(tf.name)
236
 
237
+ video_url = f"https://{base_url}/images/{filename}"
238
+ app.logger.info(f"Video URL: {video_url}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
+ # 影片說明
241
+ try:
242
+ response = client.models.generate_content(
243
+ model="gemini-2.0-flash",
244
+ config=types.GenerateContentConfig(
245
+ system_instruction="你是一個專業的影片解說員,請用繁體中文簡要說明這段影片的內容。",
246
+ response_modalities=["TEXT"],
247
+ tools=[google_search_tool],
248
+ ),
249
+ contents=[{"mime_type": "video/mp4", "data": content}, "用繁體中文描述這段影片"],
250
+ )
251
+ description = response.text
252
+ except Exception as e:
253
+ app.logger.error(f"Gemini API error (video): {e}")
254
+ description = "抱歉,無法解釋這段影片內容。"
255
 
256
+ # 回傳影片連結與說明
257
  with ApiClient(configuration) as api_client:
258
  line_bot_api = MessagingApi(api_client)
259
  line_bot_api.reply_message(
260
  ReplyMessageRequest(
261
  reply_token=event.reply_token,
262
  messages=[
263
+ TextMessage(text=f"影片連結:{video_url}"),
264
+ TextMessage(text=description),
 
 
265
  ],
266
  )
267
+ )
 
multiturn.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 東吳大學資料系 2025 LINEBOT
3
+ """
4
+
5
+ import os
6
+
7
+ from flask import Flask, abort, request
8
+ from bs4 import BeautifulSoup
9
+ import markdown
10
+
11
+ from google import genai
12
+ from google.genai import types # 加入system prompot所需的types模組
13
+
14
+ from linebot.v3 import WebhookHandler
15
+ from linebot.v3.exceptions import InvalidSignatureError
16
+ from linebot.v3.messaging import (
17
+ ApiClient,
18
+ Configuration,
19
+ MessagingApi,
20
+ ReplyMessageRequest,
21
+ TextMessage,
22
+ )
23
+ from linebot.v3.webhooks import MessageEvent, TextMessageContent
24
+
25
+
26
+ # Initialize Google Gemini
27
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
28
+ client = genai.Client(api_key=GOOGLE_API_KEY)
29
+ chat = client.chats.create(model="gemini-2.0-flash",
30
+ config=types.GenerateContentConfig(
31
+ system_instruction="你是一個中文的AI助手,請用繁體中文回答"
32
+ )
33
+ )
34
+
35
+ # Initialize Flask app
36
+ app = Flask(__name__)
37
+ channel_secret = os.getenv("YOUR_CHANNEL_SECRET")
38
+ channel_access_token = os.getenv("YOUR_CHANNEL_ACCESS_TOKEN")
39
+ configuration = Configuration(access_token=channel_access_token)
40
+ handler = WebhookHandler(channel_secret)
41
+
42
+
43
+ def query(payload: str) -> str:
44
+ """Send a prompt to Gemini and return the response text."""
45
+ response = chat.send_message(message=payload)
46
+ return response.text
47
+
48
+
49
+ @app.route("/", methods=["GET"])
50
+ def home():
51
+ """Health check endpoint."""
52
+ return {"message": "Line Webhook Server"}
53
+
54
+
55
+ @app.route("/", methods=["POST"])
56
+ def callback():
57
+ """Handle incoming webhook from LINE."""
58
+ signature = request.headers.get("X-Line-Signature")
59
+ body = request.get_data(as_text=True)
60
+ app.logger.info("Request body: %s", body)
61
+
62
+ try:
63
+ handler.handle(body, signature)
64
+ except InvalidSignatureError:
65
+ app.logger.warning(
66
+ "Invalid signature. Please check channel credentials."
67
+ )
68
+ abort(400)
69
+
70
+ return "OK"
71
+
72
+
73
+ @handler.add(MessageEvent, message=TextMessageContent)
74
+ def handle_text_message(event):
75
+ """Handle incoming text message event."""
76
+ user_input = event.message.text.strip()
77
+ response_text = query(user_input)
78
+ html_msg = markdown.markdown(response_text)
79
+ soup = BeautifulSoup(html_msg, "html.parser")
80
+
81
+ with ApiClient(configuration) as api_client:
82
+ line_bot_api = MessagingApi(api_client)
83
+ line_bot_api.reply_message_with_http_info(
84
+ ReplyMessageRequest(
85
+ reply_token=event.reply_token,
86
+ messages=[TextMessage(text=soup.get_text())],
87
+ )
88
+ )
replybot.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 東吳大學資料系 2025 LINEBOT
3
+ """
4
+
5
+ import os
6
+
7
+ from flask import Flask, abort, request
8
+ from bs4 import BeautifulSoup
9
+ import markdown
10
+
11
+ from google import genai
12
+ from linebot.v3 import WebhookHandler
13
+ from linebot.v3.exceptions import InvalidSignatureError
14
+ from linebot.v3.messaging import (
15
+ ApiClient,
16
+ Configuration,
17
+ MessagingApi,
18
+ ReplyMessageRequest,
19
+ TextMessage,
20
+ )
21
+ from linebot.v3.webhooks import MessageEvent, TextMessageContent
22
+
23
+
24
+ # Initialize Google Gemini
25
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
26
+ client = genai.Client(api_key=GOOGLE_API_KEY)
27
+
28
+ # Initialize Flask app
29
+ app = Flask(__name__)
30
+ channel_secret = os.getenv("YOUR_CHANNEL_SECRET")
31
+ channel_access_token = os.getenv("YOUR_CHANNEL_ACCESS_TOKEN")
32
+ configuration = Configuration(access_token=channel_access_token)
33
+ handler = WebhookHandler(channel_secret)
34
+
35
+
36
+ def query(payload: str) -> str:
37
+ """Send a prompt to Gemini and return the response text."""
38
+ response = client.models.generate_content(
39
+ model="gemini-2.0-flash",
40
+ contents=[payload]
41
+ )
42
+ return response.text
43
+
44
+
45
+ @app.route("/", methods=["GET"])
46
+ def home():
47
+ """Health check endpoint."""
48
+ return {"message": "Line Webhook Server"}
49
+
50
+
51
+ @app.route("/", methods=["POST"])
52
+ def callback():
53
+ """Handle incoming webhook from LINE."""
54
+ signature = request.headers.get("X-Line-Signature")
55
+ body = request.get_data(as_text=True)
56
+ app.logger.info("Request body: %s", body)
57
+
58
+ try:
59
+ handler.handle(body, signature)
60
+ except InvalidSignatureError:
61
+ app.logger.warning(
62
+ "Invalid signature. Please check channel credentials."
63
+ )
64
+ abort(400)
65
+
66
+ return "OK"
67
+
68
+
69
+ @handler.add(MessageEvent, message=TextMessageContent)
70
+ def handle_text_message(event):
71
+ """Handle incoming text message event."""
72
+ user_input = event.message.text.strip()
73
+ response_text = query(user_input)
74
+ html_msg = markdown.markdown(response_text)
75
+ soup = BeautifulSoup(html_msg, "html.parser")
76
+
77
+ with ApiClient(configuration) as api_client:
78
+ line_bot_api = MessagingApi(api_client)
79
+ line_bot_api.reply_message_with_http_info(
80
+ ReplyMessageRequest(
81
+ reply_token=event.reply_token,
82
+ messages=[TextMessage(text=soup.get_text())],
83
+ )
84
+ )
requirements.txt CHANGED
@@ -1,8 +1,8 @@
1
- Flask
2
- gunicorn
3
- line-bot-sdk
4
- markdown
5
- beautifulsoup4
6
- openai
7
- google-genai
8
- Pillow
 
1
+ Flask # 執行LINE Bot Webhook Server需要的套件
2
+ gunicorn # 執行正式的web server,讓他來反向代理執行中的Flask
3
+ line-bot-sdk # LINE Bot的必須安裝項
4
+ markdown # 讓輸出格式漂亮,並且解析markdown文件用的套件
5
+ beautifulsoup4 # 讓輸出格式漂亮的套件
6
+ openai # 使用OpenAI的模型(此次課程為示範不使用)
7
+ google-genai # 使用Google Gemini API的套件
8
+ Pillow # 處理圖片
system_prompt.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 東吳大學資料系 2025 LINEBOT
3
+ """
4
+
5
+ import os
6
+
7
+ from flask import Flask, abort, request
8
+ from bs4 import BeautifulSoup
9
+ import markdown
10
+
11
+ from google import genai
12
+ from google.genai import types # 加入system prompot所需的types模組
13
+
14
+ from linebot.v3 import WebhookHandler
15
+ from linebot.v3.exceptions import InvalidSignatureError
16
+ from linebot.v3.messaging import (
17
+ ApiClient,
18
+ Configuration,
19
+ MessagingApi,
20
+ ReplyMessageRequest,
21
+ TextMessage,
22
+ )
23
+ from linebot.v3.webhooks import MessageEvent, TextMessageContent
24
+
25
+
26
+ # Initialize Google Gemini
27
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
28
+ client = genai.Client(api_key=GOOGLE_API_KEY)
29
+
30
+ # Initialize Flask app
31
+ app = Flask(__name__)
32
+ channel_secret = os.getenv("YOUR_CHANNEL_SECRET")
33
+ channel_access_token = os.getenv("YOUR_CHANNEL_ACCESS_TOKEN")
34
+ configuration = Configuration(access_token=channel_access_token)
35
+ handler = WebhookHandler(channel_secret)
36
+
37
+
38
+ def query(payload: str) -> str:
39
+ """Send a prompt to Gemini and return the response text."""
40
+ response = client.models.generate_content(
41
+ model="gemini-2.0-flash",
42
+ config=types.GenerateContentConfig(
43
+ system_instruction="你是一個中文的AI助手,請用繁體中文回答"),
44
+ contents=payload
45
+ )
46
+ return response.text
47
+
48
+
49
+ @app.route("/", methods=["GET"])
50
+ def home():
51
+ """Health check endpoint."""
52
+ return {"message": "Line Webhook Server"}
53
+
54
+
55
+ @app.route("/", methods=["POST"])
56
+ def callback():
57
+ """Handle incoming webhook from LINE."""
58
+ signature = request.headers.get("X-Line-Signature")
59
+ body = request.get_data(as_text=True)
60
+ app.logger.info("Request body: %s", body)
61
+
62
+ try:
63
+ handler.handle(body, signature)
64
+ except InvalidSignatureError:
65
+ app.logger.warning(
66
+ "Invalid signature. Please check channel credentials."
67
+ )
68
+ abort(400)
69
+
70
+ return "OK"
71
+
72
+
73
+ @handler.add(MessageEvent, message=TextMessageContent)
74
+ def handle_text_message(event):
75
+ """Handle incoming text message event."""
76
+ user_input = event.message.text.strip()
77
+ response_text = query(user_input)
78
+ html_msg = markdown.markdown(response_text)
79
+ soup = BeautifulSoup(html_msg, "html.parser")
80
+
81
+ with ApiClient(configuration) as api_client:
82
+ line_bot_api = MessagingApi(api_client)
83
+ line_bot_api.reply_message_with_http_info(
84
+ ReplyMessageRequest(
85
+ reply_token=event.reply_token,
86
+ messages=[TextMessage(text=soup.get_text())],
87
+ )
88
+ )
with_logs.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 東吳大學資料系 2025 LINEBOT
3
+ """
4
+
5
+ import logging
6
+ import os
7
+
8
+ from flask import Flask, abort, request
9
+ from bs4 import BeautifulSoup
10
+ import markdown
11
+
12
+ from google import genai
13
+ from google.genai import types # 加入system prompot所需的types模組
14
+
15
+ from linebot.v3 import WebhookHandler
16
+ from linebot.v3.exceptions import InvalidSignatureError
17
+ from linebot.v3.messaging import (
18
+ ApiClient,
19
+ Configuration,
20
+ MessagingApi,
21
+ ReplyMessageRequest,
22
+ TextMessage,
23
+ )
24
+ from linebot.v3.webhooks import MessageEvent, TextMessageContent
25
+
26
+
27
+ # Initialize Google Gemini
28
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
29
+ client = genai.Client(api_key=GOOGLE_API_KEY)
30
+ chat = client.chats.create(model="gemini-2.0-flash")
31
+
32
+ # Initialize Flask app
33
+ app = Flask(__name__)
34
+ logging.basicConfig(
35
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
36
+ )
37
+ app.logger.setLevel(logging.INFO)
38
+
39
+ channel_secret = os.getenv("YOUR_CHANNEL_SECRET")
40
+ channel_access_token = os.getenv("YOUR_CHANNEL_ACCESS_TOKEN")
41
+ configuration = Configuration(access_token=channel_access_token)
42
+ handler = WebhookHandler(channel_secret)
43
+
44
+
45
+ def query(payload: str) -> str:
46
+ """Send a prompt to Gemini and return the response text."""
47
+ response = chat.send_message(
48
+ config=types.GenerateContentConfig(
49
+ system_instruction="你是一個中文的AI助手,請用繁體中文回答"),
50
+ message=payload
51
+ )
52
+ return response.text
53
+
54
+
55
+ @app.route("/", methods=["GET"])
56
+ def home():
57
+ """Health check endpoint."""
58
+ return {"message": "Line Webhook Server"}
59
+
60
+
61
+ @app.route("/", methods=["POST"])
62
+ def callback():
63
+ """Handle incoming webhook from LINE."""
64
+ signature = request.headers.get("X-Line-Signature")
65
+ body = request.get_data(as_text=True)
66
+ app.logger.info("Request body: %s", body)
67
+
68
+ try:
69
+ handler.handle(body, signature)
70
+ except InvalidSignatureError:
71
+ app.logger.warning(
72
+ "Invalid signature. Please check channel credentials."
73
+ )
74
+ abort(400)
75
+
76
+ return "OK"
77
+
78
+
79
+ @handler.add(MessageEvent, message=TextMessageContent)
80
+ def handle_text_message(event):
81
+ """Handle incoming text message event."""
82
+ user_input = event.message.text.strip()
83
+ response_text = query(user_input)
84
+ html_msg = markdown.markdown(response_text)
85
+ soup = BeautifulSoup(html_msg, "html.parser")
86
+
87
+ with ApiClient(configuration) as api_client:
88
+ line_bot_api = MessagingApi(api_client)
89
+ line_bot_api.reply_message_with_http_info(
90
+ ReplyMessageRequest(
91
+ reply_token=event.reply_token,
92
+ messages=[TextMessage(text=soup.get_text())],
93
+ )
94
+ )
with_search.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 東吳大學資料系 2025 LINEBOT
3
+ """
4
+
5
+ import logging
6
+ import os
7
+
8
+ from flask import Flask, abort, request
9
+ from bs4 import BeautifulSoup
10
+ import markdown
11
+
12
+ from google import genai
13
+ from google.genai import types # 加入system prompot所需的types模組
14
+ from google.genai.types import Tool, GenerateContentConfig, GoogleSearch
15
+
16
+ google_search_tool = Tool(
17
+ google_search = GoogleSearch()
18
+ )
19
+
20
+ from linebot.v3 import WebhookHandler
21
+ from linebot.v3.exceptions import InvalidSignatureError
22
+ from linebot.v3.messaging import (
23
+ ApiClient,
24
+ Configuration,
25
+ MessagingApi,
26
+ ReplyMessageRequest,
27
+ TextMessage,
28
+ )
29
+ from linebot.v3.webhooks import MessageEvent, TextMessageContent
30
+
31
+
32
+ # Initialize Google Gemini
33
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
34
+ client = genai.Client(api_key=GOOGLE_API_KEY)
35
+ chat = client.chats.create(model="gemini-2.0-flash",
36
+ config=GenerateContentConfig(
37
+ system_instruction="你是一個中文的AI助手,請用繁體中文回答",
38
+ tools=[google_search_tool],
39
+ response_modalities=["TEXT"],
40
+ ))
41
+
42
+ # Initialize Flask app
43
+ app = Flask(__name__)
44
+ logging.basicConfig(
45
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
46
+ )
47
+ app.logger.setLevel(logging.INFO)
48
+
49
+ channel_secret = os.getenv("YOUR_CHANNEL_SECRET")
50
+ channel_access_token = os.getenv("YOUR_CHANNEL_ACCESS_TOKEN")
51
+ configuration = Configuration(access_token=channel_access_token)
52
+ handler = WebhookHandler(channel_secret)
53
+
54
+
55
+ def query(payload: str) -> str:
56
+ """Send a prompt to Gemini and return the response text."""
57
+ response = chat.send_message(message=payload)
58
+ return response.text
59
+
60
+
61
+ @app.route("/", methods=["GET"])
62
+ def home():
63
+ """Health check endpoint."""
64
+ return {"message": "Line Webhook Server"}
65
+
66
+
67
+ @app.route("/", methods=["POST"])
68
+ def callback():
69
+ """Handle incoming webhook from LINE."""
70
+ signature = request.headers.get("X-Line-Signature")
71
+ body = request.get_data(as_text=True)
72
+ app.logger.info("Request body: %s", body)
73
+
74
+ try:
75
+ handler.handle(body, signature)
76
+ except InvalidSignatureError:
77
+ app.logger.warning(
78
+ "Invalid signature. Please check channel credentials."
79
+ )
80
+ abort(400)
81
+
82
+ return "OK"
83
+
84
+
85
+ @handler.add(MessageEvent, message=TextMessageContent)
86
+ def handle_text_message(event):
87
+ """Handle incoming text message event."""
88
+ user_input = event.message.text.strip()
89
+ response_text = query(user_input)
90
+ html_msg = markdown.markdown(response_text)
91
+ soup = BeautifulSoup(html_msg, "html.parser")
92
+
93
+ with ApiClient(configuration) as api_client:
94
+ line_bot_api = MessagingApi(api_client)
95
+ line_bot_api.reply_message_with_http_info(
96
+ ReplyMessageRequest(
97
+ reply_token=event.reply_token,
98
+ messages=[TextMessage(text=soup.get_text())],
99
+ )
100
+ )