mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
Merge branch 'agent-plugin' into main
This commit is contained in:
@@ -10,27 +10,31 @@ import threading
|
||||
import traceback
|
||||
import subprocess
|
||||
import websockets
|
||||
from core.handle.mcpHandle import call_mcp_tool
|
||||
from core.utils.util import (
|
||||
extract_json_from_string,
|
||||
initialize_modules,
|
||||
check_vad_update,
|
||||
check_asr_update,
|
||||
filter_sensitive_info,
|
||||
initialize_tts,
|
||||
)
|
||||
from typing import Dict, Any
|
||||
from core.mcp.manager import MCPManager
|
||||
from core.utils.modules_initialize import (
|
||||
initialize_modules,
|
||||
initialize_tts,
|
||||
initialize_asr,
|
||||
)
|
||||
from core.handle.reportHandle import report
|
||||
from core.providers.tts.default import DefaultTTS
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from core.utils.dialogue import Message, Dialogue
|
||||
from core.providers.asr.dto.dto import InterfaceType
|
||||
from core.handle.textHandle import handleTextMessage
|
||||
from core.handle.functionHandler import FunctionHandler
|
||||
from plugins_func.loadplugins import auto_import_modules
|
||||
from plugins_func.register import Action, ActionResponse
|
||||
from core.auth import AuthMiddleware, AuthenticationError
|
||||
from config.config_loader import get_private_config_from_api
|
||||
from core.handle.receiveAudioHandle import handleAudioMessage
|
||||
from core.providers.tts.dto.dto import ContentType, TTSMessageDTO, SentenceType
|
||||
from config.logger import setup_logging, build_module_string, update_module_string
|
||||
from config.manage_api_client import DeviceNotFoundException, DeviceBindException
|
||||
@@ -76,9 +80,11 @@ class ConnectionHandler:
|
||||
self.welcome_msg = None
|
||||
self.max_output_size = 0
|
||||
self.chat_history_conf = 0
|
||||
self.audio_format = "opus"
|
||||
|
||||
# 客户端状态相关
|
||||
self.client_abort = False
|
||||
self.client_is_speaking = False
|
||||
self.client_listen_mode = "auto"
|
||||
|
||||
# 线程任务相关
|
||||
@@ -111,8 +117,10 @@ class ConnectionHandler:
|
||||
self.client_voice_stop = False
|
||||
|
||||
# asr相关变量
|
||||
# 因为实际部署时可能会用到公共的本地ASR,不能把变量暴露给公共ASR
|
||||
# 所以涉及到ASR的变量,需要在这里定义,属于connection的私有变量
|
||||
self.asr_audio = []
|
||||
self.asr_server_receive = True
|
||||
self.asr_audio_queue = queue.Queue()
|
||||
|
||||
# llm相关变量
|
||||
self.llm_finish_task = True
|
||||
@@ -141,7 +149,8 @@ class ConnectionHandler:
|
||||
int(self.config.get("close_connection_no_voice_time", 120)) + 60
|
||||
) # 在原来第一道关闭的基础上加60秒,进行二道关闭
|
||||
|
||||
self.audio_format = "opus"
|
||||
# {"mcp":true} 表示启用MCP功能
|
||||
self.features = None
|
||||
|
||||
async def handle_connection(self, ws):
|
||||
try:
|
||||
@@ -247,7 +256,11 @@ class ConnectionHandler:
|
||||
if isinstance(message, str):
|
||||
await handleTextMessage(self, message)
|
||||
elif isinstance(message, bytes):
|
||||
await handleAudioMessage(self, message)
|
||||
if self.vad is None:
|
||||
return
|
||||
if self.asr is None:
|
||||
return
|
||||
self.asr_audio_queue.put(message)
|
||||
|
||||
async def handle_restart(self, message):
|
||||
"""处理服务器重启请求"""
|
||||
@@ -315,10 +328,14 @@ class ConnectionHandler:
|
||||
if self.vad is None:
|
||||
self.vad = self._vad
|
||||
if self.asr is None:
|
||||
self.asr = self._asr
|
||||
self.asr = self._initialize_asr()
|
||||
# 打开语音识别通道
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self.asr.open_audio_channels(self), self.loop
|
||||
)
|
||||
if self.tts is None:
|
||||
self.tts = self._initialize_tts()
|
||||
# 使用事件循环运行异步方法
|
||||
# 打开语音合成通道
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self.tts.open_audio_channels(self), self.loop
|
||||
)
|
||||
@@ -356,6 +373,19 @@ class ConnectionHandler:
|
||||
|
||||
return tts
|
||||
|
||||
def _initialize_asr(self):
|
||||
"""初始化ASR"""
|
||||
if self._asr.interface_type == InterfaceType.LOCAL:
|
||||
# 如果公共ASR是本地服务,则直接返回
|
||||
# 因为本地一个实例ASR,可以被多个连接共享
|
||||
asr = self._asr
|
||||
else:
|
||||
# 如果公共ASR是远程服务,则初始化一个新实例
|
||||
# 因为远程ASR,涉及到websocket连接和接收线程,需要每个连接一个实例
|
||||
asr = initialize_asr(self.config)
|
||||
|
||||
return asr
|
||||
|
||||
def _initialize_private_config(self):
|
||||
"""如果是从配置文件获取,则进行二次实例化"""
|
||||
if not self.read_config_from_api:
|
||||
@@ -459,6 +489,8 @@ class ConnectionHandler:
|
||||
self.memory = modules["memory"]
|
||||
|
||||
def _initialize_memory(self):
|
||||
if self.memory is None:
|
||||
return
|
||||
"""初始化记忆模块"""
|
||||
self.memory.init_memory(
|
||||
role_id=self.device_id,
|
||||
@@ -499,6 +531,8 @@ class ConnectionHandler:
|
||||
self.logger.bind(tag=TAG).info("使用主LLM作为意图识别模型")
|
||||
|
||||
def _initialize_intent(self):
|
||||
if self.intent is None:
|
||||
return
|
||||
self.intent_type = self.config["Intent"][
|
||||
self.config["selected_module"]["Intent"]
|
||||
]["type"]
|
||||
@@ -563,6 +597,12 @@ class ConnectionHandler:
|
||||
functions = None
|
||||
if self.intent_type == "function_call" and hasattr(self, "func_handler"):
|
||||
functions = self.func_handler.get_functions()
|
||||
if hasattr(self, "mcp_client"):
|
||||
mcp_tools = self.mcp_client.get_available_tools()
|
||||
if mcp_tools is not None and len(mcp_tools) > 0:
|
||||
if functions is None:
|
||||
functions = []
|
||||
functions.extend(mcp_tools)
|
||||
response_message = []
|
||||
|
||||
try:
|
||||
@@ -600,8 +640,10 @@ class ConnectionHandler:
|
||||
function_arguments = ""
|
||||
content_arguments = ""
|
||||
text_index = 0
|
||||
|
||||
self.client_abort = False
|
||||
for response in llm_responses:
|
||||
if self.client_abort:
|
||||
break
|
||||
if functions is not None:
|
||||
content, tools_call = response
|
||||
if "content" in response:
|
||||
@@ -627,9 +669,6 @@ class ConnectionHandler:
|
||||
if content is not None and len(content) > 0:
|
||||
if not tool_call_flag:
|
||||
response_message.append(content)
|
||||
if self.client_abort:
|
||||
break
|
||||
|
||||
if text_index == 0:
|
||||
self.tts.tts_text_queue.put(
|
||||
TTSMessageDTO(
|
||||
@@ -681,9 +720,32 @@ class ConnectionHandler:
|
||||
"arguments": function_arguments,
|
||||
}
|
||||
|
||||
# 处理MCP工具调用
|
||||
# 处理Server端MCP工具调用
|
||||
if self.mcp_manager.is_mcp_tool(function_name):
|
||||
result = self._handle_mcp_tool_call(function_call_data)
|
||||
elif hasattr(self, "mcp_client") and self.mcp_client.has_tool(
|
||||
function_name
|
||||
):
|
||||
# 如果是小智端MCP工具调用
|
||||
self.logger.bind(tag=TAG).debug(
|
||||
f"调用小智端MCP工具: {function_name}, 参数: {function_arguments}"
|
||||
)
|
||||
try:
|
||||
result = asyncio.run_coroutine_threadsafe(
|
||||
call_mcp_tool(
|
||||
self, self.mcp_client, function_name, function_arguments
|
||||
),
|
||||
self.loop,
|
||||
).result()
|
||||
self.logger.bind(tag=TAG).debug(f"MCP工具调用结果: {result}")
|
||||
result = ActionResponse(
|
||||
action=Action.REQLLM, result=result, response=""
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.bind(tag=TAG).error(f"MCP工具调用失败: {e}")
|
||||
result = ActionResponse(
|
||||
action=Action.REQLLM, result="MCP工具调用失败", response=""
|
||||
)
|
||||
else:
|
||||
# 处理系统函数
|
||||
result = self.func_handler.handle_llm_function_call(
|
||||
@@ -806,9 +868,7 @@ class ConnectionHandler:
|
||||
item = self.report_queue.get(timeout=1)
|
||||
if item is None: # 检测毒丸对象
|
||||
break
|
||||
|
||||
type, text, audio_data, report_time = item
|
||||
|
||||
try:
|
||||
# 检查线程池状态
|
||||
if self.executor is None:
|
||||
@@ -838,8 +898,8 @@ class ConnectionHandler:
|
||||
self.report_queue.task_done()
|
||||
|
||||
def clearSpeakStatus(self):
|
||||
self.client_is_speaking = False
|
||||
self.logger.bind(tag=TAG).debug(f"清除服务端讲话状态")
|
||||
self.asr_server_receive = True
|
||||
|
||||
async def close(self, ws=None):
|
||||
"""资源清理方法"""
|
||||
|
||||
Reference in New Issue
Block a user