mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
Merge branch 'main' into feature/add-fun-asr-auth
This commit is contained in:
@@ -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)`可以直接复制。
|
||||
>
|
||||
> 
|
||||
|
||||
或,您已经设置了直接可以访问的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灯”
|
||||
尝试和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)。
|
||||
|
||||

|
||||
|
||||
3. 在页面内勾选`代理(agent)`选项,在变成常亮的`对话代理(conversation agent)`内选择您步骤一配置好的语音助手名称,如图示,我这边配置好的是`ZhipuAi`并选择。
|
||||
|
||||

|
||||
|
||||
4. 选中后,点击表单左下方的`进入YAML模式`。
|
||||
|
||||

|
||||
|
||||
5. 复制其中的agent-id的值,例如图示中我的是`01JP2DYMBDF7F4ZA2DMCF2AGX2`(仅供参考)。
|
||||
|
||||

|
||||
|
||||
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`即可正常使用。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
+2
-1
@@ -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("\\;");
|
||||
|
||||
@@ -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("服务器已关闭,程序退出。")
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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}")
|
||||
@@ -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) # 抛出异常,让调用方捕获
|
||||
|
||||
@@ -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) # 抛出异常,让调用方捕获
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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}")
|
||||
@@ -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请求失败")
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user