|
@@ -24,7 +24,7 @@ from models import (
|
|
|
)
|
|
)
|
|
|
from ai_service import ai_service
|
|
from ai_service import ai_service
|
|
|
from request_logger import request_logger
|
|
from request_logger import request_logger
|
|
|
-from config import settings
|
|
|
|
|
|
|
+from config import settings, get_dashscope_ws_url
|
|
|
|
|
|
|
|
|
|
|
|
|
class WebSocketHandler:
|
|
class WebSocketHandler:
|
|
@@ -34,9 +34,8 @@ class WebSocketHandler:
|
|
|
self.websocket = websocket
|
|
self.websocket = websocket
|
|
|
self.audio_buffers: Dict[str, bytearray] = {} # requestId -> audio_buffer
|
|
self.audio_buffers: Dict[str, bytearray] = {} # requestId -> audio_buffer
|
|
|
self.cancelled_requests: set = set() # 已取消的请求ID
|
|
self.cancelled_requests: set = set() # 已取消的请求ID
|
|
|
- self.recognition_sessions: Dict[str, any] = {} # requestId -> Recognition对象
|
|
|
|
|
|
|
+ self.recognition_sessions: Dict[str, any] = {} # requestId -> OmniRealtimeConversation对象
|
|
|
self.recognition_results: Dict[str, str] = {} # requestId -> 识别结果文本
|
|
self.recognition_results: Dict[str, str] = {} # requestId -> 识别结果文本
|
|
|
- self.recognition_sentences: Dict[str, list] = {} # requestId -> 句子列表
|
|
|
|
|
self.recognition_failed: set = set() # STT识别失败的请求ID
|
|
self.recognition_failed: set = set() # STT识别失败的请求ID
|
|
|
self.event_loop: Optional[asyncio.AbstractEventLoop] = None # 保存事件循环引用
|
|
self.event_loop: Optional[asyncio.AbstractEventLoop] = None # 保存事件循环引用
|
|
|
self.request_start_times: Dict[str, float] = {} # 请求开始时间
|
|
self.request_start_times: Dict[str, float] = {} # 请求开始时间
|
|
@@ -131,13 +130,16 @@ class WebSocketHandler:
|
|
|
# 🔍 记录最后一帧音频到达时间
|
|
# 🔍 记录最后一帧音频到达时间
|
|
|
self.last_audio_frame_time[request_id] = time.time()
|
|
self.last_audio_frame_time[request_id] = time.time()
|
|
|
|
|
|
|
|
- # 发送音频帧到STT服务
|
|
|
|
|
- recognition = self.recognition_sessions.get(request_id)
|
|
|
|
|
- if recognition:
|
|
|
|
|
|
|
+ # 发送音频帧到STT服务(OmniRealtimeConversation使用base64编码)
|
|
|
|
|
+ conversation = self.recognition_sessions.get(request_id)
|
|
|
|
|
+ if conversation:
|
|
|
try:
|
|
try:
|
|
|
|
|
+ # Base64编码音频数据
|
|
|
|
|
+ audio_b64 = base64.b64encode(audio_data).decode('ascii')
|
|
|
|
|
+
|
|
|
# 在线程池中发送音频帧
|
|
# 在线程池中发送音频帧
|
|
|
send_start = time.time()
|
|
send_start = time.time()
|
|
|
- await asyncio.to_thread(recognition.send_audio_frame, audio_data)
|
|
|
|
|
|
|
+ await asyncio.to_thread(conversation.append_audio, audio_b64)
|
|
|
send_duration = (time.time() - send_start) * 1000
|
|
send_duration = (time.time() - send_start) * 1000
|
|
|
print(f"[{request_id}] ➡️ 发送至STT: {chunk_size}字节 (耗时: {send_duration:.1f}ms)")
|
|
print(f"[{request_id}] ➡️ 发送至STT: {chunk_size}字节 (耗时: {send_duration:.1f}ms)")
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
@@ -291,16 +293,23 @@ class WebSocketHandler:
|
|
|
else:
|
|
else:
|
|
|
print(f"[{request_id}] 🔍 ⚠️ 等待期间没有新的识别结果更新")
|
|
print(f"[{request_id}] 🔍 ⚠️ 等待期间没有新的识别结果更新")
|
|
|
|
|
|
|
|
- # 🔧 关键修复:如果没有收到最终结果事件,再主动等待一小段时间让 recognition_results 更新
|
|
|
|
|
|
|
+ # 🔧 关键修复:如果没有收到最终结果事件,再主动等待让 STT 最终结果到达
|
|
|
if not got_final_event:
|
|
if not got_final_event:
|
|
|
- print(f"[{request_id}] 🔄 未收到最终事件,再等待100ms让识别结果更新...")
|
|
|
|
|
- await asyncio.sleep(0.1)
|
|
|
|
|
- # 再次检查更新
|
|
|
|
|
- last_update_final = self.last_recognition_update_time.get(request_id, 0)
|
|
|
|
|
- if last_update_final > last_update_after:
|
|
|
|
|
- print(f"[{request_id}] ✅ 最终等待成功!识别结果已更新")
|
|
|
|
|
- else:
|
|
|
|
|
- print(f"[{request_id}] ⚠️ 最终等待后仍无更新,使用现有结果")
|
|
|
|
|
|
|
+ print(f"[{request_id}] 🔄 未收到最终事件,再等待500ms让识别结果更新...")
|
|
|
|
|
+ # 🎯 增加等待时间:从 100ms → 500ms
|
|
|
|
|
+ # 原因:commit() 后的最终结果需要更长时间(特别是在长时间没有中间结果时)
|
|
|
|
|
+ for i in range(5): # 分5次检查,每次100ms
|
|
|
|
|
+ await asyncio.sleep(0.1)
|
|
|
|
|
+ # 检查是否有更新
|
|
|
|
|
+ last_update_final = self.last_recognition_update_time.get(request_id, 0)
|
|
|
|
|
+ if last_update_final > last_update_after:
|
|
|
|
|
+ wait_time = (i + 1) * 100
|
|
|
|
|
+ print(f"[{request_id}] ✅ 最终等待成功!识别结果已更新 (耗时: {wait_time}ms)")
|
|
|
|
|
+ got_final_event = True
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ if not got_final_event:
|
|
|
|
|
+ print(f"[{request_id}] ⚠️ 最终等待500ms后仍无更新,使用现有结果")
|
|
|
|
|
|
|
|
# 获取STT识别结果
|
|
# 获取STT识别结果
|
|
|
text = self.recognition_results.get(request_id, "")
|
|
text = self.recognition_results.get(request_id, "")
|
|
@@ -484,109 +493,167 @@ class WebSocketHandler:
|
|
|
await self.cleanup_request(request_id)
|
|
await self.cleanup_request(request_id)
|
|
|
|
|
|
|
|
async def start_streaming_stt(self, request_id: str) -> bool:
|
|
async def start_streaming_stt(self, request_id: str) -> bool:
|
|
|
- """启动流式STT识别,返回是否成功"""
|
|
|
|
|
|
|
+ """启动流式STT识别(使用OmniRealtimeConversation API),返回是否成功"""
|
|
|
try:
|
|
try:
|
|
|
stt_init_start = time.time()
|
|
stt_init_start = time.time()
|
|
|
- print(f"[{request_id}] 🔊 启动流式STT识别...")
|
|
|
|
|
|
|
+ print(f"[{request_id}] 🔊 启动流式STT识别(OmniRealtimeConversation API)...")
|
|
|
|
|
|
|
|
# 发送"正在听..."的中间状态
|
|
# 发送"正在听..."的中间状态
|
|
|
await self.send_text_result(request_id, "Listening...", False)
|
|
await self.send_text_result(request_id, "Listening...", False)
|
|
|
|
|
|
|
|
- from dashscope.audio.asr import Recognition, RecognitionCallback, RecognitionResult
|
|
|
|
|
|
|
+ from dashscope.audio.qwen_omni import OmniRealtimeConversation, OmniRealtimeCallback
|
|
|
|
|
+ from dashscope.audio.qwen_omni.omni_realtime import TranscriptionParams, MultiModality
|
|
|
|
|
+ import dashscope
|
|
|
|
|
+
|
|
|
|
|
+ # 设置API Key(根据国际版/国内版自动选择)
|
|
|
|
|
+ dashscope.api_key = settings.active_dashscope_api_key
|
|
|
|
|
|
|
|
# 定义回调处理类
|
|
# 定义回调处理类
|
|
|
handler = self
|
|
handler = self
|
|
|
req_id = request_id
|
|
req_id = request_id
|
|
|
|
|
|
|
|
- class StreamCallback(RecognitionCallback):
|
|
|
|
|
|
|
+ class StreamCallback(OmniRealtimeCallback):
|
|
|
|
|
+ def __init__(self, conversation):
|
|
|
|
|
+ self.conversation = conversation
|
|
|
|
|
+ self.handlers = {
|
|
|
|
|
+ 'session.created': self._handle_session_created,
|
|
|
|
|
+ 'conversation.item.input_audio_transcription.completed': self._handle_final_text,
|
|
|
|
|
+ 'conversation.item.input_audio_transcription.text': self._handle_stash_text,
|
|
|
|
|
+ 'input_audio_buffer.speech_started': lambda r: print(f'[{req_id}] ======Speech Start======'),
|
|
|
|
|
+ 'input_audio_buffer.speech_stopped': lambda r: print(f'[{req_id}] ======Speech Stop======'),
|
|
|
|
|
+ 'error': self._handle_error
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
def on_open(self):
|
|
def on_open(self):
|
|
|
print(f"[{req_id}] 🔗 STT连接已打开")
|
|
print(f"[{req_id}] 🔗 STT连接已打开")
|
|
|
|
|
|
|
|
- def on_close(self):
|
|
|
|
|
- print(f"[{req_id}] 🔌 STT连接已关闭")
|
|
|
|
|
|
|
+ def on_close(self, code, msg):
|
|
|
|
|
+ print(f"[{req_id}] 🔌 STT连接已关闭 (code: {code}, msg: {msg})")
|
|
|
# 标记为失败,停止后续音频帧发送
|
|
# 标记为失败,停止后续音频帧发送
|
|
|
- handler.recognition_failed.add(req_id)
|
|
|
|
|
|
|
+ if code != 1000: # 1000 是正常关闭
|
|
|
|
|
+ handler.recognition_failed.add(req_id)
|
|
|
|
|
|
|
|
- def on_error(self, error):
|
|
|
|
|
- print(f"[{req_id}] ❌ STT错误: {error}")
|
|
|
|
|
- # 标记为失败
|
|
|
|
|
- handler.recognition_failed.add(req_id)
|
|
|
|
|
-
|
|
|
|
|
- def on_event(self, result: RecognitionResult):
|
|
|
|
|
- # 获取识别的句子
|
|
|
|
|
- sentence = result.get_sentence()
|
|
|
|
|
- if sentence and 'text' in sentence:
|
|
|
|
|
- text = sentence['text']
|
|
|
|
|
- begin_time = sentence.get('begin_time', 0)
|
|
|
|
|
- end_time = sentence.get('end_time')
|
|
|
|
|
- is_final = end_time is not None and end_time > 0
|
|
|
|
|
- status = "✅最终" if is_final else "⏳中间"
|
|
|
|
|
-
|
|
|
|
|
- print(f"[{req_id}] 💬 STT识别({status}): {text} [时间: {begin_time}-{end_time}]")
|
|
|
|
|
-
|
|
|
|
|
- # 初始化句子列表
|
|
|
|
|
- if req_id not in handler.recognition_sentences:
|
|
|
|
|
- handler.recognition_sentences[req_id] = []
|
|
|
|
|
-
|
|
|
|
|
- sentence_list = handler.recognition_sentences[req_id]
|
|
|
|
|
-
|
|
|
|
|
- # 查找是否是已有句子的更新(根据begin_time匹配)
|
|
|
|
|
- updated = False
|
|
|
|
|
- for i, sent in enumerate(sentence_list):
|
|
|
|
|
- if abs(sent['begin_time'] - begin_time) < 100: # 100ms容差
|
|
|
|
|
- # 更新现有句子
|
|
|
|
|
- sentence_list[i] = {'text': text, 'begin_time': begin_time, 'end_time': end_time}
|
|
|
|
|
- updated = True
|
|
|
|
|
- break
|
|
|
|
|
-
|
|
|
|
|
- if not updated:
|
|
|
|
|
- # 新句子,追加
|
|
|
|
|
- sentence_list.append({'text': text, 'begin_time': begin_time, 'end_time': end_time})
|
|
|
|
|
|
|
+ def on_event(self, response):
|
|
|
|
|
+ try:
|
|
|
|
|
+ event_type = response.get('type')
|
|
|
|
|
+ event_handler = self.handlers.get(event_type)
|
|
|
|
|
|
|
|
- # 按时间排序并合并所有句子
|
|
|
|
|
- sentence_list.sort(key=lambda x: x['begin_time'])
|
|
|
|
|
- accumulated_text = ' '.join([s['text'] for s in sentence_list])
|
|
|
|
|
|
|
+ # 🔍 记录所有事件(用于诊断)
|
|
|
|
|
+ if event_type not in ['input_audio_buffer.speech_started', 'input_audio_buffer.speech_stopped']:
|
|
|
|
|
+ print(f"[{req_id}] 📡 STT事件: {event_type}")
|
|
|
|
|
|
|
|
- # 🔍 记录识别结果更新时间
|
|
|
|
|
|
|
+ if event_handler:
|
|
|
|
|
+ event_handler(response)
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 🔍 记录未处理的事件
|
|
|
|
|
+ if event_type not in ['session.created', 'session.updated', 'input_audio_buffer.committed']:
|
|
|
|
|
+ print(f"[{req_id}] ⚠️ 未处理的STT事件: {event_type}")
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"[{req_id}] ❌ 事件处理错误: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ def _handle_session_created(self, response):
|
|
|
|
|
+ print(f"[{req_id}] ✅ 会话创建: {response['session']['id']}")
|
|
|
|
|
+
|
|
|
|
|
+ def _handle_final_text(self, response):
|
|
|
|
|
+ text = response.get('transcript', '')
|
|
|
|
|
+ # 🔍 增强日志:显示文本长度和完整响应
|
|
|
|
|
+ print(f"[{req_id}] 💬 STT识别(✅最终): {text} (长度: {len(text)}字)")
|
|
|
|
|
+ print(f"[{req_id}] 🔍 最终结果完整响应: {response}")
|
|
|
|
|
+
|
|
|
|
|
+ # 更新识别结果
|
|
|
|
|
+ handler.recognition_results[req_id] = text
|
|
|
|
|
+ handler.last_recognition_update_time[req_id] = time.time()
|
|
|
|
|
+
|
|
|
|
|
+ # 触发最终结果事件
|
|
|
|
|
+ if req_id in handler.stt_final_events:
|
|
|
|
|
+ try:
|
|
|
|
|
+ handler.event_loop.call_soon_threadsafe(handler.stt_final_events[req_id].set)
|
|
|
|
|
+ print(f"[{req_id}] ⚡ 触发STT最终结果事件")
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"[{req_id}] ❌ 触发事件失败: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ # 发送最终结果到前端
|
|
|
|
|
+ if handler.event_loop:
|
|
|
|
|
+ try:
|
|
|
|
|
+ asyncio.run_coroutine_threadsafe(
|
|
|
|
|
+ handler.send_text_result(req_id, text, False),
|
|
|
|
|
+ handler.event_loop
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"[{req_id}] ❌ 发送最终结果失败: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ def _handle_stash_text(self, response):
|
|
|
|
|
+ # ✅ 修复:合并 text(已确认部分)和 stash(正在识别部分)
|
|
|
|
|
+ confirmed_text = response.get('text', '') # 已确认的文本
|
|
|
|
|
+ stash_text = response.get('stash', '') # 正在识别的文本
|
|
|
|
|
+
|
|
|
|
|
+ # 合并完整文本
|
|
|
|
|
+ full_text = confirmed_text + stash_text
|
|
|
|
|
+
|
|
|
|
|
+ # 🔍 增强日志:显示完整文本
|
|
|
|
|
+ print(f"[{req_id}] 💬 STT识别(⏳中间): {full_text} (长度: {len(full_text)}字)")
|
|
|
|
|
+ print(f"[{req_id}] ├─ 已确认: {confirmed_text} ({len(confirmed_text)}字)")
|
|
|
|
|
+ print(f"[{req_id}] └─ 缓冲区: {stash_text} ({len(stash_text)}字)")
|
|
|
|
|
+
|
|
|
|
|
+ # 🔍 诊断:打印原始响应(只在文本变化时)
|
|
|
|
|
+ if full_text and full_text != handler.recognition_results.get(req_id, ""):
|
|
|
|
|
+ print(f"[{req_id}] 🔍 中间结果完整响应: {response}")
|
|
|
|
|
+
|
|
|
|
|
+ # 更新中间识别结果
|
|
|
|
|
+ if full_text:
|
|
|
|
|
+ handler.recognition_results[req_id] = full_text
|
|
|
handler.last_recognition_update_time[req_id] = time.time()
|
|
handler.last_recognition_update_time[req_id] = time.time()
|
|
|
|
|
|
|
|
- handler.recognition_results[req_id] = accumulated_text
|
|
|
|
|
- print(f"[{req_id}] 📝 累积文本: {accumulated_text} [更新时间: {time.strftime('%H:%M:%S.%f')[:-3]}]")
|
|
|
|
|
-
|
|
|
|
|
- # 🔥 如果是最终结果,触发事件
|
|
|
|
|
- if is_final and req_id in handler.stt_final_events:
|
|
|
|
|
- try:
|
|
|
|
|
- handler.event_loop.call_soon_threadsafe(handler.stt_final_events[req_id].set)
|
|
|
|
|
- print(f"[{req_id}] ⚡ 触发STT最终结果事件")
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- print(f"[{req_id}] ❌ 触发事件失败: {e}")
|
|
|
|
|
-
|
|
|
|
|
- # 从回调线程安全地发送到事件循环
|
|
|
|
|
|
|
+ # 发送中间结果到前端
|
|
|
if handler.event_loop:
|
|
if handler.event_loop:
|
|
|
try:
|
|
try:
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
asyncio.run_coroutine_threadsafe(
|
|
|
- handler.send_text_result(req_id, accumulated_text, False),
|
|
|
|
|
|
|
+ handler.send_text_result(req_id, full_text, False),
|
|
|
handler.event_loop
|
|
handler.event_loop
|
|
|
)
|
|
)
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
print(f"[{req_id}] ❌ 发送中间结果失败: {e}")
|
|
print(f"[{req_id}] ❌ 发送中间结果失败: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ def _handle_error(self, response):
|
|
|
|
|
+ error_msg = response.get('error', {}).get('message', 'Unknown error')
|
|
|
|
|
+ print(f"[{req_id}] ❌ STT错误: {error_msg}")
|
|
|
|
|
+ handler.recognition_failed.add(req_id)
|
|
|
|
|
|
|
|
- # 创建识别对象
|
|
|
|
|
- def create_recognition():
|
|
|
|
|
- return Recognition(
|
|
|
|
|
- model='paraformer-realtime-v2',
|
|
|
|
|
- format='pcm',
|
|
|
|
|
- sample_rate=16000,
|
|
|
|
|
- callback=StreamCallback()
|
|
|
|
|
|
|
+ # 创建会话对象
|
|
|
|
|
+ def create_conversation():
|
|
|
|
|
+ ws_url = get_dashscope_ws_url()
|
|
|
|
|
+ conversation = OmniRealtimeConversation(
|
|
|
|
|
+ model='qwen3-asr-flash-realtime',
|
|
|
|
|
+ url=ws_url,
|
|
|
|
|
+ callback=StreamCallback(conversation=None) # 先传None
|
|
|
)
|
|
)
|
|
|
-
|
|
|
|
|
- recognition = await asyncio.to_thread(create_recognition)
|
|
|
|
|
-
|
|
|
|
|
- # 启动识别
|
|
|
|
|
- await asyncio.to_thread(recognition.start)
|
|
|
|
|
-
|
|
|
|
|
- # 保存识别会话
|
|
|
|
|
- self.recognition_sessions[request_id] = recognition
|
|
|
|
|
|
|
+ # 注入自身引用
|
|
|
|
|
+ conversation.callback.conversation = conversation
|
|
|
|
|
+ return conversation
|
|
|
|
|
+
|
|
|
|
|
+ conversation = await asyncio.to_thread(create_conversation)
|
|
|
|
|
+
|
|
|
|
|
+ # 连接
|
|
|
|
|
+ await asyncio.to_thread(conversation.connect)
|
|
|
|
|
+
|
|
|
|
|
+ # 配置转录参数
|
|
|
|
|
+ transcription_params = TranscriptionParams(
|
|
|
|
|
+ language='zh', # 中文识别
|
|
|
|
|
+ sample_rate=16000,
|
|
|
|
|
+ input_audio_format="pcm"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 更新会话配置
|
|
|
|
|
+ await asyncio.to_thread(
|
|
|
|
|
+ conversation.update_session,
|
|
|
|
|
+ output_modalities=[MultiModality.TEXT],
|
|
|
|
|
+ enable_input_audio_transcription=True,
|
|
|
|
|
+ transcription_params=transcription_params,
|
|
|
|
|
+ enable_turn_detection=False # 禁用VAD,手动提交
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 保存会话对象
|
|
|
|
|
+ self.recognition_sessions[request_id] = conversation
|
|
|
|
|
|
|
|
# 创建最终结果事件
|
|
# 创建最终结果事件
|
|
|
self.stt_final_events[request_id] = asyncio.Event()
|
|
self.stt_final_events[request_id] = asyncio.Event()
|
|
@@ -594,9 +661,6 @@ class WebSocketHandler:
|
|
|
stt_init_duration = (time.time() - stt_init_start) * 1000
|
|
stt_init_duration = (time.time() - stt_init_start) * 1000
|
|
|
print(f"[{request_id}] ✅ STT识别会话已启动 (耗时: {stt_init_duration:.1f}ms)")
|
|
print(f"[{request_id}] ✅ STT识别会话已启动 (耗时: {stt_init_duration:.1f}ms)")
|
|
|
|
|
|
|
|
- # 🔥 优化:移除不必要的100ms延迟,直接开始
|
|
|
|
|
- # await asyncio.sleep(0.1) # 已移除
|
|
|
|
|
-
|
|
|
|
|
# 检查是否已经失败
|
|
# 检查是否已经失败
|
|
|
if request_id in self.recognition_failed:
|
|
if request_id in self.recognition_failed:
|
|
|
print(f"[{request_id}] ❌ STT连接启动后立即失败")
|
|
print(f"[{request_id}] ❌ STT连接启动后立即失败")
|
|
@@ -611,20 +675,28 @@ class WebSocketHandler:
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
async def stop_streaming_stt(self, request_id: str):
|
|
async def stop_streaming_stt(self, request_id: str):
|
|
|
- """停止流式STT识别(异步非阻塞)"""
|
|
|
|
|
|
|
+ """停止流式STT识别(异步非阻塞)- OmniRealtimeConversation版本"""
|
|
|
try:
|
|
try:
|
|
|
- recognition = self.recognition_sessions.get(request_id)
|
|
|
|
|
- if recognition:
|
|
|
|
|
- print(f"[{request_id}] 🛑 触发STT异步停止...")
|
|
|
|
|
|
|
+ conversation = self.recognition_sessions.get(request_id)
|
|
|
|
|
+ if conversation:
|
|
|
|
|
+ print(f"[{request_id}] 🛑 提交音频并触发STT异步停止...")
|
|
|
|
|
|
|
|
- # 🔥 优化:异步停止STT,不等待完成
|
|
|
|
|
- # 这样可以立即进入LLM处理,节省200-340ms
|
|
|
|
|
|
|
+ # 🔥 优化:异步提交并关闭,不等待完成
|
|
|
async def _async_stop():
|
|
async def _async_stop():
|
|
|
try:
|
|
try:
|
|
|
stop_start = time.time()
|
|
stop_start = time.time()
|
|
|
- await asyncio.to_thread(recognition.stop)
|
|
|
|
|
|
|
+ # 先提交音频(触发最终识别)
|
|
|
|
|
+ await asyncio.to_thread(conversation.commit)
|
|
|
|
|
+ print(f"[{request_id}] ✅ 音频已提交")
|
|
|
|
|
+
|
|
|
|
|
+ # 🎯 增加等待时间:从 50ms → 100ms
|
|
|
|
|
+ # 让 commit() 后的最终结果有更多时间到达
|
|
|
|
|
+ await asyncio.sleep(0.1)
|
|
|
|
|
+
|
|
|
|
|
+ # 关闭连接
|
|
|
|
|
+ await asyncio.to_thread(conversation.close)
|
|
|
stop_duration = (time.time() - stop_start) * 1000
|
|
stop_duration = (time.time() - stop_start) * 1000
|
|
|
- print(f"[{request_id}] ✅ STT识别已停止 (耗时: {stop_duration:.1f}ms)")
|
|
|
|
|
|
|
+ print(f"[{request_id}] ✅ STT连接已关闭 (耗时: {stop_duration:.1f}ms)")
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
print(f"[{request_id}] ⚠️ STT停止异常: {e}")
|
|
print(f"[{request_id}] ⚠️ STT停止异常: {e}")
|
|
|
|
|
|
|
@@ -649,10 +721,6 @@ class WebSocketHandler:
|
|
|
if request_id in self.recognition_results:
|
|
if request_id in self.recognition_results:
|
|
|
del self.recognition_results[request_id]
|
|
del self.recognition_results[request_id]
|
|
|
|
|
|
|
|
- # 清理句子列表
|
|
|
|
|
- if request_id in self.recognition_sentences:
|
|
|
|
|
- del self.recognition_sentences[request_id]
|
|
|
|
|
-
|
|
|
|
|
# 清理取消标记
|
|
# 清理取消标记
|
|
|
if request_id in self.cancelled_requests:
|
|
if request_id in self.cancelled_requests:
|
|
|
self.cancelled_requests.remove(request_id)
|
|
self.cancelled_requests.remove(request_id)
|