Real-time SMS analysis and automated responses using WebSockets with Pushbullet and OpenAI

Michaël Scherding
6 min readOct 3, 2023

--

Introduction

In the backdrop of the overwhelming influx of messages we received, I decided to automate the process of replying to these messages. The solution? A combination of Pushbullet’s API for SMS management and OpenAI’s GPT-3 for generating human-like responses.

Context

You’d think an online booking system (hello doctolib) would simplify a medical professional’s life. My wife, working in medicine, thought so too. But the day she left her phone at home, I saw the reality. Tasked with transferring her day’s messages, I was swamped. Despite her online agenda, she received a flood of appointment requests via messages. Technology promises efficiency, but the human need for direct interaction still dominates, often overwhelming the digital solutions in place.

I was wondering if we could process this problem using a funky solution.

Disclaimer

This project was developed as a side experiment and is not intended for production use. Medical information is highly sensitive, and confidentiality is paramount. If you’re considering implementing a similar solution in a real-world scenario, especially in the medical field, it’s crucial to:

  1. Ensure Data Privacy: Always prioritize the privacy and security of patient data. Ensure that any system you develop complies with regulations in your country.
  2. Consider Self-Hosting: While GPT-3 is a powerful tool, for sensitive applications, it might be more appropriate to use a self-hosted language model solution to have full control over the data and its processing.
  3. Test Extensively: Before deploying any automated system, especially in the medical domain, ensure it’s rigorously tested to avoid any potential miscommunications or errors.

Remember, while technology can be a great enabler, it’s our responsibility to use it ethically and responsibly, especially when dealing with sensitive information.

Overall Architecture

  1. Pushbullet: This service allows users to see phone notifications on their computer. It provides an API to manage SMS, which we use to listen for incoming messages and send replies.
  2. OpenAI’s GPT-3: Once we receive a message, we use GPT-3 to generate a human-like response based on the content of the incoming SMS.

Code Breakdown

Setting Up

import os
import json
import openai
import logging
import asyncio
import requests
import websockets

We start by importing the necessary libraries. The key ones here are openai for interacting with GPT-3, requests for HTTP requests, and websockets for real-time communication with Pushbullet.

Configuration

openai.api_key = os.environ.get('OPENAI_API_KEY')
access_token = os.environ.get('PUSHBULLET_ACCESS_TOKEN')
device_iden = os.environ.get('DEVICE_ID')

Sensitive information like API keys are fetched from environment variables for security reasons.

Listening for SMS

async def listen_for_sms():
while True:
try:
async with websockets.connect(f'wss://stream.pushbullet.com/websocket/{access_token}') as websocket:
while True:
response = await websocket.recv()
message_data = json.loads(response)
if message_data.get('type') == 'push' and message_data.get('push', {}).get('type') == 'sms_changed':
notifications = message_data.get('push', {}).get('notifications', [])
for notification in notifications:
thread_id = notification.get('thread_id')
sender_name = notification.get('title')
sms_text = notification.get('body')
print(f'New SMS from {sender_name}: {sms_text}')

threads_url = f'https://api.pushbullet.com/v2/permanents/{device_iden}_threads'

threads_response = requests.get(threads_url, headers=headers)
if threads_response.status_code == 200:
threads_data = threads_response.json()
threads = threads_data.get('threads', [])
for thread in threads:
if thread.get('id') == thread_id:
recipients = thread.get('recipients', [])
if recipients:
phone_number = recipients[0].get('address')
if phone_number:
await send_reply(phone_number, sms_text)
else:
logging.error(f'No phone number found for thread_id {thread_id}')
else:
logging.error(f'No recipients found for thread_id {thread_id}')
break
else:
logging.error(f'Failed to get thread data: {threads_response.text}')
except websockets.ConnectionClosedError as e:
logging.error(f"Connection closed: {e}, retrying in 1 seconds...")
await asyncio.sleep(1)
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")

This asynchronous function constantly listens for incoming SMS using Pushbullet’s WebSocket API. When a new SMS arrives, it fetches the thread details to get the sender’s phone number and then triggers a reply using GPT-3.

Generating and Sending Replies

async def send_reply(phone_number, sender_message):
try:
gpt_response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "system",
"content": (
"You are a helpful assistant for xxx, an xxx. "
"Your task is to inform patients that xxx will look at their messages later "
"and they can book a consultation directly on Doctolib at "
"https://www.doctolib.fr/xxx/xxx/xxx/booking."
)
},
{"role": "user", "content": sender_message}
]
)
gpt_message = gpt_response['choices'][0]['message']['content']
print(f'Sending response to {phone_number}: {gpt_message}')
data = {
'data': {
'target_device_iden': device_iden,
'addresses': [phone_number],
'message': gpt_message
}
}
response = requests.post('https://api.pushbullet.com/v2/texts', headers=headers, data=json.dumps(data))
response.raise_for_status()
logging.info('Reply sent successfully.')
except Exception as e:
logging.error(f'Failed to send reply: {e}')

This function takes in the sender’s phone number and their message. It then communicates with OpenAI’s GPT-3 to generate a suitable reply. The context provided to GPT-3 is that of an assistant, guiding patients to book consultations online.

Once the reply is generated, it’s sent back to the sender using Pushbullet’s API.

Result

Next steps

As with any project, there’s always room for growth and improvement. Here are some features that could be implemented in the future to enhance the capabilities and user experience of our automated SMS reply system:

  • Contextual Understanding: Integrate advanced Natural Language Processing (NLP) techniques to discern when a patient is specifically requesting an appointment. This ensures that the system doesn’t send automated replies to every message, but only to those that are relevant.
  • Pattern Recognition: Over time, the system can learn from the types of requests it receives. By identifying common phrases, keywords, or patterns in appointment requests, it can improve its accuracy in recognizing genuine appointment inquiries.
  • Fallback Mechanism: In cases where the system is unsure about the context of the message, it can either escalate the message for manual review or send a more generic response asking the patient for more clarity.
  • Agenda Access: By integrating with the agenda API (e.g., doctolib), the system can fetch the current availability of the doctor in real-time.
  • Intelligent Slot Recommendation: Upon recognizing an appointment request, the system can automatically suggest available slots to the patient based on the doctor’s current schedule.
  • Confirmation Workflow: Once a patient agrees to a proposed slot, the system can automatically book the appointment and send a confirmation message, streamlining the entire booking process.
  • Rescheduling & Cancellation: Beyond initial bookings, the chat system can also handle rescheduling or cancellation requests, updating the doctor’s schedule accordingly and notifying both the patient and the doctor.
  • Integration with Other Platforms: While starting with Doctolib, the system can be designed to be modular, allowing for easy integration with other appointment booking platforms in the future.

Full code (working on notebook)

import os
import json
import openai
import logging
import asyncio
import requests
import websockets

openai.api_key = os.environ.get('OPENAI_API_KEY')
access_token = os.environ.get('PUSHBULLET_ACCESS_TOKEN')
device_iden = os.environ.get('DEVICE_ID')

headers = {
'Access-Token': access_token,
'Content-Type': 'application/json'
}

logging.basicConfig(level=logging.INFO)

async def listen_for_sms():
while True:
try:
async with websockets.connect(f'wss://stream.pushbullet.com/websocket/{access_token}') as websocket:
while True:
response = await websocket.recv()
message_data = json.loads(response)
if message_data.get('type') == 'push' and message_data.get('push', {}).get('type') == 'sms_changed':
notifications = message_data.get('push', {}).get('notifications', [])
for notification in notifications:
thread_id = notification.get('thread_id')
sender_name = notification.get('title')
sms_text = notification.get('body')
print(f'New SMS from {sender_name}: {sms_text}')

threads_url = f'https://api.pushbullet.com/v2/permanents/{device_iden}_threads'

threads_response = requests.get(threads_url, headers=headers)
if threads_response.status_code == 200:
threads_data = threads_response.json()
threads = threads_data.get('threads', [])
for thread in threads:
if thread.get('id') == thread_id:
recipients = thread.get('recipients', [])
if recipients:
phone_number = recipients[0].get('address')
if phone_number:
await send_reply(phone_number, sms_text)
else:
logging.error(f'No phone number found for thread_id {thread_id}')
else:
logging.error(f'No recipients found for thread_id {thread_id}')
break
else:
logging.error(f'Failed to get thread data: {threads_response.text}')
except websockets.ConnectionClosedError as e:
logging.error(f"Connection closed: {e}, retrying in 1 seconds...")
await asyncio.sleep(1)
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")

async def send_reply(phone_number, sender_message):
try:
gpt_response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "system",
"content": (
"You are a helpful assistant for xxx, an xxx. "
"Your task is to inform patients that xxx will look at their messages later "
"and they can book a consultation directly on Doctolib at "
"https://www.doctolib.fr/xxx/xxx/xxx/booking."
)
},
{"role": "user", "content": sender_message}
]
)
gpt_message = gpt_response['choices'][0]['message']['content']
print(f'Sending response to {phone_number}: {gpt_message}') # Print statement added
data = {
'data': {
'target_device_iden': device_iden,
'addresses': [phone_number],
'message': gpt_message
}
}
response = requests.post('https://api.pushbullet.com/v2/texts', headers=headers, data=json.dumps(data))
response.raise_for_status()
logging.info('Reply sent successfully.')
except Exception as e:
logging.error(f'Failed to send reply: {e}')

# If you are running this in a Jupyter Notebook or similar environment:
await listen_for_sms()

# If you are running this in a standalone script:
# asyncio.get_event_loop().run_until_complete(listen_for_sms())

The full code is also available on GitHub.

Conclusion

By leveraging the power of Pushbullet and OpenAI’s GPT-3, we’ve created an automated system that can handle the influx of messages, guiding patients to book their appointments online. This not only saves time but also ensures that each patient receives a timely response.

Take care 🤟

--

--

No responses yet