admin管理员组

文章数量:1313769

I'm developing an application using Twilio's Programmable Voice API and WebSockets to stream media for an outbound call. The call is initiated, and real-time AI processing is done via a WebSocket connection. However, I'm facing an issue where the call does not end automatically when the conversation concludes. Instead, it remains active until I manually hang up the call.

Expected Behavior

  • The call should terminate automatically when a predefined condition is met (e.g., after a closing phrase).
  • The WebSocket should close when the call is completed.

Current Implementation

I have implemented the call handling using Twilio's API and Media Streams. Below is a simplified version of my implementation:

Call Initiation

import os
import json
import asyncio
import re
import logging
from dotenv import load_dotenv
from fastapi import FastAPI, WebSocket
from fastapi.websockets import WebSocketDisconnect, WebSocketState
from twilio.rest import Client
import websockets
import uvicorn
from twilio.twiml.voice_response import VoiceResponse, Connect, Hangup

load_dotenv()

TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
PHONE_NUMBER_FROM = os.getenv("PHONE_NUMBER_FROM")
DOMAIN = os.getenv("DOMAIN", "")
PORT = int(os.getenv("PORT", 6060))

twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

app = FastAPI()

async def make_call(phone_number: str):
    response = VoiceResponse()
    connect = Connect()
    connect.stream(url=f"wss://{DOMAIN}/media-stream", timeout="10", maxLength="1800")
    response.append(connect)
    response.append(Hangup())

    try:
        call = await asyncio.to_thread(
            twilio_client.calls.create,
            from_=PHONE_NUMBER_FROM,
            to=phone_number,
            twiml=str(response)
        )
        return call.sid
    except Exception as e:
        logging.error(f"Failed to initiate call: {e}")
        raise

WebSocket Handling for Media Stream

@app.websocket("/media-stream")
async def handle_media_stream(websocket: WebSocket):
    await websocket.accept()
    logging.info("WebSocket connected")

    try:
        async with websockets.connect("wss://api.example/ai-processing") as ai_ws:
            await handle_conversation(websocket, ai_ws)
    except Exception as e:
        logging.error(f"WebSocket error: {e}")
        await websocket.close(code=1011)

async def handle_conversation(websocket: WebSocket, ai_ws):
    async def receive_from_twilio():
        try:
            async for message in websocket.iter_text():
                data = json.loads(message)
                if data.get("event") == "media" and ai_ws.open:
                    await ai_ws.send(json.dumps({"type": "input_audio_buffer.append", "audio": data["media"]["payload"]}))
        except WebSocketDisconnect:
            logging.info("Twilio WebSocket disconnected")

    async def send_to_twilio():
        try:
            async for ai_message in ai_ws:
                response = json.loads(ai_message)

                if response.get("type") == "response.audio.delta":
                    audio_payload = base64.b64encode(base64.b64decode(response["delta"])).decode("utf-8")
                    await websocket.send_json({
                        "event": "media",
                        "streamSid": "12345",
                        "media": {"payload": audio_payload}
                    })
                elif response.get("type") == "function_call" and response["function"]["name"] == "end_call":
                    await end_call()
        except Exception as e:
            logging.error(f"Error in send_to_twilio: {e}")

    await asyncio.gather(receive_from_twilio(), send_to_twilio())

Function to End the Call

async def end_call():
    global active_call_sid
    if active_call_sid:
        try:
            logging.info(f"Ending call {active_call_sid}")
            await asyncio.to_thread(
                twilio_client.calls(active_call_sid).update,
                status="completed"
            )
            logging.info(f"Call {active_call_sid} ended successfully")
            active_call_sid = None
        except Exception as e:
            logging.error(f"Error ending call: {e}")
            active_call_sid = None

Open ai tool

from typing import Dict, Any

end_call_tool_definition = {
    "type": "function",
    "name": "end_call",
    "description": "Ends the current interview call after saying goodbye.",
    "parameters": {
        "type": "object",
        "properties": {
            "reason": {
                "type": "string",
                "description": "The reason for ending the call (e.g., 'interview_complete', 'user_request')"
            }
        },
        "required": ["reason"]
    }
}

async def end_call_tool(active_call_sid: str, twilio_client) -> Dict[str, Any]:
    """
    Tool to terminate the Twilio call
    """
    try:
        if active_call_sid:
            await twilio_client.calls(active_call_sid).update(status="completed")
            return {
                "status": "success",
                "message": f"Call {active_call_sid} terminated successfully"
            }
    except Exception as e:
        return {
            "status": "error",
            "message": f"Failed to terminate call: {str(e)}"
        }

Issue

  • The call does not end even after executing the end_call function.
  • The WebSocket connection remains open, and Twilio does not disconnect the media stream.
  • The function to terminate the call (end_call) logs successful execution, but the call remains active until I manually hang up.

What I Have Tried

  • Verified that end_call executes and logs a success message.
  • Ensured active_call_sid is correctly assigned.
  • Checked Twilio logs for any API errors related to call termination.
  • Tried using <Hangup/> in TwiML, but the issue persists.
  • Ensured proper WebSocket closure after call termination.
  • Also tried tools in the open ai .

Used this code as it is

本文标签: openai apiTwilio Outbound Call with WebSocket Media Stream Not Ending AutomaticallyStack Overflow