* Updated code to dial out to an operator, keep track of operator conversation while escalated and then return to conversation when finished * Removed unnecessary imports * Updated bot runner code, added call routing file and then updated the call transfer and voicemail detection examples * Updated the bot files * Made prompt one level higher in the body and an array * Updated call transfer examples to work correctly * Updated gemini voicemail detection example to work * Added twilio bot support back to the bot_runner * Moved some state management, participant management and other logic to the helper file. * Updated comments * Updated env and requirements file * Ran the examples and made sure code works. Still need to work on the prompts a bit * Fixed format issue * Add support to disable summary in call transfer * Added support for operator transfer mode * Updated readme file * Updated readme based on feedback, and handling of various properties in the json to be more flexible for future examples * Updated number of endpoints * Updated readme to remove fly deployment text and replaced with Pipecat Cloud * Starting to tweak function calls and prompts * Updated examples to more consistently call the functions and say what they need to say * Updated examples * Updated examples * Updated examples to work correctly * Add simple bot versions of dialin and dialout * Refactored the bot runner file to make adding future examples easier * Based on feedback, removed examples for multiple LLMs and also adjusted voicemail detection code to be simpler * Made sure to only capture the users transcription once * Updated readme with latest changes * Forgot to update the order of examples in one place * Fixed formatting issue * Adjusted based on james feedback * Changed default_mode to default_calltransfer_mode
212 lines
6.9 KiB
Python
212 lines
6.9 KiB
Python
# bot_runner_helpers.py
|
|
from typing import Any, Dict, Optional
|
|
|
|
from bot_constants import (
|
|
DEFAULT_CALLTRANSFER_MODE,
|
|
DEFAULT_DIALIN_EXAMPLE,
|
|
DEFAULT_SPEAK_SUMMARY,
|
|
DEFAULT_STORE_SUMMARY,
|
|
DEFAULT_TEST_IN_PREBUILT,
|
|
)
|
|
from call_connection_manager import CallConfigManager
|
|
|
|
# ----------------- Configuration Helpers ----------------- #
|
|
|
|
|
|
def determine_room_capabilities(config_body: Optional[Dict[str, Any]] = None) -> Dict[str, bool]:
|
|
"""Determine room capabilities based on the configuration.
|
|
|
|
This function examines the configuration to determine which capabilities
|
|
the Daily room should have enabled.
|
|
|
|
Args:
|
|
config_body: Configuration dictionary that determines room capabilities
|
|
|
|
Returns:
|
|
Dictionary of capability flags
|
|
"""
|
|
capabilities = {
|
|
"enable_dialin": False,
|
|
"enable_dialout": False,
|
|
# Add more capabilities here in the future as needed
|
|
}
|
|
|
|
if not config_body:
|
|
return capabilities
|
|
|
|
# Check for dialin capability
|
|
capabilities["enable_dialin"] = "dialin_settings" in config_body
|
|
|
|
# Check for dialout capability - needed for outbound calls or transfers
|
|
has_dialout_settings = "dialout_settings" in config_body
|
|
|
|
# Check if there's a transfer to an operator configured
|
|
has_call_transfer = "call_transfer" in config_body
|
|
|
|
# Enable dialout if any condition requires it
|
|
capabilities["enable_dialout"] = has_dialout_settings or has_call_transfer
|
|
|
|
return capabilities
|
|
|
|
|
|
def ensure_dialout_settings_array(body: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Ensures dialout_settings is an array of objects.
|
|
|
|
Args:
|
|
body: The configuration dictionary
|
|
|
|
Returns:
|
|
Updated configuration with dialout_settings as an array
|
|
"""
|
|
if "dialout_settings" in body:
|
|
# Convert to array if it's not already one
|
|
if not isinstance(body["dialout_settings"], list):
|
|
body["dialout_settings"] = [body["dialout_settings"]]
|
|
|
|
return body
|
|
|
|
|
|
def ensure_prompt_config(body: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Ensures the body has appropriate prompts settings, but doesn't add defaults.
|
|
|
|
Only makes sure the prompt section exists, allowing the bot script to handle defaults.
|
|
|
|
Args:
|
|
body: The configuration dictionary
|
|
|
|
Returns:
|
|
Updated configuration with prompt settings section
|
|
"""
|
|
if "prompts" not in body:
|
|
body["prompts"] = []
|
|
return body
|
|
|
|
|
|
def create_call_transfer_settings(body: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Create call transfer settings based on configuration and customer mapping.
|
|
|
|
Args:
|
|
body: The configuration dictionary
|
|
|
|
Returns:
|
|
Call transfer settings dictionary
|
|
"""
|
|
# Default transfer settings
|
|
transfer_settings = {
|
|
"mode": DEFAULT_CALLTRANSFER_MODE,
|
|
"speakSummary": DEFAULT_SPEAK_SUMMARY,
|
|
"storeSummary": DEFAULT_STORE_SUMMARY,
|
|
"testInPrebuilt": DEFAULT_TEST_IN_PREBUILT,
|
|
}
|
|
|
|
# If call_transfer already exists, merge the defaults with the existing settings
|
|
# This ensures all required fields exist while preserving user-specified values
|
|
if "call_transfer" in body:
|
|
existing_settings = body["call_transfer"]
|
|
# Update defaults with existing settings (existing values will override defaults)
|
|
for key, value in existing_settings.items():
|
|
transfer_settings[key] = value
|
|
else:
|
|
# No existing call_transfer - check if we have dialin settings for customer lookup
|
|
if "dialin_settings" in body:
|
|
# Create a temporary routing manager just for customer lookup
|
|
call_config_manager = CallConfigManager(body)
|
|
|
|
# Get caller info
|
|
caller_info = call_config_manager.get_caller_info()
|
|
from_number = caller_info.get("caller_number")
|
|
|
|
if from_number:
|
|
# Get customer name from phone number
|
|
customer_name = call_config_manager.get_customer_name(from_number)
|
|
|
|
# If we know the customer name, add it to the config for the bot to use
|
|
if customer_name:
|
|
transfer_settings["customerName"] = customer_name
|
|
|
|
return transfer_settings
|
|
|
|
|
|
def create_simple_dialin_settings(body: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Create simple dialin settings based on configuration.
|
|
|
|
Args:
|
|
body: The configuration dictionary
|
|
|
|
Returns:
|
|
Simple dialin settings dictionary
|
|
"""
|
|
# Default simple dialin settings
|
|
simple_dialin_settings = {
|
|
"testInPrebuilt": DEFAULT_TEST_IN_PREBUILT,
|
|
}
|
|
|
|
# If simple_dialin already exists, merge the defaults with the existing settings
|
|
if "simple_dialin" in body:
|
|
existing_settings = body["simple_dialin"]
|
|
# Update defaults with existing settings (existing values will override defaults)
|
|
for key, value in existing_settings.items():
|
|
simple_dialin_settings[key] = value
|
|
|
|
return simple_dialin_settings
|
|
|
|
|
|
def create_simple_dialout_settings(body: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Create simple dialout settings based on configuration.
|
|
|
|
Args:
|
|
body: The configuration dictionary
|
|
|
|
Returns:
|
|
Simple dialout settings dictionary
|
|
"""
|
|
# Default simple dialout settings
|
|
simple_dialout_settings = {
|
|
"testInPrebuilt": DEFAULT_TEST_IN_PREBUILT,
|
|
}
|
|
|
|
# If simple_dialout already exists, merge the defaults with the existing settings
|
|
if "simple_dialout" in body:
|
|
existing_settings = body["simple_dialout"]
|
|
# Update defaults with existing settings (existing values will override defaults)
|
|
for key, value in existing_settings.items():
|
|
simple_dialout_settings[key] = value
|
|
|
|
return simple_dialout_settings
|
|
|
|
|
|
async def process_dialin_request(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Process incoming dial-in request data to create a properly formatted body.
|
|
|
|
Converts camelCase fields received from webhook to snake_case format
|
|
for internal consistency across the codebase.
|
|
|
|
Args:
|
|
data: Raw dialin data from webhook
|
|
|
|
Returns:
|
|
Properly formatted configuration with snake_case keys
|
|
"""
|
|
# Create base body with dialin settings
|
|
body = {
|
|
"dialin_settings": {
|
|
"to": data.get("To", ""),
|
|
"from": data.get("From", ""),
|
|
"call_id": data.get("callId", data.get("CallSid", "")), # Convert to snake_case
|
|
"call_domain": data.get("callDomain", ""), # Convert to snake_case
|
|
}
|
|
}
|
|
|
|
# Use the global default to determine which example to run for dialin webhooks
|
|
example = DEFAULT_DIALIN_EXAMPLE
|
|
|
|
# Configure the bot based on the example
|
|
if example == "call_transfer":
|
|
# Create call transfer settings
|
|
body["call_transfer"] = create_call_transfer_settings(body)
|
|
elif example == "simple_dialin":
|
|
# Create simple dialin settings
|
|
body["simple_dialin"] = create_simple_dialin_settings(body)
|
|
|
|
return body
|