From d27ea340b72ef99ab8eadff66e3e8091df4493ad Mon Sep 17 00:00:00 2001 From: Ajay Raj Date: Wed, 26 Jul 2023 17:58:51 -0700 Subject: [PATCH 1/4] checkpoint --- .../telephony/conversation/__init__.py | 18 ++++++++++++++++++ .../streaming/telephony/conversation/call.py | 9 ++++++++- .../telephony/conversation/outbound_call.py | 17 ++++------------- .../telephony/conversation/twilio_call.py | 16 +++++++++++----- .../telephony/conversation/vonage_call.py | 1 + 5 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 vocode/streaming/telephony/conversation/__init__.py diff --git a/vocode/streaming/telephony/conversation/__init__.py b/vocode/streaming/telephony/conversation/__init__.py new file mode 100644 index 000000000..66db32023 --- /dev/null +++ b/vocode/streaming/telephony/conversation/__init__.py @@ -0,0 +1,18 @@ +from typing import Optional +from vocode.streaming.models.telephony import TwilioConfig, VonageConfig +from vocode.streaming.telephony.client.base_telephony_client import BaseTelephonyClient +from vocode.streaming.telephony.client.twilio_client import TwilioClient +from vocode.streaming.telephony.client.vonage_client import VonageClient + + +def create_telephony_client( + base_url: str, + maybe_twilio_config: Optional[TwilioConfig], + maybe_vonage_config: Optional[VonageConfig], +) -> BaseTelephonyClient: + if maybe_twilio_config is not None: + return TwilioClient(base_url=base_url, twilio_config=maybe_twilio_config) + elif maybe_vonage_config is not None: + return VonageClient(base_url=base_url, vonage_config=maybe_vonage_config) + else: + raise ValueError("No telephony config provided") diff --git a/vocode/streaming/telephony/conversation/call.py b/vocode/streaming/telephony/conversation/call.py index 37fb44273..b5ee76c36 100644 --- a/vocode/streaming/telephony/conversation/call.py +++ b/vocode/streaming/telephony/conversation/call.py @@ -16,11 +16,13 @@ TranscriberConfig, ) from vocode.streaming.synthesizer.factory import SynthesizerFactory +from vocode.streaming.telephony.client.base_telephony_client import BaseTelephonyClient from vocode.streaming.telephony.config_manager.base_config_manager import ( BaseConfigManager, ) from vocode.streaming.telephony.constants import DEFAULT_SAMPLING_RATE from vocode.streaming.streaming_conversation import StreamingConversation +from vocode.streaming.telephony.conversation import create_telephony_client from vocode.streaming.transcriber.factory import TranscriberFactory from vocode.streaming.utils.events_manager import EventsManager from vocode.streaming.utils.conversation_logger_adapter import wrap_logger @@ -34,6 +36,7 @@ class Call(StreamingConversation[TelephonyOutputDeviceType]): def __init__( self, + telephony_id: str, from_phone: str, to_phone: str, base_url: str, @@ -49,6 +52,8 @@ def __init__( events_manager: Optional[EventsManager] = None, logger: Optional[logging.Logger] = None, ): + self.telephony_id = telephony_id + self.telephony_client: BaseTelephonyClient = create_telephony_client() conversation_id = conversation_id or create_conversation_id() logger = wrap_logger( logger or logging.getLogger(__name__), @@ -78,6 +83,8 @@ def attach_ws(self, ws: WebSocket): async def attach_ws_and_start(self, ws: WebSocket): raise NotImplementedError - async def tear_down(self): + async def tear_down(self, end_call: bool = True): self.events_manager.publish_event(PhoneCallEndedEvent(conversation_id=self.id)) await self.terminate() + if end_call: + await self.telephony_client.end_call(self.telephony_id) diff --git a/vocode/streaming/telephony/conversation/outbound_call.py b/vocode/streaming/telephony/conversation/outbound_call.py index 88bc77ccb..889abf193 100644 --- a/vocode/streaming/telephony/conversation/outbound_call.py +++ b/vocode/streaming/telephony/conversation/outbound_call.py @@ -22,6 +22,7 @@ from vocode.streaming.telephony.config_manager.base_config_manager import ( BaseConfigManager, ) +from vocode.streaming.telephony.conversation import create_telephony_client from vocode.streaming.utils import create_conversation_id @@ -64,7 +65,9 @@ def __init__( account_sid=getenv("TWILIO_ACCOUNT_SID"), auth_token=getenv("TWILIO_AUTH_TOKEN"), ) - self.telephony_client = self.create_telephony_client() + self.telephony_client = create_telephony_client( + self.base_url, self.twilio_config, self.vonage_config + ) assert not output_to_speaker or isinstance( self.telephony_client, VonageClient ), "Output to speaker is only supported for Vonage calls" @@ -73,18 +76,6 @@ def __init__( self.telephony_id = None self.output_to_speaker = output_to_speaker - def create_telephony_client(self) -> BaseTelephonyClient: - if self.twilio_config is not None: - return TwilioClient( - base_url=self.base_url, twilio_config=self.twilio_config - ) - elif self.vonage_config is not None: - return VonageClient( - base_url=self.base_url, vonage_config=self.vonage_config - ) - else: - raise ValueError("No telephony config provided") - def create_transcriber_config( self, transcriber_config_override: Optional[TranscriberConfig] ) -> TranscriberConfig: diff --git a/vocode/streaming/telephony/conversation/twilio_call.py b/vocode/streaming/telephony/conversation/twilio_call.py index eedf3468c..c60265512 100644 --- a/vocode/streaming/telephony/conversation/twilio_call.py +++ b/vocode/streaming/telephony/conversation/twilio_call.py @@ -53,6 +53,7 @@ def __init__( logger: Optional[logging.Logger] = None, ): super().__init__( + twilio_sid, from_phone, to_phone, base_url, @@ -90,10 +91,16 @@ async def attach_ws_and_start(self, ws: WebSocket): twilio_call = twilio_call_ref.fetch() if self.twilio_config.record: - recordings_create_params = self.twilio_config.extra_params.get("recordings_create_params") if self.twilio_config.extra_params else None - recording = twilio_call_ref.recordings.create( - **recordings_create_params - ) if recordings_create_params else twilio_call_ref.recordings.create() + recordings_create_params = ( + self.twilio_config.extra_params.get("recordings_create_params") + if self.twilio_config.extra_params + else None + ) + recording = ( + twilio_call_ref.recordings.create(**recordings_create_params) + if recordings_create_params + else twilio_call_ref.recordings.create() + ) self.logger.info(f"Recording: {recording.sid}") if twilio_call.answered_by in ("machine_start", "fax"): @@ -157,4 +164,3 @@ async def handle_ws_message(self, message) -> Optional[PhoneCallWebsocketAction] def mark_terminated(self): super().mark_terminated() asyncio.create_task(self.telephony_client.end_call(self.twilio_sid)) - diff --git a/vocode/streaming/telephony/conversation/vonage_call.py b/vocode/streaming/telephony/conversation/vonage_call.py index 251c39950..79cc7c300 100644 --- a/vocode/streaming/telephony/conversation/vonage_call.py +++ b/vocode/streaming/telephony/conversation/vonage_call.py @@ -52,6 +52,7 @@ def __init__( logger: Optional[logging.Logger] = None, ): super().__init__( + vonage_uuid, from_phone, to_phone, base_url, From ebf53fdd2d5395fc0b596b0dd086f410281aba4f Mon Sep 17 00:00:00 2001 From: Ajay Raj Date: Wed, 26 Jul 2023 18:10:54 -0700 Subject: [PATCH 2/4] finish --- .../streaming/telephony/conversation/call.py | 23 +++++++++++++++++-- .../telephony/conversation/outbound_call.py | 17 ++++++++++---- .../telephony/conversation/twilio_call.py | 11 ++++----- .../telephony/conversation/vonage_call.py | 3 +++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/vocode/streaming/telephony/conversation/call.py b/vocode/streaming/telephony/conversation/call.py index b5ee76c36..a2fe9a0c8 100644 --- a/vocode/streaming/telephony/conversation/call.py +++ b/vocode/streaming/telephony/conversation/call.py @@ -1,3 +1,5 @@ +from app.lib.telephony.client.twilio_client import TwilioClient +from app.lib.telephony.client.vonage_client import VonageClient from fastapi import WebSocket from enum import Enum import logging @@ -5,6 +7,7 @@ from vocode.streaming.agent.factory import AgentFactory from vocode.streaming.models.agent import AgentConfig from vocode.streaming.models.events import PhoneCallEndedEvent +from vocode.streaming.models.telephony import TwilioConfig, VonageConfig from vocode.streaming.output_device.vonage_output_device import VonageOutputDevice from vocode.streaming.streaming_conversation import StreamingConversation @@ -53,7 +56,6 @@ def __init__( logger: Optional[logging.Logger] = None, ): self.telephony_id = telephony_id - self.telephony_client: BaseTelephonyClient = create_telephony_client() conversation_id = conversation_id or create_conversation_id() logger = wrap_logger( logger or logging.getLogger(__name__), @@ -75,6 +77,22 @@ def __init__( logger=logger, ) + def get_telephony_client_config(self) -> Union[TwilioConfig, VonageConfig]: + raise NotImplementedError + + def create_telephony_client(self) -> BaseTelephonyClient: + telephony_client_config = self.get_telephony_client_config() + if isinstance(telephony_client_config, TwilioConfig): + return TwilioClient( + base_url=self.base_url, twilio_config=telephony_client_config + ) + elif isinstance(telephony_client_config, VonageConfig): + return VonageClient( + base_url=self.base_url, vonage_config=telephony_client_config + ) + else: + raise ValueError("No telephony config provided") + def attach_ws(self, ws: WebSocket): self.logger.debug("Trying to attach WS to outbound call") self.output_device.ws = ws @@ -87,4 +105,5 @@ async def tear_down(self, end_call: bool = True): self.events_manager.publish_event(PhoneCallEndedEvent(conversation_id=self.id)) await self.terminate() if end_call: - await self.telephony_client.end_call(self.telephony_id) + telephony_client = self.create_telephony_client() + await telephony_client.end_call(self.telephony_id) diff --git a/vocode/streaming/telephony/conversation/outbound_call.py b/vocode/streaming/telephony/conversation/outbound_call.py index 889abf193..88bc77ccb 100644 --- a/vocode/streaming/telephony/conversation/outbound_call.py +++ b/vocode/streaming/telephony/conversation/outbound_call.py @@ -22,7 +22,6 @@ from vocode.streaming.telephony.config_manager.base_config_manager import ( BaseConfigManager, ) -from vocode.streaming.telephony.conversation import create_telephony_client from vocode.streaming.utils import create_conversation_id @@ -65,9 +64,7 @@ def __init__( account_sid=getenv("TWILIO_ACCOUNT_SID"), auth_token=getenv("TWILIO_AUTH_TOKEN"), ) - self.telephony_client = create_telephony_client( - self.base_url, self.twilio_config, self.vonage_config - ) + self.telephony_client = self.create_telephony_client() assert not output_to_speaker or isinstance( self.telephony_client, VonageClient ), "Output to speaker is only supported for Vonage calls" @@ -76,6 +73,18 @@ def __init__( self.telephony_id = None self.output_to_speaker = output_to_speaker + def create_telephony_client(self) -> BaseTelephonyClient: + if self.twilio_config is not None: + return TwilioClient( + base_url=self.base_url, twilio_config=self.twilio_config + ) + elif self.vonage_config is not None: + return VonageClient( + base_url=self.base_url, vonage_config=self.vonage_config + ) + else: + raise ValueError("No telephony config provided") + def create_transcriber_config( self, transcriber_config_override: Optional[TranscriberConfig] ) -> TranscriberConfig: diff --git a/vocode/streaming/telephony/conversation/twilio_call.py b/vocode/streaming/telephony/conversation/twilio_call.py index c60265512..38e4efcf9 100644 --- a/vocode/streaming/telephony/conversation/twilio_call.py +++ b/vocode/streaming/telephony/conversation/twilio_call.py @@ -4,13 +4,13 @@ from enum import Enum import json import logging -from typing import Optional +from typing import Optional, Union from vocode import getenv from vocode.streaming.agent.factory import AgentFactory from vocode.streaming.models.agent import AgentConfig from vocode.streaming.models.events import PhoneCallConnectedEvent -from vocode.streaming.models.telephony import TwilioConfig +from vocode.streaming.models.telephony import TwilioConfig, VonageConfig from vocode.streaming.output_device.twilio_output_device import TwilioOutputDevice from vocode.streaming.models.synthesizer import ( SynthesizerConfig, @@ -81,6 +81,9 @@ def __init__( self.twilio_sid = twilio_sid self.latest_media_timestamp = 0 + def get_telephony_client_config(self): + return self.twilio_config + def create_state_manager(self) -> TwilioCallStateManager: return TwilioCallStateManager(self) @@ -160,7 +163,3 @@ async def handle_ws_message(self, message) -> Optional[PhoneCallWebsocketAction] self.logger.debug("Stopping...") return PhoneCallWebsocketAction.CLOSE_WEBSOCKET return None - - def mark_terminated(self): - super().mark_terminated() - asyncio.create_task(self.telephony_client.end_call(self.twilio_sid)) diff --git a/vocode/streaming/telephony/conversation/vonage_call.py b/vocode/streaming/telephony/conversation/vonage_call.py index 79cc7c300..4b4e8cf85 100644 --- a/vocode/streaming/telephony/conversation/vonage_call.py +++ b/vocode/streaming/telephony/conversation/vonage_call.py @@ -86,6 +86,9 @@ def __init__( sampling_rate=VONAGE_SAMPLING_RATE, blocksize=VONAGE_CHUNK_SIZE // 2 ) + def get_telephony_client_config(self): + return self.vonage_config + def create_state_manager(self) -> VonageCallStateManager: return VonageCallStateManager(self) From ae38ea726c1375284b2f0bb326e2e035c7feb1f1 Mon Sep 17 00:00:00 2001 From: Ajay Raj Date: Wed, 26 Jul 2023 18:12:13 -0700 Subject: [PATCH 3/4] remove unneeded init --- .../telephony/conversation/__init__.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 vocode/streaming/telephony/conversation/__init__.py diff --git a/vocode/streaming/telephony/conversation/__init__.py b/vocode/streaming/telephony/conversation/__init__.py deleted file mode 100644 index 66db32023..000000000 --- a/vocode/streaming/telephony/conversation/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Optional -from vocode.streaming.models.telephony import TwilioConfig, VonageConfig -from vocode.streaming.telephony.client.base_telephony_client import BaseTelephonyClient -from vocode.streaming.telephony.client.twilio_client import TwilioClient -from vocode.streaming.telephony.client.vonage_client import VonageClient - - -def create_telephony_client( - base_url: str, - maybe_twilio_config: Optional[TwilioConfig], - maybe_vonage_config: Optional[VonageConfig], -) -> BaseTelephonyClient: - if maybe_twilio_config is not None: - return TwilioClient(base_url=base_url, twilio_config=maybe_twilio_config) - elif maybe_vonage_config is not None: - return VonageClient(base_url=base_url, vonage_config=maybe_vonage_config) - else: - raise ValueError("No telephony config provided") From 1323583ef6d97808ba2574e2428cb87bac8e7cc5 Mon Sep 17 00:00:00 2001 From: Ajay Raj Date: Wed, 26 Jul 2023 18:16:02 -0700 Subject: [PATCH 4/4] state manager change --- vocode/streaming/utils/state_manager.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/vocode/streaming/utils/state_manager.py b/vocode/streaming/utils/state_manager.py index 4f1d61f01..eb698a11e 100644 --- a/vocode/streaming/utils/state_manager.py +++ b/vocode/streaming/utils/state_manager.py @@ -21,13 +21,13 @@ def set_transcriber_endpointing_config(self, endpointing_config: EndpointingConf self._conversation.transcriber.get_transcriber_config().endpointing_config = ( endpointing_config ) - + def disable_synthesis(self): self._conversation.synthesis_enabled = False - + def enable_synthesis(self): self._conversation.synthesis_enabled = True - + async def terminate_conversation(self): await self._conversation.terminate() @@ -37,8 +37,14 @@ def __init__(self, call: "VonageCall"): super().__init__(call) self._call = call + async def terminate_conversation(self): + await self._call.tear_down() + class TwilioCallStateManager(ConversationStateManager): def __init__(self, call: "TwilioCall"): super().__init__(call) self._call = call + + async def terminate_conversation(self): + await self._call.tear_down()