AWS Polly(boto3)闲置超时无音频返回:Twilio媒体流集成问题
问题:Twilio VoiceBot闲置2小时后AWS Polly无响应,呼叫卡住
基于Twilio Media Streams构建VoiceBot,流程为Speech-to-Text(STT)→LLM→Text-to-Speech(TTS),采用AWS Polly作为TTS组件,将AWS类声明为Twilio服务器启动时的全局变量。初始运行正常,但闲置约2小时后,新呼入进入语音合成步骤时会卡住——Polly无音频返回,呼叫挂起等待响应。已尝试设置Config(tcp_keepalive=True)但无效果,相关代码如下:
import os import boto3 from botocore.config import Config import audioop import logging logger = logging.getLogger(__name__) class AWS: def __init__(self) -> None: self.sample_rate = "8000" # Ensure sample rate is a string self.session = boto3.Session( aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'], aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'], region_name=os.environ['AWS_DEFAULT_REGION'] ) self.polly: boto3.client = self.session.client("polly",config=Config(tcp_keepalive=True)) def aws_polly_synthesize_speech(self, text: str, voice_name: str): try: response = self.polly.synthesize_speech( OutputFormat="pcm", SampleRate=self.sample_rate, Text=text, VoiceId=voice_name, Engine='neural', TextType='text' ) if "AudioStream" in response: audio_stream = response['AudioStream'] raw_pcm_data = audio_stream.read() mulaw_audio = audioop.lin2ulaw(raw_pcm_data, 2) return mulaw_audio # Return raw PCM data directly logger.error("No AudioStream in the response from AWS Polly") except Exception as e: logger.error(f"Error during AWS Polly request: {e}")
原因分析
- boto3的客户端采用长连接复用机制,全局实例闲置2小时后,底层TCP连接可能被AWS负载均衡或中间网络设备主动断开,但客户端未检测到连接失效,后续请求会卡在尝试使用无效连接的状态。
tcp_keepalive=True仅开启TCP保活,但AWS服务端或中间防火墙/代理可能设置了更短的闲置超时(比如默认1小时),保活包无法阻止连接被切断。- 全局Polly客户端没有连接失效后的自动重建机制,一旦连接中断,后续请求会持续挂起等待无效响应。
解决方法
方法1:添加客户端异常捕获与重建逻辑
修改AWS类,在请求失败时捕获连接相关异常,重建Polly客户端并重试请求:
class AWS: def __init__(self) -> None: self.sample_rate = "8000" self.session = boto3.Session( aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'], aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'], region_name=os.environ['AWS_DEFAULT_REGION'] ) self._init_polly_client() def _init_polly_client(self): self.polly: boto3.client = self.session.client( "polly", config=Config( tcp_keepalive=True, # 缩短超时时间,避免长时间挂起 connect_timeout=10, read_timeout=15 ) ) def aws_polly_synthesize_speech(self, text: str, voice_name: str): try: response = self.polly.synthesize_speech( OutputFormat="pcm", SampleRate=self.sample_rate, Text=text, VoiceId=voice_name, Engine='neural', TextType='text' ) if "AudioStream" in response: audio_stream = response['AudioStream'] raw_pcm_data = audio_stream.read() mulaw_audio = audioop.lin2ulaw(raw_pcm_data, 2) return mulaw_audio logger.error("No AudioStream in the response from AWS Polly") except (botocore.exceptions.ClientError, ConnectionError) as e: # 捕获连接类异常,重建客户端后重试 logger.error(f"Polly request failed, rebuilding client: {e}") self._init_polly_client() # 重试一次请求 try: response = self.polly.synthesize_speech( OutputFormat="pcm", SampleRate=self.sample_rate, Text=text, VoiceId=voice_name, Engine='neural', TextType='text' ) if "AudioStream" in response: audio_stream = response['AudioStream'] raw_pcm_data = audio_stream.read() mulaw_audio = audioop.lin2ulaw(raw_pcm_data, 2) return mulaw_audio logger.error("No AudioStream in retry response from AWS Polly") except Exception as retry_e: logger.error(f"Polly request failed after retry: {retry_e}") except Exception as e: logger.error(f"Unexpected error during Polly request: {e}")
方法2:每次请求创建临时Polly客户端
如果并发量不高,可放弃全局客户端,每次调用合成接口时创建新的Polly客户端,彻底避免长连接闲置失效问题:
class AWS: def __init__(self) -> None: self.sample_rate = "8000" self.session = boto3.Session( aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'], aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'], region_name=os.environ['AWS_DEFAULT_REGION'] ) def aws_polly_synthesize_speech(self, text: str, voice_name: str): # 每次请求创建新客户端 polly = self.session.client( "polly", config=Config(tcp_keepalive=True) ) try: response = polly.synthesize_speech( OutputFormat="pcm", SampleRate=self.sample_rate, Text=text, VoiceId=voice_name, Engine='neural', TextType='text' ) if "AudioStream" in response: audio_stream = response['AudioStream'] raw_pcm_data = audio_stream.read() mulaw_audio = audioop.lin2ulaw(raw_pcm_data, 2) return mulaw_audio logger.error("No AudioStream in the response from AWS Polly") except Exception as e: logger.error(f"Error during AWS Polly request: {e}")
方法3:配置连接池超时参数
通过调整boto3连接池设置,让客户端自动回收闲置过久的连接:
def _init_polly_client(self): self.polly: boto3.client = self.session.client( "polly", config=Config( tcp_keepalive=True, connect_timeout=10, read_timeout=15, # 设置连接池最大闲置时间(30分钟,小于AWS默认超时) pool_connections=10, pool_maxsize=10, pool_timeout=1800 ) )
内容的提问来源于stack exchange,提问作者J7er
相关产品推荐
相关产品推荐

