From 6af46bec967850f33edd2d1a3ee521721c8d800e Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Sun, 31 Aug 2025 13:15:58 +0800 Subject: [PATCH] 'commit' --- dsLightRag/Routes/XueBanRoute.py | 17 +- .../__pycache__/XueBanRoute.cpython-310.pyc | Bin 5670 -> 5488 bytes dsLightRag/Util/TTS_Pipeline.py | 235 ------------------ dsLightRag/Util/XueBanUtil.py | 2 +- .../__pycache__/TTS_Pipeline.cpython-310.pyc | Bin 7345 -> 0 bytes .../__pycache__/XueBanUtil.cpython-310.pyc | Bin 2567 -> 8416 bytes dsLightRag/static/YunXiao/xueban.js | 12 +- 7 files changed, 11 insertions(+), 255 deletions(-) delete mode 100644 dsLightRag/Util/TTS_Pipeline.py delete mode 100644 dsLightRag/Util/__pycache__/TTS_Pipeline.cpython-310.pyc diff --git a/dsLightRag/Routes/XueBanRoute.py b/dsLightRag/Routes/XueBanRoute.py index b31759ef..23b41983 100644 --- a/dsLightRag/Routes/XueBanRoute.py +++ b/dsLightRag/Routes/XueBanRoute.py @@ -84,15 +84,6 @@ async def streaming_chat(websocket: WebSocket): logger.error(f"发送ASR结果失败: {str(e)}") return - - # 获取学伴响应内容(包含题目信息) - logger.info("获取学伴响应内容...") - llm_chunks = [] - async for chunk in get_xueban_response_async(asr_result['text'], stream=True): - llm_chunks.append(chunk) - full_llm_response = ''.join(llm_chunks) - logger.info(f"学伴响应内容: {full_llm_response}") - # 定义音频回调函数,将音频块发送给前端 async def audio_callback(audio_chunk): logger.info(f"发送音频块,大小: {len(audio_chunk)}") @@ -103,12 +94,14 @@ async def streaming_chat(websocket: WebSocket): logger.error(f"发送音频块失败: {str(e)}") raise - # 修改streaming_chat函数中的相关部分 # 实时获取LLM流式输出并处理 logger.info("开始LLM流式处理和TTS合成...") try: - # 直接使用stream_and_split_text获取LLM流式响应并断句 - text_stream = stream_and_split_text(query_text=asr_result['text']) + # 获取LLM流式响应 + llm_stream = get_xueban_response_async(asr_result['text'], stream=True) + + # 使用stream_and_split_text处理流式响应并断句 + text_stream = stream_and_split_text(llm_stream=llm_stream) # 初始化TTS处理器 tts = StreamingVolcanoTTS(max_concurrency=1) diff --git a/dsLightRag/Routes/__pycache__/XueBanRoute.cpython-310.pyc b/dsLightRag/Routes/__pycache__/XueBanRoute.cpython-310.pyc index d768879fec124af342efbf3b0044777951aca196..1d83a17f401e0d4ed9d7068de54b90630a17723d 100644 GIT binary patch delta 1112 zcmZuv+iMe97(d@hOeT}eG-)nP+Gx_t?l!eot3pKtTX$_y!SzBll+9*)rZJdHJu?MU z5=B~6gktJ>^8$%hQK3)y2Ne7pgpKH1cVB%H7ItONIce1gXPDo7=ezxW-#7DY@Z*49 z)HD_GYx2*=r8BX0JqmBu_hx_3dy`&3hJ!eXk^$mc!%3fkYVC%<4#|Mws|AT#2jVuI z3z&F_wrXF2WQgcS2Wd0H3wGiqzEyiNLfWOLgM0iWz&)J=&Z0zc{olE$tr^J}R_f}K z@khFlqK3IxESN?rZI%l2>>>=Z$MEZj&b0}FEBrA(+Y!0}w^0q%(F`JJ8UeDPh6&UG zP!%utHhS^{I|{0B<>kSaj((9bJAw)$c4URC zQ2GZ@#mT9AcpHTztVr3p0z0kvATGY(1e>#mS=#HS zxU)zy;L_MOL%U=cW*_bS`+2Clic3dv||uqKWo1^y2LrkUTEEJMVKxDDtoUDg3eTZ zzkj!IgamX8=;gq!>9M|;IPDP-=g`z!QnH0iYW`B%5^Bs>?6qDBd3LEP1}|1^s?BTj zLJ06@-NxqH4!~sNURw=1qy{NW{Ae`FA+d{2u^C@~MAAzx32~7y!QoEhj_)#tDEs7} z7;O#3y-jVZSXjtrhFdRsQly^}AUg}9gomDBslc)Eud!)S=`tuS(p28e(C={`=Q$hf zZD71hSc$HtGgr)H__%bGpXs=O)2uHz)7f_3%I1bIEE$vOf-vcqy|Vkk-i*|$iS$VU zVpuvLKukqNbNY4MFEcll!}%gv${9z1XP9E|3Q|Da9tdC$<_J39u);Uoz7*>GAG-_x AOaK4? delta 1297 zcmZuv&u<$=6rMMB?Db}My^bB%PSV723e<_?CN+%|386w$iBzJ{CRM-(BGUM9{(v!9*h}Ew z@rDa+OvX9@sDfG{_oSzgL=uq{8Q;cd(Jd{{=XnaPpqA=|+M}K-lsoX^yK{)7+Yv9+ zfmXk)G2!*Uhd#g}{u(L+IYl1YQd*jfJuKK;*?(7W>F$`fp>IOVa8u+Fa(WwDVb9>E z=CP^ih1=tv>1iFjC>xRH-_IdgHY3WeAm@x9xrrUTJ_M*0B4wlf7*VQAh0qJ#y%>|}r)4Yf4 zfW$5HW<=ie4`zLMlr<}}X4#v8c{t1V1JjV-`!leE7n;L-i+*|ii{2Y=_ddDv(>I^> z*mm!$w|cL>();wwTrRiFcRbK=sa33XQ8UKH|CR0C4mAO;v%l0@;sCgW=Y(s;msU%4 zr_`j>a!Tv$^Zp;riEf}+Dp_kTO>z|-Vr^}KpO~KZLg1tzM%ki19u)_N3)j{xhfqcE zA=cEh5MdwbkDebBM4UrGwe8w!=|l{X7y$$D-zqji^QkHZ6Dy$lOkbR{24-Iy z=lI$F&1l03IfNX|5Vc1+Wcuhu_OW?7Df^bb5`-_E=dinXVBW+q%C1G{XAYNzvt9i{ z-6>Zqxx*K|EacA!kd=i|Mx#%&Z=(zO`>{(RGh|U(r>nI_h5n!CBG=qy=VJL`@s%0r z2JL#K!bhhw{Ga9pTxFZF#p%d3w`%8>mzP$bt*%*i)v=CtgG&lw8GNp3Jy~?bC;K@z zQI+lMqQZgzF+ZIYAO@wvkKSK_<7Mb7kJaj=X*# Ld?pOM93T1z;o3qe diff --git a/dsLightRag/Util/TTS_Pipeline.py b/dsLightRag/Util/TTS_Pipeline.py deleted file mode 100644 index 06102ad5..00000000 --- a/dsLightRag/Util/TTS_Pipeline.py +++ /dev/null @@ -1,235 +0,0 @@ -import asyncio -import json -import os -import re -import uuid -from queue import Queue - -import websockets - -from Config import Config -from Util.TTS_Protocols import full_client_request, receive_message, MsgType, EventType -# 添加必要的导入 -from Util.XueBanUtil import get_xueban_response_async - - -async def stream_and_split_text(query_text=None, llm_stream=None): - """ - 流式获取LLM输出并按句子分割 - @param query_text: 查询文本(如果直接提供查询文本) - @param llm_stream: LLM流式响应生成器(如果已有流式响应) - @return: 异步生成器,每次产生一个完整句子 - """ - buffer = "" - - if llm_stream is None and query_text is not None: - # 如果没有提供llm_stream但有query_text,则使用get_xueban_response_async获取流式响应 - llm_stream = get_xueban_response_async(query_text, stream=True) - elif llm_stream is None: - raise ValueError("必须提供query_text或llm_stream参数") - - # 直接处理LLM流式输出 - async for content in llm_stream: - buffer += content - - # 使用正则表达式检测句子结束 - sentences = re.split(r'([。!?.!?])', buffer) - if len(sentences) > 1: - # 提取完整句子 - for i in range(0, len(sentences)-1, 2): - if i+1 < len(sentences): - sentence = sentences[i] + sentences[i+1] - yield sentence - - # 保留不完整的部分 - buffer = sentences[-1] - - # 处理最后剩余的部分 - if buffer: - yield buffer - -# 修改streaming_tts_pipeline函数 -async def streaming_tts_pipeline(query_text, audio_callback): - """ - 流式TTS管道:获取LLM流式输出并断句,然后使用TTS合成语音 - - Args: - query_text: 查询文本 - audio_callback: 音频数据回调函数 - """ - # 1. 获取LLM流式输出并断句 - text_stream = stream_and_split_text(query_text=query_text) - - # 2. 初始化TTS处理器 - tts = StreamingVolcanoTTS() - - # 3. 流式处理文本并生成音频 - await tts.synthesize_stream(text_stream, audio_callback) - - -class StreamingVolcanoTTS: - def __init__(self, voice_type='zh_female_wanwanxiaohe_moon_bigtts', encoding='wav', max_concurrency=2): - self.voice_type = voice_type - self.encoding = encoding - self.app_key = Config.HS_APP_ID - self.access_token = Config.HS_ACCESS_TOKEN - self.endpoint = "wss://openspeech.bytedance.com/api/v3/tts/unidirectional/stream" - self.audio_queue = Queue() - self.max_concurrency = max_concurrency # 最大并发数 - self.semaphore = asyncio.Semaphore(max_concurrency) # 并发控制信号量 - - @staticmethod - def get_resource_id(voice: str) -> str: - if voice.startswith("S_"): - return "volc.megatts.default" - return "volc.service_type.10029" - - async def synthesize_stream(self, text_stream, audio_callback): - """ - 流式合成语音 - - Args: - text_stream: 文本流生成器 - audio_callback: 音频数据回调函数,接收音频片段 - """ - # 实时处理每个文本片段(删除任务列表和gather) - async for text in text_stream: - if text.strip(): - await self._synthesize_single_with_semaphore(text, audio_callback) - - async def _synthesize_single_with_semaphore(self, text, audio_callback): - """使用信号量控制并发数的单个文本合成""" - async with self.semaphore: # 获取信号量,限制并发数 - await self._synthesize_single(text, audio_callback) - - async def _synthesize_single(self, text, audio_callback): - """合成单个文本片段""" - headers = { - "X-Api-App-Key": self.app_key, - "X-Api-Access-Key": self.access_token, - "X-Api-Resource-Id": self.get_resource_id(self.voice_type), - "X-Api-Connect-Id": str(uuid.uuid4()), - } - - websocket = await websockets.connect( - self.endpoint, additional_headers=headers, max_size=10 * 1024 * 1024 - ) - - try: - request = { - "user": { - "uid": str(uuid.uuid4()), - }, - "req_params": { - "speaker": self.voice_type, - "audio_params": { - "format": self.encoding, - "sample_rate": 24000, - "enable_timestamp": True, - }, - "text": text, - "additions": json.dumps({"disable_markdown_filter": False}), - }, - } - - # 发送请求 - await full_client_request(websocket, json.dumps(request).encode()) - - # 接收音频数据 - audio_data = bytearray() - while True: - msg = await receive_message(websocket) - - if msg.type == MsgType.FullServerResponse: - if msg.event == EventType.SessionFinished: - break - elif msg.type == MsgType.AudioOnlyServer: - audio_data.extend(msg.payload) - else: - raise RuntimeError(f"TTS conversion failed: {msg}") - - # 通过回调函数返回音频数据 - if audio_data: - await audio_callback(audio_data) - - finally: - await websocket.close() - - -async def streaming_tts_pipeline(prompt, audio_callback): - """ - 流式TTS管道:获取LLM流式输出并断句,然后使用TTS合成语音 - - Args: - prompt: 提示文本 - audio_callback: 音频数据回调函数 - """ - # 1. 获取LLM流式输出并断句 - text_stream = stream_and_split_text(prompt) - - # 2. 初始化TTS处理器 - tts = StreamingVolcanoTTS() - - # 3. 流式处理文本并生成音频 - await tts.synthesize_stream(text_stream, audio_callback) - - -def save_audio_callback(output_dir=None): - """ - 创建一个音频回调函数,用于保存音频数据到文件 - - Args: - output_dir: 输出目录,默认为当前文件所在目录下的output文件夹 - - Returns: - 音频回调函数 - """ - if output_dir is None: - output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output") - - # 确保输出目录存在 - os.makedirs(output_dir, exist_ok=True) - - def callback(audio_data): - # 生成文件名 - filename = f"pipeline_tts_{uuid.uuid4().hex[:8]}.wav" - filepath = os.path.join(output_dir, filename) - - # 保存音频文件 - with open(filepath, "wb") as f: - f.write(audio_data) - - print(f"音频片段已保存到: {filepath} ({len(audio_data)} 字节)") - - return callback - - -async def test_pipeline(): - """ - 测试流式TTS管道 - """ - # 创建音频回调函数 - audio_handler = save_audio_callback() - - # 测试提示 - prompt = "请详细解释一下量子力学的基本原理,包括波粒二象性、不确定性原理和薛定谔方程。" - - print("开始测试流式TTS管道...") - print(f"测试提示: {prompt}") - print("等待LLM生成文本并转换为语音...") - - # 运行管道 - await streaming_tts_pipeline(prompt, audio_handler) - - print("流式TTS管道测试完成!") - - -def main(): - """ - 主函数,运行测试 - """ - asyncio.run(test_pipeline()) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/dsLightRag/Util/XueBanUtil.py b/dsLightRag/Util/XueBanUtil.py index 6064f0a3..1e486a56 100644 --- a/dsLightRag/Util/XueBanUtil.py +++ b/dsLightRag/Util/XueBanUtil.py @@ -50,7 +50,7 @@ async def get_xueban_response_async(query_text: str, stream: bool = True): zhishiContent = f.read() zhishiContent = "选择作答的相应知识内容:" + zhishiContent + "\n" query_text = zhishiContent + "下面是用户提的问题:" + query_text - logger.info("query_text: " + query_text) + #logger.info("query_text: " + query_text) try: # 创建请求 diff --git a/dsLightRag/Util/__pycache__/TTS_Pipeline.cpython-310.pyc b/dsLightRag/Util/__pycache__/TTS_Pipeline.cpython-310.pyc deleted file mode 100644 index 63e7b15b6e5c20053d7cea4e470497540e79abae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7345 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 delta 807 zcmZXS&1(}u7{+I2H@h>t*=_o5(L)hY;=!l^K@lMo#9~pT#kN5Rv9npTCcAdBp{C3l zu%ZX)LB@;NVk;EVKe@oV+M{5WINu;!L-q;vC-heV(29%{x2uvGicbjU2}U za&$gy-}sh&hRs{BMzy%s!amgcL5{@hir)(C&6-|xa-V<#N;Mp+zhge z#g57=kmhdcsYi2TAaE1RvD7%|OS*&;+Si6s61ACJl#@-F$>=%3N*C{O}rLp_B_`lXcJkWU$L1N2) zvp>$!p?HwnEAS~;NAXZFOovyIyp)kYu4;D{%*$pI`;M(zPOBxj1oETp9eE|zxE!B$ z?tXr=^<}%M{m{fZ8FyQ8HLfpQn{S76^=c#@o4&i8U2J=-Q;EIX@tJY)(=5V_DB!9# z!jKTS=opR2_dV9r>tQVt@9-3K!~iK8y4;0j#6@y?_@F8s6%NVJgWgNP3gQX5VyKtb zniJp2sP0091hGop6Vl7WnY`UI6}le!?1)-sMDp=e>kKL3OhjR=?jK82kN93ZmG*%v zm9X0MDit;+C$ht8wthW|8ccy%;oopvea2^-5p6fTb5N@OR#rv~Fw7#1EVK{(0?VY! A%>V!Z diff --git a/dsLightRag/static/YunXiao/xueban.js b/dsLightRag/static/YunXiao/xueban.js index 0207dcfa..f6218992 100644 --- a/dsLightRag/static/YunXiao/xueban.js +++ b/dsLightRag/static/YunXiao/xueban.js @@ -216,7 +216,7 @@ const WebSocketManager = { console.log('当前音频队列长度:', AudioState.playback.audioQueue.length); // 如果尚未开始流式播放,则开始播放 - if (!AudioState.playback.isStreamPlaying && !AudioState.playback.isPlaying) { + if (!AudioState.playback.isStreamPlaying) { console.log('开始流式播放音频'); AudioState.playback.isStreamPlaying = true; AudioPlayer.processAudioQueue(); @@ -393,17 +393,16 @@ const AudioPlayer = { // 处理音频队列 processAudioQueue() { - // 如果正在播放或队列为空,则返回 - if (AudioState.playback.isStreamPlaying || AudioState.playback.audioQueue.length === 0) { + // 如果队列为空,则返回 + if (AudioState.playback.audioQueue.length === 0) { AudioState.playback.isStreamPlaying = false; + console.log('音频队列为空,停止流式播放'); return; } - // 设置播放状态 - AudioState.playback.isStreamPlaying = true; - // 从队列中取出第一个音频块 const audioBlob = AudioState.playback.audioQueue.shift(); + console.log('从队列取出音频块,剩余队列长度:', AudioState.playback.audioQueue.length); // 创建音频URL const audioUrl = URL.createObjectURL(audioBlob); @@ -417,7 +416,6 @@ const AudioPlayer = { .catch(error => { console.error('播放音频块失败:', error); // 播放失败,继续处理下一个 - AudioState.playback.isStreamPlaying = false; this.processAudioQueue(); }) .finally(() => {