diff --git a/docs/homeassistant-integration.md b/docs/homeassistant-integration.md index d2dc4f9d..3142244e 100644 --- a/docs/homeassistant-integration.md +++ b/docs/homeassistant-integration.md @@ -1,4 +1,8 @@ -# ESP32设备与HomeAssistant集成指南 +# 小智ESP32-开源服务端与HomeAssistant集成指南 + +[TOC] + +----- ## 简介 @@ -7,26 +11,58 @@ ## 前提条件 - 已安装并配置好`HomeAssistant` -- 已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant` -- `xiaozhi-esp32-server智控台`能正常使用 - 本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用 -## 网络环境 +## 开始前的操作(必要) + +### 1. 获取HA的网络网络地址信息 + +请访问你Home Assistant的网络地址,例如,我的HA的地址是192.168.4.7,端口则是默认的8123,则在浏览器打开 -1、我的`HomeAssistant`部署在下面,版本是`2025.3.4` ``` http://192.168.4.7:8123 ``` -2、我的`xiaozhi-esp32-server智控台`和`HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10` +> 手动查询 HA 的 IP 地址方法**(仅限小智esp32-server和HA部署在同一个网络设备[例如同一个wifi]下)**: +> +> 1. 进入 Home Assistant(前端)。 +> +> 2. 点击左下角 **设置(Settings)** → **系统(System)** → **网络(Network)**。 +> +> 3. 滑到最底部`Home Assistant 网址(Home Assistant website)`区域,在`本地网络(local network)`中,点击`眼睛`按钮,可以看到当前使用的 IP 地址(如 `192.168.1.10`)和网络接口。点击`复制连接(copy link)`可以直接复制。 +> +> ![image-20250504051716417](images/image-ha-integration-01.png) + +或,您已经设置了直接可以访问的Home Assistant的OAuth地址,您也可以在浏览器内直接访问 + ``` -http://192.168.4.7:8002 +http://homeassistant.local:8123 ``` +### 2. 登录`Home Assistant`拿到开发密钥 -## 配置步骤 +登录`HomeAssistant`,点击`左下角头像 -> 个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key,并复制保存,后续的方法都需要使用这个api key且仅出现一次(小tips: 您可以保存生成的二维码图像,后续可以扫描二维码再此提取api key)。 -### 1. 登录`HomeAssistant`整理需要控制的设备清单 +## 方法1:小智社区共建的HA调用功能 + +### 功能描述 + +- 如您后续需要增加新的设备,该方法需要手动重启`xiaozhi-esp32-server服务端`以此更新设备信息**(重要**)。 + +- 需要您确保已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant`。 + +- 需要您确保`xiaozhi-esp32-server智控台`能正常使用。 + +- 我的`xiaozhi-esp32-server智控台`和`HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10` + + ``` + http://192.168.4.7:8002 + ``` + + +### 配置步骤 + +#### 1. 登录`HomeAssistant`整理需要控制的设备清单 登录`HomeAssistant`,点击`左下角的设置`,然后进入`设备与服务`,再点击顶部的`实体`。 @@ -56,12 +92,7 @@ http://192.168.4.7:8002 这段字符,我们成为“设备清单字符”需要保存好,等一下有用。 -### 2. 登录`HomeAssistant`拿到开发密钥 - -登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。 - - -### 3. 登录`智控台` +#### 2. 登录`智控台` 使用管理员账号,登录`智控台`。点击顶部菜单`参数管理`,搜索`plugins.home_assistant.`,会有三条结果出来 @@ -77,7 +108,7 @@ http://192.168.4.7:8123 编辑`plugins.home_assistant.api_key`,把你从`HomeAssistant`复制过来的密钥,粘贴进去 -### 4. 设置`意图识别`函数 +#### 3. 设置`意图识别`函数 在智控台,点击顶部菜单“模型配置”,在左侧栏,找到“意图识别”,找到id为`Intent_function_call`的意图,点击编辑 @@ -94,18 +125,124 @@ change_role;get_weather;get_news;play_music change_role;get_weather;get_news;play_music;hass_get_state;hass_set_state ``` -### 6. 手动重启xiaozhi-server +#### 4. 手动重启xiaozhi-server 重启xiaozhi-server程序 -### 5. 确认角色配置是否设置了函数意图识别 +#### 5. 确认角色配置是否设置了函数意图识别 在智控台,点击顶部菜单“智能体管理”,找到设备所在的智能体,点击“配置角色” 确认意图识别(Intent),是否选择“函数调用意图识别” -### 6. 唤醒设别进行控制 +#### 6. 唤醒设别进行控制 -尝试和esp32说,“打开XXX灯” \ No newline at end of file +尝试和esp32说,“打开XXX灯” + +## 方法2:小智将Home Assistant的语音助手作为LLM工具 + +### 功能描述 + +- 该方法有一个比较严重的缺点——**该方法无法使用小智开源生态的function_call插件功能的能力**,因为使用Home Assistant作为小智的LLM工具会将意图识别能力转让给Home Assistant。但是**这个方法是能体验到原生的Home Assistant操作功能,且小智的聊天能力不变**。如实在介意可以使用同样是Home Assistant支持的[方法3](##方法3:使用Home Assistant的MCP服务(推荐)),能够最大程度体验到Home Assistant的功能。 + +### 配置步骤: + +#### 1. 配置Home Assistant的大模型语音助手。 + +**需要您提前配置好Home Assistant的语音助手或大模型工具。** + +#### 2. 获取Home Assistant的语言助手的Agent ID. + +1. 进入Home Assistant页面内。左侧点击`开发者助手`。 +2. 在打开的`开发者助手`内,点击`动作`选项卡(如图示操作1),在页面内的选项栏`动作`中,找到或输入`conversation.process(对话-处理)`并选择`对话(conversation): 处理`(如图示操作2)。 + +![image-20250504043539343](images/image-ha-integration-02.png) + +3. 在页面内勾选`代理(agent)`选项,在变成常亮的`对话代理(conversation agent)`内选择您步骤一配置好的语音助手名称,如图示,我这边配置好的是`ZhipuAi`并选择。 + +![image-20250504043854760](images/image-ha-integration-03.png) + +4. 选中后,点击表单左下方的`进入YAML模式`。 + +![image-20250504043951126](images/image-ha-integration-04.png) + +5. 复制其中的agent-id的值,例如图示中我的是`01JP2DYMBDF7F4ZA2DMCF2AGX2`(仅供参考)。 + +![image-20250504044046466](images/image-ha-integration-05.png) + +6. 切换到小智开源服务端`xiaozhi-esp32-server`的`config.yaml`文件内,在LLM配置中,找到Home Assistant,设置您的Home Assistant的网络地址,Api key和刚刚查询到的agent_id。 +7. 修改`config.yaml`文件内的`selected_module`属性的`LLM`为`HomeAssistant`,`Intent`为`nointent`。 +8. 重启小智开源服务端`xiaozhi-esp32-server`即可正常使用。 + +## 方法3:使用Home Assistant的MCP服务(推荐) + +### 功能描述 + +- 需要您提前在Home Assistant内集成并安装好HA集成——[Model Context Protocol Server](https://www.home-assistant.io/integrations/mcp_server/)。 + +- 这个方法与方法2都是HA官方提供的解决方法,与方法2不同的是,您可以正常使用小智开源服务端`xiaozhi-esp32-server`的开源共建的插件,同时允许您随意使用任何一个支持function_call功能的LLM大模型。 + +### 配置步骤 + +#### 1. 安装Home Assistant的MCP服务集成。 + +集成官方网址——[Model Context Protocol Server](https://www.home-assistant.io/integrations/mcp_server/)。。 + +或跟随以下手动操作。 + +> - 前往Home Assistant页面的**[设置 > 设备和服务(Settings > Devices & Services.)](https://my.home-assistant.io/redirect/integrations)**。 +> +> - 在右下角,选择 **[添加集成(Add Integration)](https://my.home-assistant.io/redirect/config_flow_start?domain=mcp_server)**按钮。 +> +> - 从列表中选择**模型上下文协议服务器(Model Context Protocol Server)**。 +> +> - 按照屏幕上的说明完成设置。 + +#### 2. 配置小智开源服务端MCP配置信息 + +切换到小智开源服务端`xiaozhi-esp32-server`的`mcp_server_settings.json`文件内,在`"mcpServers"`的括号内添加以下内容: + +```json +"Home Assistant": { + "command": "mcp-proxy", + "args": [ + "http://YOUR_HA_HOST/mcp_server/sse" + ], + "env": { + "API_ACCESS_TOKEN": "YOUR_API_ACCESS_TOKEN" + } +}, +``` + +注意: + +1. **替换配置:** + - 替换`args`内的`YOUR_HA_HOST`为您的HA服务地址,如果你的服务地址已经包含了https/http字样(例如`http://192.168.1.101:8123`),则只需要填入`192.168.1.101:8123`即可。 + - 将`env`内`API_ACCESS_TOKEN`的`YOUR_API_ACCESS_TOKEN`替换成您之前获取到的开发密钥api key。 +2. **如果你添加配置是在`"mcpServers"`的括号内后续没有新的`mcpServers`的配置时,需要把最后的逗号`,`移除**,否则可能会解析失败。 + +**最后效果参考以下(参考如下)**: + +```json + "mcpServers": { + "Home Assistant": { + "command": "mcp-proxy", + "args": [ + "http://192.168.1.101:8123/mcp_server/sse" + ], + "env": { + "API_ACCESS_TOKEN": "abcd.efghi.jkl" + } + } + } +``` + +#### 3. 配置小智开源服务端的系统配置 + +1. **选择任意一款支持function_call的LLM大模型作为小智的LLM聊天助手(但不要选择Home Assistant作为LLM工具)**,本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用,但部分时候调用不太稳定,如果像追求稳定建议把LLM设置成:DoubaoLLM,使用的具体model_name是:doubao-1-5-pro-32k-250115。 + +2. 切换到小智开源服务端`xiaozhi-esp32-server`的`config.yaml`文件内,设置您的LLM大模型配置,并且将`selected_module`配置的`Intent`调整为`function_call`。 + +3. 重启小智开源服务端`xiaozhi-esp32-server`即可正常使用。 \ No newline at end of file diff --git a/docs/images/image-ha-integration-01.png b/docs/images/image-ha-integration-01.png new file mode 100644 index 00000000..eb05461a Binary files /dev/null and b/docs/images/image-ha-integration-01.png differ diff --git a/docs/images/image-ha-integration-02.png b/docs/images/image-ha-integration-02.png new file mode 100644 index 00000000..7431defa Binary files /dev/null and b/docs/images/image-ha-integration-02.png differ diff --git a/docs/images/image-ha-integration-03.png b/docs/images/image-ha-integration-03.png new file mode 100644 index 00000000..3f1aa596 Binary files /dev/null and b/docs/images/image-ha-integration-03.png differ diff --git a/docs/images/image-ha-integration-04.png b/docs/images/image-ha-integration-04.png new file mode 100644 index 00000000..29b402e5 Binary files /dev/null and b/docs/images/image-ha-integration-04.png differ diff --git a/docs/images/image-ha-integration-05.png b/docs/images/image-ha-integration-05.png new file mode 100644 index 00000000..1aa8ffc2 Binary files /dev/null and b/docs/images/image-ha-integration-05.png differ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java index a15df002..ee42c934 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java @@ -257,7 +257,8 @@ public class ConfigServiceImpl implements ConfigService { if (intentLLMModelId != null && intentLLMModelId.equals(llmModelId)) { intentLLMModelId = null; } - } else if ("function_call".equals(map.get("type"))) { + } + if (map.get("functions") != null) { String functionStr = (String) map.get("functions"); if (StringUtils.isNotBlank(functionStr)) { String[] functions = functionStr.split("\\;"); diff --git a/main/xiaozhi-server/app.py b/main/xiaozhi-server/app.py index 4ad57855..619fb5c7 100644 --- a/main/xiaozhi-server/app.py +++ b/main/xiaozhi-server/app.py @@ -7,6 +7,7 @@ from core.ota_server import SimpleOtaServer from core.utils.util import check_ffmpeg_installed from config.logger import setup_logging from core.utils.util import get_local_ip +from aioconsole import ainput TAG = __name__ logger = setup_logging() @@ -34,10 +35,19 @@ async def wait_for_exit() -> None: pass +async def monitor_stdin(): + """监控标准输入,消费回车键""" + while True: + await ainput() # 异步等待输入,消费回车 + + async def main(): check_ffmpeg_installed() config = load_config() + # 添加 stdin 监控任务 + stdin_task = asyncio.create_task(monitor_stdin()) + # 启动 WebSocket 服务器 ws_server = WebSocketServer(config) ws_task = asyncio.create_task(ws_server.start()) @@ -78,19 +88,22 @@ async def main(): ) try: - await wait_for_exit() # 监听退出信号 + await wait_for_exit() # 阻塞直到收到退出信号 except asyncio.CancelledError: print("任务被取消,清理资源中...") finally: + # 取消所有任务(关键修复点) + stdin_task.cancel() ws_task.cancel() if ota_task: ota_task.cancel() - try: - await ws_task - if ota_task: - await ota_task - except asyncio.CancelledError: - pass + + # 等待任务终止(必须加超时) + await asyncio.wait( + [stdin_task, ws_task, ota_task] if ota_task else [stdin_task, ws_task], + timeout=3.0, + return_when=asyncio.ALL_COMPLETED + ) print("服务器已关闭,程序退出。") diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 3b82dba0..b053db6a 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -389,6 +389,12 @@ LLM: model_name: deepseek-r1-distill-llama-8b@q4_k_m # 使用的模型名称,需要预先在社区下载 url: http://localhost:1234/v1 # LM Studio服务地址 api_key: lm-studio # LM Studio服务的固定API Key + HomeAssistant: + # 定义LLM API类型 + type: homeassistant + base_url: http://homeassistant.local:8123 + agent_id: conversation.chatgpt + api_key: 你的home assistant api访问令牌 FastgptLLM: # 定义LLM API类型 type: fastgpt diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 58e594e2..2a85c0ad 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -1,6 +1,8 @@ import os import copy import json +import subprocess +import sys import uuid import time import queue @@ -241,6 +243,44 @@ class ConnectionHandler: elif isinstance(message, bytes): await handleAudioMessage(self, message) + async def handle_restart(self, message): + """处理服务器重启请求""" + try: + + self.logger.bind(tag=TAG).info("收到服务器重启指令,准备执行...") + + # 发送确认响应 + await self.websocket.send(json.dumps({ + "type": "server_response", + "status": "success", + "message": "服务器重启中..." + })) + + # 异步执行重启操作 + def restart_server(): + """实际执行重启的方法""" + time.sleep(1) + self.logger.bind(tag=TAG).info("执行服务器重启...") + subprocess.Popen( + [sys.executable, "app.py"], + stdin=sys.stdin, + stdout=sys.stdout, + stderr=sys.stderr, + start_new_session=True + ) + os._exit(0) + + # 使用线程执行重启避免阻塞事件循环 + threading.Thread(target=restart_server, daemon=True).start() + + except Exception as e: + self.logger.bind(tag=TAG).error(f"重启失败: {str(e)}") + await self.websocket.send(json.dumps({ + "type": "server_response", + "status": "error", + "message": f"Restart failed: {str(e)}" + })) + def _initialize_components(self): """初始化组件""" if self.config.get("prompt") is not None: diff --git a/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/main/xiaozhi-server/core/handle/receiveAudioHandle.py index 4d000972..352d0656 100644 --- a/main/xiaozhi-server/core/handle/receiveAudioHandle.py +++ b/main/xiaozhi-server/core/handle/receiveAudioHandle.py @@ -16,7 +16,7 @@ async def handleAudioMessage(conn, audio): if not conn.asr_server_receive: conn.logger.bind(tag=TAG).debug(f"前期数据处理中,暂停接收") return - if conn.client_listen_mode == "auto": + if conn.client_listen_mode == "auto" or conn.client_listen_mode == "realtime": have_voice = conn.vad.is_vad(conn, audio) else: have_voice = conn.client_have_voice diff --git a/main/xiaozhi-server/core/handle/textHandle.py b/main/xiaozhi-server/core/handle/textHandle.py index 7ed36122..a3acd3f0 100644 --- a/main/xiaozhi-server/core/handle/textHandle.py +++ b/main/xiaozhi-server/core/handle/textHandle.py @@ -136,5 +136,8 @@ async def handleTextMessage(conn, message): } ) ) + # 重启服务器 + elif msg_json["action"] == "restart": + await conn.handle_restart(msg_json) except json.JSONDecodeError: await conn.websocket.send(message) diff --git a/main/xiaozhi-server/core/providers/llm/homeassistant/homeassistant.py b/main/xiaozhi-server/core/providers/llm/homeassistant/homeassistant.py new file mode 100644 index 00000000..6e53695f --- /dev/null +++ b/main/xiaozhi-server/core/providers/llm/homeassistant/homeassistant.py @@ -0,0 +1,72 @@ +import requests +from requests.exceptions import RequestException +from config.logger import setup_logging +from core.providers.llm.base import LLMProviderBase + +TAG = __name__ +logger = setup_logging() + + +class LLMProvider(LLMProviderBase): + def __init__(self, config): + self.agent_id = config.get("agent_id") # 对应 agent_id + self.api_key = config.get("api_key") + self.base_url = config.get("base_url", config.get("url")) # 默认使用 base_url + self.api_url = f"{self.base_url}/api/conversation/process" # 拼接完整的 API URL + + def response(self, session_id, dialogue): + try: + # home assistant语音助手自带意图,无需使用xiaozhi ai自带的,只需要把用户说的话传递给home assistant即可 + + # 提取最后一个 role 为 'user' 的 content + input_text = None + if isinstance(dialogue, list): # 确保 dialogue 是一个列表 + # 逆序遍历,找到最后一个 role 为 'user' 的消息 + for message in reversed(dialogue): + if message.get("role") == "user": # 找到 role 为 'user' 的消息 + input_text = message.get("content", "") + break # 找到后立即退出循环 + + # 构造请求数据 + payload = { + "text": input_text, + "agent_id": self.agent_id, + "conversation_id": session_id, # 使用 session_id 作为 conversation_id + } + # 设置请求头 + headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json", + } + + # 发起 POST 请求 + response = requests.post(self.api_url, json=payload, headers=headers) + + # 检查请求是否成功 + response.raise_for_status() + + # 解析返回数据 + data = response.json() + speech = ( + data.get("response", {}) + .get("speech", {}) + .get("plain", {}) + .get("speech", "") + ) + + # 返回生成的内容 + if speech: + yield speech + else: + logger.bind(tag=TAG).warning("API 返回数据中没有 speech 内容") + + except RequestException as e: + logger.bind(tag=TAG).error(f"HTTP 请求错误: {e}") + except Exception as e: + logger.bind(tag=TAG).error(f"生成响应时出错: {e}") + + def response_with_functions(self, session_id, dialogue, functions=None): + logger.bind(tag=TAG).info( + f"homeassistant不支持(function call),建议使用意图识别使用:nointent" + ) + return self.response(session_id, dialogue) diff --git a/main/xiaozhi-server/core/providers/tts/cozecn.py b/main/xiaozhi-server/core/providers/tts/cozecn.py index 1f9244f6..56314f4f 100644 --- a/main/xiaozhi-server/core/providers/tts/cozecn.py +++ b/main/xiaozhi-server/core/providers/tts/cozecn.py @@ -38,9 +38,13 @@ class TTSProvider(TTSProviderBase): "Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json", } - response = requests.request( - "POST", self.api_url, json=request_json, headers=headers - ) - data = response.content - file_to_save = open(output_file, "wb") - file_to_save.write(data) + + try: + response = requests.request( + "POST", self.api_url, json=request_json, headers=headers + ) + data = response.content + file_to_save = open(output_file, "wb") + file_to_save.write(data) + except Exception as e: + raise Exception(f"{__name__} error: {e}") \ No newline at end of file diff --git a/main/xiaozhi-server/core/providers/tts/custom.py b/main/xiaozhi-server/core/providers/tts/custom.py index 3417790f..5b8669da 100644 --- a/main/xiaozhi-server/core/providers/tts/custom.py +++ b/main/xiaozhi-server/core/providers/tts/custom.py @@ -32,4 +32,6 @@ class TTSProvider(TTSProviderBase): with open(output_file, "wb") as file: file.write(resp.content) else: - logger.bind(tag=TAG).error(f"Custom TTS请求失败: {resp.status_code} - {resp.text}") + error_msg = f"Custom TTS请求失败: {resp.status_code} - {resp.text}" + logger.bind(tag=TAG).error(error_msg) + raise Exception(error_msg) # 抛出异常,让调用方捕获 diff --git a/main/xiaozhi-server/core/providers/tts/edge.py b/main/xiaozhi-server/core/providers/tts/edge.py index b86e4087..3d9b2547 100644 --- a/main/xiaozhi-server/core/providers/tts/edge.py +++ b/main/xiaozhi-server/core/providers/tts/edge.py @@ -20,14 +20,18 @@ class TTSProvider(TTSProviderBase): ) async def text_to_speak(self, text, output_file): - communicate = edge_tts.Communicate(text, voice=self.voice) - # 确保目录存在并创建空文件 - os.makedirs(os.path.dirname(output_file), exist_ok=True) - with open(output_file, "wb") as f: - pass + try: + communicate = edge_tts.Communicate(text, voice=self.voice) + # 确保目录存在并创建空文件 + os.makedirs(os.path.dirname(output_file), exist_ok=True) + with open(output_file, "wb") as f: + pass - # 流式写入音频数据 - with open(output_file, "ab") as f: # 改为追加模式避免覆盖 - async for chunk in communicate.stream(): - if chunk["type"] == "audio": # 只处理音频数据块 - f.write(chunk["data"]) + # 流式写入音频数据 + with open(output_file, "ab") as f: # 改为追加模式避免覆盖 + async for chunk in communicate.stream(): + if chunk["type"] == "audio": # 只处理音频数据块 + f.write(chunk["data"]) + except Exception as e: + error_msg = f"Edge TTS请求失败: {e}" + raise Exception(error_msg) # 抛出异常,让调用方捕获 \ No newline at end of file diff --git a/main/xiaozhi-server/core/providers/tts/fishspeech.py b/main/xiaozhi-server/core/providers/tts/fishspeech.py index 316dbed7..627c6e99 100644 --- a/main/xiaozhi-server/core/providers/tts/fishspeech.py +++ b/main/xiaozhi-server/core/providers/tts/fishspeech.py @@ -177,5 +177,7 @@ class TTSProvider(TTSProviderBase): audio_file.write(audio_content) else: - print(f"Request failed with status code {response.status_code}") + error_msg = f"Request failed with status code {response.status_code}" + print(error_msg) print(response.json()) + raise Exception(error_msg) diff --git a/main/xiaozhi-server/core/providers/tts/gpt_sovits_v2.py b/main/xiaozhi-server/core/providers/tts/gpt_sovits_v2.py index b2aad88a..2d58679d 100644 --- a/main/xiaozhi-server/core/providers/tts/gpt_sovits_v2.py +++ b/main/xiaozhi-server/core/providers/tts/gpt_sovits_v2.py @@ -105,6 +105,6 @@ class TTSProvider(TTSProviderBase): with open(output_file, "wb") as file: file.write(resp.content) else: - logger.bind(tag=TAG).error( - f"GPT_SoVITS_V2 TTS请求失败: {resp.status_code} - {resp.text}" - ) + error_msg = f"GPT_SoVITS_V2 TTS请求失败: {resp.status_code} - {resp.text}" + logger.bind(tag=TAG).error(error_msg) + raise Exception(error_msg) diff --git a/main/xiaozhi-server/core/providers/tts/gpt_sovits_v3.py b/main/xiaozhi-server/core/providers/tts/gpt_sovits_v3.py index 488280f1..d4da23ed 100644 --- a/main/xiaozhi-server/core/providers/tts/gpt_sovits_v3.py +++ b/main/xiaozhi-server/core/providers/tts/gpt_sovits_v3.py @@ -64,6 +64,7 @@ class TTSProvider(TTSProviderBase): with open(output_file, "wb") as file: file.write(resp.content) else: - logger.bind(tag=TAG).error( - f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}" - ) + error_msg = f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}" + logger.bind(tag=TAG).error(error_msg) + raise Exception(error_msg) + diff --git a/main/xiaozhi-server/core/providers/tts/siliconflow.py b/main/xiaozhi-server/core/providers/tts/siliconflow.py index b2f564cc..9e30f721 100644 --- a/main/xiaozhi-server/core/providers/tts/siliconflow.py +++ b/main/xiaozhi-server/core/providers/tts/siliconflow.py @@ -39,9 +39,12 @@ class TTSProvider(TTSProviderBase): "Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json", } - response = requests.request( - "POST", self.api_url, json=request_json, headers=headers - ) - data = response.content - file_to_save = open(output_file, "wb") - file_to_save.write(data) + try: + response = requests.request( + "POST", self.api_url, json=request_json, headers=headers + ) + data = response.content + file_to_save = open(output_file, "wb") + file_to_save.write(data) + except Exception as e: + raise Exception(f"{__name__} error: {e}") \ No newline at end of file diff --git a/main/xiaozhi-server/core/providers/tts/ttson.py b/main/xiaozhi-server/core/providers/tts/ttson.py index 047d7a67..f3a0fbdc 100644 --- a/main/xiaozhi-server/core/providers/tts/ttson.py +++ b/main/xiaozhi-server/core/providers/tts/ttson.py @@ -58,8 +58,8 @@ class TTSProvider(TTSProviderBase): resp = requests.request("POST", url, data=payload) if resp.status_code != 200: - logger.bind(tag=TAG).error(f"TTS请求失败: {resp.text}") - return None + logger.bind(tag=TAG).error(f"TTSON 请求失败: {resp.text}") + raise Exception(f"{__name__}: TTS请求失败") resp_json = resp.json() try: result = ( @@ -71,13 +71,15 @@ class TTSProvider(TTSProviderBase): + "&voice_audio_path=" + resp_json["voice_path"] ) + + audio_content = requests.get(result) + with open(output_file, "wb") as f: + f.write(audio_content.content) + return True + voice_path = resp_json.get("voice_path") + des_path = output_file + shutil.move(voice_path, des_path) + except Exception as e: print("error:", e) - - audio_content = requests.get(result) - with open(output_file, "wb") as f: - f.write(audio_content.content) - return True - voice_path = resp_json.get("voice_path") - des_path = output_file - shutil.move(voice_path, des_path) + raise Exception(f"{__name__}: TTS请求失败") \ No newline at end of file diff --git a/main/xiaozhi-server/mcp_server_settings.json b/main/xiaozhi-server/mcp_server_settings.json index dc173f37..a1bddf06 100644 --- a/main/xiaozhi-server/mcp_server_settings.json +++ b/main/xiaozhi-server/mcp_server_settings.json @@ -7,6 +7,15 @@ "当前支持stdio/sse两种模式。" ], "mcpServers": { + "Home Assistant": { + "command": "mcp-proxy", + "args": [ + "http://YOUR_HA_HOST/mcp_server/sse" + ], + "env": { + "API_ACCESS_TOKEN": "YOUR_API_ACCESS_TOKEN" + } + }, "filesystem": { "command": "npx", "args": [ diff --git a/main/xiaozhi-server/requirements.txt b/main/xiaozhi-server/requirements.txt index 0a6b9b75..2b6f1dc9 100755 --- a/main/xiaozhi-server/requirements.txt +++ b/main/xiaozhi-server/requirements.txt @@ -28,4 +28,5 @@ PySocks==1.7.1 dashscope==1.23.1 baidu-aip==4.16.13 chardet==5.2.0 +aioconsole==0.8.1 markitdown==0.1.1 \ No newline at end of file