JasonFinley0821 commited on
Commit
e060de2
·
1 Parent(s): 2be2326

feat : linebot add agents

Browse files
Files changed (2) hide show
  1. app.py +0 -5
  2. services/linebot.py +128 -17
app.py CHANGED
@@ -3,7 +3,6 @@ from fastapi.responses import JSONResponse
3
  from fastapi.staticfiles import StaticFiles
4
  from fastapi.middleware.cors import CORSMiddleware # 匯入 FastAPI 的 CORS 中介軟體
5
  import requests
6
- from collections import defaultdict # 匯入 defaultdict,用於建立預設值的字典
7
  from typing import Annotated # 推薦用於 Pydantic v2+
8
  from linebot.exceptions import InvalidSignatureError # 匯入 Line 簽章無效的例外
9
  from services.linebot import line_handle
@@ -27,10 +26,6 @@ os.makedirs(STATIC_DIR, exist_ok=True)
27
 
28
  load_dotenv()
29
 
30
- # 使用 defaultdict 模擬用戶訊息歷史存儲
31
- # 鍵(key)為 user_id,值(value)為一個儲存訊息的列表(list)
32
- user_message_history = defaultdict(list)
33
-
34
  # =====================
35
  # 初始化 FastAPI
36
  # =====================
 
3
  from fastapi.staticfiles import StaticFiles
4
  from fastapi.middleware.cors import CORSMiddleware # 匯入 FastAPI 的 CORS 中介軟體
5
  import requests
 
6
  from typing import Annotated # 推薦用於 Pydantic v2+
7
  from linebot.exceptions import InvalidSignatureError # 匯入 Line 簽章無效的例外
8
  from services.linebot import line_handle
 
26
 
27
  load_dotenv()
28
 
 
 
 
 
29
  # =====================
30
  # 初始化 FastAPI
31
  # =====================
services/linebot.py CHANGED
@@ -1,6 +1,7 @@
1
  import os
2
  from dotenv import load_dotenv # 匯入 dotenv 以載入 .env 環境變數檔案
3
  from typing import Optional
 
4
  from linebot import LineBotApi, WebhookHandler # 匯入 Line Bot SDK
5
  from linebot.exceptions import InvalidSignatureError # 匯入 Line 簽章無效的例外
6
  from linebot.models import ( # 匯入 Line Bot 的各種訊息模型
@@ -12,6 +13,8 @@ from linebot.models import ( # 匯入 Line Bot 的各種訊息模型
12
  FollowEvent
13
  )
14
 
 
 
15
  load_dotenv()
16
  # --- 設定和初始化 ---
17
  # 設置 Line Bot 的 API 金鑰和秘密金鑰 (從環境變數讀取)
@@ -26,32 +29,140 @@ except Exception as e:
26
  line_bot_api = None
27
  line_handler = None
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  # --- 事件處理器定義 ---
31
 
32
  # 1. 處理所有接收到的文字訊息事件
33
- @line_handler.add(MessageEvent, message=TextMessage)
34
- def handle_text_message(event: MessageEvent):
35
  """
36
  處理接收到的文字訊息,並進行回覆。
37
  """
38
- if event.source.user_id:
39
- user_id = event.source.user_id
40
- text = event.message.text
41
- print(f"收到來自用戶 {user_id} 的訊息: {text}")
42
 
43
- # 這裡可以加入您的核心邏輯,例如:
44
- # - 呼叫 LLM Agent 進行對話和工具使用
45
- # - 處理特定關鍵字
46
 
47
- # 簡單的回覆範例
48
- reply_text = f"你說了: {text}。我準備好執行你的指令了!"
49
-
50
- # 傳送回覆訊息
51
- line_bot_api.reply_message(
52
- event.reply_token,
53
- TextSendMessage(text=reply_text)
54
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
 
57
  # 2. 處理關注 (Follow) 事件
 
1
  import os
2
  from dotenv import load_dotenv # 匯入 dotenv 以載入 .env 環境變數檔案
3
  from typing import Optional
4
+ from collections import defaultdict # 匯入 defaultdict,用於建立預設值的字典
5
  from linebot import LineBotApi, WebhookHandler # 匯入 Line Bot SDK
6
  from linebot.exceptions import InvalidSignatureError # 匯入 Line 簽章無效的例外
7
  from linebot.models import ( # 匯入 Line Bot 的各種訊息模型
 
13
  FollowEvent
14
  )
15
 
16
+ from agents import agent_executor
17
+
18
  load_dotenv()
19
  # --- 設定和初始化 ---
20
  # 設置 Line Bot 的 API 金鑰和秘密金鑰 (從環境變數讀取)
 
29
  line_bot_api = None
30
  line_handler = None
31
 
32
+ # 使用 defaultdict 模擬用戶訊息歷史存儲
33
+ # 鍵(key)為 user_id,值(value)為一個儲存訊息的列表(list)
34
+ user_message_history = defaultdict(list)
35
+
36
+ def get_image_url_from_line(message_id):
37
+ """
38
+ 從 Line 訊息 ID 獲取圖片內容並儲存到暫存檔案。
39
+
40
+ Args:
41
+ message_id: Line 訊息的 ID。
42
+
43
+ Returns:
44
+ 成功時回傳圖片儲存的本地路徑,失敗時回傳 None。
45
+ """
46
+ try:
47
+ # 透過 Line Bot API 獲取訊息內容
48
+ message_content = line_bot_api.get_message_content(message_id)
49
+ # 定義暫存檔案路徑
50
+ file_path = f"/tmp/{message_id}.png"
51
+ # 將圖片內容以二進位寫入模式寫入檔案
52
+ with open(file_path, "wb") as f:
53
+ for chunk in message_content.iter_content():
54
+ f.write(chunk)
55
+ print(f"✅ 圖片成功儲存到:{file_path}")
56
+ return file_path
57
+ except Exception as e:
58
+ print(f"❌ 圖片取得失敗:{e}")
59
+ return None
60
+
61
+ def store_user_message(user_id, message_type, message_content):
62
+ """
63
+ 儲存用戶的訊息到 user_message_history 字典中。
64
+
65
+ Args:
66
+ user_id: 用戶的 ID。
67
+ message_type: 訊息類型 (例如 "image" 或 "text")。
68
+ message_content: 訊息內容 (例如圖片路徑或文字)。
69
+ """
70
+ user_message_history[user_id].append(
71
+ {"type": message_type, "content": message_content}
72
+ )
73
+
74
+ def get_previous_message(user_id):
75
+ """
76
+ 獲取用戶的上一則訊息。
77
+
78
+ Args:
79
+ user_id: 用戶的 ID。
80
+
81
+ Returns:
82
+ 如果歷史紀錄存在,回傳上一則訊息的字典;否則回傳預設的文字訊息。
83
+ """
84
+ if user_id in user_message_history and len(user_message_history[user_id]) > 0:
85
+ # 回傳最後一則訊息
86
+ return user_message_history[user_id][-1]
87
+ # 如果沒有歷史紀錄,回傳一個預設值
88
+ return {"type": "text", "content": "No message!"}
89
 
90
  # --- 事件處理器定義 ---
91
 
92
  # 1. 處理所有接收到的文字訊息事件
93
+ @line_handler.add(MessageEvent, message=(ImageMessage, TextMessage))
94
+ def handle_message(event):
95
  """
96
  處理接收到的文字訊息,並進行回覆。
97
  """
 
 
 
 
98
 
99
+ # 獲取用戶 ID
100
+ user_id = event.source.user_id
 
101
 
102
+ # 情況一:處理圖片上傳
103
+ if event.message.type == "image":
104
+ # 獲取 Line 傳來的圖片,並儲存到本地
105
+ image_path = get_image_url_from_line(event.message.id)
106
+ if image_path:
107
+ # 將圖片路徑儲存到用戶的訊息歷史中
108
+ store_user_message(user_id, "image", image_path)
109
+ # 回覆用戶,告知圖片已收到,並請他輸入問題
110
+ line_bot_api.reply_message(
111
+ event.reply_token, TextSendMessage(text="圖片已接收成功囉,幫我輸入你想詢問的問題喔~")
112
+ )
113
+ else:
114
+ line_bot_api.reply_message(
115
+ event.reply_token, TextSendMessage(text="沒有接收到圖片~")
116
+ )
117
+
118
+ elif event.message.type == "text":
119
+
120
+ user_text = event.message.text # 獲取用戶傳來的文字
121
+ # 獲取該用戶的「上一則」訊息
122
+ previous_message = get_previous_message(user_id)
123
+ print(f"上一則訊息: {previous_message}") # 在後台印出除錯訊息
124
+
125
+ # 根據上一則訊息類型,動態組合給代理人的輸入
126
+ if previous_message["type"] == "image":
127
+ # 如果上一則是圖片,代表用戶現在的文字是「針對圖片的提問」
128
+ image_path = previous_message["content"]
129
+ agent_input = {
130
+ "input": f"請根據這張圖片回答問題。圖片的路徑是 {image_path},我的問題是:{user_text}"
131
+ }
132
+ # 清除上一則圖片訊息,避免下一次文字訊息還被當作是圖片問答
133
+ user_message_history[user_id].pop()
134
+ else:
135
+ # 如果上一則不是圖片 (或沒有上一則),代表這是一般的文字提問 (可能是要求生成圖片)
136
+ agent_input = {"input": user_text}
137
+
138
+ try:
139
+ # 運行 LangChain 代理人
140
+ response = agent_executor.invoke(agent_input)
141
+ # 獲取代理人最終的輸出
142
+ out = response["output"]
143
+
144
+ # 檢查輸出中是否包含 'https' (判斷是否為生成的圖片 URL)
145
+ if 'https' in out:
146
+ # 解析 URL (這裡的解析方式比較簡易,可能需要更穩健的正規表達式)
147
+ img_tmp = 'https'+out.split('https')[1]
148
+ image_url = img_tmp.split('png')[0]+'png'
149
+
150
+ # 使用 push_message 同時推送文字和圖片
151
+ line_bot_api.push_message(
152
+ event.source.user_id,
153
+ [
154
+ TextSendMessage(text="✨ 這是我為你生成的圖片喔~"),
155
+ ImageSendMessage(original_content_url=image_url, preview_image_url=image_url)
156
+ ]
157
+ )
158
+ else:
159
+ # 如果輸出不是 URL,則直接回覆文字
160
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))
161
+ except Exception as e:
162
+ # 處理代理人執行時的錯誤
163
+ print(f"代理人執行出錯: {e}")
164
+ out = f"代理人執行出錯!錯誤訊息:{e}"
165
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))
166
 
167
 
168
  # 2. 處理關注 (Follow) 事件