open source

This commit is contained in:
Ajay Raj 2023-03-28 00:15:34 -07:00
commit a93bfc1ec9
61 changed files with 4013 additions and 126 deletions

View file

@ -0,0 +1,143 @@
import logging
from typing import Optional
from fastapi import APIRouter, Form, Response
from pydantic import BaseModel
from vocode.streaming.agent.base_agent import BaseAgent
from vocode.streaming.models.agent import AgentConfig
from vocode.streaming.models.synthesizer import (
AzureSynthesizerConfig,
SynthesizerConfig,
)
from vocode.streaming.models.transcriber import (
DeepgramTranscriberConfig,
PunctuationEndpointingConfig,
TranscriberConfig,
)
from vocode.streaming.synthesizer.base_synthesizer import BaseSynthesizer
from vocode.streaming.telephony.config_manager.base_config_manager import (
BaseConfigManager,
)
from vocode.streaming.telephony.constants import (
DEFAULT_AUDIO_ENCODING,
DEFAULT_CHUNK_SIZE,
DEFAULT_SAMPLING_RATE,
)
from vocode.streaming.telephony.server.router.calls import CallsRouter
from vocode.streaming.telephony.server.router.twiml import TwiMLRouter
from vocode.streaming.models.telephony import (
CallConfig,
CallEntity,
CreateOutboundCall,
CreateInboundCall,
DialIntoZoomCall,
EndOutboundCall,
TwilioConfig,
)
from twilio.rest import Client
from vocode.streaming.telephony.conversation.call import Call
from vocode.streaming.telephony.templates import Templater
from vocode.streaming.transcriber.base_transcriber import BaseTranscriber
from vocode.streaming.utils import create_conversation_id
class InboundCallConfig(BaseModel):
url: str
agent_config: AgentConfig
twilio_config: TwilioConfig
transcriber_config: Optional[TranscriberConfig] = None
synthesizer_config: Optional[SynthesizerConfig] = None
class TelephonyServer:
def __init__(
self,
base_url: str,
config_manager: BaseConfigManager,
inbound_call_configs: list[InboundCallConfig] = [],
logger: Optional[logging.Logger] = None,
):
self.base_url = base_url
self.logger = logger or logging.getLogger(__name__)
self.router = APIRouter()
self.config_manager = config_manager
self.templater = Templater()
self.router.include_router(
CallsRouter(
base_url=base_url,
templater=self.templater,
config_manager=self.config_manager,
logger=self.logger,
).get_router()
)
self.router.include_router(
TwiMLRouter(
base_url=base_url, templater=self.templater, logger=self.logger
).get_router()
)
for config in inbound_call_configs:
self.router.add_api_route(
config.url,
self.create_inbound_route(
agent_config=config.agent_config,
twilio_config=config.twilio_config,
transcriber_config=config.transcriber_config,
synthesizer_config=config.synthesizer_config,
),
methods=["POST"],
)
logger.info(f"Set up inbound call TwiML at https://{base_url}{config.url}")
def create_inbound_route(
self,
agent_config: AgentConfig,
twilio_config: TwilioConfig,
transcriber_config: Optional[TranscriberConfig] = None,
synthesizer_config: Optional[SynthesizerConfig] = None,
):
def route(twilio_sid: str = Form(alias="CallSid")) -> Response:
call_config = CallConfig(
transcriber_config=transcriber_config
or DeepgramTranscriberConfig(
sampling_rate=DEFAULT_SAMPLING_RATE,
audio_encoding=DEFAULT_AUDIO_ENCODING,
chunk_size=DEFAULT_CHUNK_SIZE,
model="voicemail",
endpointing_config=PunctuationEndpointingConfig(),
),
agent_config=agent_config,
synthesizer_config=synthesizer_config
or AzureSynthesizerConfig(
sampling_rate=DEFAULT_SAMPLING_RATE,
audio_encoding=DEFAULT_AUDIO_ENCODING,
),
twilio_config=twilio_config,
twilio_sid=twilio_sid,
)
conversation_id = create_conversation_id()
self.config_manager.save_config(conversation_id, call_config)
return self.templater.get_connection_twiml(
base_url=self.base_url, call_id=conversation_id
)
return route
async def end_outbound_call(self, conversation_id: str):
# TODO validation via twilio_client
call_config = self.config_manager.get_config(conversation_id)
if not call_config:
raise ValueError("Call not found")
call = Call.from_call_config(
self.base_url,
call_config,
self.config_manager,
conversation_id,
self.logger,
)
call.end_twilio_call()
return {"id": call.id}
def get_router(self) -> APIRouter:
return self.router

View file

@ -0,0 +1,45 @@
from typing import Optional
import logging
from fastapi import APIRouter, HTTPException, WebSocket
from vocode.streaming.telephony.config_manager.base_config_manager import (
BaseConfigManager,
)
from vocode.streaming.telephony.conversation.call import Call
from vocode.streaming.telephony.templates import Templater
class CallsRouter:
def __init__(
self,
base_url: str,
templater: Templater,
config_manager: BaseConfigManager,
logger: Optional[logging.Logger] = None,
):
super().__init__()
self.base_url = base_url
self.templater = templater
self.config_manager = config_manager
self.logger = logger or logging.getLogger(__name__)
self.router = APIRouter()
self.router.websocket("/connect_call/{id}")(self.connect_call)
async def connect_call(self, websocket: WebSocket, id: str):
await websocket.accept()
self.logger.debug("Phone WS connection opened for chat {}".format(id))
call_config = self.config_manager.get_config(id)
if not call_config:
raise HTTPException(status_code=400, detail="No active phone call")
call: Call = Call.from_call_config(
self.base_url, call_config, self.config_manager, id, self.logger
)
await call.attach_ws_and_start(websocket)
self.config_manager.delete_config(call.id)
self.logger.debug("Phone WS connection closed for chat {}".format(id))
def get_router(self) -> APIRouter:
return self.router

View file

@ -0,0 +1,29 @@
import logging
from typing import Optional
from fastapi import APIRouter
from vocode.streaming.telephony.templates import Templater
class TwiMLRouter:
def __init__(
self,
base_url: str,
templater: Templater,
logger: Optional[logging.Logger] = None,
):
super().__init__()
self.base_url = base_url
self.templater = templater
self.logger = logger or logging.getLogger(__name__)
self.router = APIRouter()
self.router.add_api_route(
"/twiml/initiate_call/{id}", self.call_twiml, methods=["POST"]
)
def call_twiml(self, id: str):
return self.templater.get_connection_twiml(base_url=self.base_url, call_id=id)
def get_router(self) -> APIRouter:
return self.router