Skip to content

Commit

Permalink
Azure func code, requirements, readme, images. Two alternative types …
Browse files Browse the repository at this point in the history
…of helpers uploaded.
  • Loading branch information
lperezmo committed Dec 17, 2023
1 parent 752b384 commit 1488479
Show file tree
Hide file tree
Showing 14 changed files with 620 additions and 2 deletions.
11 changes: 11 additions & 0 deletions .funcignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.venv
.github
.vscode
docs
__pycache__
*.pyc
*.pyo
*.pyd
*.db
helper_ai.py
test_error.py
68 changes: 66 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,66 @@
# ai-sms-assistant
An AI-powered, function-calling text assistant written in Python and hosted through Azure Functions.
# SMS Helper

## Overview
SMS Helper is an AI-powered, function-calling text assistant designed to process and respond to SMS messages. This application is written in Python and hosted through Azure Functions, utilizing Twilio as the SMS texting platform.

## Features
- Backend can call external tools to perform various functions, such as scheduling text and phone reminders.
- This functionality is supported through by allowing AI to call [flask-automated-reminders](https://github.com/lperezmo/flask-automated-reminders).
- Azure Functions allow us to write the backend purely in Python & easily deploy changes.
- Uses Twilio for SMS functionalities and integration.

## Deploy on Azure (VS Code)
1. Create an Azure account & create a function with all defaults (Python).
2. Create required environment variables on your function.
- `ACCOUNT_SID`
- `AUTH_TOKEN`
- `TWIL_NUMBER`
- `TWIL_EXAMPLE_NUMBER`
- `OPENAI_API_KEY`
- `SENTRY_DSN`

![Setting Env variables in Azure](images\azure_func_env_variables.png)
2. In VS Code, install the Azure Functions extension (this will make it way easier).

<img src="images\extension.png" alt="Extension" width="200">

3. Open the folder where this repo is located.
* **(Optional)** Edit `__init__.py` code to use alternative helper if you want to send less messages per request.
* **(Optional)** If using the 'schedule reminders' function, edit system message and API call to get the current time and date if you want something different than pacific time.
4. Go to the extension, and under 'Workspace' click on the little thunder sign and select 'Deploy to existing function...'.

![Deploy](images\deploy.png)

5. Follow the prompts and done. Get your URL endpoint from Azure & load on Twilio.



## Load on Twilio
1. Create Twilio account.
2. Go to Phone Numbers > Manage > Active Numbers > Your number > Messaging configuration.
3. Set when a message comes in to use a webhook with your Azure Function endpoint (see image below)

![Twilio config](images\messaging_configuration.png)

*Note: Your endpoint will look like this:
`https://YOUR-FUNCTION-NAME.azurewebsites.net/api/sms_helper`*

## Summary
1. Configure Azure & Twilio
2. Create function in Azure
3. Deploy
4. Load on Twilio
5. Text your new SMS bud

## Requirements
The project requires the following dependencies:
- `azure-functions==1.17.0`
- `twilio==8.10.2`
- `openai==1.3.2`
- `sentry-sdk`

## Contributing
Contributions to SMS Helper are welcome. Just submit a pull request and I'll take a look at it.

## License
GPL-3.0 license
232 changes: 232 additions & 0 deletions alternative_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import os
import json
import logging
import requests
from openai import OpenAI
import azure.functions as func
from twilio.rest import Client

#------------------------------------#
# Load environment variables
#------------------------------------#
ACCOUNT_SID = os.environ["ACCOUNT_SID"]
AUTH_TOKEN = os.environ["AUTH_TOKEN"]
TWIL_NUMBER = os.environ["TWIL_NUMBER"]
TWIL_EXAMPLE_NUMBER = os.environ["TWIL_EXAMPLE_NUMBER"]

#------------------------------------#
# OpenAI and Twilio Clients
#------------------------------------#
ai_client = OpenAI()
CLIENT = Client(ACCOUNT_SID, AUTH_TOKEN)

#------------------------------------#
# Security check
#------------------------------------#
def check_pin_and_reply(PIN, incoming_message):
"""
Generate a reply based on the incoming message.
Parameters
----------
PIN : str
Security PIN
incoming_message : str
Incoming message from Twilio
Returns
-------
message : str
Reply message
"""
if incoming_message.strip() == PIN:
return """Welcome back lord overlord Luis.
- I can schedule calls, texts, and reminders for you.
- I can also just answer questions or anything like that.
- Text 'yolo' to see this message again"""
else:
messages = CLIENT.messages.list(from_=send_to, to=send_from)
sent_pin = False
for message in messages:
if message.body.strip() == PIN:
sent_pin = True
if sent_pin:
follow_up_reply = get_follow_up_text(incoming_message)
return follow_up_reply
else:
return "Please provide security PIN to continue"

#------------------------------------#
# Current time
#------------------------------------#
def get_time():
"""
Robustly get the current time from an API.
Parameters
----------
None
Returns
-------
str
Current time
"""
max_retries = 3
attempts = 0
while attempts < max_retries:
try:
response = requests.get('http://worldtimeapi.org/api/timezone/America/Los_Angeles')
response.raise_for_status() # This will raise an exception for HTTP error codes

res = response.json()
datetime = res.get('datetime')
abbreviation = res.get('abbreviation')
day_of_week = res.get('day_of_week')

if datetime and abbreviation and day_of_week is not None:
return f"{datetime} {abbreviation} day of the week {day_of_week}"
else:
raise ValueError("Incomplete time data received")

except (requests.RequestException, ValueError) as e:
attempts += 1
if attempts == max_retries:
return "Failed to get time after several attempts."

#-----------------------------------------#
# Generate JSON body to schedule reminder
#-----------------------------------------#
def schedule_reminder(natural_language_request):
"""
Generate JSON body to schedule reminder
Parameters
----------
natural_language_request : str
Natural language request from user
Returns
-------
JSON body to schedule reminder
"""
sys_prompt = """Your job is to create the JSON body for an API call to schedule texts and calls. Then , you will schedule the text or call for the user will request based on pacific time (given in pacific time). If user asks for a reminder today at 6 pm that is 18:00 (24 hour notation).
If the user requests to be called or messaged on their work phone, set to_phone variable to '+12221110000' else send it to default phone '+15554443333'. Use twilio = True by default.
Example endpoint: http://YOUR-ENDPOINT.elasticbeanstalk.com/schedule_single_reminder
Example call:
{
"time": "18:20",
"day": "2023-11-27",
"message_body": "This is the reminder body!",
"call": "True",
"twilio": "True",
"to_number": "+15554443333"
}
Example message:
{
"time":"23:46",
"day":"2023-11-27",
"message_body":"text reminder to check email",
"to_number":"+15554443333",
"twilio":"True",
"call":"False"
}
"""
curr_time = get_time()
ai_client = OpenAI()
completion = ai_client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=[
{"role": "system", "content": f"{sys_prompt}"},
{"role": "user", "content": f"{natural_language_request}. <Current Time>: {curr_time}"},
],
response_format={ "type": "json_object" },
)

return json.loads(completion.choices[0].message.content)

#------------------------------------#
# Follow up text
#------------------------------------#
def get_follow_up_text(send_to, send_from, incoming_message):
"""Send follow up text
Parameters
----------
send_to : str
Phone number to send text to
send_from : str
Phone number to send text from
incoming_message : str
Incoming message from Twilio
Returns
-------
message : str
Response from the AI to the user
"""
if incoming_message == 'yolo':
return """Welcome back lord overlord Luis.
- I can schedule calls, texts, and reminders for you.
- I can also just answer questions or anything like that.
- Text 'yolo' to see this message again"""
else:
tools = [
{
"type": "function",
"function": {
"name": "schedule_reminder",
"description": "Schedule a reminder using natural language",
"parameters": {
"type": "object",
"properties": {
"natural_language_request": {
"type": "string",
"description": "Requested reminder in natural language. Example: 'Remind me to call mom tomorrow at 6pm' or 'Send me a message with a Matrix quote on wednesday at 8am'",
}
},
"required": ["natural_language_request"],
},
}
}
]
#----------------------------------------------------#
# AI w/tools - reply or use tools to schedule reminder
#----------------------------------------------------#
completion = ai_client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=[
{"role": "system", "content": f"You are an AI assistant that can schedule reminders (like calls and texts) if asked to do so. Be informative, funny, and helpful, and keep your messages clear and short. To schedule reminder just pass a natural language request to the function 'schedule_reminder'"},
{"role": "user", "content": f"{incoming_message}"}
],
tools=tools,
tool_choice="auto"
)
message = completion.choices[0].message.content
if message==None:
message = "Just a minute while I schedule your reminder."
else:
return message
#----------------------------------------------------#
# If tools are called, call the tools function
#----------------------------------------------------#
if completion.choices[0].message.tool_calls:
if completion.choices[0].message.tool_calls[0].function.name == 'schedule_reminder':
args = completion.choices[0].message.tool_calls[0].function.arguments
args_dict = json.loads(args)
try:
#--------------------------------#
# Schedule reminder
#--------------------------------#
json_body = schedule_reminder(**args_dict)
url_endpoint = "http://YOUR-ENDPOINT.elasticbeanstalk.com/schedule_single_reminder"
headers = {'Content-Type': 'application/json'}
response = requests.post(url_endpoint, headers=headers, data=json.dumps(json_body))
if response.status_code == 200:
return "Your reminder has been scheduled."
except Exception as e:
logging.error(f"Error: {e}")
return "Error scheduling reminder."
Loading

0 comments on commit 1488479

Please sign in to comment.