From ba2cc8c447ea29a825d9073122b0b236544adad7 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Sun, 9 Feb 2025 16:44:57 +0800 Subject: [PATCH] =?UTF-8?q?update:=E5=8A=A0=E5=85=A5=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=80=9F=E5=BA=A6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 62 +++++++++++++++++++++++++------- config.yaml | 13 +++++-- core/connection.py | 3 +- core/handle/audioHandle.py | 1 + core/handle/textHandle.py | 5 ++- core/utils/llm.py | 73 +++++++++++++++++++++++++++++++++++--- core/utils/tts.py | 13 ++++--- 7 files changed, 143 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 9811a442..fb670639 100644 --- a/README.md +++ b/README.md @@ -333,22 +333,14 @@ https://espressif.github.io/esp-launchpad/ ## 1、TTS 经常失败,经常超时 -建议:如果`EdgeTTS`慢或经常失败,可以更换成`火山引擎的豆包TTS`,如果两个都慢,可能所处的网络环境需要优化一下。 -有部分网友反馈,使用`EdgeTTS`时,最好不要使用梯子,容易失败。 +建议:如果`EdgeTTS`经常失败,先检查一下是否用了梯子。 +如果用的是`火山引擎的豆包TTS`经常失败,最好使用付费版本,因为他们的测试版本只有2个并发。 -## 2、大模型回复有点慢 - -建议:大模型和TTS都是依赖接口,如果网络环境不佳,可以考虑换成本地模型。或多尝试切换不同的接口模型。 - -## 3、为啥我的ChatGLMLLM回复有点问题,明明它才是小智,却把我当小智。 - -建议: 可以先修改`配置文件`里的提示词。也可以把免费的`glm-4-flash`,换成`智谱`其他收费版本的模型。 - -## 4、我想通过小智控制电灯、空调、远程开关机等操作。 +## 2、我想通过小智控制电灯、空调、远程开关机等操作。 建议:在配置文件里,将`LLM`设置成`DifyLLM`,然后通过`Dify`编排智能体实现。 -## 5、我说话很慢,我停顿一下,小智老是抢我的话,咋办。 +## 3、我说话很慢,我停顿一下,小智老是抢我的话,咋办。 建议:在配置文件里,找到这一段,将`min_silence_duration_ms`值改大一点,比如改成`1000`。 @@ -360,7 +352,51 @@ VAD: min_silence_duration_ms: 700 # 如果说话停顿比较长,可以把这个值设置大一些 ``` -## 6、更多问题,可联系我们反馈 +## 3、如何才能提高小智对话响应速度? + +本项目的默认配置,是成本最低的配置。建议刚上手的童鞋,使用默认的免费模型,先解决了“跑得动”的问题,再解决“跑得快”的问题。 +如果要提高响应速度,需要更换各组件来解决。以下是本项目各组件的响应速度测试Tip。 + +以下内容和结论仅供参考,不构成任何形式的承诺或保证。 + +### LLM 类组件 + +| 排名 | 组件名称 | 响应速度(ms) | +|:---|:------------|:--------:| +| 1 | AliLLM | 630 | +| 2 | ChatGLMLLM | 2000 | +| 3 | DeepSeekLLM | 6800 | + +``` +测试地点:广东省佛山市禅城区 +测试时间:2025年2月9日 16:12 +宽带运营商:中国移动 +测试方法:更换配置后,执行core/utils/llm.py文件 +``` + +### TTS 类组件 + +| 排名 | 组件名称 | 响应速度(ms) | +|:---|:----------|:--------:| +| 1 | DoubaoTTS | 645 | +| 2 | EdgeTTS | 1019 | + +``` +测试地点:广东省佛山市禅城区 +测试时间:2025年2月9日 16:12 +宽带运营商:中国移动 +测试方法:更换配置后,执行core/utils/tts.py文件 + +``` + +### 结论 + +`2025年2月9日`,如果我的电脑在`广东省佛山市禅城区`,且使用的是`中国移动`网络,我会优先使用: + +- LLM:`AliLLM` +- TTS:`DoubaoTTS` + +## 4、更多问题,可联系我们反馈 ![图片](docs/images/wechat.jpg) diff --git a/config.yaml b/config.yaml index 361e657d..68b4b978 100644 --- a/config.yaml +++ b/config.yaml @@ -42,6 +42,11 @@ VAD: min_silence_duration_ms: 700 # 如果说话停顿比较长,可以把这个值设置大一些 LLM: + AliLLM: + # 可在这里找到你的 api_key https://bailian.console.aliyun.com/?apiKey=1#/api-key + base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + model_name: qwen-turbo + api_key: 你的阿里云dashscope api key DeepSeekLLM: # 可在这里找到你的api key https://platform.deepseek.com/ model_name: deepseek-chat @@ -63,9 +68,11 @@ TTS: output_file: tmp/ DoubaoTTS: # 火山引擎语音合成服务,需要先在火山引擎控制台创建应用并获取appid和access_token + # 山引擎语音一定要购买花钱,起步价30元,就有100并发了。如果用免费的只有2个并发,会经常报tts错误 + # 购买服务后,购买免费的音色后,可能要等半小时左右,才能使用。 # 地址:https://console.volcengine.com/speech/service/8 - voice: BV407_V2_streaming + voice: BV001_streaming output_file: tmp/ - appid: 你的火山引擎appid - access_token: 你的火山引擎access_token + appid: 你的火山引擎语音合成服务appid + access_token: 你的火山引擎语音合成服务access_token cluster: volcano_tts diff --git a/core/connection.py b/core/connection.py index 919ee52c..cb2d06d0 100644 --- a/core/connection.py +++ b/core/connection.py @@ -111,7 +111,7 @@ class ConnectionHandler: # 提交 LLM 任务 try: start_time = time.time() # 记录开始时间 - llm_responses = self.llm.response(self, self.dialogue.get_llm_dialogue()) + llm_responses = self.llm.response(self.session_id, self.dialogue.get_llm_dialogue()) except Exception as e: self.logger.error(f"LLM 处理出错 {query}: {e}") return None @@ -203,6 +203,7 @@ class ConnectionHandler: def recode_first_last_text(self, text): if not self.tts_first_text: + self.logger.info(f"大模型说出第一句话: {text}") self.tts_first_text = text self.tts_last_text = text diff --git a/core/handle/audioHandle.py b/core/handle/audioHandle.py index 985a4a6d..d15d1ff5 100644 --- a/core/handle/audioHandle.py +++ b/core/handle/audioHandle.py @@ -56,6 +56,7 @@ async def sendAudioMessage(conn, audios, duration, text): base_delay = conn.tts_duration if text == conn.tts_first_text: + logger.info(f"发送第一段语音: {text}") conn.tts_start_speak_time = time.time() await conn.websocket.send(json.dumps({ "type": "tts", diff --git a/core/handle/textHandle.py b/core/handle/textHandle.py index 3cb933c6..23da0fbd 100644 --- a/core/handle/textHandle.py +++ b/core/handle/textHandle.py @@ -12,6 +12,9 @@ async def handleTextMessage(conn, message): logger.info(f"收到文本消息:{message}") try: msg_json = json.loads(message) + if isinstance(msg_json, int): + await conn.websocket.send(message) + return if msg_json["type"] == "hello": await handleHelloMessage(conn) elif msg_json["type"] == "abort": @@ -33,4 +36,4 @@ async def handleTextMessage(conn, message): if "text" in msg_json: await startToChat(conn, msg_json["text"]) except json.JSONDecodeError: - await conn.websocket.send(message) \ No newline at end of file + await conn.websocket.send(message) diff --git a/core/utils/llm.py b/core/utils/llm.py index ca1fa540..0b1e8ccc 100644 --- a/core/utils/llm.py +++ b/core/utils/llm.py @@ -2,6 +2,10 @@ import json import logging import openai import requests +from datetime import datetime +from core.utils.util import is_segment +from core.utils.util import get_string_no_punctuation_or_emoji +from core.utils.util import read_config, get_project_dir from abc import ABC, abstractmethod logger = logging.getLogger(__name__) @@ -9,7 +13,7 @@ logger = logging.getLogger(__name__) class LLM(ABC): @abstractmethod - def response(self, conn, dialogue): + def response(self, session_id, dialogue): """LLM response generator""" pass @@ -21,7 +25,7 @@ class DeepSeekLLM(LLM): self.base_url = config.get("url") self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url) - def response(self, conn, dialogue): + def response(self, session_id, dialogue): logger.info(f"Generating response using {dialogue}") try: responses = self.client.chat.completions.create( @@ -47,7 +51,7 @@ class ChatGLMLLM(LLM): self.base_url = config.get("url") self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url) - def response(self, conn, dialogue): + def response(self, session_id, dialogue): try: responses = self.client.chat.completions.create( model=self.model_name, @@ -64,13 +68,36 @@ class ChatGLMLLM(LLM): except Exception as e: logger.error(f"Error in response generation: {e}") +class AliLLM(LLM): + def __init__(self, config): + self.model_name = config.get("model_name") + self.api_key = config.get("api_key") + self.base_url = config.get("base_url") + self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url) + + def response(self, session_id, dialogue): + try: + responses = self.client.chat.completions.create( + model=self.model_name, + messages=dialogue, + stream=True + ) + for chunk in responses: + # 检查是否存在有效的choice且content不为空 + if chunk.choices and len(chunk.choices) > 0: + delta = chunk.choices[0].delta + content = getattr(delta, 'content', '') + if content: # 仅在content非空时生成 + yield content + except Exception as e: + logger.error(f"Error in response generation: {e}") class DifyLLM(LLM): def __init__(self, config): self.api_key = config["api_key"] self.base_url = config.get("base_url", "https://api.dify.ai/v1").rstrip('/') - def response(self, conn,dialogue): + def response(self, session_id, dialogue): try: # 取最后一条用户消息 last_msg = next(m for m in reversed(dialogue) if m["role"] == "user") @@ -82,7 +109,7 @@ class DifyLLM(LLM): json={ "query": last_msg["content"], "response_mode": "streaming", - "user": conn.session_id, + "user": session_id, "inputs": {} }, stream=True @@ -103,9 +130,45 @@ def create_instance(class_name, *args, **kwargs): "DeepSeekLLM": DeepSeekLLM, "ChatGLMLLM": ChatGLMLLM, "DifyLLM": DifyLLM, + "AliLLM": AliLLM, # 可扩展其他LLM实现 } if cls := cls_map.get(class_name): return cls(*args, **kwargs) raise ValueError(f"不支持的LLM类型: {class_name}") + + +if __name__ == "__main__": + """ + 响应速度测试 + """ + config = read_config(get_project_dir() + "config.yaml") + llm = create_instance( + config["selected_module"]["LLM"], + config["LLM"][config["selected_module"]["LLM"]] + ) + + start_time = datetime.now() + + dialogue = [] + dialogue.append({"role": "system", "content": config.get("prompt")}) + dialogue.append({"role": "user", "content": "你好小智"}) + llm_responses = llm.response("test", dialogue) + response_message = [] + first_text = None + start = 0 + + for content in llm_responses: + response_message.append(content) + + if is_segment(response_message): + segment_text = "".join(response_message[start:]) + segment_text = get_string_no_punctuation_or_emoji(segment_text) + if len(segment_text) > 0: + if first_text is None: + first_text = segment_text + print("大模型首次返回耗时:" + str(datetime.now() - start_time)) + start = len(response_message) + + print("大模型返回总耗时:" + str(datetime.now() - start_time)) diff --git a/core/utils/tts.py b/core/utils/tts.py index 4051b5f1..a18a0eb9 100644 --- a/core/utils/tts.py +++ b/core/utils/tts.py @@ -20,7 +20,6 @@ class TTS(ABC): def __init__(self, config, delete_audio_file): self.delete_audio_file = delete_audio_file self.output_file = config.get("output_file") - self.delete_audio_file = delete_audio_file @abstractmethod def generate_filename(self): @@ -164,6 +163,9 @@ def create_instance(class_name, *args, **kwargs): if __name__ == "__main__": + """ + 响应速度测试 + """ config = read_config(get_project_dir() + "config.yaml") tts = create_instance( config["selected_module"]["TTS"], @@ -171,6 +173,9 @@ if __name__ == "__main__": config["delete_audio"] ) tts.output_file = get_project_dir() + tts.output_file - file_path = tts.to_tts("你好,测试") - print(file_path) - print(tts.wav_to_opus_data(file_path)) + start = datetime.now() + file_path = tts.to_tts("你好,测试,我是人工智能小智") + print("语音合成耗时:" + str(datetime.now() - start)) + start = datetime.now() + tts.wav_to_opus_data(file_path) + print("语音opus耗时:" + str(datetime.now() - start))