Add Vonage support to telephony runner utilities
This commit is contained in:
1
changelog/3265.changed.2.md
Normal file
1
changelog/3265.changed.2.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
- Updated the development runner utility to support parsing `VonageFrameSerializer` websocket messages and handling/passing the to and from numbers via the `RunnerArguments`.
|
||||||
@@ -88,6 +88,11 @@ def _detect_transport_type_from_message(message_data: dict) -> str:
|
|||||||
logger.trace("Auto-detected: EXOTEL")
|
logger.trace("Auto-detected: EXOTEL")
|
||||||
return "exotel"
|
return "exotel"
|
||||||
|
|
||||||
|
# Vonage detection
|
||||||
|
if message_data.get("event") == "websocket:connected" and "content-type" in message_data:
|
||||||
|
logger.trace("Auto-detected: VONAGE")
|
||||||
|
return "vonage"
|
||||||
|
|
||||||
logger.trace("Auto-detection failed - unknown format")
|
logger.trace("Auto-detection failed - unknown format")
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
@@ -135,6 +140,16 @@ async def parse_telephony_websocket(websocket: WebSocket):
|
|||||||
"to": str,
|
"to": str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- Vonage::
|
||||||
|
|
||||||
|
{
|
||||||
|
"content_type": str, # e.g., "audio/l16;rate=16000"
|
||||||
|
"from": str,
|
||||||
|
"to": str,
|
||||||
|
"call_uuid": str,
|
||||||
|
"conversation_uuid": str,
|
||||||
|
}
|
||||||
|
|
||||||
Example usage::
|
Example usage::
|
||||||
|
|
||||||
transport_type, call_data = await parse_telephony_websocket(websocket)
|
transport_type, call_data = await parse_telephony_websocket(websocket)
|
||||||
@@ -153,17 +168,23 @@ async def parse_telephony_websocket(websocket: WebSocket):
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
first_message = {}
|
first_message = {}
|
||||||
|
|
||||||
# Second message
|
# Try auto-detection on first message
|
||||||
second_message_raw = await start_data.__anext__()
|
|
||||||
logger.trace(f"Second message: {second_message_raw}")
|
|
||||||
try:
|
|
||||||
second_message = json.loads(second_message_raw)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
second_message = {}
|
|
||||||
|
|
||||||
# Try auto-detection on both messages
|
|
||||||
detected_type_first = _detect_transport_type_from_message(first_message)
|
detected_type_first = _detect_transport_type_from_message(first_message)
|
||||||
detected_type_second = _detect_transport_type_from_message(second_message)
|
|
||||||
|
# Vonage only sends one text message at start, then binary audio
|
||||||
|
# For other providers, read second message
|
||||||
|
if detected_type_first != "vonage":
|
||||||
|
second_message_raw = await start_data.__anext__()
|
||||||
|
logger.debug(f"Second message: {second_message_raw}")
|
||||||
|
try:
|
||||||
|
second_message = json.loads(second_message_raw)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
second_message = {}
|
||||||
|
|
||||||
|
detected_type_second = _detect_transport_type_from_message(second_message)
|
||||||
|
else:
|
||||||
|
second_message = {}
|
||||||
|
detected_type_second = "unknown"
|
||||||
|
|
||||||
# Use the successful detection
|
# Use the successful detection
|
||||||
if detected_type_first != "unknown":
|
if detected_type_first != "unknown":
|
||||||
@@ -219,6 +240,21 @@ async def parse_telephony_websocket(websocket: WebSocket):
|
|||||||
"custom_parameters": start_data.get("custom_parameters", ""),
|
"custom_parameters": start_data.get("custom_parameters", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elif transport_type == "vonage":
|
||||||
|
# Vonage sends websocket:connected event with content-type
|
||||||
|
content_type = call_data_raw.get("content-type", "audio/l16;rate=16000")
|
||||||
|
call_data = {
|
||||||
|
"content_type": content_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract phone numbers and call info from websocket state (set by server.py)
|
||||||
|
if hasattr(websocket, "state") and hasattr(websocket.state, "vonage_call_data"):
|
||||||
|
vonage_data = websocket.state.vonage_call_data
|
||||||
|
call_data["from"] = vonage_data.get("from", "")
|
||||||
|
call_data["to"] = vonage_data.get("to", "")
|
||||||
|
call_data["call_uuid"] = vonage_data.get("call_uuid", "")
|
||||||
|
call_data["conversation_uuid"] = vonage_data.get("conversation_uuid", "")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
call_data = {}
|
call_data = {}
|
||||||
|
|
||||||
@@ -465,10 +501,24 @@ async def _create_telephony_transport(
|
|||||||
stream_sid=call_data["stream_id"],
|
stream_sid=call_data["stream_id"],
|
||||||
call_sid=call_data["call_id"],
|
call_sid=call_data["call_id"],
|
||||||
)
|
)
|
||||||
|
elif transport_type == "vonage":
|
||||||
|
from pipecat.serializers.vonage import VonageFrameSerializer
|
||||||
|
|
||||||
|
content_type = call_data.get("content_type", "audio/l16;rate=16000")
|
||||||
|
sample_rate = 16000 # Default
|
||||||
|
if "rate=" in content_type:
|
||||||
|
try:
|
||||||
|
sample_rate = int(content_type.split("rate=")[1].split(";")[0])
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logger.warning(f"Could not parse sample rate from {content_type}, using 16000")
|
||||||
|
|
||||||
|
params.serializer = VonageFrameSerializer(
|
||||||
|
params=VonageFrameSerializer.InputParams(vonage_sample_rate=sample_rate)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unsupported telephony provider: {transport_type}. "
|
f"Unsupported telephony provider: {transport_type}. "
|
||||||
f"Supported providers: twilio, telnyx, plivo, exotel"
|
f"Supported providers: twilio, telnyx, plivo, exotel, vonage"
|
||||||
)
|
)
|
||||||
|
|
||||||
return FastAPIWebsocketTransport(websocket=websocket, params=params)
|
return FastAPIWebsocketTransport(websocket=websocket, params=params)
|
||||||
@@ -485,7 +535,7 @@ async def create_transport(
|
|||||||
Args:
|
Args:
|
||||||
runner_args: Arguments from the runner.
|
runner_args: Arguments from the runner.
|
||||||
transport_params: Dict mapping transport names to parameter factory functions.
|
transport_params: Dict mapping transport names to parameter factory functions.
|
||||||
Keys should be: "daily", "webrtc", "twilio", "telnyx", "plivo", "exotel"
|
Keys should be: "daily", "webrtc", "twilio", "telnyx", "plivo", "exotel", "vonage"
|
||||||
Values should be functions that return transport parameters when called.
|
Values should be functions that return transport parameters when called.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -532,6 +582,12 @@ async def create_transport(
|
|||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
# add_wav_header and serializer will be set automatically
|
# add_wav_header and serializer will be set automatically
|
||||||
),
|
),
|
||||||
|
"vonage": lambda: FastAPIWebsocketParams(
|
||||||
|
audio_in_enabled=True,
|
||||||
|
audio_out_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
# add_wav_header and serializer will be set automatically
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
transport = await create_transport(runner_args, transport_params)
|
transport = await create_transport(runner_args, transport_params)
|
||||||
|
|||||||
Reference in New Issue
Block a user