You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vonage连续语音API问题:handle_recording端点NCCO不执行

问题:Vonage语音交互流程中handle_recording端点返回的NCCO无法生效

我正在搭建本地后端与Vonage的集成,预期流程为:用户拨打Vonage号码后听到开场白,语音被录制并由应用解析,随后返回音频响应播放给用户,该流程循环直到用户挂断。目前所有代码均可正常执行,但handle_recording端点返回的NCCO始终无法生效。

尝试过的解决思路:

  • 仅包含talk动作的简单NCCO请求
  • 使用Vonage客户端类的streaming和update方法
  • 通过WebSocket实现

代码如下:

from fastapi import APIRouter, Request, BackgroundTasks, HTTPException
from fastapi.responses import JSONResponse
import base64
import os
import vonage
import httpx
import asyncio

router = APIRouter()

async def cleanup_temp_file(file_path: str):
    try:
        os.remove(file_path)
        print(f"Temporary file {file_path} deleted.")
    except Exception as e:
        print(f"Error deleting temporary file {file_path}: {e}")

@router.post("/webhooks/answer")
@router.get("/webhooks/answer")
async def answer_call(request: Request):
    ncco = [
        {
            "action": "talk",
            "text": "Hello, please start speaking.",
            "endOnSilence": 3  # End on 3 seconds of silence
        },
        {
            "action": "record",
            "eventUrl": [f"{request.url.scheme}://{request.url.hostname}/webhooks/record"],
            "endOnSilence": 3,  # End on 3 seconds of silence
            "endOnKey": "#"
        }
    ]
    return JSONResponse(content=ncco)

@router.post("/webhooks/record")
async def handle_recording_post(request: Request, background_tasks: BackgroundTasks):
    data = await request.json()
    print(f"POST request received at /webhooks/record with data: {data}")

    recording_url = data.get("recording_url")
    conversation_uuid = data.get("conversation_uuid")

    if not recording_url or not conversation_uuid:
        return JSONResponse(status_code=400, content={"message": "Recording URL or conversation UUID missing"})

    client = vonage.Client(
        key=os.getenv("VONAGE_API_KEY"),
        secret=os.getenv("VONAGE_API_SECRET"),
        application_id=os.getenv("VONAGE_APPLICATION_ID"),
        private_key="./private.key"
    )

    recording = client.voice.get_recording(recording_url)
    audio_base64 = base64.b64encode(recording).decode('utf-8')

    payload = {
        "user": "vonage_user",
        "text": audio_base64
    }

    audio_to_audio_endpoint = os.getenv("AUDIO_TO_AUDIO_ENDPOINT")
    headers = {
        'Content-Type': 'application/json',
        'X-API-Key': "123_3_1"
    }

    async with httpx.AsyncClient(timeout=30.0) as client:  # Increased timeout
        retries = 3
        for attempt in range(retries):
            try:
                response = await client.post(audio_to_audio_endpoint, json=payload, headers=headers)
                if response.status_code == 200:
                    break
            except httpx.RequestError as e:
                print(f"Attempt {attempt + 1} failed with error: {e}")
                await asyncio.sleep(2)  # Wait before retrying
        else:
            return JSONResponse(status_code=500, content={"message": "Failed to process audio after retries"})


    if response.status_code != 200:
        return JSONResponse(status_code=500,
                            content={"message": f"Failed to process audio with response: {response.text}"})

    bot_audio_base64 = response.json()[1].get("audio")
    bot_audio_content = base64.b64decode(bot_audio_base64)

    # Save the audio file to a static directory to be served
    static_dir = "./static"
    os.makedirs(static_dir, exist_ok=True)
    audio_file_path = os.path.join(static_dir, f"{conversation_uuid}.mp3")
    with open(audio_file_path, "wb") as audio_file:
        audio_file.write(bot_audio_content)

    # Add a background task to clean up the temporary file after streaming
    background_tasks.add_task(cleanup_temp_file, audio_file_path)

    ncco = [
        {
            "action": "stream",
            "streamUrl": [f"{request.url.scheme}://{request.url.hostname}/static/{os.path.basename(audio_file_path)}"],
            "loop": 1,  # Ensuring the audio streams continuously
        },
        {
            "action": "record",
            "eventUrl": [f"{request.url.scheme}://{request.url.hostname}/webhooks/record"],
            "endOnSilence": 3,  # End on 3 seconds of silence
            "endOnKey": "#"
        }
    ]

    # Log NCCO for debugging
    print(f"Returning NCCO: {ncco}")
    return JSONResponse(content=ncco)

@router.get("/webhooks/record")
async def handle_recording_get(request: Request):
    data = request.query_params
    print(f"GET request received at /webhooks/record with query params: {data}")

    # Handle different statuses or actions as needed, for now we just return 200 OK
    return JSONResponse(status_code=200, content={"message": "GET request received and handled"})

内容的提问来源于stack exchange,提问作者Denis Tsvetanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:32:02