Compare commits

...
7 Commits
Author SHA1 Message Date
hrzandGitHub 4912f385c6 fix:智能体音色未随manager生效bug
* fix:连接manager后无法使用functioncallbug

* fix:意图识别使用llm无法播放音乐bug

* fix:manager第一图识别使用独立llm无法初始化llm的bug

* fix:智能体音色未随manager生效bug

* update:添加edgeTTS音色
2025-04-13 19:01:19 +08:00
hrzandGitHub de8c762d79 fix:manager意图识别bug (#762)
* fix:连接manager后无法使用functioncallbug

* fix:意图识别使用llm无法播放音乐bug

* fix:manager第一图识别使用独立llm无法初始化llm的bug
2025-04-13 18:10:17 +08:00
hrzandGitHub 056659304a Merge pull request #752 from xinnan-tech/web-api-model-fix
fix:修复了"模型配置"问题
2025-04-13 10:27:09 +08:00
hrzandGitHub 525bf18a03 Merge pull request #748 from xinnan-tech/web--vue-device-modify
Web  vue device modify
2025-04-13 10:26:40 +08:00
CGD e3928ffbcc fix:修复了"模型配置"问题 2025-04-12 22:53:41 +08:00
CGD 7c73f060ed update:添加了按钮样式 2025-04-12 09:28:19 +08:00
CGD f0aa7df3b0 update:完成“分页器”样式的替换 2025-04-12 08:27:03 +08:00
27 changed files with 469 additions and 188 deletions
@@ -44,7 +44,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
@Override @Override
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) { public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
IPage<AgentEntity> page = agentDao.selectPage( IPage<AgentEntity> page = agentDao.selectPage(
getPage(params, "sort", true), getPage(params, "agent_name", true),
new QueryWrapper<>()); new QueryWrapper<>());
return new PageData<>(page.getRecords(), page.getTotal()); return new PageData<>(page.getRecords(), page.getTotal());
} }
@@ -231,8 +231,9 @@ public class ConfigServiceImpl implements ConfigService {
boolean isCache) { boolean isCache) {
Map<String, String> selectedModule = new HashMap<>(); Map<String, String> selectedModule = new HashMap<>();
String[] modelTypes = { "VAD", "ASR", "LLM", "TTS", "Memory", "Intent" }; String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM" };
String[] modelIds = { vadModelId, asrModelId, llmModelId, ttsModelId, memModelId, intentModelId }; String[] modelIds = { vadModelId, asrModelId, ttsModelId, memModelId, intentModelId, llmModelId };
String intentLLMModelId = null;
for (int i = 0; i < modelIds.length; i++) { for (int i = 0; i < modelIds.length; i++) {
if (modelIds[i] == null) { if (modelIds[i] == null) {
@@ -246,10 +247,27 @@ public class ConfigServiceImpl implements ConfigService {
if ("TTS".equals(modelTypes[i]) && voice != null) { if ("TTS".equals(modelTypes[i]) && voice != null) {
((Map<String, Object>) model.getConfigJson()).put("private_voice", voice); ((Map<String, Object>) model.getConfigJson()).put("private_voice", voice);
} }
// 如果是Intent类型,且type=intent_llm,则给他添加附加模型
if ("Intent".equals(modelTypes[i])) {
Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
if ("intent_llm".equals(map.get("type"))) {
intentLLMModelId = (String) map.get("llm");
if (intentLLMModelId != null && intentLLMModelId.equals(llmModelId)) {
intentLLMModelId = null;
}
}
}
// 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型
if ("LLM".equals(modelTypes[i]) && intentLLMModelId != null) {
ModelConfigEntity intentLLM = modelConfigService.getModelById(intentLLMModelId, isCache);
typeConfig.put(intentLLM.getId(), intentLLM.getConfigJson());
}
} }
result.put(modelTypes[i], typeConfig); result.put(modelTypes[i], typeConfig);
selectedModule.put(modelTypes[i], model.getId()); selectedModule.put(modelTypes[i], model.getId());
} }
result.put("selected_module", selectedModule); result.put("selected_module", selectedModule);
if (StringUtils.isNotBlank(prompt)) { if (StringUtils.isNotBlank(prompt)) {
prompt = prompt.replace("{{assistant_name}}", "小智"); prompt = prompt.replace("{{assistant_name}}", "小智");
@@ -221,7 +221,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
params.put(Constant.PAGE, dto.getPage()); params.put(Constant.PAGE, dto.getPage());
params.put(Constant.LIMIT, dto.getLimit()); params.put(Constant.LIMIT, dto.getLimit());
IPage<DeviceEntity> page = baseDao.selectPage( IPage<DeviceEntity> page = baseDao.selectPage(
getPage(params, "sort", true), getPage(params, "mac_address", true),
// 定义查询条件 // 定义查询条件
new QueryWrapper<DeviceEntity>() new QueryWrapper<DeviceEntity>()
// 必须设备关键词查找 // 必须设备关键词查找
@@ -37,7 +37,7 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
@Override @Override
public PageData<SysParamsDTO> page(Map<String, Object> params) { public PageData<SysParamsDTO> page(Map<String, Object> params) {
IPage<SysParamsEntity> page = baseDao.selectPage( IPage<SysParamsEntity> page = baseDao.selectPage(
getPage(params, Constant.CREATE_DATE, false), getPage(params, null, false),
getWrapper(params)); getWrapper(params));
return getPageData(page, SysParamsDTO.class); return getPageData(page, SysParamsDTO.class);
@@ -47,7 +47,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
params.put(Constant.PAGE, dto.getPage()); params.put(Constant.PAGE, dto.getPage());
params.put(Constant.LIMIT, dto.getLimit()); params.put(Constant.LIMIT, dto.getLimit());
IPage<TimbreEntity> page = baseDao.selectPage( IPage<TimbreEntity> page = baseDao.selectPage(
getPage(params, "sort", true), getPage(params, null, true),
// 定义查询条件 // 定义查询条件
new QueryWrapper<TimbreEntity>() new QueryWrapper<TimbreEntity>()
// 必须按照ttsID查找 // 必须按照ttsID查找
@@ -0,0 +1,3 @@
-- 对0.3.0版本之前的参数进行修改
update `sys_params` set param_value = '.mp3;.wav;.p3' where param_code = 'plugins.play_music.music_ext';
update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\"}' where id = 'Intent_intent_llm';
@@ -0,0 +1,18 @@
-- 对0.3.0版本之前的参数进行修改
update `sys_params` set param_value = '.mp3;.wav;.p3' where param_code = 'plugins.play_music.music_ext';
update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\"}' where id = 'Intent_intent_llm';
-- 添加edge音色
delete from `ai_tts_voice` where tts_model_id = 'TTS_EdgeTTS';
INSERT INTO `ai_tts_voice` VALUES
('TTS_EdgeTTS0001', 'TTS_EdgeTTS', 'EdgeTTS女声-晓晓', 'zh-CN-XiaoxiaoNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0002', 'TTS_EdgeTTS', 'EdgeTTS男声-云扬', 'zh-CN-YunyangNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0003', 'TTS_EdgeTTS', 'EdgeTTS女声-晓伊', 'zh-CN-XiaoyiNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0004', 'TTS_EdgeTTS', 'EdgeTTS男声-云健', 'zh-CN-YunjianNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0005', 'TTS_EdgeTTS', 'EdgeTTS男声-云希', 'zh-CN-YunxiNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0006', 'TTS_EdgeTTS', 'EdgeTTS男声-云夏', 'zh-CN-YunxiaNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0007', 'TTS_EdgeTTS', 'EdgeTTS女声-辽宁小贝', 'zh-CN-liaoning-XiaobeiNeural', '辽宁', NULL, NULL, 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0008', 'TTS_EdgeTTS', 'EdgeTTS女声-陕西小妮', 'zh-CN-shaanxi-XiaoniNeural', '陕西', NULL, NULL, 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0009', 'TTS_EdgeTTS', 'EdgeTTS女声-香港海佳', 'zh-HK-HiuGaaiNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0010', 'TTS_EdgeTTS', 'EdgeTTS女声-香港海曼', 'zh-HK-HiuMaanNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL),
('TTS_EdgeTTS0011', 'TTS_EdgeTTS', 'EdgeTTS男声-香港万龙', 'zh-HK-WanLungNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL);
@@ -50,4 +50,11 @@ databaseChangeLog:
changes: changes:
- sqlFile: - sqlFile:
encoding: utf8 encoding: utf8
path: classpath:db/changelog/202504112058.sql path: classpath:db/changelog/202504112058.sql
- changeSet:
id: 202504131543
author: John
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202504131543.sql
+18 -15
View File
@@ -1,7 +1,6 @@
import { getServiceUrl } from '../api'; import { getServiceUrl } from '../api';
import RequestService from '../httpRequest'; import RequestService from '../httpRequest';
export default { export default {
// 获取模型配置列表 // 获取模型配置列表
getModelList(params, callback) { getModelList(params, callback) {
@@ -162,21 +161,25 @@ export default {
// 更新模型配置 // 更新模型配置
updateModel(params, callback) { updateModel(params, callback) {
const { modelType, provideCode, id, formData } = params; const { modelType, provideCode, id, formData } = params;
const payload = {
...formData,
configJson: formData.configJson
};
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`) .url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`)
.method('PUT') .method('PUT')
.data(formData) .data(payload)
.success((res) => { .success((res) => {
RequestService.clearRequestTime(); RequestService.clearRequestTime();
callback(res); callback(res);
}) })
.fail((err) => { .fail((err) => {
console.error('更新模型失败:', err); console.error('更新模型失败:', err);
this.$message.error(err.msg || '更新模型失败'); this.$message.error(err.msg || '更新模型失败');
RequestService.reAjaxFun(() => { RequestService.reAjaxFun(() => {
this.updateModel(params, callback); this.updateModel(params, callback);
}); });
}).send(); }).send();
}, },
// 设置默认模型 // 设置默认模型
setDefaultModel(id, callback) { setDefaultModel(id, callback) {
@@ -94,14 +94,6 @@
<script> <script>
import Api from '@/apis/api'; import Api from '@/apis/api';
const DEFAULT_CONFIG_JSON = {
type: "",
base_url: "",
model_name: "",
api_key: "",
empty: false
};
export default { export default {
name: "ModelEditDialog", name: "ModelEditDialog",
props: { props: {
@@ -132,7 +124,7 @@ export default {
docLink: "", docLink: "",
remark: "", remark: "",
sort: 0, sort: 0,
configJson: JSON.parse(JSON.stringify(DEFAULT_CONFIG_JSON)) configJson: {}
} }
}; };
}, },
@@ -183,8 +175,11 @@ export default {
docLink: "", docLink: "",
remark: "", remark: "",
sort: 0, sort: 0,
configJson: JSON.parse(JSON.stringify(DEFAULT_CONFIG_JSON)) configJson: {}
}; };
this.dynamicCallInfoFields.forEach(field => {
this.$set(this.form.configJson, field.prop, '');
});
}, },
resetProviders() { resetProviders() {
this.providers = []; this.providers = [];
@@ -266,6 +261,8 @@ export default {
this.dynamicCallInfoFields.forEach(field => { this.dynamicCallInfoFields.forEach(field => {
if (!configJson.hasOwnProperty(field.prop)) { if (!configJson.hasOwnProperty(field.prop)) {
configJson[field.prop] = ''; configJson[field.prop] = '';
} else if (typeof configJson[field.prop] !== 'string') {
configJson[field.prop] = String(configJson[field.prop]);
} }
}); });
@@ -280,7 +277,6 @@ export default {
remark: model.remark, remark: model.remark,
sort: Number(model.sort) || 0, sort: Number(model.sort) || 0,
configJson: { configJson: {
...DEFAULT_CONFIG_JSON,
...configJson ...configJson
} }
}; };
+158 -27
View File
@@ -4,10 +4,8 @@
<el-main style="padding: 20px; display: flex; flex-direction: column;"> <el-main style="padding: 20px; display: flex; flex-direction: column;">
<div class="table-container"> <div class="table-container">
<h3 class="device-list-title">设备列表</h3> <h3 class="device-list-title">设备列表</h3>
<el-button type="primary" class="add-device-btn" @click="handleAddDevice"> <el-table ref="deviceTable" :data="paginatedDeviceList" @selection-change="handleSelectionChange" style="width: 100%; margin-top: 20px" border stripe>
+ 添加设备 <el-table-column type="selection" align="center" width="60"></el-table-column>
</el-button>
<el-table :data="paginatedDeviceList" style="width: 100%; margin-top: 20px" border stripe>
<el-table-column label="设备型号" prop="model" flex></el-table-column> <el-table-column label="设备型号" prop="model" flex></el-table-column>
<el-table-column label="固件版本" prop="firmwareVersion" width="120"></el-table-column> <el-table-column label="固件版本" prop="firmwareVersion" width="120"></el-table-column>
<el-table-column label="Mac地址" prop="macAddress"></el-table-column> <el-table-column label="Mac地址" prop="macAddress"></el-table-column>
@@ -39,9 +37,31 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-pagination class="pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange" <div class="table_bottom">
:current-page="currentPage" :page-sizes="[5, 10, 20, 50]" :page-size="pageSize" <div class="ctrl_btn">
layout="total, sizes, prev, pager, next, jumper" :total="deviceList.length"></el-pagination> <el-button size="mini" type="primary" class="select-all-btn" @click="toggleAllSelection">
{{ isAllSelected ? '取消全选' : '全选' }}
</el-button>
<el-button type="primary" size="mini" class="add-device-btn" @click="handleAddDevice">
新增
</el-button>
</div>
<div class="custom-pagination">
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">首页</button>
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">上一页</button>
<button
v-for="page in visiblePages"
:key="page"
class="pagination-btn"
:class="{ active: page === currentPage }"
@click="goToPage(page)"
>
{{ page }}
</button>
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">下一页</button>
<span class="total-text">{{ deviceList.length }}条记录</span>
</div>
</div>
</div> </div>
<div class="copyright"> <div class="copyright">
©2025 xiaozhi-esp32-server ©2025 xiaozhi-esp32-server
@@ -62,6 +82,8 @@ export default {
data() { data() {
return { return {
addDeviceDialogVisible: false, addDeviceDialogVisible: false,
selectedDevices: [],
isAllSelected: false,
currentAgentId: this.$route.query.agentId || '', currentAgentId: this.$route.query.agentId || '',
currentPage: 1, currentPage: 1,
pageSize: 5, pageSize: 5,
@@ -75,7 +97,25 @@ export default {
const start = (this.currentPage - 1) * this.pageSize; const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize; const end = start + this.pageSize;
return this.deviceList.slice(start, end); return this.deviceList.slice(start, end);
} },
pageCount() {
return Math.ceil(this.deviceList.length / this.pageSize);
},
visiblePages() {
const pages = [];
const maxVisible = 3;
let start = Math.max(1, this.currentPage - 1);
let end = Math.min(this.pageCount, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
},
}, },
mounted() { mounted() {
const agentId = this.$route.query.agentId; const agentId = this.$route.query.agentId;
@@ -84,6 +124,16 @@ export default {
} }
}, },
methods: { methods: {
handleSelectionChange(val) {
this.selectedDevices = val;
this.isAllSelected = val.length === this.paginatedDeviceList.length;
},
toggleAllSelection() {
this.$refs.deviceTable.toggleAllSelection();
},
handleAddDevice() { handleAddDevice() {
this.addDeviceDialogVisible = true; this.addDeviceDialogVisible = true;
}, },
@@ -115,12 +165,19 @@ export default {
}); });
}); });
}, },
handleSizeChange(val) { goFirst() {
this.pageSize = val; this.currentPage = 1;
}, },
handleCurrentChange(val) { goPrev() {
this.currentPage = val; if (this.currentPage > 1) this.currentPage--;
}, },
goNext() {
if (this.currentPage < this.pageCount) this.currentPage++;
},
goToPage(page) {
this.currentPage = page;
},
fetchBindDevices(agentId) { fetchBindDevices(agentId) {
this.loading = true; this.loading = true;
Api.device.getAgentBindDevices(agentId, ({ data }) => { Api.device.getAgentBindDevices(agentId, ({ data }) => {
@@ -180,21 +237,18 @@ export default {
} }
.add-device-btn { .add-device-btn {
float: right; background: linear-gradient(135deg, #6b8cff, #a966ff) !important;
background: #409eff; border: none !important;
border: none; color: white !important;
border-radius: 10px; margin-left: 10px;
width: 105px;
height: 32px; height: 32px;
display: flex; padding: 7px 12px;
align-items: center; border-radius: 4px !important;
justify-content: center; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
font-size: 14px;
gap: 8px;
margin-bottom: 15px;
&:hover { &:hover {
background: #3a8ee6; transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
} }
} }
@@ -213,8 +267,85 @@ export default {
vertical-align: middle; vertical-align: middle;
} }
.pagination { .custom-pagination {
margin-top: 20px; display: flex;
text-align: right; align-items: center;
gap: 8px;
margin-top: 15px;
} }
.pagination-btn {
min-width: 28px;
height: 32px;
padding: 0 12px;
border-radius: 4px;
border: 1px solid #e4e7ed;
background: #dee7ff;
color: #606266;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.pagination-btn:first-child,
.pagination-btn:nth-child(2),
.pagination-btn:nth-last-child(2) {
min-width: 60px;
}
.pagination-btn:hover {
background: #d7dce6;
}
.pagination-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.pagination-btn.active {
background: #5f70f3 !important;
color: #ffffff !important;
border-color: #5f70f3 !important;
}
.total-text {
color: #909399;
font-size: 14px;
margin-left: 10px;
}
.table_bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
.ctrl_btn {
display: flex;
gap: 8px;
padding-left: 26px;
.el-button {
min-width: 72px;
height: 32px;
padding: 7px 12px;
border-radius: 4px;
border: none;
transition: all 0.3s;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
}
.el-button--primary {
background: #5f70f3;
color: white;
}
}
</style> </style>
+17 -10
View File
@@ -104,8 +104,6 @@ class ConnectionHandler:
self.close_after_chat = False # 是否在聊天结束后关闭连接 self.close_after_chat = False # 是否在聊天结束后关闭连接
self.use_function_call_mode = False self.use_function_call_mode = False
if self.config["selected_module"]["Intent"] == "function_call":
self.use_function_call_mode = True
async def handle_connection(self, ws): async def handle_connection(self, ws):
try: try:
@@ -222,37 +220,37 @@ class ConnectionHandler:
) )
if private_config.get("VAD", None) is not None: if private_config.get("VAD", None) is not None:
init_vad = True init_vad = True
self.config["vad"] = private_config["VAD"] self.config["VAD"] = private_config["VAD"]
self.config["selected_module"]["VAD"] = private_config["selected_module"][ self.config["selected_module"]["VAD"] = private_config["selected_module"][
"VAD" "VAD"
] ]
if private_config.get("ASR", None) is not None: if private_config.get("ASR", None) is not None:
init_asr = True init_asr = True
self.config["asr"] = private_config["ASR"] self.config["ASR"] = private_config["ASR"]
self.config["selected_module"]["ASR"] = private_config["selected_module"][ self.config["selected_module"]["ASR"] = private_config["selected_module"][
"ASR" "ASR"
] ]
if private_config.get("LLM", None) is not None: if private_config.get("LLM", None) is not None:
init_llm = True init_llm = True
self.config["llm"] = private_config["LLM"] self.config["LLM"] = private_config["LLM"]
self.config["selected_module"]["LLM"] = private_config["selected_module"][ self.config["selected_module"]["LLM"] = private_config["selected_module"][
"LLM" "LLM"
] ]
if private_config.get("TTS", None) is not None: if private_config.get("TTS", None) is not None:
init_tts = True init_tts = True
self.config["tts"] = private_config["TTS"] self.config["TTS"] = private_config["TTS"]
self.config["selected_module"]["TTS"] = private_config["selected_module"][ self.config["selected_module"]["TTS"] = private_config["selected_module"][
"TTS" "TTS"
] ]
if private_config.get("Memory", None) is not None: if private_config.get("Memory", None) is not None:
init_memory = True init_memory = True
self.config["memory"] = private_config["Memory"] self.config["Memory"] = private_config["Memory"]
self.config["selected_module"]["Memory"] = private_config[ self.config["selected_module"]["Memory"] = private_config[
"selected_module" "selected_module"
]["Memory"] ]["Memory"]
if private_config.get("Intent", None) is not None: if private_config.get("Intent", None) is not None:
init_intent = True init_intent = True
self.config["intent"] = private_config["Intent"] self.config["Intent"] = private_config["Intent"]
self.config["selected_module"]["Intent"] = private_config[ self.config["selected_module"]["Intent"] = private_config[
"selected_module" "selected_module"
]["Intent"] ]["Intent"]
@@ -287,17 +285,26 @@ class ConnectionHandler:
self.memory.init_memory(device_id, self.llm) self.memory.init_memory(device_id, self.llm)
def _initialize_intent(self): def _initialize_intent(self):
if (
self.config["Intent"][self.config["selected_module"]["Intent"]]["type"]
== "function_call"
):
self.use_function_call_mode = True
"""初始化意图识别模块""" """初始化意图识别模块"""
# 获取意图识别配置 # 获取意图识别配置
intent_config = self.config["Intent"] intent_config = self.config["Intent"]
intent_type = self.config["selected_module"]["Intent"] intent_type = self.config["Intent"][self.config["selected_module"]["Intent"]][
"type"
]
# 如果使用 nointent,直接返回 # 如果使用 nointent,直接返回
if intent_type == "nointent": if intent_type == "nointent":
return return
# 使用 intent_llm 模式 # 使用 intent_llm 模式
elif intent_type == "intent_llm": elif intent_type == "intent_llm":
intent_llm_name = intent_config["intent_llm"]["llm"] intent_llm_name = intent_config[self.config["selected_module"]["Intent"]][
"llm"
]
if intent_llm_name and intent_llm_name in self.config["LLM"]: if intent_llm_name and intent_llm_name in self.config["LLM"]:
# 如果配置了专用LLM,则创建独立的LLM实例 # 如果配置了专用LLM,则创建独立的LLM实例
@@ -57,7 +57,9 @@ class FunctionHandler:
def register_config_functions(self): def register_config_functions(self):
"""注册配置中的函数,可以不同客户端使用不同的配置""" """注册配置中的函数,可以不同客户端使用不同的配置"""
for func in self.config["Intent"]["function_call"].get("functions", []): for func in self.config["Intent"][self.config["selected_module"]["Intent"]].get(
"functions", []
):
self.function_registry.register_function(func) self.function_registry.register_function(func)
"""home assistant需要初始化提示词""" """home assistant需要初始化提示词"""
@@ -76,6 +76,11 @@ async def process_intent_result(conn, intent_result, original_text):
if function_name == "continue_chat": if function_name == "continue_chat":
return False return False
if function_name == "play_music":
funcItem = conn.func_handler.get_function(function_name)
if not funcItem:
conn.func_handler.function_registry.register_function("play_music")
function_args = None function_args = None
if "arguments" in intent_data["function_call"]: if "arguments" in intent_data["function_call"]:
function_args = intent_data["function_call"]["arguments"] function_args = intent_data["function_call"]["arguments"]
+1 -3
View File
@@ -269,14 +269,12 @@ def register_device_type(descriptor):
# 用于接受前端设备推送的搜索iot描述 # 用于接受前端设备推送的搜索iot描述
async def handleIotDescriptors(conn, descriptors): async def handleIotDescriptors(conn, descriptors):
if not conn.use_function_call_mode:
return
wait_max_time = 5 wait_max_time = 5
while conn.func_handler is None or not conn.func_handler.finish_init: while conn.func_handler is None or not conn.func_handler.finish_init:
await asyncio.sleep(1) await asyncio.sleep(1)
wait_max_time -= 1 wait_max_time -= 1
if wait_max_time <= 0: if wait_max_time <= 0:
logger.bind(tag=TAG).error("连接对象没有func_handler") logger.bind(tag=TAG).debug("连接对象没有func_handler")
return return
"""处理物联网描述""" """处理物联网描述"""
functions_changed = False functions_changed = False
@@ -15,7 +15,15 @@ class LLMProvider(LLMProviderBase):
self.base_url = config.get("base_url") self.base_url = config.get("base_url")
else: else:
self.base_url = config.get("url") self.base_url = config.get("url")
self.max_tokens = config.get("max_tokens", 500) max_tokens = config.get("max_tokens")
if max_tokens is None or max_tokens == "":
max_tokens = 500
try:
max_tokens = int(max_tokens)
except (ValueError, TypeError):
max_tokens = 500
self.max_tokens = max_tokens
check_model_key("LLM", self.api_key) check_model_key("LLM", self.api_key)
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url) self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
@@ -12,14 +12,20 @@ class TTSProvider(TTSProviderBase):
super().__init__(config, delete_audio_file) super().__init__(config, delete_audio_file)
self.model = config.get("model") self.model = config.get("model")
self.access_token = config.get("access_token") self.access_token = config.get("access_token")
self.voice = config.get("voice") if config.get("private_voice"):
self.voice = config.get("private_voice")
else:
self.voice = config.get("voice")
self.response_format = config.get("response_format") self.response_format = config.get("response_format")
self.host = "api.coze.cn" self.host = "api.coze.cn"
self.api_url = f"https://{self.host}/v1/audio/speech" self.api_url = f"https://{self.host}/v1/audio/speech"
def generate_filename(self, extension=".wav"): def generate_filename(self, extension=".wav"):
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}") return os.path.join(
self.output_file,
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
)
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
request_json = { request_json = {
@@ -30,9 +36,11 @@ class TTSProvider(TTSProviderBase):
} }
headers = { headers = {
"Authorization": f"Bearer {self.access_token}", "Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json" "Content-Type": "application/json",
} }
response = requests.request("POST", self.api_url, json=request_json, headers=headers) response = requests.request(
"POST", self.api_url, json=request_json, headers=headers
)
data = response.content data = response.content
file_to_save = open(output_file, "wb") file_to_save = open(output_file, "wb")
file_to_save.write(data) file_to_save.write(data)
@@ -18,7 +18,12 @@ class TTSProvider(TTSProviderBase):
self.appid = config.get("appid") self.appid = config.get("appid")
self.access_token = config.get("access_token") self.access_token = config.get("access_token")
self.cluster = config.get("cluster") self.cluster = config.get("cluster")
self.voice = config.get("voice")
if config.get("private_voice"):
self.voice = config.get("private_voice")
else:
self.voice = config.get("voice")
self.api_url = config.get("api_url") self.api_url = config.get("api_url")
self.authorization = config.get("authorization") self.authorization = config.get("authorization")
self.header = {"Authorization": f"{self.authorization}{self.access_token}"} self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
+11 -5
View File
@@ -8,20 +8,26 @@ from core.providers.tts.base import TTSProviderBase
class TTSProvider(TTSProviderBase): class TTSProvider(TTSProviderBase):
def __init__(self, config, delete_audio_file): def __init__(self, config, delete_audio_file):
super().__init__(config, delete_audio_file) super().__init__(config, delete_audio_file)
self.voice = config.get("voice") if config.get("private_voice"):
self.voice = config.get("private_voice")
else:
self.voice = config.get("voice")
def generate_filename(self, extension=".mp3"): def generate_filename(self, extension=".mp3"):
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}") return os.path.join(
self.output_file,
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
)
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
communicate = edge_tts.Communicate(text, voice=self.voice) communicate = edge_tts.Communicate(text, voice=self.voice)
# 确保目录存在并创建空文件 # 确保目录存在并创建空文件
os.makedirs(os.path.dirname(output_file), exist_ok=True) os.makedirs(os.path.dirname(output_file), exist_ok=True)
with open(output_file, 'wb') as f: with open(output_file, "wb") as f:
pass pass
# 流式写入音频数据 # 流式写入音频数据
with open(output_file, 'ab') as f: # 改为追加模式避免覆盖 with open(output_file, "ab") as f: # 改为追加模式避免覆盖
async for chunk in communicate.stream(): async for chunk in communicate.stream():
if chunk["type"] == "audio": # 只处理音频数据块 if chunk["type"] == "audio": # 只处理音频数据块
f.write(chunk["data"]) f.write(chunk["data"])
@@ -12,28 +12,33 @@ class TTSProvider(TTSProviderBase):
self.group_id = config.get("group_id") self.group_id = config.get("group_id")
self.api_key = config.get("api_key") self.api_key = config.get("api_key")
self.model = config.get("model") self.model = config.get("model")
self.voice_id = config.get("voice_id") if config.get("private_voice"):
self.voice_id = config.get("private_voice")
else:
self.voice_id = config.get("voice_id")
default_voice_setting = { default_voice_setting = {
"voice_id": "female-shaonv", "voice_id": "female-shaonv",
"speed": 1, "speed": 1,
"vol": 1, "vol": 1,
"pitch": 0, "pitch": 0,
"emotion": "happy" "emotion": "happy",
}
default_pronunciation_dict = {
"tone": [
"处理/(chu3)(li3)", "危险/dangerous"
]
} }
default_pronunciation_dict = {"tone": ["处理/(chu3)(li3)", "危险/dangerous"]}
defult_audio_setting = { defult_audio_setting = {
"sample_rate": 32000, "sample_rate": 32000,
"bitrate": 128000, "bitrate": 128000,
"format": "mp3", "format": "mp3",
"channel": 1 "channel": 1,
}
self.voice_setting = {
**default_voice_setting,
**config.get("voice_setting", {}),
}
self.pronunciation_dict = {
**default_pronunciation_dict,
**config.get("pronunciation_dict", {}),
} }
self.voice_setting = {**default_voice_setting, **config.get("voice_setting", {})}
self.pronunciation_dict = {**default_pronunciation_dict, **config.get("pronunciation_dict", {})}
self.audio_setting = {**defult_audio_setting, **config.get("audio_setting", {})} self.audio_setting = {**defult_audio_setting, **config.get("audio_setting", {})}
self.timber_weights = config.get("timber_weights", []) self.timber_weights = config.get("timber_weights", [])
@@ -44,11 +49,14 @@ class TTSProvider(TTSProviderBase):
self.api_url = f"https://{self.host}/v1/t2a_v2?GroupId={self.group_id}" self.api_url = f"https://{self.host}/v1/t2a_v2?GroupId={self.group_id}"
self.header = { self.header = {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}" "Authorization": f"Bearer {self.api_key}",
} }
def generate_filename(self, extension=".mp3"): def generate_filename(self, extension=".mp3"):
return os.path.join(self.output_file, f"tts-{__name__}{datetime.now().date()}@{uuid.uuid4().hex}{extension}") return os.path.join(
self.output_file,
f"tts-{__name__}{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
)
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
request_json = { request_json = {
@@ -65,13 +73,17 @@ class TTSProvider(TTSProviderBase):
request_json["voice_setting"]["voice_id"] = "" request_json["voice_setting"]["voice_id"] = ""
try: try:
resp = requests.post(self.api_url, json.dumps(request_json), headers=self.header) resp = requests.post(
self.api_url, json.dumps(request_json), headers=self.header
)
# 检查返回请求数据的status_code是否为0 # 检查返回请求数据的status_code是否为0
if resp.json()["base_resp"]["status_code"] == 0: if resp.json()["base_resp"]["status_code"] == 0:
data = resp.json()['data']['audio'] data = resp.json()["data"]["audio"]
file_to_save = open(output_file, "wb") file_to_save = open(output_file, "wb")
file_to_save.write(bytes.fromhex(data)) file_to_save.write(bytes.fromhex(data))
else: else:
raise Exception(f"{__name__} status_code: {resp.status_code} response: {resp.content}") raise Exception(
f"{__name__} status_code: {resp.status_code} response: {resp.content}"
)
except Exception as e: except Exception as e:
raise Exception(f"{__name__} error: {e}") raise Exception(f"{__name__} error: {e}")
@@ -16,7 +16,10 @@ class TTSProvider(TTSProviderBase):
self.api_key = config.get("api_key") self.api_key = config.get("api_key")
self.api_url = config.get("api_url", "https://api.openai.com/v1/audio/speech") self.api_url = config.get("api_url", "https://api.openai.com/v1/audio/speech")
self.model = config.get("model", "tts-1") self.model = config.get("model", "tts-1")
self.voice = config.get("voice", "alloy") if config.get("private_voice"):
self.voice = config.get("private_voice")
else:
self.voice = config.get("voice", "alloy")
self.response_format = "wav" self.response_format = "wav"
self.speed = config.get("speed", 1.0) self.speed = config.get("speed", 1.0)
self.output_file = config.get("output_dir", "tmp/") self.output_file = config.get("output_dir", "tmp/")
@@ -10,7 +10,10 @@ class TTSProvider(TTSProviderBase):
super().__init__(config, delete_audio_file) super().__init__(config, delete_audio_file)
self.model = config.get("model") self.model = config.get("model")
self.access_token = config.get("access_token") self.access_token = config.get("access_token")
self.voice = config.get("voice") if config.get("private_voice"):
self.voice = config.get("private_voice")
else:
self.voice = config.get("voice")
self.response_format = config.get("response_format") self.response_format = config.get("response_format")
self.sample_rate = config.get("sample_rate") self.sample_rate = config.get("sample_rate")
self.speed = config.get("speed") self.speed = config.get("speed")
@@ -20,7 +23,10 @@ class TTSProvider(TTSProviderBase):
self.api_url = f"https://{self.host}/v1/audio/speech" self.api_url = f"https://{self.host}/v1/audio/speech"
def generate_filename(self, extension=".wav"): def generate_filename(self, extension=".wav"):
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}") return os.path.join(
self.output_file,
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
)
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
request_json = { request_json = {
@@ -31,9 +37,11 @@ class TTSProvider(TTSProviderBase):
} }
headers = { headers = {
"Authorization": f"Bearer {self.access_token}", "Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json" "Content-Type": "application/json",
} }
response = requests.request("POST", self.api_url, json=request_json, headers=headers) response = requests.request(
"POST", self.api_url, json=request_json, headers=headers
)
data = response.content data = response.content
file_to_save = open(output_file, "wb") file_to_save = open(output_file, "wb")
file_to_save.write(data) file_to_save.write(data)
@@ -16,7 +16,10 @@ class TTSProvider(TTSProviderBase):
self.appid = config.get("appid") self.appid = config.get("appid")
self.secret_id = config.get("secret_id") self.secret_id = config.get("secret_id")
self.secret_key = config.get("secret_key") self.secret_key = config.get("secret_key")
self.voice = config.get("voice") if config.get("private_voice"):
self.voice = config.get("private_voice")
else:
self.voice = config.get("voice")
self.api_url = "https://tts.tencentcloudapi.com" # 正确的API端点 self.api_url = "https://tts.tencentcloudapi.com" # 正确的API端点
self.region = config.get("region") self.region = config.get("region")
self.output_file = config.get("output_dir") self.output_file = config.get("output_dir")
@@ -25,35 +28,36 @@ class TTSProvider(TTSProviderBase):
"""生成鉴权请求头""" """生成鉴权请求头"""
# 获取当前UTC时间戳 # 获取当前UTC时间戳
timestamp = int(time.time()) timestamp = int(time.time())
# 使用UTC时间计算日期 # 使用UTC时间计算日期
utc_date = datetime.fromtimestamp(timestamp, tz=timezone.utc).strftime('%Y-%m-%d') utc_date = datetime.fromtimestamp(timestamp, tz=timezone.utc).strftime(
"%Y-%m-%d"
)
# 服务名称必须是 "tts" # 服务名称必须是 "tts"
service = "tts" service = "tts"
# 拼接凭证范围 # 拼接凭证范围
credential_scope = f"{utc_date}/{service}/tc3_request" credential_scope = f"{utc_date}/{service}/tc3_request"
# 使用TC3-HMAC-SHA256签名方法 # 使用TC3-HMAC-SHA256签名方法
algorithm = "TC3-HMAC-SHA256" algorithm = "TC3-HMAC-SHA256"
# 构建规范请求字符串 # 构建规范请求字符串
http_request_method = "POST" http_request_method = "POST"
canonical_uri = "/" canonical_uri = "/"
canonical_querystring = "" canonical_querystring = ""
# 请求头必须包含host和content-type,且按字典序排列 # 请求头必须包含host和content-type,且按字典序排列
canonical_headers = ( canonical_headers = (
f"content-type:application/json\n" f"content-type:application/json\n" f"host:tts.tencentcloudapi.com\n"
f"host:tts.tencentcloudapi.com\n"
) )
signed_headers = "content-type;host" signed_headers = "content-type;host"
# 请求体哈希值 # 请求体哈希值
payload = json.dumps(request_body) payload = json.dumps(request_body)
payload_hash = hashlib.sha256(payload.encode('utf-8')).hexdigest() payload_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest()
# 构建规范请求字符串 # 构建规范请求字符串
canonical_request = ( canonical_request = (
f"{http_request_method}\n" f"{http_request_method}\n"
@@ -63,10 +67,12 @@ class TTSProvider(TTSProviderBase):
f"{signed_headers}\n" f"{signed_headers}\n"
f"{payload_hash}" f"{payload_hash}"
) )
# 计算规范请求的哈希值 # 计算规范请求的哈希值
hashed_canonical_request = hashlib.sha256(canonical_request.encode('utf-8')).hexdigest() hashed_canonical_request = hashlib.sha256(
canonical_request.encode("utf-8")
).hexdigest()
# 构建待签名字符串 # 构建待签名字符串
string_to_sign = ( string_to_sign = (
f"{algorithm}\n" f"{algorithm}\n"
@@ -74,19 +80,19 @@ class TTSProvider(TTSProviderBase):
f"{credential_scope}\n" f"{credential_scope}\n"
f"{hashed_canonical_request}" f"{hashed_canonical_request}"
) )
# 计算签名密钥 # 计算签名密钥
secret_date = self._hmac_sha256(f"TC3{self.secret_key}".encode('utf-8'), utc_date) secret_date = self._hmac_sha256(
f"TC3{self.secret_key}".encode("utf-8"), utc_date
)
secret_service = self._hmac_sha256(secret_date, service) secret_service = self._hmac_sha256(secret_date, service)
secret_signing = self._hmac_sha256(secret_service, "tc3_request") secret_signing = self._hmac_sha256(secret_service, "tc3_request")
# 计算签名 # 计算签名
signature = hmac.new( signature = hmac.new(
secret_signing, secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest() ).hexdigest()
# 构建授权头 # 构建授权头
authorization = ( authorization = (
f"{algorithm} " f"{algorithm} "
@@ -94,7 +100,7 @@ class TTSProvider(TTSProviderBase):
f"SignedHeaders={signed_headers}, " f"SignedHeaders={signed_headers}, "
f"Signature={signature}" f"Signature={signature}"
) )
# 构建请求头 # 构建请求头
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -104,19 +110,22 @@ class TTSProvider(TTSProviderBase):
"X-TC-Timestamp": str(timestamp), "X-TC-Timestamp": str(timestamp),
"X-TC-Version": "2019-08-23", "X-TC-Version": "2019-08-23",
"X-TC-Region": self.region, "X-TC-Region": self.region,
"X-TC-Language": "zh-CN" "X-TC-Language": "zh-CN",
} }
return headers return headers
def _hmac_sha256(self, key, msg): def _hmac_sha256(self, key, msg):
"""HMAC-SHA256加密""" """HMAC-SHA256加密"""
if isinstance(msg, str): if isinstance(msg, str):
msg = msg.encode('utf-8') msg = msg.encode("utf-8")
return hmac.new(key, msg, hashlib.sha256).digest() return hmac.new(key, msg, hashlib.sha256).digest()
def generate_filename(self, extension=".wav"): def generate_filename(self, extension=".wav"):
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}") return os.path.join(
self.output_file,
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
)
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
# 构建请求体 # 构建请求体
@@ -129,19 +138,23 @@ class TTSProvider(TTSProviderBase):
try: try:
# 获取请求头(每次请求都重新生成,以确保时间戳和签名是最新的) # 获取请求头(每次请求都重新生成,以确保时间戳和签名是最新的)
headers = self._get_auth_headers(request_json) headers = self._get_auth_headers(request_json)
# 发送请求 # 发送请求
resp = requests.post(self.api_url, json.dumps(request_json), headers=headers) resp = requests.post(
self.api_url, json.dumps(request_json), headers=headers
)
# 检查响应 # 检查响应
if resp.status_code == 200: if resp.status_code == 200:
response_data = resp.json() response_data = resp.json()
# 检查是否成功 # 检查是否成功
if response_data.get("Response", {}).get("Error") is not None: if response_data.get("Response", {}).get("Error") is not None:
error_info = response_data["Response"]["Error"] error_info = response_data["Response"]["Error"]
raise Exception(f"API返回错误: {error_info['Code']}: {error_info['Message']}") raise Exception(
f"API返回错误: {error_info['Code']}: {error_info['Message']}"
)
# 提取音频数据 # 提取音频数据
audio_data = response_data["Response"].get("Audio") audio_data = response_data["Response"].get("Audio")
if audio_data: if audio_data:
@@ -151,6 +164,8 @@ class TTSProvider(TTSProviderBase):
else: else:
raise Exception(f"{__name__}: 没有返回音频数据: {response_data}") raise Exception(f"{__name__}: 没有返回音频数据: {response_data}")
else: else:
raise Exception(f"{__name__} status_code: {resp.status_code} response: {resp.content}") raise Exception(
f"{__name__} status_code: {resp.status_code} response: {resp.content}"
)
except Exception as e: except Exception as e:
raise Exception(f"{__name__} error: {e}") raise Exception(f"{__name__} error: {e}")
+36 -22
View File
@@ -10,8 +10,14 @@ from core.providers.tts.base import TTSProviderBase
class TTSProvider(TTSProviderBase): class TTSProvider(TTSProviderBase):
def __init__(self, config, delete_audio_file): def __init__(self, config, delete_audio_file):
super().__init__(config, delete_audio_file) super().__init__(config, delete_audio_file)
self.url = config.get("url", "https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=") self.url = config.get(
self.voice_id = config.get("voice_id", 1695) "url",
"https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=",
)
if config.get("private_voice"):
self.voice_id = int(config.get("private_voice"))
else:
self.voice_id = int(config.get("voice_id", 1695))
self.token = config.get("token") self.token = config.get("token")
self.to_lang = config.get("to_lang") self.to_lang = config.get("to_lang")
self.volume_change_dB = config.get("volume_change_dB", 0) self.volume_change_dB = config.get("volume_change_dB", 0)
@@ -21,37 +27,45 @@ class TTSProvider(TTSProviderBase):
self.pitch_factor = config.get("pitch_factor", 0) self.pitch_factor = config.get("pitch_factor", 0)
self.format = config.get("format", "mp3") self.format = config.get("format", "mp3")
self.emotion = config.get("emotion", 1) self.emotion = config.get("emotion", 1)
self.header = { self.header = {"Content-Type": "application/json"}
"Content-Type": "application/json"
}
def generate_filename(self, extension=".mp3"): def generate_filename(self, extension=".mp3"):
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}") return os.path.join(
self.output_file,
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
)
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
url = f'{self.url}{self.token}' url = f"{self.url}{self.token}"
result = "firefly" result = "firefly"
payload = json.dumps({ payload = json.dumps(
"to_lang": self.to_lang, {
"text": text, "to_lang": self.to_lang,
"emotion": self.emotion, "text": text,
"format": self.format, "emotion": self.emotion,
"volume_change_dB": self.volume_change_dB, "format": self.format,
"voice_id": self.voice_id, "volume_change_dB": self.volume_change_dB,
"pitch_factor": self.pitch_factor, "voice_id": self.voice_id,
"speed_factor": self.speed_factor, "pitch_factor": self.pitch_factor,
"token": self.token "speed_factor": self.speed_factor,
}) "token": self.token,
}
)
resp = requests.request("POST", url, data=payload) resp = requests.request("POST", url, data=payload)
if resp.status_code != 200: if resp.status_code != 200:
return None return None
resp_json = resp.json() resp_json = resp.json()
try: try:
result = resp_json['url'] + ':' + str( result = (
resp_json[ resp_json["url"]
'port']) + '/flashsummary/retrieveFileData?stream=True&token=' + self.token + '&voice_audio_path=' + \ + ":"
resp_json['voice_path'] + str(resp_json["port"])
+ "/flashsummary/retrieveFileData?stream=True&token="
+ self.token
+ "&voice_audio_path="
+ resp_json["voice_path"]
)
except Exception as e: except Exception as e:
print("error:", e) print("error:", e)
@@ -42,7 +42,8 @@ class VADProvider(VADProviderBase):
audio_tensor = torch.from_numpy(audio_float32) audio_tensor = torch.from_numpy(audio_float32)
# 检测语音活动 # 检测语音活动
speech_prob = self.model(audio_tensor, 16000).item() with torch.no_grad():
speech_prob = self.model(audio_tensor, 16000).item()
client_have_voice = speech_prob >= self.vad_threshold client_have_voice = speech_prob >= self.vad_threshold
# 如果之前有声音,但本次没有声音,且与上次有声音的时间查已经超过了静默阈值,则认为已经说完一句话 # 如果之前有声音,但本次没有声音,且与上次有声音的时间查已经超过了静默阈值,则认为已经说完一句话
@@ -1,34 +1,43 @@
from plugins_func.register import register_function,ToolType, ActionResponse, Action from plugins_func.register import register_function, ToolType, ActionResponse, Action
from config.logger import setup_logging from config.logger import setup_logging
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
handle_exit_intent_function_desc = { handle_exit_intent_function_desc = {
"type": "function", "type": "function",
"function": { "function": {
"name": "handle_exit_intent", "name": "handle_exit_intent",
"description": "当用户想结束对话或需要退出系统时调用", "description": "当用户想结束对话或需要退出系统时调用",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {
"say_goodbye": { "say_goodbye": {
"type": "string", "type": "string",
"description": "和用户友好结束对话的告别语" "description": "和用户友好结束对话的告别语",
}
},
"required": ["say_goodbye"]
}
} }
} },
"required": ["say_goodbye"],
},
},
}
@register_function('handle_exit_intent', handle_exit_intent_function_desc, ToolType.SYSTEM_CTL)
def handle_exit_intent(conn, say_goodbye: str): @register_function(
"handle_exit_intent", handle_exit_intent_function_desc, ToolType.SYSTEM_CTL
)
def handle_exit_intent(conn, say_goodbye: str | None = None):
# 处理退出意图 # 处理退出意图
try: try:
if say_goodbye is None:
say_goodbye = "再见,祝您生活愉快!"
conn.close_after_chat = True conn.close_after_chat = True
logger.bind(tag=TAG).info(f"退出意图已处理:{say_goodbye}") logger.bind(tag=TAG).info(f"退出意图已处理:{say_goodbye}")
return ActionResponse(action=Action.RESPONSE, result="退出意图已处理", response=say_goodbye) return ActionResponse(
action=Action.RESPONSE, result="退出意图已处理", response=say_goodbye
)
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"处理退出意图错误: {e}") logger.bind(tag=TAG).error(f"处理退出意图错误: {e}")
return ActionResponse(action=Action.NONE, result="退出意图处理失败", response="") return ActionResponse(
action=Action.NONE, result="退出意图处理失败", response=""
)
@@ -9,7 +9,9 @@ HASS_CACHE = {}
def append_devices_to_prompt(conn): def append_devices_to_prompt(conn):
if conn.use_function_call_mode: if conn.use_function_call_mode:
funcs = conn.config["Intent"]["function_call"].get("functions", []) funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get(
"functions", []
)
if "hass_get_state" in funcs or "hass_set_state" in funcs: if "hass_get_state" in funcs or "hass_set_state" in funcs:
prompt = "下面是我家智能设备,可以通过homeassistant控制\n" prompt = "下面是我家智能设备,可以通过homeassistant控制\n"
devices = conn.config["plugins"]["home_assistant"].get("devices", []) devices = conn.config["plugins"]["home_assistant"].get("devices", [])
@@ -26,7 +28,9 @@ def initialize_hass_handler(conn):
global HASS_CACHE global HASS_CACHE
if HASS_CACHE == {}: if HASS_CACHE == {}:
if conn.use_function_call_mode: if conn.use_function_call_mode:
funcs = conn.config["Intent"]["function_call"].get("functions", []) funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get(
"functions", []
)
if "hass_get_state" in funcs or "hass_set_state" in funcs: if "hass_get_state" in funcs or "hass_set_state" in funcs:
HASS_CACHE["base_url"] = conn.config["plugins"]["home_assistant"].get( HASS_CACHE["base_url"] = conn.config["plugins"]["home_assistant"].get(
"base_url" "base_url"