Overview

Vocode supports using agents with inbound and outbound phone calls. Users can create their own agents and use them to fulfill a variety of use cases like information collection, appointment scheduling, sales, customer support, and more.

In this guide, we’ll go over how to do inbound and outbound phone calls both self-hosted and hosted.

Self-hosted

Requirements

  1. Ngrok (used to host the TelephonyServer locally)
  2. ffmpeg a. If you have Homebrew installed, run brew install ffmpeg
  3. Redis a. If you have Homebrew installed, run brew install redis
  4. (optional) Docker

Environments

  1. Copy the .env.template file and fill in the values of your API keys. You’ll need to get API keys for:
cp .env.template .env
  1. Set up hosting so that Twilio can hit your server. An easy way to do this is ngrok: in our code we set it up to be running on port 3000, run:
ngrok http 3000

Copy the URL that is tunneling localhost:3000 to your .env without https://, e.g.

BASE_URL=asdf1234.ngrok.app

Telephony Server

The TelephonyServer is–as implied by the name–a server that is responsible for receiving and making phone calls. The server is built using FastAPI and utilizes Twilio for telephony services.

Clone the Vocode repo or copy the Telephony app directory.

Running the server

Pick one of these two ways to run the server: 1. Run everything with Docker, 2. Run Python directly

Option 1: Run everything With Docker

  1. Build the telephony app Docker image. From the telephony_app directory, run:
docker build -t vocode-telephony-app .
  1. Run the application using docker-compose. From the telephony_app directory, run:
docker-compose up

Option 2: Run Python directly

Run the following steps from the telephony_app directory.

  1. Install Poetry and install dependencies.
poetry install
  1. Run Redis with the default port of 6379.

For example, using Homebrew:

brew services start redis

Or if you prefer to use Docker for this part:

docker run -dp 6379:6379 -it redis/redis-stack:latest
  1. Run the server with uvicorn (should be already installed in step 1).
poetry run uvicorn main:app --port 3000

Setting up an inbound number

  1. Create a Twilio account
  2. Once inside your dashboard, go to Phone Numbers -> Manage -> Buy a number to get a phone number.
  3. Then, go to Phone Numbers -> Manage -> Active Numbers and select the number you want to set up.
  4. Update the config to point the Webhook URL to https://<YOUR BASE URL>/inbound_call - if you’re using ngrok, it looks like https://asdf1234.ngrok.app/inbound_call

  1. Hit Save and call the number!

Executing outbound calls

Make sure the server we just set up is already running. Then, in outbound_call.py

Replace the to_phone with the number you want to call and the from_phone with the number you want to call from. In order to make a call from the from_phone, you must have access to it via Twilio (either a number purchased via Twilio or verify the caller ID).

Note: To ensure legal compliance with robocall regulations in California, the following code snippet from the Vocode library utilizes Twilio Line Intelligence to check if calls are made to mobile phones: For Canadian phone numbers, the Twilio Lookup API may not return carrier data due to the Canadian Local Number Portability Consortium (CLNPC) requirements. More information on this issue can be found in the Twilio Support Article.

Run the script with poetry run python outbound_call.py.

Configuration

Both the OutboundCall (in outbound_call.py) and InboundCallConfig (in telephony_app.py) classes can accept a TranscriberConfig, AgentConfig or SynthesizerConfig - the default transcriber is Deepgram and the default synthesizer is Azure.

This example sets up an agent that spells every word that is sent to it - any text-in, text-out function can be turned into a voice conversation by subclassing BaseAgent and creating an AgentFactory.

class SpellerAgentConfig(AgentConfig, type="agent_speller"):
    pass


class SpellerAgent(BaseAgent):
    def __init__(self, agent_config: SpellerAgentConfig):
        super().__init__(agent_config=agent_config)

    async def respond(
        self,
        human_input,
        conversation_id: str,
        is_interrupt: bool = False,
    ) -> Tuple[Optional[str], bool]:
        return "".join(c + " " for c in human_input), False


class SpellerAgentFactory(AgentFactory):
    def create_agent(self, agent_config: AgentConfig) -> BaseAgent:
        return SpellerAgent(agent_config=agent_config)

An AgentFactory instance is passed into the TelephonyServer in telephony_app.py.

We provide a small set of agents with already created AgentConfigs, including, importantly, one that sets up ChatGPT with a configured prompt: see our Python Quickstart for more info.

Accessing call information in your agent

We store the to and from numbers in the ConfigManager - so if you’d like to access them in your agent, you can instantiate the manager to hook into the same Redis instance:

class SpellerAgent(BaseAgent):
    def __init__(self, agent_config: SpellerAgentConfig):
        super().__init__(agent_config=agent_config)
        self.config_manager = RedisConfigManager()

    async def respond(
        self,
        human_input,
        conversation_id: str,
        is_interrupt: bool = False,
    ) -> Tuple[Optional[str], bool]:
        call_config = self.config_manager.get_config(conversation_id)
        if call_config is not None:
            from_phone = call_config.twilio_from
            to_phone = call_config.twilio_to
        return "".join(c + " " for c in human_input), False

Hosted

Authentication

Head to our dashboard to get an API key.

To use your API key, set the API key on the module as follows:

import vocode
vocode.api_key = '{YOUR_API_KEY}'

Outbound Calls

The OutboundCall class provides a simple abstraction for creating outbound calls.

OutboundCall Class

recipient
CallEntity
required

The recipient entity for the phone call.

caller
CallEntity
required

The caller entity for the phone call.

agent_config
AgentConfig
required

The configuration for the AI agent.

Example: Making an outbound call

Prior to running this script to initiate an outbound call, you still need the TelephonyServer from above to be running.

import os
from vocode.streaming.telephony.hosted.outbound_call import OutboundCall
from vocode.streaming.models.agent import ChatGPTAgentConfig
from vocode.streaming.models.telephony import CallEntity, TwilioConfig
from vocode.streaming.models.message import BaseMessage

import vocode
vocode.api_key = '{YOUR_API_KEY}'


if __name__ == '__main__':
    call = OutboundCall(
        recipient=CallEntity(
            phone_number="+12345678900",
        ),
        caller=CallEntity(
            phone_number="+12345678900",
        ),
        agent_config=ChatGPTAgentConfig(
            initial_message=BaseMessage(text="Hello!"),
            prompt_preamble="Have a pleasant conversation about life",
        ),
        twilio_config=TwilioConfig(
            account_sid=os.getenv("TWILIO_ACCOUNT_SID"),
            auth_token=os.getenv("TWILIO_AUTH_TOKEN"),
        )
    )
    call.start()
    input("Press enter to end the call...")
    call.end()

How outbound calls work

Here’s what happens under the hood for an outbound call.

The OutboundCall calls Twilio to initiate the call, giving it a URL to call after the call is initiated:

        twilio_call = self.twilio_client.calls.create(
            url=f"https://{self.base_url}/twiml/initiate_call/{self.conversation_id}",

In your main.py you create and start a TelephonyServer. The TelephonyServer itself includes a couple of routers:

  • TwiMLRouter
  • CallsRouter

TwiMLRouter includes the matching route:

        self.router.add_api_route(
            "/twiml/initiate_call/{id}", self.call_twiml, methods=["POST"]
        )

This route returns the template connect_call.xml which uses TwiML to tell Twilio to establish a websocket with a given URL:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Connect>
    <Stream url="wss://{{ base_url }}/connect_call/{{ id }}" />
  </Connect>
</Response>

CallsRouter includes that second route:

 self.router.websocket("/connect_call/{id}")(self.connect_call)

This is where we get the websocket connection. We then create the Call object and start it:

await call.attach_ws_and_start(websocket)

The Call is a kind of StreamingConversation that manages the various pieces: transcriber, synthesizer, agent, etc.

Inbound Calls

The InboundCallServer class provides a simple abstraction to create an endpoint that can host your agent to respond to inbound calls. In order to implement an inbound call agent, start by defining an InboundCallServer like so:

from vocode.streaming.telephony.hosted.inbound_call_server import InboundCallServer
from vocode.streaming.models.agent import EchoAgentConfig
from vocode.streaming.models.message import BaseMessage
from vocode.streaming.models.telephony import TwilioConfig

if __name__ == '__main__':
    server = InboundCallServer(
        agent_config=EchoAgentConfig(initial_message=BaseMessage(text="hello!")),
        twilio_config=TwilioConfig(
            account_sid=os.getenv("TWILIO_ACCOUNT_SID"),
            auth_token=os.getenv("TWILIO_AUTH_TOKEN"),
        )
    )
    server.run(port=3000)

This spins up a FastAPI web server with a /vocode route that returns a TwiML object that we will need in the next step. If you’re using our Replit template, this is already set up for you!

Next, we’ll set up a programmable phone number on Twilio that can accept the TwiML object from above. To do this,

  1. Create a Twilio account
  2. Find your Twilio credentials: this is found on under Account Info in the dashboard.

    Tip: if you already have existing Twilio integrations, set up the number in a subaccount

  3. Set up TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN in your environment. If you’re using Replit, use Replit secrets. Otherwise, packages like python-dotenv are useful for this.
  4. Once inside your dashboard, go to Phone Numbers -> Manage -> Buy a number to get a phone number.
  5. Then, go to Phone Numbers -> Manage -> Active Numbers and select the number you want to set up.
  6. Update the config to point the Webhook URL to the /vocode route we just set up. ngrok one option to quickly to host the server.

    If you’re using the Replit template, use the provided hosted URL that looks something like https://<repl>.<username>.repl.co/vocode

  7. Hit Save and call the number!