The example application referenced in this article can be found here.
Pipecat is an open-source framework for building voice agents from separate STT,
LLM and TTS services. JambonzFrameSerializer connects a Pipecat pipeline to a
jambonz call over a websocket: jambonz streams the caller’s audio to the agent
using the listen verb, and the agent speaks back on the
same connection.
Unlike the agent verb, where jambonz runs the conversation, here Pipecat runs the pipeline and jambonz handles the call. Use this when you already have a Pipecat application, or want turn-taking, context and tool calling to live in your own code.
JambonzFrameSerializer is part of Pipecat from version 1.16.0. Install it with
pip install "pipecat-ai[websocket]>=1.16.0".
The verb
Your application’s calling webhook answers the call with a listen verb that
points at the agent’s websocket:
bidirectionalAudio.streaming defaults to false, and while it is false the
audio your agent sends back is discarded. Nothing is logged and no error is
raised; the caller hears silence.
Sample rates
sampleRate is the rate jambonz sends the caller’s audio at. It defaults to
8000; 16000, 24000, 48000 and 64000 are also allowed. The serializer
reads the rate from the first message on the websocket, so the same agent
handles narrowband and wideband calls without changes. To get wideband audio,
set it here.
bidirectionalAudio.sampleRate is the rate jambonz reads the agent’s audio at.
It has no default; if omitted, jambonz uses the call’s negotiated codec rate,
which depends on the carrier and the far end. Nothing on the websocket reports
it, so the agent has to be told.
Pass the same value to the serializer as audio_out_sample_rate. Pipecat
then converts the agent’s audio to that rate before sending it. If the two
differ, the agent’s speech plays fast or slow. Using one rate for
sampleRate, bidirectionalAudio.sampleRate and audio_out_sample_rate keeps
it simple.
mixType
Use mono, the default. The serializer rejects the other two values:
stereocarries both sides of the call as two interleaved channels.mixedsums both sides into one channel, which would feed the agent’s own speech back to it.
The agent
The serializer plugs into Pipecat’s websocket transport like any other telephony serializer:
The rest of the pipeline (STT, LLM, TTS) is standard Pipecat. The example application uses Deepgram and OpenAI, runs on Pipecat’s development runner, and serves the calling webhook and the websocket from the same process.
Apart from audio_out_sample_rate, the serializer needs no configuration. The
caller’s sample rate and the call’s details come from the first message jambonz
sends. After that message,
serializer.call_sid and serializer.trace_id are available for matching your
logs with jambonz’s, and serializer.call_meta holds the whole message.
DTMF
With passDtmf set, each digit the caller presses arrives on the websocket and
becomes a Pipecat InputDTMFFrame:
Handle it in a frame processor:
Ending the call
To hang up from the agent, end the pipeline:
The serializer sends disconnect, which ends the listen verb. jambonz then
calls the verb’s actionHook if it has one, and otherwise hangs up.
If your application should keep the call after the pipeline ends, pass
JambonzFrameSerializer.InputParams(disconnect_on_end=False).
Interruptions
When the caller talks over the agent, Pipecat interrupts the response and the
serializer sends killAudio, which stops playback and discards any buffered
audio. Nothing else is needed.
Trying it
You don’t need a phone number to test. A SIP softphone registered to your account works:
- Run the example and expose it with
ngrok http 7860. - Create an application whose calling webhook is
https://<your-ngrok-host>/. - Add a SIP client, register a softphone with it, and set the application as the account’s device calling application.
- Dial any number from the softphone.
To test the narrowband path, limit the softphone to PCMU or PCMA and set both
sample rates in the verb to 8000. That matches the audio from a G.711 PSTN
carrier.