Skip to content

Commit

Permalink
capture exceptions + move send_sms/voice_respond to twilio_bot.py
Browse files Browse the repository at this point in the history
  • Loading branch information
SanderGi committed Jul 12, 2024
1 parent 936aac5 commit b383316
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 94 deletions.
2 changes: 1 addition & 1 deletion bots/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
create_personal_channel,
SlackBot,
)
from routers.twilio_api import create_voice_call, send_sms_message
from daras_ai_v2.twilio_bot import create_voice_call, send_sms_message
from daras_ai_v2.vector_search import references_as_prompt
from gooeysite.bg_db_conn import get_celery_result_db_safe
from recipes.VideoBots import ReplyButton, messages_as_prompt
Expand Down
3 changes: 2 additions & 1 deletion daras_ai_v2/bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,11 @@ def _mock_api_output(input_text):
def msg_handler(bot: BotInterface):
try:
_msg_handler(bot)
except Exception:
except Exception as e:
bot.send_msg(
text=bot.translate("Sorry, an error occurred. Please try again later."),
)
capture_exception(e)


@db_middleware
Expand Down
92 changes: 88 additions & 4 deletions daras_ai_v2/twilio_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
from daras_ai_v2.bots import BotInterface, ReplyButton
from phonenumber_field.phonenumber import PhoneNumber

from twilio.rest import Client
from twilio.twiml.voice_response import VoiceResponse
from daras_ai_v2.fastapi_tricks import get_route_url

from uuid import uuid4
import base64


class TwilioSMS(BotInterface):
Expand Down Expand Up @@ -34,8 +39,6 @@ def send_msg(
should_translate: bool = False,
update_msg_id: str | None = None,
) -> str | None:
from routers.twilio_api import send_sms_message

assert buttons is None, "Interactive mode is not implemented yet"
assert update_msg_id is None, "Twilio does not support un-sms-ing things"

Expand Down Expand Up @@ -109,8 +112,6 @@ def send_msg(
should_translate: bool = False,
update_msg_id: str | None = None,
) -> str | None:
from routers.twilio_api import twilio_voice_call_respond

assert documents is None, "Twilio does not support sending documents via Voice"
assert video is None, "Twilio does not support sending videos via Voice"
assert buttons is None, "Interactive mode is not implemented yet"
Expand All @@ -137,3 +138,86 @@ def send_msg(

def mark_read(self):
pass # handled in the webhook


def twilio_voice_call_respond(
text: str | None,
audio_url: str | None,
queue_name: str,
call_sid: str,
bi: BotIntegration,
):
"""Respond to the user in the queue with the given text and audio URL."""
from routers.twilio_api import twilio_voice_call_response

text = text
audio_url = audio_url
text = base64.b64encode(text.encode()).decode() if text else "N"
audio_url = base64.b64encode(audio_url.encode()).decode() if audio_url else "N"

queue_sid = None
client = Client(bi.twilio_account_sid, bi.twilio_auth_token)
for queue in client.queues.list():
if queue.friendly_name == queue_name:
queue_sid = queue.sid
break
assert queue_sid, "Queue not found"

client.queues(queue_sid).members(call_sid).update(
url=get_route_url(
twilio_voice_call_response,
dict(bi_id=bi.id, text=text, audio_url=audio_url),
),
method="POST",
)

return queue_sid


def create_voice_call(convo: Conversation, text: str | None, audio_url: str | None):
"""Create a new voice call saying the given text and audio URL and then hanging up. Useful for notifications."""
from routers.twilio_api import say

assert (
convo.twilio_phone_number
), "This is not a Twilio conversation, it has no phone number."

bi: BotIntegration = convo.bot_integration
client = Client(bi.twilio_account_sid, bi.twilio_auth_token)

resp = VoiceResponse()
if text:
say(resp, text, bi)
if audio_url:
resp.play(audio_url)

call = client.calls.create(
twiml=str(resp),
to=convo.twilio_phone_number.as_e164,
from_=bi.twilio_phone_number.as_e164,
)

return call


def send_sms_message(
convo: Conversation, text: str | None, media_url: str | None = None
):
"""Send an SMS message to the given conversation."""

assert (
convo.twilio_phone_number
), "This is not a Twilio conversation, it has no phone number."

account_sid = convo.bot_integration.twilio_account_sid
auth_token = convo.bot_integration.twilio_auth_token
client = Client(account_sid, auth_token)

message = client.messages.create(
body=text or "",
media_url=media_url,
from_=convo.bot_integration.twilio_phone_number.as_e164,
to=convo.twilio_phone_number.as_e164,
)

return message
103 changes: 15 additions & 88 deletions routers/twilio_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from starlette.background import BackgroundTasks
from daras_ai_v2.fastapi_tricks import fastapi_request_urlencoded_body, get_route_url
import base64
from sentry_sdk import capture_exception

router = APIRouter()

Expand Down Expand Up @@ -37,8 +38,9 @@ def say(resp: VoiceResponse, text: str, bi: BotIntegration):
sr = page.run_doc_sr(run_id, uid)
state = sr.to_dict()
resp.play(state["audio_url"])
except Exception:
except Exception as e:
resp.say(text, voice=bi.twilio_voice)
capture_exception(e)
else:
resp.say(text, voice=bi.twilio_voice)

Expand All @@ -60,7 +62,8 @@ def twilio_voice_call(
twilio_account_sid=account_sid,
twilio_phone_number=PhoneNumber.from_string(phone_number),
)
except BotIntegration.DoesNotExist:
except BotIntegration.DoesNotExist as e:
capture_exception(e)
return Response(status_code=404)

text = bi.twilio_initial_text.strip()
Expand Down Expand Up @@ -114,7 +117,8 @@ def twilio_voice_call_asked(
bi = BotIntegration.objects.get(
twilio_account_sid=account_sid, twilio_phone_number=phone_number
)
except BotIntegration.DoesNotExist:
except BotIntegration.DoesNotExist as e:
capture_exception(e)
return Response(status_code=404)

# start processing the user's question
Expand Down Expand Up @@ -163,7 +167,8 @@ def twilio_voice_call_asked_audio(
bi = BotIntegration.objects.get(
twilio_account_sid=account_sid, twilio_phone_number=phone_number
)
except BotIntegration.DoesNotExist:
except BotIntegration.DoesNotExist as e:
capture_exception(e)
return Response(status_code=404)

# start processing the user's question
Expand Down Expand Up @@ -196,7 +201,8 @@ def twilio_voice_call_wait(bi_id: int):

try:
bi = BotIntegration.objects.get(id=bi_id)
except BotIntegration.DoesNotExist:
except BotIntegration.DoesNotExist as e:
capture_exception(e)
return Response(status_code=404)

resp = VoiceResponse()
Expand All @@ -219,39 +225,6 @@ def twilio_voice_call_wait(bi_id: int):
return Response(str(resp), headers={"Content-Type": "text/xml"})


def twilio_voice_call_respond(
text: str | None,
audio_url: str | None,
queue_name: str,
call_sid: str,
bi: BotIntegration,
):
"""Respond to the user in the queue with the given text and audio URL."""

text = text
audio_url = audio_url
text = base64.b64encode(text.encode()).decode() if text else "N"
audio_url = base64.b64encode(audio_url.encode()).decode() if audio_url else "N"

queue_sid = None
client = Client(bi.twilio_account_sid, bi.twilio_auth_token)
for queue in client.queues.list():
if queue.friendly_name == queue_name:
queue_sid = queue.sid
break
assert queue_sid, "Queue not found"

client.queues(queue_sid).members(call_sid).update(
url=get_route_url(
twilio_voice_call_response,
dict(bi_id=bi.id, text=text, audio_url=audio_url),
),
method="POST",
)

return queue_sid


@router.post("/__/twilio/voice/response/{bi_id}/{text}/{audio_url}/")
def twilio_voice_call_response(bi_id: int, text: str, audio_url: str):
"""Response is ready, user has been dequeued, send the response and ask for the next one."""
Expand All @@ -261,7 +234,8 @@ def twilio_voice_call_response(bi_id: int, text: str, audio_url: str):

try:
bi = BotIntegration.objects.get(id=bi_id)
except BotIntegration.DoesNotExist:
except BotIntegration.DoesNotExist as e:
capture_exception(e)
return Response(status_code=404)

resp = VoiceResponse()
Expand Down Expand Up @@ -354,7 +328,8 @@ def twilio_sms(
bi = BotIntegration.objects.get(
twilio_phone_number=PhoneNumber.from_string(phone_number)
)
except BotIntegration.DoesNotExist:
except BotIntegration.DoesNotExist as e:
capture_exception(e)
return Response(status_code=404)

convo, created = Conversation.objects.get_or_create(
Expand Down Expand Up @@ -420,54 +395,6 @@ def start_voice_call_session(
return call


def create_voice_call(convo: Conversation, text: str | None, audio_url: str | None):
"""Create a new voice call saying the given text and audio URL and then hanging up. Useful for notifications."""

assert (
convo.twilio_phone_number
), "This is not a Twilio conversation, it has no phone number."

bi: BotIntegration = convo.bot_integration
client = Client(bi.twilio_account_sid, bi.twilio_auth_token)

resp = VoiceResponse()
if text:
say(resp, text, bi)
if audio_url:
resp.play(audio_url)

call = client.calls.create(
twiml=str(resp),
to=convo.twilio_phone_number.as_e164,
from_=bi.twilio_phone_number.as_e164,
)

return call


def send_sms_message(
convo: Conversation, text: str | None, media_url: str | None = None
):
"""Send an SMS message to the given conversation."""

assert (
convo.twilio_phone_number
), "This is not a Twilio conversation, it has no phone number."

account_sid = convo.bot_integration.twilio_account_sid
auth_token = convo.bot_integration.twilio_auth_token
client = Client(account_sid, auth_token)

message = client.messages.create(
body=text or "",
media_url=media_url,
from_=convo.bot_integration.twilio_phone_number.as_e164,
to=convo.twilio_phone_number.as_e164,
)

return message


def twilio_connect(
current_run: SavedRun,
published_run: PublishedRun,
Expand Down

0 comments on commit b383316

Please sign in to comment.