mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 15:13:55 +08:00
Merge branch 'xinnan-tech:main' into custom_tts_api
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# 基于虾哥编译好的固件配置自定义服务器
|
||||
|
||||
## 第1步 确认版本
|
||||
烧录虾哥已经编译好的1.6.1版本固件
|
||||
烧录虾哥已经编译好的[1.6.1版本以上固件](https://github.com/78/xiaozhi-esp32/releases)
|
||||
|
||||
## 第2步 准备你的ota地址
|
||||
如果你按照教程使用的是全模块部署,就应该会有ota地址。
|
||||
@@ -29,6 +29,7 @@ wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||
|
||||
## 第3步 进入配网模式
|
||||
进入机器的配网模式,在页面顶部,点击“高级选项”,在里面输入你服务器的`ota`地址,点击保存。重启设备
|
||||

|
||||
|
||||
## 第4步 唤醒小智,查看日志输出
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 205 KiB |
@@ -200,4 +200,9 @@ public interface Constant {
|
||||
* 版本号
|
||||
*/
|
||||
public static final String VERSION = "0.4.3";
|
||||
|
||||
/**
|
||||
* 无效固件URL
|
||||
*/
|
||||
String INVALID_FIRMWARE_URL = "http://xiaozhi.server.com:8002/xiaozhi/otaMag/download/NOT_ACTIVATED_FIRMWARE_THIS_IS_A_INVALID_URL";
|
||||
}
|
||||
+2
-2
@@ -142,7 +142,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
if (deviceById == null) {
|
||||
DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware();
|
||||
firmware.setVersion(deviceReport.getApplication().getVersion());
|
||||
firmware.setUrl("http://xiaozhi.server.com:8002/xiaozhi/otaMag/download/NOT_ACTIVATED_FIRMWARE_THIS_IS_A_INVALID_URL");
|
||||
firmware.setUrl(Constant.INVALID_FIRMWARE_URL);
|
||||
response.setFirmware(firmware);
|
||||
} else {
|
||||
// 只有在设备已绑定且autoUpdate不为0的情况下才返回固件升级信息
|
||||
@@ -378,7 +378,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
}
|
||||
|
||||
firmware.setVersion(ota == null ? currentVersion : ota.getVersion());
|
||||
firmware.setUrl(downloadUrl == null ? "" : downloadUrl);
|
||||
firmware.setUrl(downloadUrl == null ? Constant.INVALID_FIRMWARE_URL : downloadUrl);
|
||||
return firmware;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<!-- 右侧元素 -->
|
||||
<div class="header-right">
|
||||
<div class="search-container" v-if="$route.path === '/home'">
|
||||
<div class="search-container" v-if="$route.path === '/home' && !(isSuperAdmin && isSmallScreen)">
|
||||
<el-input v-model="search" placeholder="输入名称搜索.." class="custom-search-input"
|
||||
@keyup.enter.native="handleSearch">
|
||||
<i slot="suffix" class="el-icon-search search-icon" @click="handleSearch"></i>
|
||||
@@ -103,7 +103,8 @@ export default {
|
||||
},
|
||||
isChangePasswordDialogVisible: false, // 控制修改密码弹窗的显示
|
||||
userDropdownVisible: false,
|
||||
paramDropdownVisible: false
|
||||
paramDropdownVisible: false,
|
||||
isSmallScreen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -113,7 +114,13 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchUserInfo()
|
||||
this.fetchUserInfo();
|
||||
this.checkScreenSize();
|
||||
window.addEventListener('resize', this.checkScreenSize);
|
||||
},
|
||||
//移除事件监听器
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.checkScreenSize);
|
||||
},
|
||||
methods: {
|
||||
goHome() {
|
||||
@@ -147,7 +154,9 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
checkScreenSize() {
|
||||
this.isSmallScreen = window.innerWidth <= 1386;
|
||||
},
|
||||
// 处理搜索
|
||||
handleSearch() {
|
||||
const searchValue = this.search.trim();
|
||||
@@ -354,45 +363,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.search-container {
|
||||
margin-right: 10px;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.header-left {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.search-container {
|
||||
max-width: 145px;
|
||||
}
|
||||
|
||||
.custom-search-input>>>.el-input__inner {
|
||||
padding-left: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.search-container {
|
||||
max-width: 120px;
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.equipment-management.more-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
@@ -411,13 +381,4 @@ export default {
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.equipment-management.more-dropdown .el-dropdown-menu {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
top: 60px;
|
||||
z-index: 2000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -231,7 +231,9 @@ class ConnectionHandler:
|
||||
# 创建新事件循环(避免与主循环冲突)
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(self.memory.save_memory(self.dialogue.dialogue))
|
||||
loop.run_until_complete(
|
||||
self.memory.save_memory(self.dialogue.dialogue)
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.bind(tag=TAG).error(f"保存记忆失败: {e}")
|
||||
finally:
|
||||
@@ -441,10 +443,10 @@ class ConnectionHandler:
|
||||
def _initialize_memory(self):
|
||||
"""初始化记忆模块"""
|
||||
self.memory.init_memory(
|
||||
self.device_id,
|
||||
self.llm,
|
||||
self.config["summaryMemory"],
|
||||
not self.read_config_from_api,
|
||||
role_id=self.device_id,
|
||||
llm=self.llm,
|
||||
summary_memory=self.config.get("summaryMemory", None),
|
||||
save_to_file=not self.read_config_from_api,
|
||||
)
|
||||
|
||||
def _initialize_intent(self):
|
||||
|
||||
@@ -215,7 +215,7 @@ class IntentProvider(IntentProviderBase):
|
||||
|
||||
# 记录识别到的function call
|
||||
logger.bind(tag=TAG).info(
|
||||
f"识别到function call: {function_name}, 参数: {function_args}"
|
||||
f"llm 识别到意图: {function_name}, 参数: {function_args}"
|
||||
)
|
||||
|
||||
# 添加到缓存
|
||||
|
||||
@@ -21,27 +21,67 @@ class LLMProvider(LLMProviderBase):
|
||||
api_key="ollama" # Ollama doesn't need an API key but OpenAI client requires one
|
||||
)
|
||||
|
||||
# 检查是否是qwen3模型
|
||||
self.is_qwen3 = self.model_name and self.model_name.lower().startswith("qwen3")
|
||||
|
||||
def response(self, session_id, dialogue):
|
||||
try:
|
||||
# 如果是qwen3模型,在用户最后一条消息中添加/no_think指令
|
||||
if self.is_qwen3:
|
||||
# 复制对话列表,避免修改原始对话
|
||||
dialogue_copy = dialogue.copy()
|
||||
|
||||
# 找到最后一条用户消息
|
||||
for i in range(len(dialogue_copy) - 1, -1, -1):
|
||||
if dialogue_copy[i]["role"] == "user":
|
||||
# 在用户消息前添加/no_think指令
|
||||
dialogue_copy[i]["content"] = "/no_think " + dialogue_copy[i]["content"]
|
||||
logger.bind(tag=TAG).debug(f"为qwen3模型添加/no_think指令")
|
||||
break
|
||||
|
||||
# 使用修改后的对话
|
||||
dialogue = dialogue_copy
|
||||
|
||||
responses = self.client.chat.completions.create(
|
||||
model=self.model_name,
|
||||
messages=dialogue,
|
||||
stream=True
|
||||
)
|
||||
is_active=True
|
||||
is_active = True
|
||||
# 用于处理跨chunk的标签
|
||||
buffer = ""
|
||||
|
||||
for chunk in responses:
|
||||
try:
|
||||
delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None
|
||||
content = delta.content if hasattr(delta, 'content') else ''
|
||||
|
||||
if content:
|
||||
if '<think>' in content:
|
||||
# 将内容添加到缓冲区
|
||||
buffer += content
|
||||
|
||||
# 处理缓冲区中的标签
|
||||
while '<think>' in buffer and '</think>' in buffer:
|
||||
# 找到完整的<think></think>标签并移除
|
||||
pre = buffer.split('<think>', 1)[0]
|
||||
post = buffer.split('</think>', 1)[1]
|
||||
buffer = pre + post
|
||||
|
||||
# 处理只有开始标签的情况
|
||||
if '<think>' in buffer:
|
||||
is_active = False
|
||||
content = content.split('<think>')[0]
|
||||
if '</think>' in content:
|
||||
buffer = buffer.split('<think>', 1)[0]
|
||||
|
||||
# 处理只有结束标签的情况
|
||||
if '</think>' in buffer:
|
||||
is_active = True
|
||||
content = content.split('</think>')[-1]
|
||||
if is_active:
|
||||
yield content
|
||||
buffer = buffer.split('</think>', 1)[1]
|
||||
|
||||
# 如果当前处于活动状态且缓冲区有内容,则输出
|
||||
if is_active and buffer:
|
||||
yield buffer
|
||||
buffer = "" # 清空缓冲区
|
||||
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Error processing chunk: {e}")
|
||||
|
||||
@@ -51,6 +91,22 @@ class LLMProvider(LLMProviderBase):
|
||||
|
||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||
try:
|
||||
# 如果是qwen3模型,在用户最后一条消息中添加/no_think指令
|
||||
if self.is_qwen3:
|
||||
# 复制对话列表,避免修改原始对话
|
||||
dialogue_copy = dialogue.copy()
|
||||
|
||||
# 找到最后一条用户消息
|
||||
for i in range(len(dialogue_copy) - 1, -1, -1):
|
||||
if dialogue_copy[i]["role"] == "user":
|
||||
# 在用户消息前添加/no_think指令
|
||||
dialogue_copy[i]["content"] = "/no_think " + dialogue_copy[i]["content"]
|
||||
logger.bind(tag=TAG).debug(f"为qwen3模型添加/no_think指令")
|
||||
break
|
||||
|
||||
# 使用修改后的对话
|
||||
dialogue = dialogue_copy
|
||||
|
||||
stream = self.client.chat.completions.create(
|
||||
model=self.model_name,
|
||||
messages=dialogue,
|
||||
@@ -58,8 +114,49 @@ class LLMProvider(LLMProviderBase):
|
||||
tools=functions,
|
||||
)
|
||||
|
||||
is_active = True
|
||||
buffer = ""
|
||||
|
||||
for chunk in stream:
|
||||
yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
|
||||
try:
|
||||
delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None
|
||||
content = delta.content if hasattr(delta, 'content') else None
|
||||
tool_calls = delta.tool_calls if hasattr(delta, 'tool_calls') else None
|
||||
|
||||
# 如果是工具调用,直接传递
|
||||
if tool_calls:
|
||||
yield None, tool_calls
|
||||
continue
|
||||
|
||||
# 处理文本内容
|
||||
if content:
|
||||
# 将内容添加到缓冲区
|
||||
buffer += content
|
||||
|
||||
# 处理缓冲区中的标签
|
||||
while '<think>' in buffer and '</think>' in buffer:
|
||||
# 找到完整的<think></think>标签并移除
|
||||
pre = buffer.split('<think>', 1)[0]
|
||||
post = buffer.split('</think>', 1)[1]
|
||||
buffer = pre + post
|
||||
|
||||
# 处理只有开始标签的情况
|
||||
if '<think>' in buffer:
|
||||
is_active = False
|
||||
buffer = buffer.split('<think>', 1)[0]
|
||||
|
||||
# 处理只有结束标签的情况
|
||||
if '</think>' in buffer:
|
||||
is_active = True
|
||||
buffer = buffer.split('</think>', 1)[1]
|
||||
|
||||
# 如果当前处于活动状态且缓冲区有内容,则输出
|
||||
if is_active and buffer:
|
||||
yield buffer, None
|
||||
buffer = "" # 清空缓冲区
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Error processing function chunk: {e}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}")
|
||||
|
||||
@@ -21,6 +21,6 @@ class MemoryProviderBase(ABC):
|
||||
"""Query memories for specific role based on similarity"""
|
||||
return "please implement query method"
|
||||
|
||||
def init_memory(self, role_id, llm, summary_memory=None):
|
||||
def init_memory(self, role_id, llm, **kwargs):
|
||||
self.role_id = role_id
|
||||
self.llm = llm
|
||||
|
||||
@@ -112,8 +112,10 @@ class MemoryProvider(MemoryProviderBase):
|
||||
self.memory_path = get_project_dir() + "data/.memory.yaml"
|
||||
self.load_memory(summary_memory)
|
||||
|
||||
def init_memory(self, role_id, llm, summary_memory=None, save_to_file=True):
|
||||
super().init_memory(role_id, llm)
|
||||
def init_memory(
|
||||
self, role_id, llm, summary_memory=None, save_to_file=True, **kwargs
|
||||
):
|
||||
super().init_memory(role_id, llm, **kwargs)
|
||||
self.save_to_file = save_to_file
|
||||
self.load_memory(summary_memory)
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ def initialize_modules(
|
||||
modules["memory"] = memory.create_instance(
|
||||
memory_type,
|
||||
config["Memory"][select_memory_module],
|
||||
config['summaryMemory'],
|
||||
config.get('summaryMemory', None),
|
||||
)
|
||||
logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}")
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ services:
|
||||
expose:
|
||||
- 6379
|
||||
container_name: xiaozhi-esp32-server-redis
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
|
||||
@@ -22,11 +22,12 @@ mem0ai==0.1.62
|
||||
bs4==0.0.2
|
||||
modelscope==1.23.2
|
||||
sherpa_onnx==1.11.0
|
||||
mcp==1.7.1
|
||||
mcp==1.8.1
|
||||
cnlunar==0.2.0
|
||||
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
|
||||
markitdown==0.1.1
|
||||
mcp-proxy==0.6.0
|
||||
|
||||
Reference in New Issue
Block a user