monster commit

This commit is contained in:
Ajay Raj 2023-03-03 18:24:56 -08:00
commit de6d76c955
14 changed files with 155 additions and 66 deletions

View file

@ -20,9 +20,11 @@ if __name__ == "__main__":
input_device=microphone_input, input_device=microphone_input,
output_device=speaker_output, output_device=speaker_output,
transcriber_config=DeepgramTranscriberConfig.from_input_device(microphone_input), transcriber_config=DeepgramTranscriberConfig.from_input_device(microphone_input),
agent_config=ChatGPTAgentConfig( agent_config=WebSocketUserImplementedAgentConfig(
initial_message="Hello!", initial_message="Hello!",
prompt_preamble="Vocode is an SDK that allows developers to create voice bots like this one in less than 10 lines of code. The AI is explaining to the human what Vocode is." respond=WebSocketUserImplementedAgentConfig.RouteConfig(
url="ws://localhost:3001/respond"
)
), ),
synthesizer_config=AzureSynthesizerConfig.from_output_device(speaker_output) synthesizer_config=AzureSynthesizerConfig.from_output_device(speaker_output)
) )

19
simple_outbound_call.py Normal file
View file

@ -0,0 +1,19 @@
from vocode.outbound_call import OutboundCall
from vocode.models.telephony import CallEntity
from vocode.models.agent import EchoAgentConfig, WebSocketUserImplementedAgentConfig
if __name__ == '__main__':
call = OutboundCall(
recipient=CallEntity(
phone_number="+14088926228",
),
caller=CallEntity(
phone_number="+14086600744",
),
agent_config=WebSocketUserImplementedAgentConfig(
respond=WebSocketUserImplementedAgentConfig.RouteConfig(
url="ws://localhost:3001/respond"
)
)
)
call.start()

View file

@ -1,12 +1,27 @@
from vocode.user_implemented_agent.restful_agent import RESTfulAgent from vocode.user_implemented_agent.restful_agent import RESTfulAgent
from vocode.models.agent import RESTfulAgentOutput, RESTfulAgentText, RESTfulAgentEnd, WebSocketAgentMessage, WebSocketAgentTextMessage, WebSocketAgentStopMessage
from vocode.user_implemented_agent.websocket_agent import WebSocketAgent from vocode.user_implemented_agent.websocket_agent import WebSocketAgent
class EchoAgent(WebSocketAgent): class TestRESTfulAgent(RESTfulAgent):
async def respond(self, input: str) -> str: async def respond(self, input: str) -> RESTfulAgentOutput:
print(input) print(input)
return ''.join(i + j for i, j in zip(input, ' ' * len(input))) if "bye" in input:
return RESTfulAgentEnd()
else:
spelt = ''.join(i + j for i, j in zip(input, ' ' * len(input)))
return RESTfulAgentText(response=spelt)
class TestWebSocketAgent(WebSocketAgent):
async def respond(self, input: str) -> WebSocketAgentMessage:
print(input)
if "bye" in input:
return WebSocketAgentStopMessage()
else:
spelt = ''.join(i + j for i, j in zip(input, ' ' * len(input)))
return WebSocketAgentTextMessage.from_text(spelt)
if __name__ == "__main__": if __name__ == "__main__":
agent = EchoAgent() agent = TestWebSocketAgent()
agent.run() agent.run(port=3001)

View file

@ -3,4 +3,5 @@ from dotenv import load_dotenv
load_dotenv() load_dotenv()
api_key = os.getenv("VOCODE_API_KEY") api_key = os.getenv("VOCODE_API_KEY")
BASE_URL = "api.vocode.dev"

View file

@ -1,4 +1,6 @@
import websockets import websockets
from websockets.exceptions import ConnectionClosedOK
from websockets.client import WebSocketClientProtocol
import asyncio import asyncio
from dotenv import load_dotenv from dotenv import load_dotenv
import os import os
@ -14,10 +16,9 @@ from .models.transcriber import TranscriberConfig
from .models.agent import AgentConfig from .models.agent import AgentConfig
from .models.synthesizer import SynthesizerConfig from .models.synthesizer import SynthesizerConfig
from .models.websocket import ReadyMessage, AudioMessage, StartMessage, StopMessage from .models.websocket import ReadyMessage, AudioMessage, StartMessage, StopMessage
from . import api_key from . import api_key, BASE_URL
VOCODE_WEBSOCKET_URL = f"wss://api.vocode.dev/conversation"
VOCODE_WEBSOCKET_URL = f"wss://{BASE_URL}/conversation"
class Conversation: class Conversation:
def __init__( def __init__(
@ -62,7 +63,7 @@ class Conversation:
async def start(self): async def start(self):
async with websockets.connect(f"{VOCODE_WEBSOCKET_URL}?key={api_key}") as ws: async with websockets.connect(f"{VOCODE_WEBSOCKET_URL}?key={api_key}") as ws:
async def sender(ws): async def sender(ws: WebSocketClientProtocol):
start_message = StartMessage( start_message = StartMessage(
transcriber_config=self.transcriber_config, transcriber_config=self.transcriber_config,
agent_config=self.agent_config, agent_config=self.agent_config,
@ -74,11 +75,15 @@ class Conversation:
while self.active: while self.active:
data = self.input_device.get_audio() data = self.input_device.get_audio()
if data: if data:
await ws.send(AudioMessage.from_bytes(data).json()) try:
await ws.send(AudioMessage.from_bytes(data).json())
except ConnectionClosedOK:
self.deactivate()
return
await asyncio.sleep(0) await asyncio.sleep(0)
await ws.send(StopMessage().json()) await ws.send(StopMessage().json())
async def receiver(ws): async def receiver(ws: WebSocketClientProtocol):
ReadyMessage.parse_raw(await ws.recv()) ReadyMessage.parse_raw(await ws.recv())
self.receiver_ready = True self.receiver_ready = True
async for msg in ws: async for msg in ws:

View file

@ -4,14 +4,14 @@ from .model import TypedModel, BaseModel
class AgentType(str, Enum): class AgentType(str, Enum):
BASE = "base" BASE = "agent_base"
LLM = "llm" LLM = "agent_llm"
CHAT_GPT_ALPHA = "chat_gpt_alpha" CHAT_GPT_ALPHA = "agent_chat_gpt_alpha"
CHAT_GPT = "chat_gpt" CHAT_GPT = "agent_chat_gpt"
ECHO = "echo" ECHO = "agent_echo"
INFORMATION_RETRIEVAL = "information_retrieval" INFORMATION_RETRIEVAL = "agent_information_retrieval"
RESTFUL_USER_IMPLEMENTED = "restful_user_implemented" RESTFUL_USER_IMPLEMENTED = "agent_restful_user_implemented"
WEBSOCKET_USER_IMPLEMENTED = "websocket_user_implemented" WEBSOCKET_USER_IMPLEMENTED = "agent_websocket_user_implemented"
class AgentConfig(TypedModel, type=AgentType.BASE): class AgentConfig(TypedModel, type=AgentType.BASE):
@ -57,9 +57,20 @@ class RESTfulUserImplementedAgentConfig(AgentConfig, type=AgentType.RESTFUL_USER
class RESTfulAgentInput(BaseModel): class RESTfulAgentInput(BaseModel):
human_input: str human_input: str
class RESTfulAgentOutput(BaseModel): class RESTfulAgentOutputType(str, Enum):
BASE = "restful_agent_base"
TEXT = "restful_agent_text"
END = "restful_agent_end"
class RESTfulAgentOutput(TypedModel, type=RESTfulAgentOutputType.BASE):
pass
class RESTfulAgentText(RESTfulAgentOutput, type=RESTfulAgentOutputType.TEXT):
response: str response: str
class RESTfulAgentEnd(RESTfulAgentOutput, type=RESTfulAgentOutputType.END):
pass
class WebSocketUserImplementedAgentConfig(AgentConfig, type=AgentType.WEBSOCKET_USER_IMPLEMENTED): class WebSocketUserImplementedAgentConfig(AgentConfig, type=AgentType.WEBSOCKET_USER_IMPLEMENTED):
class RouteConfig(BaseModel): class RouteConfig(BaseModel):
url: str url: str
@ -70,15 +81,15 @@ class WebSocketUserImplementedAgentConfig(AgentConfig, type=AgentType.WEBSOCKET_
# send_message_on_cut_off: bool = False # send_message_on_cut_off: bool = False
class WebSocketAgentMessageType(str, Enum): class WebSocketAgentMessageType(str, Enum):
AGENT_BASE = 'agent_base' BASE = 'websocket_agent_base'
AGENT_START = 'agent_start' START = 'websocket_agent_start'
AGENT_TEXT = 'agent_text' TEXT = 'websocket_agent_text'
AGENT_READY = 'agent_ready' READY = 'websocket_agent_ready'
AGENT_STOP = 'agent_stop' STOP = 'websocket_agent_stop'
class WebSocketAgentMessage(TypedModel, type=WebSocketAgentMessageType.AGENT_BASE): pass class WebSocketAgentMessage(TypedModel, type=WebSocketAgentMessageType.BASE): pass
class AgentTextMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.AGENT_TEXT): class WebSocketAgentTextMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.TEXT):
class Payload(BaseModel): class Payload(BaseModel):
text: str text: str
@ -89,11 +100,11 @@ class AgentTextMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.AGE
return cls(data=cls.Payload(text=text)) return cls(data=cls.Payload(text=text))
class AgentStartMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.AGENT_START): class WebSocketAgentStartMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.START):
pass pass
class AgentReadyMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.AGENT_READY): class WebSocketAgentReadyMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.READY):
pass pass
class AgentStopMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.AGENT_STOP): class WebSocketAgentStopMessage(WebSocketAgentMessage, type=WebSocketAgentMessageType.STOP):
pass pass

View file

@ -4,10 +4,10 @@ from .audio_encoding import AudioEncoding
from ..output_device.base_output_device import BaseOutputDevice from ..output_device.base_output_device import BaseOutputDevice
class SynthesizerType(str, Enum): class SynthesizerType(str, Enum):
BASE = "base" BASE = "synthesizer_base"
AZURE = "azure" AZURE = "synthesizer_azure"
GOOGLE = "google" GOOGLE = "synthesizer_google"
ELEVEN_LABS = "eleven_labs" ELEVEN_LABS = "synthesizer_eleven_labs"
class SynthesizerConfig(TypedModel, type=SynthesizerType.BASE): class SynthesizerConfig(TypedModel, type=SynthesizerType.BASE):
sampling_rate: int sampling_rate: int

View file

@ -1,14 +1,11 @@
from pydantic import BaseModel from vocode.models.model import BaseModel
from vocode.models.agent import AgentConfig, InformationRetrievalAgentConfig from vocode.models.agent import AgentConfig, InformationRetrievalAgentConfig
class CallEntity(BaseModel): class CallEntity(BaseModel):
phone_number: str phone_number: str
descriptor: str
class CreateCallRequest(BaseModel): class CreateCallRequest(BaseModel):
recipient: CallEntity recipient: CallEntity
caller: CallEntity caller: CallEntity
agent_config: InformationRetrievalAgentConfig # TODO switch to AgentConfig agent_config: AgentConfig
# TODO add IVR/etc. # TODO add IVR/etc.

View file

@ -6,10 +6,10 @@ from ..input_device.base_input_device import BaseInputDevice
class TranscriberType(str, Enum): class TranscriberType(str, Enum):
BASE = "base" BASE = "transcriber_base"
DEEPGRAM = "deepgram" DEEPGRAM = "transcriber_deepgram"
GOOGLE = "google" GOOGLE = "transcriber_google"
ASSEMBLY_AI = "assembly_ai" ASSEMBLY_AI = "transcriber_assembly_ai"
class EndpointingConfig(BaseModel): class EndpointingConfig(BaseModel):

View file

@ -6,11 +6,11 @@ from .agent import AgentConfig
from .synthesizer import SynthesizerConfig from .synthesizer import SynthesizerConfig
class WebSocketMessageType(str, Enum): class WebSocketMessageType(str, Enum):
BASE = 'base' BASE = 'websocket_base'
START = 'start' START = 'websocket_start'
AUDIO = 'audio' AUDIO = 'websocket_audio'
READY = 'ready' READY = 'websocket_ready'
STOP = 'stop' STOP = 'websocket_stop'
class WebSocketMessage(TypedModel, type=WebSocketMessageType.BASE): pass class WebSocketMessage(TypedModel, type=WebSocketMessageType.BASE): pass

27
vocode/outbound_call.py Normal file
View file

@ -0,0 +1,27 @@
from .models.telephony import CallEntity, CreateCallRequest
import requests
from . import api_key, BASE_URL
VOCODE_OUTBOUND_CALL_URL = f"https://{BASE_URL}/create_outbound_call"
class OutboundCall:
def __init__(self, recipient: CallEntity, caller: CallEntity, agent_config):
self.recipient = recipient
self.caller = caller
self.agent_config = agent_config
def start(self):
return requests.post(
VOCODE_OUTBOUND_CALL_URL,
headers={
"Authorization": f"Bearer {api_key}"
},
json=CreateCallRequest(
recipient=self.recipient,
caller=self.caller,
agent_config=self.agent_config
).dict()
)

View file

@ -5,9 +5,6 @@ class BaseAgent():
def __init__(self): def __init__(self):
self.app = FastAPI() self.app = FastAPI()
async def respond(self, human_input) -> str:
raise NotImplementedError
def run(self, host="localhost", port=3000): def run(self, host="localhost", port=3000):
uvicorn.run(self.app, host=host, port=port) uvicorn.run(self.app, host=host, port=port)

View file

@ -1,6 +1,7 @@
from .base_agent import BaseAgent from .base_agent import BaseAgent
from ..models.agent import RESTfulAgentInput, RESTfulAgentOutput from ..models.agent import RESTfulAgentInput, RESTfulAgentOutput, RESTfulAgentText, RESTfulAgentEnd
from pydantic import BaseModel from pydantic import BaseModel
from typing import Union
from fastapi import APIRouter from fastapi import APIRouter
class RESTfulAgent(BaseAgent): class RESTfulAgent(BaseAgent):
@ -9,7 +10,10 @@ class RESTfulAgent(BaseAgent):
super().__init__() super().__init__()
self.app.post("/respond")(self.respond_rest) self.app.post("/respond")(self.respond_rest)
async def respond_rest(self, request: RESTfulAgentInput) -> RESTfulAgentOutput: async def respond(self, human_input) -> RESTfulAgentOutput:
response = await self.respond(request.human_input) raise NotImplementedError
return RESTfulAgentOutput(response=response)
async def respond_rest(self, request: RESTfulAgentInput) -> Union[RESTfulAgentText, RESTfulAgentEnd]:
response = await self.respond(request.human_input)
return response

View file

@ -1,8 +1,16 @@
from .base_agent import BaseAgent from .base_agent import BaseAgent
from pydantic import BaseModel from pydantic import BaseModel
import typing import typing
from typing import Union
from fastapi import APIRouter, WebSocket from fastapi import APIRouter, WebSocket
from ..models.agent import AgentStartMessage, AgentReadyMessage, AgentTextMessage, WebSocketAgentMessage, WebSocketAgentMessageType from ..models.agent import (
WebSocketAgentStartMessage,
WebSocketAgentReadyMessage,
WebSocketAgentTextMessage,
WebSocketAgentStopMessage,
WebSocketAgentMessage,
WebSocketAgentMessageType
)
class WebSocketAgent(BaseAgent): class WebSocketAgent(BaseAgent):
@ -10,16 +18,19 @@ class WebSocketAgent(BaseAgent):
super().__init__() super().__init__()
self.app.websocket("/respond")(self.respond_websocket) self.app.websocket("/respond")(self.respond_websocket)
async def respond(self, human_input) -> Union[WebSocketAgentTextMessage, WebSocketAgentStopMessage]:
raise NotImplementedError
async def respond_websocket(self, websocket: WebSocket): async def respond_websocket(self, websocket: WebSocket):
await websocket.accept() await websocket.accept()
AgentStartMessage.parse_obj(await websocket.receive_json()) WebSocketAgentStartMessage.parse_obj(await websocket.receive_json())
await websocket.send_text(AgentReadyMessage().json()) await websocket.send_text(WebSocketAgentReadyMessage().json())
while True: while True:
message = WebSocketAgentMessage.parse_obj(await websocket.receive_json()) input_message = WebSocketAgentMessage.parse_obj(await websocket.receive_json())
if message.type == WebSocketAgentMessageType.AGENT_STOP: if input_message.type == WebSocketAgentMessageType.STOP:
break break
text_message = typing.cast(AgentTextMessage, message) text_message = typing.cast(WebSocketAgentTextMessage, input_message)
response = await self.respond(text_message.data.text) output_response = await self.respond(text_message.data.text)
await websocket.send_text(AgentTextMessage.from_text(response).json()) await websocket.send_text(output_response.json())
await websocket.close() await websocket.close()