Files
banban/talkingq-url/handlers/response_coordinator.py
2026-03-24 15:04:36 +08:00

185 lines
7.0 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
from typing import Dict, List
from config import settings
from utils.logger import session_logger
from services.audio_session import AudioSession
from services.conversation_history import conversation_history_manager
from services.interrupt_handler import interrupt_handler
from services.task_manager import task_manager
from services.text_generator import TextGenerator
from services.tts_synthesizer import TTSSynthesizer
from services.audio_sender import AudioSender
from services.interruption_helper import InterruptionHelper
import re
async def _cleanup_queues(text_queue: asyncio.Queue, url_queue: asyncio.Queue, device_id: str, session_id: str):
"""清理响应处理中使用的队列"""
try:
# 清理text_queue
text_count = 0
while not text_queue.empty():
try:
item = text_queue.get_nowait()
text_queue.task_done()
text_count += 1
del item
except asyncio.QueueEmpty:
break
# 清理url_queue
url_count = 0
while not url_queue.empty():
try:
item = url_queue.get_nowait()
url_queue.task_done()
url_count += 1
del item
except asyncio.QueueEmpty:
break
session_logger.info(
device_id, session_id,
f"队列清理完成 - text_queue: {text_count}项, url_queue: {url_count}"
)
except Exception as e:
session_logger.error(
device_id, session_id, f"队列清理时出错: {e}"
)
SENSITIVE_RESPONSES = [
"你好,我无法给到相关内容",
"我无法给到相关内容",
"Sorry, I didn't catch that",
"I didn't catch that",
"让我们换一个话题",
"Let's change the topic",
"Changeons de sujet",
"Lassen Sie uns das Thema wechseln",
"Cambiemos de tema",
"Mari kita tukar topik"
]
def is_sensitive_response(reply: str) -> bool:
"""
检查回复是否为敏感话题回复,考虑不同语言的标点符号差异
"""
def normalize_text(text):
text = text.lower()
punctuations = r'[!"#$%&\'()*+,-./:;<=>?@\[\\\]^_`{|}~""''、。,!?:;()【】「」『』〈〉《》〔〕…—¥€£¥]'
text = re.sub(punctuations, '', text)
text = re.sub(r'\s+', '', text)
full_to_half = str.maketrans({
'': '0', '': '1', '': '2', '': '3', '': '4',
'': '5', '': '6', '': '7', '': '8', '': '9',
'': 'a', '': 'b', '': 'c', '': 'd', '': 'e',
'': 'f', '': 'g', '': 'h', '': 'i', '': 'j',
'': 'k', '': 'l', '': 'm', '': 'n', '': 'o',
'': 'p', '': 'q', '': 'r', '': 's', '': 't',
'': 'u', '': 'v', '': 'w', '': 'x', '': 'y',
'': 'z', '': 'a', '': 'b', '': 'c', '': 'd',
'': 'e', '': 'f', '': 'g', '': 'h', '': 'i',
'': 'j', '': 'k', '': 'l', '': 'm', '': 'n',
'': 'o', '': 'p', '': 'q', '': 'r', '': 's',
'': 't', '': 'u', '': 'v', '': 'w', '': 'x',
'': 'y', '': 'z', ' ': ''
})
text = text.translate(full_to_half)
return text
normalized_reply = normalize_text(reply)
for sensitive_reply in SENSITIVE_RESPONSES:
normalized_sensitive = normalize_text(sensitive_reply)
if normalized_sensitive in normalized_reply:
return True
return False
async def process_response(
transcript: str,
history: List[Dict[str, str]],
selected_role: dict,
device_id: str,
session_id: str,
device_history,
session: AudioSession,
language: str = None,
):
text_queue = asyncio.Queue() # 从LLM到TTS传递文本
url_queue = asyncio.Queue() # 从TTS到发送任务传递音频URL
session_key = (device_id, session_id)
await InterruptionHelper.register_queue_cleanup(session_key, url_queue)
await InterruptionHelper.register_queue_cleanup(session_key, text_queue)
text_generator = TextGenerator(device_id, session_id)
tts_synthesizer = TTSSynthesizer(device_id, session_id)
audio_sender = AudioSender(device_id, session_id, session.websocket)
llm_service = session.llm_service
tts_service = session.tts_service
if isinstance(tts_service.__class__.__name__, str) and tts_service.__class__.__name__ == "AliyunTTS":
tts_service = await get_tts(selected_role, language)
session.tts_service = tts_service
session_logger.warning(
device_id, session_id, "检测到AliyunTTS已不再支持自动切换到MiniMaxTTS"
)
text_gen_task = await task_manager.create_task(
text_generator.generate_text(
llm_service, transcript, history, selected_role, text_queue
),
device_id=device_id,
session_key=(device_id, session_id),
task_type="text_generation"
)
tts_task = await task_manager.create_task(
tts_synthesizer.synthesize_audio(
tts_service, text_queue, url_queue, selected_role, language # 传递语言参数到TTS
),
device_id=device_id,
session_key=(device_id, session_id),
task_type="tts_synthesis"
)
sender_task = await task_manager.create_task(
audio_sender.send_audio_urls(url_queue),
device_id=device_id,
session_key=(device_id, session_id),
task_type="audio_sender"
)
reply = ""
try:
reply = await text_gen_task
await asyncio.gather(tts_task, sender_task)
except Exception as e:
session_logger.error(
device_id, session_id, f"响应处理过程中出错: {e}", exc_info=True
)
finally:
# 清理队列
await _cleanup_queues(text_queue, url_queue, device_id, session_id)
if not interrupt_handler.is_interrupted(session_key):
if is_sensitive_response(reply):
session_logger.info(
device_id, session_id, "检测到敏感话题回复,该轮对话将不被添加到历史记录"
)
else:
device_history.history.append({"user": transcript, "assistant": reply})
if len(device_history.history) > settings.max_conversation_history:
device_history.history = device_history.history[-settings.max_conversation_history:]
role_key = selected_role.get("role_key", settings.selected_role_key)
device_history.role_key = role_key
await conversation_history_manager.set_history(device_id, device_history, role_key)
session_logger.info(
device_id,
session_id,
f"已将本轮对话添加到角色 {role_key} 的历史记录并保存到数据库"
)
return reply