walkerhsu
commited on
Commit
·
c74033b
1
Parent(s):
132b0f4
init
Browse files- agents/__pycache__/gmail_agent.cpython-313.pyc +0 -0
- agents/__pycache__/google_calendar_agent.cpython-313.pyc +0 -0
- agents/gmail_agent.py +107 -0
- agents/google_calendar_agent.py +287 -0
- app.py +77 -0
- requirements.txt +4 -0
- utils/__pycache__/auth.cpython-313.pyc +0 -0
- utils/__pycache__/llm.cpython-313.pyc +0 -0
- utils/auth.py +25 -0
- utils/llm.py +22 -0
agents/__pycache__/gmail_agent.cpython-313.pyc
ADDED
Binary file (5.61 kB). View file
|
|
agents/__pycache__/google_calendar_agent.cpython-313.pyc
ADDED
Binary file (12 kB). View file
|
|
agents/gmail_agent.py
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
import json
|
3 |
+
import base64
|
4 |
+
from googleapiclient.discovery import build
|
5 |
+
from utils.auth import authenticate_google_services
|
6 |
+
|
7 |
+
class Gmail_Agent:
|
8 |
+
def __init__(self, creds=None):
|
9 |
+
if not creds:
|
10 |
+
creds = authenticate_google_services()
|
11 |
+
self.service = build('gmail', 'v1', credentials=creds)
|
12 |
+
|
13 |
+
def read_gmail_messages(self, queries, max_results=5):
|
14 |
+
query = " ".join(queries)
|
15 |
+
results = self.service.users().messages().list(userId='me', maxResults=max_results, q=query).execute()
|
16 |
+
messages = results.get('messages', [])
|
17 |
+
return messages
|
18 |
+
|
19 |
+
def format_messages(self, messages):
|
20 |
+
formatted_content = "\n\n==========\n\n"
|
21 |
+
for message in messages:
|
22 |
+
msg = self.service.users().messages().get(userId='me', id=message['id']).execute()
|
23 |
+
import json
|
24 |
+
with open('results.json', 'w', encoding='utf-8') as f:
|
25 |
+
json.dump(msg, f)
|
26 |
+
internal_Date = int(msg['internalDate'])
|
27 |
+
formatted_Date = datetime.fromtimestamp(internal_Date / 1000).strftime('%Y-%m-%d %H:%M:%S')
|
28 |
+
formatted_content += f"信件日期: {formatted_Date}\n"
|
29 |
+
content = self._parse_gmail_response(msg['payload']['parts'])
|
30 |
+
formatted_content+=f"信件內容: {content}\n\n==========\n\n"
|
31 |
+
return formatted_content
|
32 |
+
|
33 |
+
def send_email(self, to, subject, message_content):
|
34 |
+
try:
|
35 |
+
from email.message import EmailMessage
|
36 |
+
message = EmailMessage()
|
37 |
+
message.set_content(message_content)
|
38 |
+
message["To"] = ", ".join(to)
|
39 |
+
message["Subject"] = subject
|
40 |
+
encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
41 |
+
create_message = {"raw": encoded_message}
|
42 |
+
send_message = (
|
43 |
+
self.service.users()
|
44 |
+
.messages()
|
45 |
+
.send(userId="me", body=create_message)
|
46 |
+
.execute()
|
47 |
+
)
|
48 |
+
print(f'Message Id: {send_message["id"]}')
|
49 |
+
formatted_content = "✅ Gmail 已傳送給所有使用者,請至您的 Gmail 寄件備份確認!"
|
50 |
+
except Exception as error:
|
51 |
+
print(f"An error occurred: {error}")
|
52 |
+
send_message = None
|
53 |
+
formatted_content = f"❌ 信件未能成功寄出,以下是錯誤訊息(英文):{error}"
|
54 |
+
return formatted_content
|
55 |
+
|
56 |
+
def get_all_tools(self):
|
57 |
+
LLM_tools = []
|
58 |
+
LLM_tools.append({
|
59 |
+
"name": "query_gmail_tool",
|
60 |
+
"description": "query the gmails with specified queries",
|
61 |
+
"parameters": {
|
62 |
+
"type": "object",
|
63 |
+
"properties": {
|
64 |
+
"queries": {
|
65 |
+
"type": "array",
|
66 |
+
"items": {"type": "string"},
|
67 |
+
"description": "Queries that can be used to filter the gmails. (e.g., ['before:2025/06/01', 'after:2025/05/01', 'from:[email protected]'])",
|
68 |
+
},
|
69 |
+
},
|
70 |
+
"required": ["queries"]
|
71 |
+
},
|
72 |
+
})
|
73 |
+
LLM_tools.append({
|
74 |
+
"name": "send_email_tool",
|
75 |
+
"description": "Send an email to a specified recipient with a specified subject and message",
|
76 |
+
"parameters": {
|
77 |
+
"type": "object",
|
78 |
+
"properties": {
|
79 |
+
"to": {
|
80 |
+
"type": "array",
|
81 |
+
"items": {"type": "string"},
|
82 |
+
"description": "An array of email addresses of all the recipients"
|
83 |
+
},
|
84 |
+
"subject": {
|
85 |
+
"type": "string",
|
86 |
+
"description": "The subject of the email"
|
87 |
+
},
|
88 |
+
"message_content": {
|
89 |
+
"type": "string",
|
90 |
+
"description": "The content of the email"
|
91 |
+
}
|
92 |
+
},
|
93 |
+
"required": ["to", "subject", "message_content"]
|
94 |
+
}
|
95 |
+
})
|
96 |
+
return LLM_tools
|
97 |
+
|
98 |
+
def _parse_gmail_response(self, parts):
|
99 |
+
for part in parts:
|
100 |
+
if "parts" in part:
|
101 |
+
content = self._parse_gmail_response(part["parts"])
|
102 |
+
if content:
|
103 |
+
return content
|
104 |
+
if part['mimeType'] == 'text/plain' and part['body']['size'] != 0:
|
105 |
+
decoded_data = base64.urlsafe_b64decode(part['body']['data']).decode('utf-8')
|
106 |
+
return decoded_data
|
107 |
+
return None
|
agents/google_calendar_agent.py
ADDED
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from googleapiclient.discovery import build
|
2 |
+
from datetime import datetime, timedelta
|
3 |
+
from utils.auth import authenticate_google_services
|
4 |
+
|
5 |
+
class GoogleCalendar_Agent:
|
6 |
+
def __init__(self, creds=None, timezone="Asia/Taipei"):
|
7 |
+
if not creds:
|
8 |
+
creds = authenticate_google_services()
|
9 |
+
self.service = build("calendar", "v3", credentials=creds)
|
10 |
+
self.timezone = timezone
|
11 |
+
|
12 |
+
def list_events(self, n: int=5, queries=None) -> str:
|
13 |
+
now = datetime.utcnow().isoformat() + 'Z'
|
14 |
+
events_result = self.service.events().list(
|
15 |
+
calendarId='primary',
|
16 |
+
timeMin=now,
|
17 |
+
maxResults=n,
|
18 |
+
singleEvents=True,
|
19 |
+
orderBy='startTime'
|
20 |
+
).execute()
|
21 |
+
events = events_result.get('items', [])
|
22 |
+
if not events:
|
23 |
+
return "🔕 找不到即將到來的行事曆事件。"
|
24 |
+
output = "📅 您接下來的行事曆事件如下:\n"
|
25 |
+
for event in events:
|
26 |
+
start = event['start'].get('dateTime', event['start'].get('date'))
|
27 |
+
summary = event.get('summary', 'No title')
|
28 |
+
output += f"• {summary} at {start}\n"
|
29 |
+
return output
|
30 |
+
|
31 |
+
def create_event(self,
|
32 |
+
summary: str,
|
33 |
+
start_time: str,
|
34 |
+
end_time: str,
|
35 |
+
location: str = None,
|
36 |
+
description: str = None,
|
37 |
+
attendees: list = None,
|
38 |
+
reminder_minutes: int = None) -> str:
|
39 |
+
event = {
|
40 |
+
'summary': summary,
|
41 |
+
'start': {'dateTime': start_time, 'timeZone': self.timezone},
|
42 |
+
'end': {'dateTime': end_time, 'timeZone': self.timezone}
|
43 |
+
}
|
44 |
+
if location:
|
45 |
+
event['location'] = location
|
46 |
+
if description:
|
47 |
+
event['description'] = description
|
48 |
+
if attendees:
|
49 |
+
event['attendees'] = [{'email': email} for email in attendees]
|
50 |
+
if reminder_minutes:
|
51 |
+
event['reminders'] = {
|
52 |
+
'useDefault': False,
|
53 |
+
'overrides': [{'method': 'popup', 'minutes': reminder_minutes}]
|
54 |
+
}
|
55 |
+
try:
|
56 |
+
created_event = self.service.events().insert(
|
57 |
+
calendarId='primary',
|
58 |
+
sendUpdates='all',
|
59 |
+
body=event
|
60 |
+
).execute()
|
61 |
+
return f"""✅ 已成功建立事件:\n標題 : {summary}\n開始時間 : {start_time}\n結束時間 : {end_time}\n地點 : {location or '無'}\n說明 : {description or '無'}\n參與者 : {', '.join(attendees) if attendees else '無'}\n提醒時間 : {reminder_minutes if reminder_minutes is not None else '使用預設提醒'}\n時區 : {self.timezone}\n事件連結 : {created_event.get('htmlLink')}"""
|
62 |
+
except Exception as e:
|
63 |
+
return f"❌ 建立事件失敗,以下是錯誤訊息(英文): {e}"
|
64 |
+
|
65 |
+
def find_event(self, query: str,) -> list:
|
66 |
+
now = datetime.utcnow().isoformat() + 'Z'
|
67 |
+
events_result = self.service.events().list(
|
68 |
+
calendarId='primary',
|
69 |
+
timeMin=now,
|
70 |
+
singleEvents=True,
|
71 |
+
orderBy='startTime',
|
72 |
+
q=query
|
73 |
+
).execute()
|
74 |
+
return events_result.get('items', [])
|
75 |
+
|
76 |
+
def update_event(self, query: str,
|
77 |
+
new_summary=None,
|
78 |
+
new_location=None,
|
79 |
+
new_description=None,
|
80 |
+
new_start=None,
|
81 |
+
new_end=None,
|
82 |
+
new_attendees=None,
|
83 |
+
reminder_minutes=None):
|
84 |
+
events = self.find_event(query)
|
85 |
+
if not events:
|
86 |
+
return f"❌ 找不到包含「{query}」的事件"
|
87 |
+
if all(arg is None for arg in [
|
88 |
+
new_summary, new_location, new_description,
|
89 |
+
new_start, new_end, new_attendees, reminder_minutes
|
90 |
+
]):
|
91 |
+
return f"⚠️ 沒有指定任何要更新的欄位,未執行更新。"
|
92 |
+
for event in events:
|
93 |
+
event_id = event['id']
|
94 |
+
try:
|
95 |
+
if new_summary:
|
96 |
+
event['summary'] = new_summary
|
97 |
+
if new_location:
|
98 |
+
event['location'] = new_location
|
99 |
+
if new_description:
|
100 |
+
event['description'] = new_description
|
101 |
+
if new_attendees:
|
102 |
+
event['attendees'] = [{'email': email} for email in new_attendees]
|
103 |
+
if reminder_minutes:
|
104 |
+
event['reminders'] = {
|
105 |
+
'useDefault': False,
|
106 |
+
'overrides': [{'method': 'popup', 'minutes': reminder_minutes}]
|
107 |
+
}
|
108 |
+
if new_start or new_end:
|
109 |
+
event['start']['dateTime'] = new_start
|
110 |
+
event['end']['dateTime'] = new_end
|
111 |
+
event['start']['timeZone'] = 'Asia/Taipei'
|
112 |
+
event['end']['timeZone'] = 'Asia/Taipei'
|
113 |
+
updated_event = self.service.events().update(
|
114 |
+
calendarId='primary',
|
115 |
+
eventId=event_id,
|
116 |
+
sendUpdates='all',
|
117 |
+
body=event
|
118 |
+
).execute()
|
119 |
+
return f"✅ 已成功更新事件:{updated_event['summary']}"
|
120 |
+
except Exception as e:
|
121 |
+
return f"❌ 更新事件失敗,以下是錯誤訊息(英文): {e}"
|
122 |
+
|
123 |
+
def delete_event(self, query: str):
|
124 |
+
now = datetime.utcnow().isoformat() + 'Z'
|
125 |
+
event_results = self.find_event(query)
|
126 |
+
if len(event_results) == 0:
|
127 |
+
return f"❌ 找不到包含「{query}」的事件"
|
128 |
+
for event in event_results:
|
129 |
+
target_id = event["id"]
|
130 |
+
try:
|
131 |
+
self.service.events().delete(
|
132 |
+
calendarId='primary',
|
133 |
+
eventId=target_id,
|
134 |
+
sendUpdates='all'
|
135 |
+
).execute()
|
136 |
+
return f"🗑️ 已刪除事件:{event['summary']}(ID: {target_id})"
|
137 |
+
except Exception as e:
|
138 |
+
return f"❌ 刪除事件失敗(ID: {target_id}),以下是錯誤訊息(英文): {e}"
|
139 |
+
|
140 |
+
def get_event_details(self, query: str):
|
141 |
+
events = self.find_event(query)
|
142 |
+
if not events:
|
143 |
+
return f"❌ 找不到符合 '{query}' 的事件!"
|
144 |
+
event = events[0]
|
145 |
+
summary = event.get('summary', 'No Title')
|
146 |
+
start_time = event['start'].get('dateTime', event['start'].get('date'))
|
147 |
+
end_time = event['end'].get('dateTime', event['end'].get('date'))
|
148 |
+
formatted_string = f"事件: {summary}\n開始時間: {start_time}\n結束時間: {end_time}"
|
149 |
+
attendees = event.get('attendees', [])
|
150 |
+
emails = [attendee.get('email') for attendee in attendees if attendee.get('email')]
|
151 |
+
if emails:
|
152 |
+
formatted_string += f"\n參與者: {', '.join(emails)}"
|
153 |
+
else:
|
154 |
+
formatted_string += "\n無參與者"
|
155 |
+
return formatted_string
|
156 |
+
|
157 |
+
def get_all_tools(self):
|
158 |
+
LLM_tools = []
|
159 |
+
LLM_tools.append({
|
160 |
+
"name": "create_Calendar_event",
|
161 |
+
"description": "Create a new Google Calendar event by specifying summary, start time, and end time, and optionally location, description, attendees, and reminder.",
|
162 |
+
"parameters": {
|
163 |
+
"type": "object",
|
164 |
+
"properties": {
|
165 |
+
"summary": {
|
166 |
+
"type": "string",
|
167 |
+
"description": "Title or summary of the event (e.g., 'Lunch with Alice')"
|
168 |
+
},
|
169 |
+
"start_time": {
|
170 |
+
"type": "string",
|
171 |
+
"format": "date-time",
|
172 |
+
"description": "Start time of the event in ISO format (e.g., '2025-07-01T14:00:00')"
|
173 |
+
},
|
174 |
+
"end_time": {
|
175 |
+
"type": "string",
|
176 |
+
"format": "date-time",
|
177 |
+
"description": "End time of the event in ISO format (e.g., '2025-07-01T15:00:00')"
|
178 |
+
},
|
179 |
+
"location": {
|
180 |
+
"type": "string",
|
181 |
+
"description": "Location of the event (e.g., 'Taipei 101')"
|
182 |
+
},
|
183 |
+
"description": {
|
184 |
+
"type": "string",
|
185 |
+
"description": "Detailed description of the event"
|
186 |
+
},
|
187 |
+
"attendees": {
|
188 |
+
"type": "array",
|
189 |
+
"items": {"type": "string"},
|
190 |
+
"description": "List of email addresses to invite to the event"
|
191 |
+
},
|
192 |
+
"reminder_minutes": {
|
193 |
+
"type": "integer",
|
194 |
+
"description": "Number of minutes before the event to trigger a popup reminder"
|
195 |
+
}
|
196 |
+
},
|
197 |
+
"required": ["summary", "start_time", "end_time"]
|
198 |
+
},
|
199 |
+
})
|
200 |
+
LLM_tools.append({
|
201 |
+
"name": "list_Calendar_events",
|
202 |
+
"description": "List your upcoming Google Calendar events.",
|
203 |
+
"parameters": {
|
204 |
+
"type": "object",
|
205 |
+
"properties": {
|
206 |
+
"n": {
|
207 |
+
"type": "integer",
|
208 |
+
"default": 5,
|
209 |
+
"description": "Number of upcoming events to list (default: 5)"
|
210 |
+
}
|
211 |
+
}
|
212 |
+
}
|
213 |
+
})
|
214 |
+
LLM_tools.append({
|
215 |
+
"name": "update_Calendar_event",
|
216 |
+
"description": "Update a Google Calendar event using a keyword query to find it. You can update summary, location, description, start/end time, attendees, or reminders.",
|
217 |
+
"parameters": {
|
218 |
+
"type": "object",
|
219 |
+
"properties": {
|
220 |
+
"query": {
|
221 |
+
"type": "string",
|
222 |
+
"description": "Keyword to find the event to update (e.g., 'meeting with Bob')"
|
223 |
+
},
|
224 |
+
"new_summary": {
|
225 |
+
"type": "string",
|
226 |
+
"description": "New summary/title for the event"
|
227 |
+
},
|
228 |
+
"new_location": {
|
229 |
+
"type": "string",
|
230 |
+
"description": "New location for the event"
|
231 |
+
},
|
232 |
+
"new_description": {
|
233 |
+
"type": "string",
|
234 |
+
"description": "New description for the event"
|
235 |
+
},
|
236 |
+
"new_start": {
|
237 |
+
"type": "string",
|
238 |
+
"format": "date-time",
|
239 |
+
"description": "New start time in ISO format"
|
240 |
+
},
|
241 |
+
"new_end": {
|
242 |
+
"type": "string",
|
243 |
+
"format": "date-time",
|
244 |
+
"description": "New end time in ISO format"
|
245 |
+
},
|
246 |
+
"new_attendees": {
|
247 |
+
"type": "array",
|
248 |
+
"items": {"type": "string"},
|
249 |
+
"description": "New list of attendees (email addresses)"
|
250 |
+
},
|
251 |
+
"reminder_minutes": {
|
252 |
+
"type": "integer",
|
253 |
+
"description": "Updated popup reminder time in minutes"
|
254 |
+
}
|
255 |
+
},
|
256 |
+
"required": ["query"]
|
257 |
+
}
|
258 |
+
})
|
259 |
+
LLM_tools.append({
|
260 |
+
"name": "delete_Calendar_event",
|
261 |
+
"description": "Delete a Google Calendar event using a keyword query to find it.",
|
262 |
+
"parameters": {
|
263 |
+
"type": "object",
|
264 |
+
"properties": {
|
265 |
+
"query": {
|
266 |
+
"type": "string",
|
267 |
+
"description": "Keyword to find the event to delete (e.g., 'meeting with Bob')"
|
268 |
+
}
|
269 |
+
},
|
270 |
+
"required": ["query"]
|
271 |
+
}
|
272 |
+
})
|
273 |
+
LLM_tools.append({
|
274 |
+
"name": "get_event_details",
|
275 |
+
"description": "Find the attendee email addresses and the summary of a specific event, based on a keyword.",
|
276 |
+
"parameters": {
|
277 |
+
"type": "object",
|
278 |
+
"properties": {
|
279 |
+
"query": {
|
280 |
+
"type": "string",
|
281 |
+
"description": "Keyword to find the event and extract attendees and summary of that event."
|
282 |
+
}
|
283 |
+
},
|
284 |
+
"required": ["query"]
|
285 |
+
}
|
286 |
+
})
|
287 |
+
return LLM_tools
|
app.py
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import gradio as gr
|
3 |
+
from utils.auth import authenticate_google_services
|
4 |
+
from utils.llm import Google_Gemini_LLM
|
5 |
+
from agents.gmail_agent import Gmail_Agent
|
6 |
+
from agents.google_calendar_agent import GoogleCalendar_Agent
|
7 |
+
|
8 |
+
# Authenticate and initialize agents
|
9 |
+
creds = authenticate_google_services()
|
10 |
+
GMAIL_AGENT = Gmail_Agent(creds)
|
11 |
+
CALENDAR_Agent = GoogleCalendar_Agent(creds)
|
12 |
+
|
13 |
+
# Initialize LLM
|
14 |
+
# Use os.environ.get instead of userdata.get
|
15 |
+
GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
|
16 |
+
if not GOOGLE_API_KEY:
|
17 |
+
raise ValueError("GOOGLE_API_KEY environment variable not set")
|
18 |
+
|
19 |
+
MODEL = "gemini-1.5-flash"
|
20 |
+
YOUR_AI_AGENT = Google_Gemini_LLM(GOOGLE_API_KEY, MODEL)
|
21 |
+
YOUR_AI_AGENT.append_function_tools(GMAIL_AGENT.get_all_tools())
|
22 |
+
YOUR_AI_AGENT.append_function_tools(CALENDAR_Agent.get_all_tools())
|
23 |
+
|
24 |
+
def generate_response(user_prompt, history):
|
25 |
+
response = YOUR_AI_AGENT.generate_content(user_prompt)
|
26 |
+
while(response.function_call):
|
27 |
+
function_name = response.function_call.name
|
28 |
+
function_args = response.function_call.args
|
29 |
+
if function_name == "query_gmail_tool":
|
30 |
+
messages = GMAIL_AGENT.read_gmail_messages(**function_args)
|
31 |
+
if not messages:
|
32 |
+
formatted_content = "❌ 找不到對應的 Gmail,請確認您的搜尋條件是否合理,或者請重新執行!"
|
33 |
+
return formatted_content
|
34 |
+
else:
|
35 |
+
formatted_content = GMAIL_AGENT.format_messages(messages)
|
36 |
+
user_prompt += f"\n根據使用者的搜尋條件,以下是對應的信件內容(可能有多封信件):<信件內容開始>{formatted_content}<信件內容結束>. 請仔細閱讀以上的信件內容,並將資料整理統整給使用者!請以簡潔且新處的方式傳達內容!"
|
37 |
+
elif function_name == "send_email_tool":
|
38 |
+
formatted_content = GMAIL_AGENT.send_email(**function_args)
|
39 |
+
return formatted_content
|
40 |
+
elif function_name == "list_Calendar_events":
|
41 |
+
formatted_content = CALENDAR_Agent.list_events(**function_args)
|
42 |
+
user_prompt += f"\n根據使用者的搜尋條件,以下是從使用者的日曆中找到對應的事件內容(可能有多封信件): <事件內容開始>{formatted_content}<事件內容結束>. 請仔細閱讀以上的事件內容,並將資料統整給使用者!請以簡潔且新處的方式傳達內容!"
|
43 |
+
elif function_name == "create_Calendar_event":
|
44 |
+
formatted_content = CALENDAR_Agent.create_event(**function_args)
|
45 |
+
return formatted_content
|
46 |
+
elif function_name == "update_Calendar_event":
|
47 |
+
formatted_content = CALENDAR_Agent.update_event(**function_args)
|
48 |
+
return formatted_content
|
49 |
+
elif function_name == "delete_Calendar_event":
|
50 |
+
formatted_content = CALENDAR_Agent.delete_event(**function_args)
|
51 |
+
return formatted_content
|
52 |
+
elif function_name == "get_event_details":
|
53 |
+
formatted_content = CALENDAR_Agent.get_event_details(**function_args)
|
54 |
+
user_prompt += f"\n根據使用者的搜尋條件,以下是從使用者的日曆中找到對應的事件內容 <事件內容開始>{formatted_content}<事件內容結束>. 請仔細閱讀以上的事件內容,並且完成使用者的指示(例如:統整事件內容、寄送提醒信件給參與事件的人...等等)!"
|
55 |
+
else:
|
56 |
+
raise NotImplementedError(f"Function Call {function_name} is not supported yet.")
|
57 |
+
response = YOUR_AI_AGENT.generate_content(user_prompt)
|
58 |
+
return response.text
|
59 |
+
|
60 |
+
# It's better to get the email from the user's input
|
61 |
+
# than to hardcode it.
|
62 |
+
demo = gr.ChatInterface(
|
63 |
+
fn=generate_response,
|
64 |
+
chatbot=gr.Chatbot(
|
65 |
+
label="📬 AI 助理",
|
66 |
+
),
|
67 |
+
title="Gmail & Calendar 智慧助理",
|
68 |
+
description="輸入自然語言,讓 AI 幫你管理 Gmail 或 Google 行事曆",
|
69 |
+
examples=[
|
70 |
+
"找出2025/6月標註星號的信件",
|
71 |
+
"請幫我列出近期的事件",
|
72 |
+
"建立2025/8/10早上10點的網球活動",
|
73 |
+
]
|
74 |
+
)
|
75 |
+
|
76 |
+
if __name__ == "__main__":
|
77 |
+
demo.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
google-api-python-client
|
2 |
+
google-auth-httplib2
|
3 |
+
google-auth-oauthlib
|
4 |
+
gradio
|
utils/__pycache__/auth.cpython-313.pyc
ADDED
Binary file (1.58 kB). View file
|
|
utils/__pycache__/llm.cpython-313.pyc
ADDED
Binary file (1.79 kB). View file
|
|
utils/auth.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import os, pickle
|
3 |
+
from google_auth_oauthlib.flow import InstalledAppFlow
|
4 |
+
from google.auth.transport.requests import Request
|
5 |
+
|
6 |
+
SCOPES = [
|
7 |
+
'https://www.googleapis.com/auth/calendar',
|
8 |
+
'https://www.googleapis.com/auth/gmail.readonly',
|
9 |
+
'https://www.googleapis.com/auth/gmail.send'
|
10 |
+
]
|
11 |
+
|
12 |
+
def authenticate_google_services():
|
13 |
+
creds = None
|
14 |
+
if os.path.exists('token.pkl'):
|
15 |
+
with open('token.pkl', 'rb') as token:
|
16 |
+
creds = pickle.load(token)
|
17 |
+
if not creds or not creds.valid:
|
18 |
+
if creds and creds.expired and creds.refresh_token:
|
19 |
+
creds.refresh(Request())
|
20 |
+
else:
|
21 |
+
flow = InstalledAppFlow.from_client_secrets_file('client_secret.json', SCOPES)
|
22 |
+
creds = flow.run_console()
|
23 |
+
with open('token.pkl', 'wb') as token:
|
24 |
+
pickle.dump(creds, token)
|
25 |
+
return creds
|
utils/llm.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import google.generativeai as genai
|
3 |
+
from google.generativeai import types
|
4 |
+
|
5 |
+
class Google_Gemini_LLM:
|
6 |
+
def __init__(self, api_key, model="gemini-1.5-flash"):
|
7 |
+
self.api_key = api_key
|
8 |
+
self.model = model
|
9 |
+
self.client = genai.Client(api_key=api_key)
|
10 |
+
self.LLM_tools = []
|
11 |
+
|
12 |
+
def generate_content(self, contents):
|
13 |
+
tools = types.Tool(function_declarations=self.LLM_tools)
|
14 |
+
config = types.GenerateContentConfig(tools=[tools])
|
15 |
+
response = self.client.models.generate_content(
|
16 |
+
model=self.model, contents=contents, config=config
|
17 |
+
)
|
18 |
+
return response.candidates[0].content.parts[0]
|
19 |
+
|
20 |
+
def append_function_tools(self, tools):
|
21 |
+
for tool in tools:
|
22 |
+
self.LLM_tools.append(tool)
|