Spaces:
Sleeping
Sleeping
Commit ·
e060de2
1
Parent(s): 2be2326
feat : linebot add agents
Browse files- app.py +0 -5
- 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
|
| 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 |
-
|
| 45 |
-
# - 處理特定關鍵字
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 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) 事件
|