mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 01:53:53 +08:00
Merge branch 'main' into py_wakeup_audio
This commit is contained in:
@@ -140,10 +140,6 @@ class ConnectionHandler:
|
||||
self.func_handler = None
|
||||
|
||||
self.cmd_exit = self.config["exit_commands"]
|
||||
self.max_cmd_length = 0
|
||||
for cmd in self.cmd_exit:
|
||||
if len(cmd) > self.max_cmd_length:
|
||||
self.max_cmd_length = len(cmd)
|
||||
|
||||
# 是否在聊天结束后关闭连接
|
||||
self.close_after_chat = False
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from config.logger import setup_logging
|
||||
from http import HTTPStatus
|
||||
import dashscope
|
||||
from dashscope import Application
|
||||
from core.providers.llm.base import LLMProviderBase
|
||||
from core.utils.util import check_model_key
|
||||
import time
|
||||
|
||||
TAG = __name__
|
||||
logger = setup_logging()
|
||||
@@ -15,6 +17,7 @@ class LLMProvider(LLMProviderBase):
|
||||
self.base_url = config.get("base_url")
|
||||
self.is_No_prompt = config.get("is_no_prompt")
|
||||
self.memory_id = config.get("ali_memory_id")
|
||||
self.streaming_chunk_size = config.get("streaming_chunk_size", 3) # 每次流式返回的字符数
|
||||
check_model_key("AliBLLLM", self.api_key)
|
||||
|
||||
def response(self, session_id, dialogue):
|
||||
@@ -32,6 +35,8 @@ class LLMProvider(LLMProviderBase):
|
||||
"app_id": self.app_id,
|
||||
"session_id": session_id,
|
||||
"messages": dialogue,
|
||||
# 开启SDK原生流式
|
||||
"stream": True,
|
||||
}
|
||||
if self.memory_id != False:
|
||||
# 百练memory需要prompt参数
|
||||
@@ -42,25 +47,63 @@ class LLMProvider(LLMProviderBase):
|
||||
f"【阿里百练API服务】处理后的prompt: {prompt}"
|
||||
)
|
||||
|
||||
# 可选地设置自定义API基地址(若配置为兼容模式URL则忽略)
|
||||
if self.base_url and ("/api/" in self.base_url):
|
||||
dashscope.base_http_api_url = self.base_url
|
||||
|
||||
responses = Application.call(**call_params)
|
||||
if responses.status_code != HTTPStatus.OK:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"code={responses.status_code}, "
|
||||
f"message={responses.message}, "
|
||||
f"请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code"
|
||||
)
|
||||
yield "【阿里百练API服务响应异常】"
|
||||
else:
|
||||
logger.bind(tag=TAG).debug(
|
||||
f"【阿里百练API服务】构造参数: {call_params}"
|
||||
)
|
||||
yield responses.output.text
|
||||
|
||||
# 流式处理(SDK在stream=True时返回可迭代对象;否则返回单次响应对象)
|
||||
logger.bind(tag=TAG).debug(
|
||||
f"【阿里百练API服务】构造参数: {dict(call_params, api_key='***')}"
|
||||
)
|
||||
|
||||
last_text = ""
|
||||
try:
|
||||
for resp in responses:
|
||||
if resp.status_code != HTTPStatus.OK:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"code={resp.status_code}, message={resp.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code"
|
||||
)
|
||||
continue
|
||||
current_text = getattr(getattr(resp, "output", None), "text", None)
|
||||
if current_text is None:
|
||||
continue
|
||||
# SDK流式为增量覆盖,计算差量输出
|
||||
if len(current_text) >= len(last_text):
|
||||
delta = current_text[len(last_text):]
|
||||
else:
|
||||
# 避免偶发回退
|
||||
delta = current_text
|
||||
if delta:
|
||||
yield delta
|
||||
last_text = current_text
|
||||
except TypeError:
|
||||
# 非流式回落(一次性返回)
|
||||
if responses.status_code != HTTPStatus.OK:
|
||||
logger.bind(tag=TAG).error(
|
||||
f"code={responses.status_code}, message={responses.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code"
|
||||
)
|
||||
yield "【阿里百练API服务响应异常】"
|
||||
else:
|
||||
full_text = getattr(getattr(responses, "output", None), "text", "")
|
||||
logger.bind(tag=TAG).info(
|
||||
f"【阿里百练API服务】完整响应长度: {len(full_text)}"
|
||||
)
|
||||
for i in range(0, len(full_text), self.streaming_chunk_size):
|
||||
chunk = full_text[i:i + self.streaming_chunk_size]
|
||||
if chunk:
|
||||
yield chunk
|
||||
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"【阿里百练API服务】响应异常: {e}")
|
||||
yield "【LLM服务响应异常】"
|
||||
|
||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||
logger.bind(tag=TAG).error(
|
||||
f"阿里百练暂未实现完整的工具调用(function call),建议使用其他意图识别"
|
||||
# 阿里百练当前未支持原生的 function call。为保持兼容,这里回退到普通文本流式输出。
|
||||
# 上层会按 (content, tool_calls) 的形式消费,这里始终返回 (token, None)
|
||||
logger.bind(tag=TAG).warning(
|
||||
"阿里百练未实现原生 function call,已回退为纯文本流式输出"
|
||||
)
|
||||
for token in self.response(session_id, dialogue):
|
||||
yield token, None
|
||||
|
||||
@@ -18,8 +18,8 @@ class ServerMCPExecutor(ToolExecutor):
|
||||
"""初始化MCP管理器"""
|
||||
if not self._initialized:
|
||||
self.mcp_manager = ServerMCPManager(self.conn)
|
||||
await self.mcp_manager.initialize_servers()
|
||||
self._initialized = True
|
||||
await self.mcp_manager.initialize_servers()
|
||||
|
||||
async def execute(
|
||||
self, conn, tool_name: str, arguments: Dict[str, Any]
|
||||
|
||||
@@ -68,6 +68,9 @@ class ServerMCPManager:
|
||||
|
||||
# 输出当前支持的服务端MCP工具列表
|
||||
if hasattr(self.conn, "func_handler") and self.conn.func_handler:
|
||||
# 刷新工具缓存以确保服务端MCP工具被正确加载
|
||||
if hasattr(self.conn.func_handler, "tool_manager"):
|
||||
self.conn.func_handler.tool_manager.refresh_tools()
|
||||
self.conn.func_handler.current_support_functions()
|
||||
|
||||
def get_all_tools(self) -> List[Dict[str, Any]]:
|
||||
|
||||
@@ -33,7 +33,6 @@ class TTSProviderBase(ABC):
|
||||
def __init__(self, config, delete_audio_file):
|
||||
self.interface_type = InterfaceType.NON_STREAM
|
||||
self.conn = None
|
||||
self.tts_timeout = 10
|
||||
self.delete_audio_file = delete_audio_file
|
||||
self.audio_file_type = "wav"
|
||||
self.output_file = config.get("output_dir", "tmp/")
|
||||
@@ -192,7 +191,6 @@ class TTSProviderBase(ABC):
|
||||
|
||||
async def open_audio_channels(self, conn):
|
||||
self.conn = conn
|
||||
self.tts_timeout = conn.config.get("tts_timeout", 10)
|
||||
# tts 消化线程
|
||||
self.tts_priority_thread = threading.Thread(
|
||||
target=self.tts_text_priority_thread, daemon=True
|
||||
|
||||
@@ -8,6 +8,7 @@ import wave
|
||||
import websockets
|
||||
from core.providers.tts.base import TTSProviderBase
|
||||
from config.logger import setup_logging
|
||||
from datetime import datetime
|
||||
|
||||
TAG = __name__
|
||||
logger = setup_logging()
|
||||
@@ -18,11 +19,12 @@ class TTSProvider(TTSProviderBase):
|
||||
super().__init__(config, delete_audio_file)
|
||||
self.url = config.get("url", "ws://192.168.1.10:8092/paddlespeech/tts/streaming")
|
||||
self.protocol = config.get("protocol", "websocket")
|
||||
|
||||
if config.get("private_voice"):
|
||||
self.spk_id = int(config.get("private_voice"))
|
||||
else:
|
||||
self.spk_id = int(config.get("spk_id", "0"))
|
||||
|
||||
self.spk_id = int(config.get("spk_id", "0"))
|
||||
|
||||
sample_rate = config.get("sample_rate", 24000)
|
||||
self.sample_rate = float(sample_rate) if sample_rate else 24000
|
||||
|
||||
@@ -32,7 +34,21 @@ class TTSProvider(TTSProviderBase):
|
||||
volume = config.get("volume", 1.0)
|
||||
self.volume = float(volume) if volume else 1.0
|
||||
|
||||
self.save_path = config.get("save_path", "./streaming_tts.wav")
|
||||
self.delete_audio_file = config.get("delete_audio", True)
|
||||
if not self.delete_audio_file:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
save_path = config.get("save_path")
|
||||
if save_path:
|
||||
if not save_path.endswith('.wav'):
|
||||
save_path = f"{save_path}_{timestamp}.wav"
|
||||
else:
|
||||
other_path = save_path[:-4]
|
||||
save_path = f"{other_path}_{timestamp}.wav"
|
||||
self.save_path = save_path
|
||||
else:
|
||||
self.save_path = f"./streaming_tts_{timestamp}.wav"
|
||||
else:
|
||||
self.save_path = None
|
||||
|
||||
async def pcm_to_wav(self, pcm_data: bytes, sample_rate: int = 24000, num_channels: int = 1,
|
||||
bits_per_sample: int = 16) -> bytes:
|
||||
@@ -151,6 +167,12 @@ class TTSProvider(TTSProviderBase):
|
||||
# 接收结束响应避免服务抛出异常
|
||||
await ws.recv()
|
||||
|
||||
# 根据配置决定是否保存文件
|
||||
if not self.delete_audio_file and self.save_path:
|
||||
with open(self.save_path, "wb") as f:
|
||||
f.write(wav_data)
|
||||
logger.bind(tag=TAG).info(f"音频文件已保存到: {self.save_path}")
|
||||
|
||||
# 返回或保存音频数据
|
||||
if output_file:
|
||||
with open(output_file, "wb") as file_to_save:
|
||||
@@ -159,4 +181,4 @@ class TTSProvider(TTSProviderBase):
|
||||
return wav_data
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Error during TTS WebSocket request: {e} while processing text: {text}")
|
||||
raise Exception(f"Error during TTS WebSocket request: {e} while processing text: {text}")
|
||||
Reference in New Issue
Block a user