185 lines
7.0 KiB
Python
185 lines
7.0 KiB
Python
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': '0', '1': '1', '2': '2', '3': '3', '4': '4',
|
||
'5': '5', '6': '6', '7': '7', '8': '8', '9': '9',
|
||
'A': 'a', 'B': 'b', 'C': 'c', 'D': 'd', 'E': 'e',
|
||
'F': 'f', 'G': 'g', 'H': 'h', 'I': 'i', 'J': 'j',
|
||
'K': 'k', 'L': 'l', 'M': 'm', 'N': 'n', 'O': 'o',
|
||
'P': 'p', 'Q': 'q', 'R': 'r', 'S': 's', 'T': 't',
|
||
'U': 'u', 'V': 'v', 'W': 'w', 'X': 'x', 'Y': 'y',
|
||
'Z': 'z', 'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd',
|
||
'e': 'e', 'f': 'f', 'g': 'g', 'h': 'h', 'i': 'i',
|
||
'j': 'j', 'k': 'k', 'l': 'l', 'm': 'm', 'n': 'n',
|
||
'o': 'o', 'p': 'p', 'q': 'q', 'r': 'r', 's': 's',
|
||
't': 't', 'u': 'u', 'v': 'v', 'w': 'w', 'x': 'x',
|
||
'y': 'y', 'z': '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
|