mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 16:43:55 +08:00
@@ -102,6 +102,19 @@ class ConnectionHandler:
|
|||||||
if self.config["selected_module"]["Intent"] == 'function_call':
|
if self.config["selected_module"]["Intent"] == 'function_call':
|
||||||
self.use_function_call_mode = True
|
self.use_function_call_mode = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 加载插件
|
||||||
|
if self.use_function_call_mode:
|
||||||
|
self.func_handler = FunctionHandler(self)
|
||||||
|
self.logger.bind(tag=TAG).info("初始化FunctionHandler成功")
|
||||||
|
else:
|
||||||
|
self.func_handler = None
|
||||||
|
self.logger.bind(tag=TAG).info("未启用function_call模式,跳过FunctionHandler初始化")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.bind(tag=TAG).error(f"初始化FunctionHandler失败: {str(e)}")
|
||||||
|
self.func_handler = None
|
||||||
|
self.use_function_call_mode = False
|
||||||
|
|
||||||
self.mcp_manager = MCPManager(self)
|
self.mcp_manager = MCPManager(self)
|
||||||
|
|
||||||
async def handle_connection(self, ws):
|
async def handle_connection(self, ws):
|
||||||
@@ -168,13 +181,23 @@ class ConnectionHandler:
|
|||||||
|
|
||||||
except AuthenticationError as e:
|
except AuthenticationError as e:
|
||||||
self.logger.bind(tag=TAG).error(f"Authentication failed: {str(e)}")
|
self.logger.bind(tag=TAG).error(f"Authentication failed: {str(e)}")
|
||||||
|
await self.close(ws)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
stack_trace = traceback.format_exc()
|
stack_trace = traceback.format_exc()
|
||||||
self.logger.bind(tag=TAG).error(f"Connection error: {str(e)}-{stack_trace}")
|
self.logger.bind(tag=TAG).error(f"Connection error: {str(e)}-{stack_trace}")
|
||||||
|
await self.close(ws)
|
||||||
return
|
return
|
||||||
finally:
|
finally:
|
||||||
|
self._save_and_close(ws)
|
||||||
|
|
||||||
|
async def _save_and_close(self, ws):
|
||||||
|
"""保存记忆并关闭连接"""
|
||||||
|
try:
|
||||||
await self.memory.save_memory(self.dialogue.dialogue)
|
await self.memory.save_memory(self.dialogue.dialogue)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.bind(tag=TAG).error(f"保存记忆失败: {e}")
|
||||||
|
finally:
|
||||||
await self.close(ws)
|
await self.close(ws)
|
||||||
|
|
||||||
async def _route_message(self, message):
|
async def _route_message(self, message):
|
||||||
@@ -191,9 +214,6 @@ class ConnectionHandler:
|
|||||||
self.prompt = self.private_config.private_config.get("prompt", self.prompt)
|
self.prompt = self.private_config.private_config.get("prompt", self.prompt)
|
||||||
self.dialogue.put(Message(role="system", content=self.prompt))
|
self.dialogue.put(Message(role="system", content=self.prompt))
|
||||||
|
|
||||||
"""加载插件"""
|
|
||||||
self.func_handler = FunctionHandler(self)
|
|
||||||
|
|
||||||
"""加载记忆"""
|
"""加载记忆"""
|
||||||
device_id = self.headers.get("device-id", None)
|
device_id = self.headers.get("device-id", None)
|
||||||
self.memory.init_memory(device_id, self.llm)
|
self.memory.init_memory(device_id, self.llm)
|
||||||
@@ -294,7 +314,7 @@ class ConnectionHandler:
|
|||||||
break
|
break
|
||||||
|
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}")
|
# self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}")
|
||||||
|
|
||||||
# 合并当前全部文本并处理未分割部分
|
# 合并当前全部文本并处理未分割部分
|
||||||
full_text = "".join(response_message)
|
full_text = "".join(response_message)
|
||||||
@@ -413,7 +433,7 @@ class ConnectionHandler:
|
|||||||
break
|
break
|
||||||
|
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}")
|
# self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}")
|
||||||
|
|
||||||
# 处理文本分段和TTS逻辑
|
# 处理文本分段和TTS逻辑
|
||||||
# 合并当前全部文本并处理未分割部分
|
# 合并当前全部文本并处理未分割部分
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import time
|
|||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
WAKEUP_CONFIG = {"dir": "config/assets/", "file_name": "wakeup_words", "create_time": time.time(), "refresh_time": 10,
|
WAKEUP_CONFIG = {"dir": "config/assets/", "file_name": "wakeup_words", "create_time": time.time(), "refresh_time": 10,
|
||||||
"words": ["很高兴见到你", "你好啊", "我们又见面了", "最近可好?", "很高兴再次和你谈话", "在干嘛"]}
|
"words": ["很高兴见到你", "你好啊", "我们又见面了", "最近可好?", "很高兴再次和你谈话", "在干嘛"], "text": ""}
|
||||||
|
|
||||||
|
|
||||||
async def handleHelloMessage(conn):
|
async def handleHelloMessage(conn):
|
||||||
@@ -36,7 +36,10 @@ async def checkWakeupWords(conn, text):
|
|||||||
asyncio.create_task(wakeupWordsResponse(conn))
|
asyncio.create_task(wakeupWordsResponse(conn))
|
||||||
return False
|
return False
|
||||||
opus_packets, duration = conn.tts.audio_to_opus_data(file)
|
opus_packets, duration = conn.tts.audio_to_opus_data(file)
|
||||||
conn.audio_play_queue.put((opus_packets, text, 0))
|
text_hello = WAKEUP_CONFIG["text"]
|
||||||
|
if not text_hello:
|
||||||
|
text_hello, _ = await conn.asr.speech_to_text(opus_packets, conn.session_id)
|
||||||
|
conn.audio_play_queue.put((opus_packets, text_hello, 0))
|
||||||
if time.time() - WAKEUP_CONFIG["create_time"] > WAKEUP_CONFIG["refresh_time"]:
|
if time.time() - WAKEUP_CONFIG["create_time"] > WAKEUP_CONFIG["refresh_time"]:
|
||||||
asyncio.create_task(wakeupWordsResponse(conn))
|
asyncio.create_task(wakeupWordsResponse(conn))
|
||||||
return True
|
return True
|
||||||
@@ -62,6 +65,7 @@ async def wakeupWordsResponse(conn):
|
|||||||
wakeup_word = random.choice(WAKEUP_CONFIG["words"])
|
wakeup_word = random.choice(WAKEUP_CONFIG["words"])
|
||||||
result = conn.llm.response_no_stream(conn.config["prompt"], wakeup_word)
|
result = conn.llm.response_no_stream(conn.config["prompt"], wakeup_word)
|
||||||
tts_file = await asyncio.to_thread(conn.tts.to_tts, result)
|
tts_file = await asyncio.to_thread(conn.tts.to_tts, result)
|
||||||
|
|
||||||
if tts_file is not None and os.path.exists(tts_file):
|
if tts_file is not None and os.path.exists(tts_file):
|
||||||
file_type = os.path.splitext(tts_file)[1]
|
file_type = os.path.splitext(tts_file)[1]
|
||||||
if file_type:
|
if file_type:
|
||||||
@@ -72,3 +76,4 @@ async def wakeupWordsResponse(conn):
|
|||||||
"""将文件挪到"wakeup_words.mp3"""
|
"""将文件挪到"wakeup_words.mp3"""
|
||||||
shutil.move(tts_file, WAKEUP_CONFIG["dir"] + "my_" + WAKEUP_CONFIG["file_name"] + "." + file_type)
|
shutil.move(tts_file, WAKEUP_CONFIG["dir"] + "my_" + WAKEUP_CONFIG["file_name"] + "." + file_type)
|
||||||
WAKEUP_CONFIG["create_time"] = time.time()
|
WAKEUP_CONFIG["create_time"] = time.time()
|
||||||
|
WAKEUP_CONFIG["text"] = result
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ async def check_direct_exit(conn, text):
|
|||||||
for cmd in cmd_exit:
|
for cmd in cmd_exit:
|
||||||
if text == cmd:
|
if text == cmd:
|
||||||
logger.bind(tag=TAG).info(f"识别到明确的退出命令: {text}")
|
logger.bind(tag=TAG).info(f"识别到明确的退出命令: {text}")
|
||||||
|
await send_stt_message(conn, text)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ logger = setup_logging()
|
|||||||
|
|
||||||
async def handleAudioMessage(conn, audio):
|
async def handleAudioMessage(conn, audio):
|
||||||
if not conn.asr_server_receive:
|
if not conn.asr_server_receive:
|
||||||
logger.bind(tag=TAG).debug(f"前期数据处理中,暂停接收")
|
# logger.bind(tag=TAG).debug(f"前期数据处理中,暂停接收")
|
||||||
return
|
return
|
||||||
if conn.client_listen_mode == "auto":
|
if conn.client_listen_mode == "auto":
|
||||||
have_voice = conn.vad.is_vad(conn, audio)
|
have_voice = conn.vad.is_vad(conn, audio)
|
||||||
@@ -21,7 +21,7 @@ async def handleAudioMessage(conn, audio):
|
|||||||
if have_voice == False and conn.client_have_voice == False:
|
if have_voice == False and conn.client_have_voice == False:
|
||||||
await no_voice_close_connect(conn)
|
await no_voice_close_connect(conn)
|
||||||
conn.asr_audio.append(audio)
|
conn.asr_audio.append(audio)
|
||||||
conn.asr_audio = conn.asr_audio[-5:] # 保留最新的5帧音频内容,解决ASR句首丢字问题
|
conn.asr_audio = conn.asr_audio[-10:] # 保留最新的10帧音频内容,解决ASR句首丢字问题
|
||||||
return
|
return
|
||||||
conn.client_no_voice_last_time = 0.0
|
conn.client_no_voice_last_time = 0.0
|
||||||
conn.asr_audio.append(audio)
|
conn.asr_audio.append(audio)
|
||||||
@@ -30,7 +30,7 @@ async def handleAudioMessage(conn, audio):
|
|||||||
conn.client_abort = False
|
conn.client_abort = False
|
||||||
conn.asr_server_receive = False
|
conn.asr_server_receive = False
|
||||||
# 音频太短了,无法识别
|
# 音频太短了,无法识别
|
||||||
if len(conn.asr_audio) < 10:
|
if len(conn.asr_audio) < 15:
|
||||||
conn.asr_server_receive = True
|
conn.asr_server_receive = True
|
||||||
else:
|
else:
|
||||||
text, file_path = await conn.asr.speech_to_text(conn.asr_audio, conn.session_id)
|
text, file_path = await conn.asr.speech_to_text(conn.asr_audio, conn.session_id)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ async def sendAudio(conn, audios):
|
|||||||
pre_buffer = min(3, len(audios))
|
pre_buffer = min(3, len(audios))
|
||||||
for i in range(pre_buffer):
|
for i in range(pre_buffer):
|
||||||
await conn.websocket.send(audios[i])
|
await conn.websocket.send(audios[i])
|
||||||
conn.logger.bind(tag=TAG).debug(f"预缓冲帧 {i}, 时间: {(time.perf_counter() - start_time) * 1000:.2f}ms")
|
# conn.logger.bind(tag=TAG).debug(f"预缓冲帧 {i}, 时间: {(time.perf_counter() - start_time) * 1000:.2f}ms")
|
||||||
|
|
||||||
# 正常播放剩余帧
|
# 正常播放剩余帧
|
||||||
for opus_packet in audios[pre_buffer:]:
|
for opus_packet in audios[pre_buffer:]:
|
||||||
@@ -51,7 +51,7 @@ async def sendAudio(conn, audios):
|
|||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
await conn.websocket.send(opus_packet)
|
await conn.websocket.send(opus_packet)
|
||||||
conn.logger.bind(tag=TAG).debug(f"发送帧,位置: {play_position}ms, 实际间隔: {(time.perf_counter() - current_time) * 1000:.2f}ms")
|
# conn.logger.bind(tag=TAG).debug(f"发送帧,位置: {play_position}ms, 实际间隔: {(time.perf_counter() - current_time) * 1000:.2f}ms")
|
||||||
|
|
||||||
play_position += frame_duration
|
play_position += frame_duration
|
||||||
|
|
||||||
|
|||||||
@@ -46,15 +46,17 @@ def _handle_device_action(conn, func, success_message, error_message, *args, **k
|
|||||||
return ActionResponse(action=Action.RESPONSE, result=None, response=response)
|
return ActionResponse(action=Action.RESPONSE, result=None, response=response)
|
||||||
|
|
||||||
# 设备控制
|
# 设备控制
|
||||||
|
# TODO: 和自动注册的iot方法重复了,也许可以删掉这里手动定义的实现?
|
||||||
|
# 另外观察到function_call准确率不够高,可能是llm的问题
|
||||||
handle_device_function_desc = {
|
handle_device_function_desc = {
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "handle_device",
|
"name": "handle_device",
|
||||||
"description": (
|
"description": (
|
||||||
"用户想要获取或者设置设备的音量/亮度大小,或者用户觉得声音/亮度过高或过低,或者用户想提高或降低音量/亮度。"
|
"用户想要获取或者设置设备的音量/亮度大小,或者用户觉得声音/亮度过高或过低,或者用户想提高或降低音量/亮度。"
|
||||||
"比如用户说现在亮度多少,参数为:device_type:Backlight,action:get。"
|
"比如用户说现在亮度多少,参数为:device_type:Screen,action:get。"
|
||||||
"比如用户说设置音量为50,参数为:device_type:Speaker,action:set,value:50。"
|
"比如用户说设置音量为50,参数为:device_type:Speaker,action:set,value:50。"
|
||||||
"比如用户说亮度太高了,参数为:device_type:Backlight,action:lower。"
|
"比如用户说亮度太高了,参数为:device_type:Screen,action:lower。"
|
||||||
"比如用户说调大音量,参数为:device_type:Speaker,action:raise。"
|
"比如用户说调大音量,参数为:device_type:Speaker,action:raise。"
|
||||||
),
|
),
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -62,7 +64,7 @@ handle_device_function_desc = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"device_type": {
|
"device_type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "设备类型,可选值:Speaker(音量),Backlight(亮度)"
|
"description": "设备类型,可选值:Speaker(音量),Screen(亮度)"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -82,7 +84,7 @@ handle_device_function_desc = {
|
|||||||
def handle_device(conn, device_type: str, action: str, value: int = None):
|
def handle_device(conn, device_type: str, action: str, value: int = None):
|
||||||
if device_type == "Speaker":
|
if device_type == "Speaker":
|
||||||
method_name, property_name, device_name = "SetVolume", "volume", "音量"
|
method_name, property_name, device_name = "SetVolume", "volume", "音量"
|
||||||
elif device_type == "Backlight":
|
elif device_type == "Screen":
|
||||||
method_name, property_name, device_name = "SetBrightness", "brightness", "亮度"
|
method_name, property_name, device_name = "SetBrightness", "brightness", "亮度"
|
||||||
else:
|
else:
|
||||||
raise Exception(f"未识别的设备类型: {device_type}")
|
raise Exception(f"未识别的设备类型: {device_type}")
|
||||||
|
|||||||
Reference in New Issue
Block a user