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

如何用Python实时动态处理Twilio通话并实现条件转接?

问题:通话进行中条件转接至个人号码的实现难题

我正在开发一款具备以下功能的应用:

  • 发起外呼通话
  • 被叫方接听后启动流媒体传输
  • 使用Vosk实时转录流内容
  • 检测关键词以区分两种不同的自动应答
  • 识别到正确的自动应答后,将通话转接至我的个人号码

我卡在第5步,不知道如何在通话进行中拨打我的个人号码。我需要条件性使用<Dial>动词,但目前不清楚怎么实现。

技术背景

我使用的技术栈包括:Twilio Functions、支持WebSocket的Flask服务器,以及ngrok用于将本地主机暴露给Twilio。


模块1:向Twilio发送请求的逻辑

from twilio.rest import Client
from twilio.twiml.voice_response import VoiceResponse, Start
from twilio.request_validator import RequestValidator
from server import file_path
import os
import requests
import xml.etree.cElementTree as ET

# 全局变量
client = Client(account_sid, auth_token)
# Twilio函数URL
call_test_url = "https://my_function_url.com"
# 安全验证
validator = RequestValidator(auth_token)

def call_request():
    # 从文件读取ngrok公网URL
    if os.path.exists(file_path):
        with open(file_path, "r") as file:
              public_url = file.read().strip()

        stream_url = public_url.replace("https", "wss") + "/stream"
        # 动态生成Twiml的工具函数
        stream_twiml = generate_stream_twiml(stream_url, "15")

        request_params = {
            "to" : "...",
            "from" : "...",
            "callback_url" : public_url + "/callstatus",
            "twiml" : stream_twiml
        }
        twilio_signature = validator.compute_signature(call_test_url, request_params)
        request_headers = {
            "X-Twilio-Signature" : twilio_signature
        }
        my_call_request = requests.post(call_test_url, headers=request_headers, data=request_params)
        print(my_call_request.content)

        return my_call_request
    
    else:
        print("未找到存储公网端点的文件。")

if __name__ == "__main__":
    call_request()

模块2:Flask服务器代码

from flask import Flask, request
from flask_sock import Sock
from vosk import SetLogLevel
import time
import audioop
import base64
import json
import os
import vosk
import logging

port = 1234
file_path = "存储ngrok公网URL的文件路径"

app = Flask(__name__)
sock = Sock(app)
log = logging.getLogger('werkzeug')
log.disabled = True
model = vosk.Model('my-model')

CL = '\x1b[0K'
BS = '\x08'

@app.route('/callstatus', methods=['POST'])
def get_call_status():
    from twilio_script import call_request
    call_status = request.form.get('CallStatus')
    call_sid = request.form.get('CallSID')
    print(f"通话状态: {call_status}")
    if call_status == "busy":
        print("被叫方忙线,重新呼叫...")
        time.sleep(0.5)
        call_request()

    return call_status

@sock.route('/stream')
def stream(ws):
    """接收并转录音频流"""
    rec = vosk.KaldiRecognizer(model, 16000)
    while True:
        message = ws.receive()
        packet = json.loads(message)
        if packet['event'] == 'start':
            print('流媒体传输开始')
        elif packet['event'] == 'stop':
            print('\n流媒体传输结束')
        elif packet['event'] == 'media':
            key_string = "..." # 用于区分自动应答的关键词
            audio = base64.b64decode(packet['media']['payload'])
            audio = audioop.ulaw2lin(audio, 2)
            audio = audioop.ratecv(audio, 2, 1, 8000, 16000, None)[0]
            if rec.AcceptWaveform(audio):
                r = json.loads(rec.Result())
                full_output = CL + r['text'] + ' '
                print("\n 转录结果: ", full_output)

                if key_string in full_output:
                   print("识别到目标应答,开始转接通话...")
                   # 此处需要添加转接逻辑
                   break
            else:
                r = json.loads(rec.PartialResult())
                partial_output = CL + r['partial'] + BS * len(r['partial'])
                print("\n 临时转录结果: ", partial_output)
                
                if key_string in partial_output:
                   print("识别到目标应答,开始转接通话...")
                   # 此处需要添加转接逻辑
                   break
    
if __name__ == "__main__":
    if os.path.exists(file_path):
        print("删除旧文件...")
        os.remove(file_path)

    from pyngrok import ngrok
    public_url = ngrok.connect(port, bind_tls=True).public_url

    with open(file_path, "w") as file:
        file.write(public_url)

    app.run(port=port, debug=True, use_reloader=False)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:09:55