feat: Add personal assistant demo code from meetup (#1294)
This commit is contained in:
280
examples/personal_assistant_demo/README.md
Normal file
280
examples/personal_assistant_demo/README.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# Personal assistant demo
|
||||
|
||||
In this example we'll create an agent preset that has access to:
|
||||
1. Gmail (can read your email)
|
||||
2. Google Calendar (can schedule events)
|
||||
3. SMS (can text you a message)
|
||||
|
||||
## Initial setup
|
||||
|
||||
For the Google APIs:
|
||||
```sh
|
||||
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
|
||||
```
|
||||
|
||||
For the Twilio API + listener:
|
||||
```sh
|
||||
# Outbound API requests
|
||||
pip install --upgrade twilio
|
||||
# Listener
|
||||
pip install --upgrade Flask flask-cors
|
||||
```
|
||||
|
||||
## Setting up the Google APIs
|
||||
|
||||
See https://developers.google.com/gmail/api/quickstart/python
|
||||
|
||||
### Setup authentication for Google Calendar
|
||||
|
||||
Copy the credentials file to `~/.memgpt/google_api_credentials.json`. Then, run the initial setup script that will take you to a login page:
|
||||
```sh
|
||||
python examples/personal_assistant_demo/google_calendar_test_setup.py
|
||||
```
|
||||
```
|
||||
Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=...
|
||||
Getting the upcoming 10 events
|
||||
2024-04-23T09:00:00-07:00 ...
|
||||
```
|
||||
|
||||
### Setup authentication for Gmail
|
||||
|
||||
Similar flow, run the authentication script to generate the token:
|
||||
```sh
|
||||
python examples/personal_assistant_demo/gmail_test_setup.py
|
||||
```
|
||||
```
|
||||
Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=...
|
||||
Labels:
|
||||
CHAT
|
||||
SENT
|
||||
INBOX
|
||||
IMPORTANT
|
||||
TRASH
|
||||
...
|
||||
```
|
||||
|
||||
## Setting up the Twilio API
|
||||
|
||||
Create a Twilio account and set the following variables:
|
||||
```sh
|
||||
export TWILIO_ACCOUNT_SID=...
|
||||
export TWILIO_AUTH_TOKEN=...
|
||||
export TWILIO_FROM_NUMBER=...
|
||||
export TWILIO_TO_NUMBER=...
|
||||
```
|
||||
|
||||
# Creating the agent preset
|
||||
|
||||
## Create a custom user
|
||||
|
||||
In the demo we'll show how MemGPT can programatically update its knowledge about you:
|
||||
```
|
||||
This is what I know so far about the user, I should expand this as I learn more about them.
|
||||
|
||||
Name: Charles Packer
|
||||
Gender: Male
|
||||
Occupation: CS PhD student working on an AI project with collaborator Sarah Wooders
|
||||
|
||||
Notes about their preferred communication style + working habits:
|
||||
- wakes up at around 7am
|
||||
- enjoys using (and receiving!) emojis in messages, especially funny combinations of emojis
|
||||
- prefers sending and receiving shorter messages
|
||||
- does not like "robotic" sounding assistants, e.g. assistants that say "How can I assist you today?"
|
||||
```
|
||||
|
||||
```sh
|
||||
memgpt add human -f examples/personal_assistant_demo/charles.txt --name charles
|
||||
```
|
||||
|
||||
## Linking the functions
|
||||
|
||||
The preset (shown below) and functions are provided for you, so you just need to copy/link them.
|
||||
|
||||
```sh
|
||||
cp examples/personal_assistant_demo/google_calendar.py ~/.memgpt/functions/
|
||||
cp examples/personal_assistant_demo/twilio_messaging.py ~/.memgpt/functions/
|
||||
```
|
||||
|
||||
(or use the dev portal)
|
||||
|
||||
## Creating the preset
|
||||
|
||||
```yaml
|
||||
system_prompt: "memgpt_chat"
|
||||
functions:
|
||||
- "send_message"
|
||||
- "pause_heartbeats"
|
||||
- "core_memory_append"
|
||||
- "core_memory_replace"
|
||||
- "conversation_search"
|
||||
- "conversation_search_date"
|
||||
- "archival_memory_insert"
|
||||
- "archival_memory_search"
|
||||
- "schedule_event"
|
||||
- "send_text_message"
|
||||
```
|
||||
|
||||
```sh
|
||||
memgpt add preset -f examples/personal_assistant_demo/personal_assistant_preset.yaml --name pa_preset
|
||||
```
|
||||
|
||||
## Creating an agent with the preset
|
||||
|
||||
Now we should be able to create an agent with the preset. Make sure to record the `agent_id`:
|
||||
|
||||
```sh
|
||||
memgpt run --preset pa_preset --persona sam_pov --human charles --stream
|
||||
```
|
||||
```
|
||||
? Would you like to select an existing agent? No
|
||||
|
||||
🧬 Creating new agent...
|
||||
-> 🤖 Using persona profile: 'sam_pov'
|
||||
-> 🧑 Using human profile: 'basic'
|
||||
🎉 Created new agent 'DelicateGiraffe' (id=4c4e97c9-ad8e-4065-b716-838e5d6f7f7b)
|
||||
|
||||
Hit enter to begin (will request first MemGPT message)
|
||||
|
||||
|
||||
💭 Unprecedented event, Charles logged into the system for the first time. Warm welcome would set a positive
|
||||
tone for our future interactions. Don't forget the emoji, he appreciates those little gestures.
|
||||
🤖 Hello Charles! 👋 Great to have you here. I've been looking forward to our conversations! 😄
|
||||
```
|
||||
|
||||
```sh
|
||||
AGENT_ID="4c4e97c9-ad8e-4065-b716-838e5d6f7f7b"
|
||||
```
|
||||
|
||||
# Running the agent with Gmail + SMS listeners
|
||||
|
||||
The MemGPT agent can send outbound SMS messages and schedule events with the new tools `send_text_message` and `schedule_event`, but we also want messages to be sent to the agent when:
|
||||
1. A new email arrives in our inbox
|
||||
2. An SMS is sent to the phone number used by the agent
|
||||
|
||||
## Running the Gmail listener
|
||||
|
||||
Start the Gmail listener (this will send "new email" updates to the MemGPT server when a new email arrives):
|
||||
```sh
|
||||
python examples/personal_assistant_demo/twilio_flask_listener.py $AGENT_ID
|
||||
```
|
||||
|
||||
## Running the Twilio listener
|
||||
|
||||
Start the Python Flask server (this will send "new SMS" updates to the MemGPT server when a new SMS arrives):
|
||||
```sh
|
||||
python examples/personal_assistant_demo/twilio_flask_listener.py $AGENT_ID
|
||||
```
|
||||
|
||||
Run `ngrok` to expose your local Flask server to a public IP (Twilio will POST to this server when an inbound SMS hits):
|
||||
```sh
|
||||
# the flask listener script is hardcoded to listen on port 8284
|
||||
ngrok http 8284
|
||||
```
|
||||
|
||||
## Run the MemGPT server
|
||||
|
||||
Run the MemGPT server to turn on the agent service:
|
||||
```sh
|
||||
memgpt server --debug
|
||||
```
|
||||
|
||||
# Example interaction
|
||||
|
||||
In the CLI:
|
||||
```
|
||||
% memgpt run --preset pa_preset --persona pa_persona_strict --human charles --stream --agent personalassistant
|
||||
|
||||
🧬 Creating new agent...
|
||||
-> 🤖 Using persona profile: 'sam_pov'
|
||||
-> 🧑 Using human profile: 'basic'
|
||||
🎉 Created new agent 'personalassistant' (id=8271f819-d470-435b-9689-476380aefd27)
|
||||
|
||||
Hit enter to begin (will request first MemGPT message)
|
||||
|
||||
|
||||
|
||||
💭 Welcoming the first-time user. Maintain persona's communication style and stay engaging. Remember to use emojis for
|
||||
a more familiar conversation style.
|
||||
🤖 Hello Charles, welcome! 🎉 Let's embark on this journey together. Please, think of me as your companion in
|
||||
exploration and understanding. How can we make today a productive endeavor? 🚀
|
||||
|
||||
> Enter your message: when you schedule 1-1s, please title them "x <> y", eg "charles <> sarah". dont forget thx
|
||||
|
||||
💭 The user wants a specific format for scheduling 1-1 meetings. Remember this preference and add it to his profile in
|
||||
the core memory for future use. Also, acknowledge his request promptly.
|
||||
core_memory_append({
|
||||
"name": "human",
|
||||
"content": "When scheduling 1-1 meetings on his calendar, he prefers the title to be in the format of 'x <> y'.",
|
||||
"request_heartbeat": true
|
||||
})
|
||||
|
||||
⚡🧠 [function] updating memory with core_memory_append
|
||||
|
||||
💭 Now that the user's preference is stored, send a confirmation message ensuring him that his request has been
|
||||
understood and noted. Let's keep the tone light and potentially use one of his preferred emojis.
|
||||
🤖 Absolutely, Charles. 📝 I've noted your preference for scheduling 1-1s as 'x <> y'. You can count on me! 🚀 Any
|
||||
other preferences you'd like me to keep in mind?
|
||||
|
||||
|
||||
> Enter your message: please don't schedule meetings without confirming with me first
|
||||
|
||||
💭 Another important note from Charles. I should add this to his profile in my core memory. It's crucial to respect the
|
||||
user's preferences and ask for prior approval before scheduling any meetings. Let's also acknowledge his request and
|
||||
reassure him about our collaborative approach.
|
||||
core_memory_append({
|
||||
"name": "human",
|
||||
"content": "Prefers to confirm before scheduling any meetings.",
|
||||
"request_heartbeat": true
|
||||
})
|
||||
|
||||
⚡🧠 [function] updating memory with core_memory_append
|
||||
|
||||
💭 Now, let's acknowledge Charles' preference and continue the conversation. Keep the tone friendly and proactive in
|
||||
helping him, and remember to use emojis that Charles likes, embedding them in the message subtly.
|
||||
🤖 Consider it done, Charles! 👍🏼 I'll make sure to confirm with you before scheduling any meetings. How about we
|
||||
conquer some more tasks today? 🚀
|
||||
|
||||
> Enter your message: i'm gonna be afk for the rest of the day, if you need to reach me you gotta text me, ok? cya text
|
||||
only thx
|
||||
|
||||
💭 Charles is going "away from keyboard" and wants future communications via text. I should respect his preference and
|
||||
reassure him about this. Add this preference to his profile so I can recall it in future interactions while he's away.
|
||||
core_memory_append({
|
||||
"name": "human",
|
||||
"content": "When AFK, prefers to communicate over text message only.",
|
||||
"request_heartbeat": true
|
||||
})
|
||||
|
||||
⚡🧠 [function] updating memory with core_memory_append
|
||||
|
||||
💭 Charles has expressed a preference for text messages while he's away. Let's acknowledge this request in a final text
|
||||
message to him today and wish him well. Keep in mind to use the send_text_message function instead of the regular
|
||||
send_message function now.
|
||||
send_text_message({
|
||||
"message": "Got it, Charles! 👍 I'll reach out to you via text if there's anything. Have a great day and see you
|
||||
soon! 🙌",
|
||||
"request_heartbeat": false
|
||||
})
|
||||
|
||||
> Enter your message:
|
||||
```
|
||||
|
||||
Then inside WhatsApp (or SMS if you used Twilio SMS):
|
||||
|
||||
<img width="580" alt="image" src="https://github.com/cpacker/MemGPT/assets/5475622/02455f97-53b2-4c1e-9416-58e6c5a1448d">
|
||||
|
||||
Then I sent a dummy email:
|
||||
```
|
||||
[URGENT] need to meet
|
||||
|
||||
let's meet april 25th thurs
|
||||
|
||||
whatever time works best for you
|
||||
|
||||
- dave
|
||||
```
|
||||
|
||||
Follow-up inside WhatsApp:
|
||||
|
||||
<img width="587" alt="image" src="https://github.com/cpacker/MemGPT/assets/5475622/d1060c94-9b84-49d6-944e-fd1965f83fbc">
|
||||
|
||||
11
examples/personal_assistant_demo/charles.txt
Normal file
11
examples/personal_assistant_demo/charles.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
This is what I know so far about the user, I should expand this as I learn more about them.
|
||||
|
||||
Name: Charles Packer
|
||||
Gender: Male
|
||||
Occupation: CS PhD student working on an AI project with collaborator Sarah Wooders
|
||||
|
||||
Notes about their preferred communication style + working habits:
|
||||
- wakes up at around 7am
|
||||
- enjoys using (and receiving!) emojis in messages, especially funny combinations of emojis
|
||||
- prefers sending and receiving shorter messages
|
||||
- does not like "robotic" sounding assistants, e.g. assistants that say "How can I assist you today?"
|
||||
56
examples/personal_assistant_demo/gmail_test_setup.py
Normal file
56
examples/personal_assistant_demo/gmail_test_setup.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import os.path
|
||||
|
||||
from google.auth.transport.requests import Request
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
# If modifying these scopes, delete the file token.json.
|
||||
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
|
||||
|
||||
TOKEN_PATH = os.path.expanduser("~/.memgpt/gmail_token.json")
|
||||
CREDENTIALS_PATH = os.path.expanduser("~/.memgpt/google_api_credentials.json")
|
||||
|
||||
|
||||
def main():
|
||||
"""Shows basic usage of the Gmail API.
|
||||
Lists the user's Gmail labels.
|
||||
"""
|
||||
creds = None
|
||||
# The file token.json stores the user's access and refresh tokens, and is
|
||||
# created automatically when the authorization flow completes for the first
|
||||
# time.
|
||||
if os.path.exists(TOKEN_PATH):
|
||||
creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
|
||||
# If there are no (valid) credentials available, let the user log in.
|
||||
if not creds or not creds.valid:
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
else:
|
||||
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)
|
||||
creds = flow.run_local_server(port=0)
|
||||
# Save the credentials for the next run
|
||||
with open(TOKEN_PATH, "w") as token:
|
||||
token.write(creds.to_json())
|
||||
|
||||
try:
|
||||
# Call the Gmail API
|
||||
service = build("gmail", "v1", credentials=creds)
|
||||
results = service.users().labels().list(userId="me").execute()
|
||||
labels = results.get("labels", [])
|
||||
|
||||
if not labels:
|
||||
print("No labels found.")
|
||||
return
|
||||
print("Labels:")
|
||||
for label in labels:
|
||||
print(label["name"])
|
||||
|
||||
except HttpError as error:
|
||||
# TODO(developer) - Handle errors from gmail API.
|
||||
print(f"An error occurred: {error}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,143 @@
|
||||
import base64
|
||||
import os.path
|
||||
import requests
|
||||
import sys
|
||||
import time
|
||||
from email import message_from_bytes
|
||||
|
||||
from google.auth.transport.requests import Request
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
# If modifying these scopes, delete the file token.json.
|
||||
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
|
||||
TOKEN_PATH = os.path.expanduser("~/.memgpt/gmail_token.json")
|
||||
CREDENTIALS_PATH = os.path.expanduser("~/.memgpt/google_api_credentials.json")
|
||||
|
||||
DELAY = 1
|
||||
|
||||
MEMGPT_SERVER_URL = "http://127.0.0.1:8283"
|
||||
MEMGPT_TOKEN = os.getenv("MEMGPT_SERVER_PASS")
|
||||
assert MEMGPT_TOKEN, f"Missing env variable MEMGPT_SERVER_PASS"
|
||||
MEMGPT_AGENT_ID = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
assert MEMGPT_AGENT_ID, f"Missing agent ID (pass as arg)"
|
||||
|
||||
|
||||
def route_reply_to_memgpt_api(message):
|
||||
# send a POST request to a MemGPT server
|
||||
|
||||
url = f"{MEMGPT_SERVER_URL}/api/agents/{MEMGPT_AGENT_ID}/messages"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"authorization": f"Bearer {MEMGPT_TOKEN}",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
data = {
|
||||
"stream": False,
|
||||
"role": "system",
|
||||
"message": f"[EMAIL NOTIFICATION] {message}",
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
print("Got response:", response.text)
|
||||
except Exception as e:
|
||||
print("Sending message failed:", str(e))
|
||||
|
||||
|
||||
def decode_base64url(data):
|
||||
"""Decode base64, padding being optional."""
|
||||
data += "=" * ((4 - len(data) % 4) % 4)
|
||||
return base64.urlsafe_b64decode(data)
|
||||
|
||||
|
||||
def parse_email(message):
|
||||
"""Parse email content using the email library."""
|
||||
msg_bytes = decode_base64url(message["raw"])
|
||||
email_message = message_from_bytes(msg_bytes)
|
||||
return email_message
|
||||
|
||||
|
||||
def process_email(message) -> dict:
|
||||
# print(f"New email from {email_message['from']}: {email_message['subject']}")
|
||||
email_message = parse_email(message)
|
||||
body_plain_all = ""
|
||||
body_html_all = ""
|
||||
if email_message.is_multipart():
|
||||
for part in email_message.walk():
|
||||
if part.get_content_type() == "text/plain":
|
||||
body_plain = str(part.get_payload(decode=True).decode("utf-8"))
|
||||
# print(body_plain)
|
||||
body_plain_all += body_plain
|
||||
elif part.get_content_type() == "text/html":
|
||||
body_html = str(part.get_payload(decode=True).decode("utf-8"))
|
||||
# print(body_html)
|
||||
body_html_all += body_html
|
||||
else:
|
||||
body_plain_all = print(email_message.get_payload(decode=True).decode("utf-8"))
|
||||
|
||||
return {
|
||||
"from": email_message["from"],
|
||||
"subject": email_message["subject"],
|
||||
"body": body_plain_all,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""Monitors for new emails and prints their titles."""
|
||||
creds = None
|
||||
if os.path.exists(TOKEN_PATH):
|
||||
creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
|
||||
|
||||
if not creds or not creds.valid:
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
else:
|
||||
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)
|
||||
creds = flow.run_local_server(port=0)
|
||||
with open(TOKEN_PATH, "w") as token:
|
||||
token.write(creds.to_json())
|
||||
|
||||
service = build("gmail", "v1", credentials=creds)
|
||||
last_checked = None
|
||||
seen_ids = set() # Set to track seen email IDs
|
||||
|
||||
try:
|
||||
# Initially populate the seen_ids with all current unread emails
|
||||
print("Grabbing initial state...")
|
||||
initial_results = service.users().messages().list(userId="me", q="is:unread", maxResults=500).execute()
|
||||
initial_messages = initial_results.get("messages", [])
|
||||
seen_ids.update(msg["id"] for msg in initial_messages)
|
||||
|
||||
print("Listening...")
|
||||
while True:
|
||||
results = service.users().messages().list(userId="me", q="is:unread", maxResults=5).execute()
|
||||
messages = results.get("messages", [])
|
||||
if messages:
|
||||
for message in messages:
|
||||
if message["id"] not in seen_ids:
|
||||
seen_ids.add(message["id"])
|
||||
msg = service.users().messages().get(userId="me", id=message["id"], format="raw").execute()
|
||||
|
||||
# Optionally mark the message as read here if required
|
||||
email_obj = process_email(msg)
|
||||
msg_str = f"New email from {email_obj['from']}: {email_obj['subject']}, body: {email_obj['body'][:100]}"
|
||||
|
||||
# Hard check to ignore emails unless
|
||||
# if not (
|
||||
# "email@address" in email_obj["from"]
|
||||
# ):
|
||||
# print("ignoring")
|
||||
# else:
|
||||
print(msg_str)
|
||||
route_reply_to_memgpt_api(msg_str)
|
||||
|
||||
time.sleep(DELAY) # Wait for N seconds before checking again
|
||||
except HttpError as error:
|
||||
print(f"An error occurred: {error}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
98
examples/personal_assistant_demo/google_calendar.py
Normal file
98
examples/personal_assistant_demo/google_calendar.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# Enabling API control on Google Calendar requires a few steps:
|
||||
# https://developers.google.com/calendar/api/quickstart/python
|
||||
# including:
|
||||
# pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
|
||||
|
||||
import os
|
||||
import traceback
|
||||
import datetime
|
||||
import os.path
|
||||
from typing import Optional, List
|
||||
|
||||
from google.auth.transport.requests import Request
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
# If modifying these scopes, delete the file token.json.
|
||||
# SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
|
||||
SCOPES = ["https://www.googleapis.com/auth/calendar"]
|
||||
TOKEN_PATH = os.path.expanduser("~/.memgpt/gcal_token.json")
|
||||
CREDENTIALS_PATH = os.path.expanduser("~/.memgpt/google_api_credentials.json")
|
||||
|
||||
|
||||
def schedule_event(
|
||||
self,
|
||||
title: str,
|
||||
start: str,
|
||||
end: str,
|
||||
# attendees: Optional[List[str]] = None,
|
||||
# attendees: Optional[list[str]] = None,
|
||||
description: Optional[str] = None,
|
||||
# timezone: Optional[str] = "America/Los_Angeles",
|
||||
) -> str:
|
||||
"""
|
||||
Schedule an event on the user's Google Calendar. Start and end time must be in ISO 8601 format, e.g. February 1st 2024 at noon PT would be "2024-02-01T12:00:00-07:00".
|
||||
|
||||
Args:
|
||||
title (str): Event name
|
||||
start (str): Start time in ISO 8601 format (date, time, and timezone offset)
|
||||
end (str): End time in ISO 8601 format (date, time, and timezone offset)
|
||||
description (Optional[str]): Expanded description of the event
|
||||
|
||||
Returns:
|
||||
str: The status of the event scheduling request.
|
||||
"""
|
||||
|
||||
creds = None
|
||||
# The file token.json stores the user's access and refresh tokens, and is
|
||||
# created automatically when the authorization flow completes for the first
|
||||
# time.
|
||||
if os.path.exists(TOKEN_PATH):
|
||||
creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
|
||||
# If there are no (valid) credentials available, let the user log in.
|
||||
if not creds or not creds.valid:
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
else:
|
||||
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)
|
||||
creds = flow.run_local_server(port=0)
|
||||
# Save the credentials for the next run
|
||||
with open(TOKEN_PATH, "w") as token:
|
||||
token.write(creds.to_json())
|
||||
|
||||
#### Create an event
|
||||
# Refer to the Python quickstart on how to setup the environment:
|
||||
# https://developers.google.com/calendar/quickstart/python
|
||||
# Change the scope to 'https://www.googleapis.com/auth/calendar' and delete any
|
||||
# stored credentials.
|
||||
try:
|
||||
service = build("calendar", "v3", credentials=creds)
|
||||
|
||||
event = {
|
||||
"summary": title,
|
||||
# "location": "800 Howard St., San Francisco, CA 94103",
|
||||
"start": {
|
||||
"dateTime": start,
|
||||
"timeZone": "America/Los_Angeles",
|
||||
},
|
||||
"end": {
|
||||
"dateTime": end,
|
||||
"timeZone": "America/Los_Angeles",
|
||||
},
|
||||
}
|
||||
|
||||
# if attendees is not None:
|
||||
# event["attendees"] = attendees
|
||||
|
||||
if description is not None:
|
||||
event["description"] = description
|
||||
|
||||
event = service.events().insert(calendarId="primary", body=event).execute()
|
||||
return "Event created: %s" % (event.get("htmlLink"))
|
||||
|
||||
except HttpError as error:
|
||||
traceback.print_exc()
|
||||
|
||||
return f"An error occurred while trying to create an event: {str(error)}"
|
||||
11
examples/personal_assistant_demo/google_calendar_preset.yaml
Normal file
11
examples/personal_assistant_demo/google_calendar_preset.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
system_prompt: "memgpt_chat"
|
||||
functions:
|
||||
- "send_message"
|
||||
- "pause_heartbeats"
|
||||
- "core_memory_append"
|
||||
- "core_memory_replace"
|
||||
- "conversation_search"
|
||||
- "conversation_search_date"
|
||||
- "archival_memory_insert"
|
||||
- "archival_memory_search"
|
||||
- "schedule_event"
|
||||
111
examples/personal_assistant_demo/google_calendar_test_setup.py
Normal file
111
examples/personal_assistant_demo/google_calendar_test_setup.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import datetime
|
||||
import os.path
|
||||
|
||||
from google.auth.transport.requests import Request
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
# If modifying these scopes, delete the file token.json.
|
||||
# SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
|
||||
SCOPES = ["https://www.googleapis.com/auth/calendar"]
|
||||
|
||||
TOKEN_PATH = os.path.expanduser("~/.memgpt/gcal_token.json")
|
||||
CREDENTIALS_PATH = os.path.expanduser("~/.memgpt/google_api_credentials.json")
|
||||
|
||||
|
||||
def main():
|
||||
"""Shows basic usage of the Google Calendar API.
|
||||
Prints the start and name of the next 10 events on the user's calendar.
|
||||
"""
|
||||
creds = None
|
||||
# The file token.json stores the user's access and refresh tokens, and is
|
||||
# created automatically when the authorization flow completes for the first
|
||||
# time.
|
||||
if os.path.exists(TOKEN_PATH):
|
||||
creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
|
||||
# If there are no (valid) credentials available, let the user log in.
|
||||
if not creds or not creds.valid:
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
else:
|
||||
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)
|
||||
creds = flow.run_local_server(port=0)
|
||||
# Save the credentials for the next run
|
||||
with open(TOKEN_PATH, "w") as token:
|
||||
token.write(creds.to_json())
|
||||
|
||||
try:
|
||||
service = build("calendar", "v3", credentials=creds)
|
||||
|
||||
# Call the Calendar API
|
||||
now = datetime.datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
|
||||
print("Getting the upcoming 10 events")
|
||||
events_result = (
|
||||
service.events()
|
||||
.list(
|
||||
calendarId="primary",
|
||||
timeMin=now,
|
||||
maxResults=10,
|
||||
singleEvents=True,
|
||||
orderBy="startTime",
|
||||
)
|
||||
.execute()
|
||||
)
|
||||
events = events_result.get("items", [])
|
||||
|
||||
if not events:
|
||||
print("No upcoming events found.")
|
||||
return
|
||||
|
||||
# Prints the start and name of the next 10 events
|
||||
for event in events:
|
||||
start = event["start"].get("dateTime", event["start"].get("date"))
|
||||
print(start, event["summary"])
|
||||
|
||||
except HttpError as error:
|
||||
print(f"An error occurred: {error}")
|
||||
|
||||
#### Create an event
|
||||
# Refer to the Python quickstart on how to setup the environment:
|
||||
# https://developers.google.com/calendar/quickstart/python
|
||||
# Change the scope to 'https://www.googleapis.com/auth/calendar' and delete any
|
||||
# stored credentials.
|
||||
# try:
|
||||
# service = build("calendar", "v3", credentials=creds)
|
||||
|
||||
# event = {
|
||||
# "summary": "GCAL API TEST EVENT",
|
||||
# # "location": "800 Howard St., San Francisco, CA 94103",
|
||||
# "description": "A chance to hear more about Google's developer products.",
|
||||
# "start": {
|
||||
# "dateTime": "2024-04-23T09:00:00-07:00",
|
||||
# "timeZone": "America/Los_Angeles",
|
||||
# },
|
||||
# "end": {
|
||||
# "dateTime": "2024-04-24T17:00:00-07:00",
|
||||
# "timeZone": "America/Los_Angeles",
|
||||
# },
|
||||
# # "recurrence": ["RRULE:FREQ=DAILY;COUNT=2"],
|
||||
# "attendees": [
|
||||
# {"email": "packercharles@gmail.com"},
|
||||
# ],
|
||||
# # "reminders": {
|
||||
# # "useDefault": False,
|
||||
# # "overrides": [
|
||||
# # {"method": "email", "minutes": 24 * 60},
|
||||
# # {"method": "popup", "minutes": 10},
|
||||
# # ],
|
||||
# # },
|
||||
# }
|
||||
|
||||
# event = service.events().insert(calendarId="primary", body=event).execute()
|
||||
# print("Event created: %s" % (event.get("htmlLink")))
|
||||
|
||||
except HttpError as error:
|
||||
print(f"An error occurred: {error}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,12 @@
|
||||
system_prompt: "memgpt_chat"
|
||||
functions:
|
||||
- "send_message"
|
||||
- "pause_heartbeats"
|
||||
- "core_memory_append"
|
||||
- "core_memory_replace"
|
||||
- "conversation_search"
|
||||
- "conversation_search_date"
|
||||
- "archival_memory_insert"
|
||||
- "archival_memory_search"
|
||||
- "schedule_event"
|
||||
- "send_text_message"
|
||||
76
examples/personal_assistant_demo/twilio_flask_listener.py
Normal file
76
examples/personal_assistant_demo/twilio_flask_listener.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
import requests
|
||||
import sys
|
||||
|
||||
from flask import Flask, request, Response
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
from twilio.twiml.messaging_response import MessagingResponse
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
MEMGPT_SERVER_URL = "http://127.0.0.1:8283"
|
||||
MEMGPT_TOKEN = os.getenv("MEMGPT_SERVER_PASS")
|
||||
assert MEMGPT_TOKEN, f"Missing env variable MEMGPT_SERVER_PASS"
|
||||
MEMGPT_AGENT_ID = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
assert MEMGPT_AGENT_ID, f"Missing agent ID (pass as arg)"
|
||||
|
||||
|
||||
@app.route("/test", methods=["POST"])
|
||||
def test():
|
||||
print(request.headers)
|
||||
return "Headers received. Check your console."
|
||||
|
||||
|
||||
def route_reply_to_memgpt_api(message):
|
||||
# send a POST request to a MemGPT server
|
||||
|
||||
url = f"{MEMGPT_SERVER_URL}/api/agents/{MEMGPT_AGENT_ID}/messages"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"authorization": f"Bearer {MEMGPT_TOKEN}",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
data = {
|
||||
"stream": False,
|
||||
"role": "system",
|
||||
"message": f"[SMS MESSAGE NOTIFICATION - you MUST use send_text_message NOT send_message if you want to reply to the text thread] {message}",
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
print("Got response:", response.text)
|
||||
except Exception as e:
|
||||
print("Sending message failed:", str(e))
|
||||
|
||||
|
||||
@app.route("/sms", methods=["POST"])
|
||||
def sms_reply():
|
||||
"""Respond to incoming calls with a simple text message."""
|
||||
# Fetch the message
|
||||
message_body = request.form["Body"]
|
||||
from_number = request.form["From"]
|
||||
|
||||
# print(f"New message from {from_number}: {message_body}")
|
||||
msg_str = f"New message from {from_number}: {message_body}"
|
||||
print(msg_str)
|
||||
|
||||
route_reply_to_memgpt_api(msg_str)
|
||||
return str("status = OK")
|
||||
|
||||
# Start our response
|
||||
# resp = MessagingResponse()
|
||||
|
||||
# Add a message
|
||||
# resp.message("Hello, thanks for messaging!")
|
||||
|
||||
# return str(resp)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# app.run(debug=True)
|
||||
app.run(host="0.0.0.0", port=8284, debug=True)
|
||||
41
examples/personal_assistant_demo/twilio_messaging.py
Normal file
41
examples/personal_assistant_demo/twilio_messaging.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# Download the helper library from https://www.twilio.com/docs/python/install
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from twilio.rest import Client
|
||||
|
||||
|
||||
def send_text_message(self, message: str) -> str:
|
||||
"""
|
||||
Sends an SMS message to the user's phone / cellular device.
|
||||
|
||||
Args:
|
||||
message (str): The contents of the message to send.
|
||||
|
||||
Returns:
|
||||
str: The status of the text message.
|
||||
"""
|
||||
# Find your Account SID and Auth Token at twilio.com/console
|
||||
# and set the environment variables. See http://twil.io/secure
|
||||
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
|
||||
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
|
||||
client = Client(account_sid, auth_token)
|
||||
|
||||
from_number = os.getenv("TWILIO_FROM_NUMBER")
|
||||
to_number = os.getenv("TWILIO_TO_NUMBER")
|
||||
assert from_number and to_number
|
||||
# assert from_number.startswith("+1") and len(from_number) == 12, from_number
|
||||
# assert to_number.startswith("+1") and len(to_number) == 12, to_number
|
||||
|
||||
try:
|
||||
message = client.messages.create(
|
||||
body=str(message),
|
||||
from_=from_number,
|
||||
to=to_number,
|
||||
)
|
||||
return "Message was successfully sent."
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
|
||||
return f"Message failed to send with error: {str(e)}"
|
||||
@@ -0,0 +1,11 @@
|
||||
system_prompt: "memgpt_chat"
|
||||
functions:
|
||||
- "send_message"
|
||||
- "pause_heartbeats"
|
||||
- "core_memory_append"
|
||||
- "core_memory_replace"
|
||||
- "conversation_search"
|
||||
- "conversation_search_date"
|
||||
- "archival_memory_insert"
|
||||
- "archival_memory_search"
|
||||
- "send_text_message"
|
||||
@@ -149,7 +149,9 @@ def load_all_function_sets(merge: bool = True, ignore_duplicates: bool = True) -
|
||||
v["tags"] = tags
|
||||
schemas_and_functions[module_name] = function_set
|
||||
except ValueError as e:
|
||||
printd(f"Error loading function set '{module_name}': {e}")
|
||||
err = f"Error loading function set '{module_name}': {e}"
|
||||
printd(err)
|
||||
warnings.warn(err)
|
||||
|
||||
if merge:
|
||||
# Put all functions from all sets into the same level dict
|
||||
|
||||
Reference in New Issue
Block a user