|
|
@@ -0,0 +1,164 @@
|
|
|
+# 豆包API超时错误修复
|
|
|
+
|
|
|
+## 问题现象
|
|
|
+
|
|
|
+1. **前端无法收到AI回复**:部分会话开始录音后,无法收到豆包返回的音频
|
|
|
+2. **后端错误日志**:
|
|
|
+ ```
|
|
|
+ ❌ 豆包错误 (Event None): sami error: codes=52000042,
|
|
|
+ desc=DialogAudioIdleTimeoutError, please use audio_file mod
|
|
|
+ ```
|
|
|
+3. **连接池被占用**:失效的连接占用连接池,导致活跃连接数持续增加
|
|
|
+
|
|
|
+## 根本原因
|
|
|
+
|
|
|
+### 1. 豆包API空闲超时机制
|
|
|
+- 豆包API对话有空闲超时限制(约30-60秒无音频数据)
|
|
|
+- 超时后连接会收到 `DialogAudioIdleTimeoutError` 错误
|
|
|
+- 此时连接已失效,不会再返回任何音频数据
|
|
|
+
|
|
|
+### 2. 连接池未清理失效连接
|
|
|
+- 连接池从连接中获取一个已超时的连接
|
|
|
+- 旧代码只记录错误,继续使用失效连接
|
|
|
+- 失效连接不会被释放,占用连接池资源
|
|
|
+
|
|
|
+### 3. 健康检查不完善
|
|
|
+- 只检查 `is_connected()` 状态(WebSocket层面)
|
|
|
+- 未检查豆包API层面的错误状态
|
|
|
+
|
|
|
+## 修复方案
|
|
|
+
|
|
|
+### 1. 豆包客户端 (`doubao_api/realtime_dialog_client.py`)
|
|
|
+
|
|
|
+**添加致命错误标志:**
|
|
|
+```python
|
|
|
+self.has_fatal_error = False # 标记是否遇到致命错误
|
|
|
+```
|
|
|
+
|
|
|
+**更新连接检查:**
|
|
|
+```python
|
|
|
+def is_connected(self) -> bool:
|
|
|
+ if self.has_fatal_error:
|
|
|
+ return False
|
|
|
+ return self.connected and self.ws and self.ws.open
|
|
|
+```
|
|
|
+
|
|
|
+**捕获超时错误:**
|
|
|
+```python
|
|
|
+if 'IdleTimeoutError' in error_msg or 'TimeoutError' in error_msg:
|
|
|
+ logger.error(f"🚫 检测到致命错误,标记连接为不可用")
|
|
|
+ self.has_fatal_error = True
|
|
|
+ self.connected = False
|
|
|
+ return False # 停止接收
|
|
|
+```
|
|
|
+
|
|
|
+### 2. WebSocket处理器 (`websocket_handler.py`)
|
|
|
+
|
|
|
+**主动检测连接失效:**
|
|
|
+```python
|
|
|
+# 检查豆包连接是否健康
|
|
|
+if not self.doubao_client.is_connected():
|
|
|
+ logger.error(f"豆包连接已失效,停止处理")
|
|
|
+ await self.send_error(
|
|
|
+ ErrorCode.INTERNAL_ERROR,
|
|
|
+ "语音服务连接异常,请刷新页面重试"
|
|
|
+ )
|
|
|
+ break
|
|
|
+```
|
|
|
+
|
|
|
+### 3. 连接池 (`connection_pool.py`)
|
|
|
+
|
|
|
+**增强健康检查:**
|
|
|
+```python
|
|
|
+async def _is_connection_healthy(self, connection) -> bool:
|
|
|
+ # 检查是否有致命错误
|
|
|
+ if hasattr(connection, 'has_fatal_error') and connection.has_fatal_error:
|
|
|
+ logger.warning(f"连接有致命错误: {connection.session_id}")
|
|
|
+ return False
|
|
|
+
|
|
|
+ # 检查连接是否还活着
|
|
|
+ if not connection or not connection.is_connected():
|
|
|
+ return False
|
|
|
+ ...
|
|
|
+```
|
|
|
+
|
|
|
+**主动清理失效连接:**
|
|
|
+```python
|
|
|
+# 在定期健康检查中
|
|
|
+for conn in temp_connections:
|
|
|
+ if await self._is_connection_healthy(conn):
|
|
|
+ await self.idle_connections.put(conn)
|
|
|
+ else:
|
|
|
+ # 销毁失效连接并创建新的
|
|
|
+ await conn.close()
|
|
|
+ new_conn = await self._create_connection(-1)
|
|
|
+ await self.idle_connections.put(new_conn)
|
|
|
+```
|
|
|
+
|
|
|
+**释放时销毁失效连接:**
|
|
|
+```python
|
|
|
+if await self._is_connection_healthy(connection):
|
|
|
+ await self.idle_connections.put(connection)
|
|
|
+else:
|
|
|
+ # 先关闭旧连接
|
|
|
+ await connection.close()
|
|
|
+ # 创建新连接替换
|
|
|
+ new_conn = await self._create_connection(-1)
|
|
|
+ await self.idle_connections.put(new_conn)
|
|
|
+```
|
|
|
+
|
|
|
+## 效果
|
|
|
+
|
|
|
+1. ✅ 自动检测并标记失效的豆包连接
|
|
|
+2. ✅ 失效连接不会被重用
|
|
|
+3. ✅ 定期健康检查主动清理失效连接
|
|
|
+4. ✅ 失效连接被销毁后自动创建新连接补充
|
|
|
+5. ✅ 前端收到错误提示,可以刷新页面重连
|
|
|
+
|
|
|
+## 监控日志
|
|
|
+
|
|
|
+### 正常情况:
|
|
|
+```
|
|
|
+连接池健康检查 - 空闲: 7, 活跃: 3, 总计: 10/10
|
|
|
+```
|
|
|
+
|
|
|
+### 检测到失效连接:
|
|
|
+```
|
|
|
+🚫 检测到致命错误,标记连接为不可用: session_xxx
|
|
|
+连接有致命错误: session_xxx
|
|
|
+清理不健康的空闲连接: session_xxx
|
|
|
+🔧 清理了 2 个不健康的连接并重新创建
|
|
|
+```
|
|
|
+
|
|
|
+### 使用中检测到失效:
|
|
|
+```
|
|
|
+豆包连接已失效 (has_fatal_error=True),停止处理
|
|
|
+前端收到错误:语音服务连接异常,请刷新页面重试
|
|
|
+```
|
|
|
+
|
|
|
+## 配置优化
|
|
|
+
|
|
|
+已调整 `backend/config.py` 参数:
|
|
|
+```python
|
|
|
+CONNECTION_MAX_IDLE_TIME: int = 25 # 从180秒降到25秒
|
|
|
+CONNECTION_HEALTH_CHECK_INTERVAL: int = 20 # 从30秒降到20秒
|
|
|
+```
|
|
|
+
|
|
|
+**原因**:豆包API的实际超时时间约30-60秒,将空闲时间设为25秒可以在豆包超时前主动清理连接。
|
|
|
+
|
|
|
+## 测试验证
|
|
|
+
|
|
|
+1. **重启后端服务**:让新配置生效
|
|
|
+ ```bash
|
|
|
+ cd backend && ./start.sh
|
|
|
+ ```
|
|
|
+
|
|
|
+2. **观察健康检查日志**:
|
|
|
+ - 每20秒会打印健康检查报告
|
|
|
+ - 如果有失效连接,会自动清理并重新创建
|
|
|
+
|
|
|
+3. **测试长时间空闲**:
|
|
|
+ - 打开页面后等待30秒不操作
|
|
|
+ - 然后开始录音,应该正常工作
|
|
|
+ - 如果出错,前端会提示"语音服务连接异常,请刷新页面重试"
|
|
|
+
|