From 980930cd42d1fbe00f471b0bb76ad44167ca846c Mon Sep 17 00:00:00 2001 From: 3030332422 <3030332422@qq.com> Date: Mon, 16 Mar 2026 13:11:45 +0800 Subject: [PATCH 01/19] =?UTF-8?q?update=EF=BC=9A=E5=9C=A8=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=AB=AF=E6=98=BE=E7=A4=BA=E5=B7=A5=E5=85=B7=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/handle/sendAudioHandle.py | 10 ++++++++++ .../core/providers/tools/unified_tool_handler.py | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/main/xiaozhi-server/core/handle/sendAudioHandle.py b/main/xiaozhi-server/core/handle/sendAudioHandle.py index 63a2411c..20850ac2 100644 --- a/main/xiaozhi-server/core/handle/sendAudioHandle.py +++ b/main/xiaozhi-server/core/handle/sendAudioHandle.py @@ -316,3 +316,13 @@ async def send_stt_message(conn: "ConnectionHandler", text): await send_tts_message(conn, "start") # 发送start消息后客户端状态会处于说话中状态,同步服务端状态 conn.client_is_speaking = True + + +async def send_display_message(conn: "ConnectionHandler", text): + """发送纯显示消息""" + message = { + "type": "stt", + "text": text, + "session_id": conn.session_id + } + await conn.websocket.send(json.dumps(message)) diff --git a/main/xiaozhi-server/core/providers/tools/unified_tool_handler.py b/main/xiaozhi-server/core/providers/tools/unified_tool_handler.py index 6f289632..a5d4c3d8 100644 --- a/main/xiaozhi-server/core/providers/tools/unified_tool_handler.py +++ b/main/xiaozhi-server/core/providers/tools/unified_tool_handler.py @@ -13,6 +13,7 @@ from .server_mcp import ServerMCPExecutor from .device_iot import DeviceIoTExecutor from .device_mcp import DeviceMCPExecutor from .mcp_endpoint import MCPEndpointExecutor +from core.handle.sendAudioHandle import send_display_message class UnifiedToolHandler: @@ -167,6 +168,12 @@ class UnifiedToolHandler: self.logger.debug(f"调用函数: {function_name}, 参数: {arguments}") + # 发送工具调用显示消息到设备 + try: + await send_display_message(self.conn, f"% {function_name}") + except Exception as e: + self.logger.warning(f"发送工具调用显示消息失败: {e}") + # 执行工具调用 result = await self.tool_manager.execute_tool(function_name, arguments) return result From 0d2cb7b3c90d27753ec7e5a07a860ad46a9470d3 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Tue, 17 Mar 2026 16:13:35 +0800 Subject: [PATCH 02/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=EF=BC=8C?= =?UTF-8?q?=E6=9A=82=E6=97=B6=E5=8D=95=E5=B7=A5=E5=85=B7=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ChatHistoryDialog.vue | 48 +++++++++++++++- main/xiaozhi-server/core/connection.py | 57 ++++++++++++------- 2 files changed, 83 insertions(+), 22 deletions(-) diff --git a/main/manager-web/src/components/ChatHistoryDialog.vue b/main/manager-web/src/components/ChatHistoryDialog.vue index 12c06621..81a2dcd9 100644 --- a/main/manager-web/src/components/ChatHistoryDialog.vue +++ b/main/manager-web/src/components/ChatHistoryDialog.vue @@ -21,11 +21,22 @@
{{ message.content }}
-
+
- {{ extractContentFromString(message.content) }} + +
@@ -154,6 +165,13 @@ export default { // 尝试解析为 JSON try { const jsonObj = JSON.parse(content); + + // 如果是数组格式(包含 text 和 tool) + if (Array.isArray(jsonObj)) { + return jsonObj; + } + + // 如果是对象且有 content 字段 if (jsonObj && typeof jsonObj === 'object' && jsonObj.content) { return jsonObj.content; } @@ -442,6 +460,32 @@ export default { color: white; } +.content-wrapper { + width: 100%; +} + +.text-content { + display: block; + margin-bottom: 4px; +} + +.tool-call-text { + color: #1890ff; + font-family: 'Courier New', monospace; + font-weight: 500; + font-size: 12px; + display: block; + margin-top: 4px; +} + +.user-message .tool-call-text { + color: #e6f7ff; +} + +.tool-message .message-content { + background-color: #e6ebff; +} + .loading, .no-more { text-align: center; diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index c60c1a4a..3ff9865a 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -1073,38 +1073,55 @@ class ConnectionHandler: ) # 如需要大模型先处理一轮,添加相关处理后的日志情况 + content_parts = [] if len(response_message) > 0: text_buff = "".join(response_message) + content_parts.append({"type": "text", "text": text_buff}) self.tts_MessageText = text_buff self.dialogue.put(Message(role="assistant", content=text_buff)) response_message.clear() + tool_call_data = tool_calls_list[0] # 只取第一个工具 + + # 构建工具调用的显示文本 + try: + tool_input = json.loads(tool_call_data['arguments']) if tool_call_data['arguments'] else {} + except: + tool_input = {} + + # 格式化工具调用为简洁文本,如: get_weather({"location": "北京"}) + tool_text = f"{tool_call_data['name']}({json.dumps(tool_input, ensure_ascii=False)})" + content_parts.append({"type": "tool", "text": tool_text}) + + # 先上报包含文本和工具调用的内容(使用chatType=3表示工具调用) + tool_call_timestamp = int(time.time()) + report_content = json.dumps(content_parts, ensure_ascii=False) + self.report_queue.put((3, report_content, None, tool_call_timestamp)) + self.logger.bind(tag=TAG).debug( - f"检测到 {len(tool_calls_list)} 个工具调用" + f"function_name={tool_call_data['name']}, function_id={tool_call_data['id']}, function_arguments={tool_call_data['arguments']}" ) - # 收集所有工具调用的 Future - futures_with_data = [] - for tool_call_data in tool_calls_list: - self.logger.bind(tag=TAG).debug( - f"function_name={tool_call_data['name']}, function_id={tool_call_data['id']}, function_arguments={tool_call_data['arguments']}" - ) + # 执行单个工具 + future = asyncio.run_coroutine_threadsafe( + self.func_handler.handle_llm_function_call( + self, tool_call_data + ), + self.loop, + ) + result = future.result() + tool_results = [(result, tool_call_data)] - future = asyncio.run_coroutine_threadsafe( - self.func_handler.handle_llm_function_call( - self, tool_call_data - ), - self.loop, - ) - futures_with_data.append((future, tool_call_data)) + # 工具执行完成后,单独上报结果(时间戳+1确保在工具调用之后) + if result and result.result: + tool_result_text = str(result.result) - # 等待协程结束(实际等待时长为最慢的那个) - tool_results = [] - for future, tool_call_data in futures_with_data: - result = future.result() - tool_results.append((result, tool_call_data)) + # 格式化为 {"result": ...} + result_display = f'{{"result":"{tool_result_text}"}}' + result_content = json.dumps([{"type": "tool_result", "text": result_display}], ensure_ascii=False) + self.report_queue.put((3, result_content, None, tool_call_timestamp + 1)) - # 统一处理所有工具调用结果 + # 统一处理工具调用结果 if tool_results: self._handle_function_result(tool_results, depth=depth) From 1a5dd2d9d9c296a5a83a3d6741a001864700f47d Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Wed, 18 Mar 2026 16:40:32 +0800 Subject: [PATCH 03/19] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E9=9F=B3=E9=A2=91=E4=B8=8A=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 15 +++++++++++++-- main/xiaozhi-server/core/handle/reportHandle.py | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 3ff9865a..9808ba0b 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -1074,11 +1074,22 @@ class ConnectionHandler: # 如需要大模型先处理一轮,添加相关处理后的日志情况 content_parts = [] + tool_speak_opus_data = None if len(response_message) > 0: text_buff = "".join(response_message) content_parts.append({"type": "text", "text": text_buff}) self.tts_MessageText = text_buff self.dialogue.put(Message(role="assistant", content=text_buff)) + # 与主流程独立开来合成相关音频 + try: + future = self.executor.submit(self.tts.to_tts, text_buff) + audio_datas = future.result() + if audio_datas: + tool_speak_opus_data = audio_datas + for audio_data in audio_datas: + self.tts.tts_audio_queue.put((SentenceType.MIDDLE, audio_data, text_buff)) + except Exception as e: + self.logger.bind(tag=TAG).warning(f"工具调用前文本音频合成失败: {e}") response_message.clear() tool_call_data = tool_calls_list[0] # 只取第一个工具 @@ -1093,10 +1104,10 @@ class ConnectionHandler: tool_text = f"{tool_call_data['name']}({json.dumps(tool_input, ensure_ascii=False)})" content_parts.append({"type": "tool", "text": tool_text}) - # 先上报包含文本和工具调用的内容(使用chatType=3表示工具调用) + # 上报包含文本、音频和工具调用的内容(使用chatType=3表示工具调用) tool_call_timestamp = int(time.time()) report_content = json.dumps(content_parts, ensure_ascii=False) - self.report_queue.put((3, report_content, None, tool_call_timestamp)) + self.report_queue.put((3, report_content, tool_speak_opus_data, tool_call_timestamp)) self.logger.bind(tag=TAG).debug( f"function_name={tool_call_data['name']}, function_id={tool_call_data['id']}, function_arguments={tool_call_data['arguments']}" diff --git a/main/xiaozhi-server/core/handle/reportHandle.py b/main/xiaozhi-server/core/handle/reportHandle.py index b4f2d3f6..50617852 100644 --- a/main/xiaozhi-server/core/handle/reportHandle.py +++ b/main/xiaozhi-server/core/handle/reportHandle.py @@ -26,7 +26,7 @@ async def report(conn: "ConnectionHandler", type, text, opus_data, report_time): Args: conn: 连接对象 - type: 上报类型,1为用户,2为智能体 + type: 上报类型,1为用户,2为智能体,3为工具调用 text: 合成文本 opus_data: opus音频数据 report_time: 上报时间 From f40dc4a658b0e1447421b76cce6dc523cf4cc018 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Thu, 19 Mar 2026 11:56:29 +0800 Subject: [PATCH 04/19] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8F=8C=E9=87=8D?= =?UTF-8?q?=E5=9B=9E=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 36 ++++++++------------------ 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 9808ba0b..7a43da1d 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -1073,41 +1073,27 @@ class ConnectionHandler: ) # 如需要大模型先处理一轮,添加相关处理后的日志情况 - content_parts = [] - tool_speak_opus_data = None if len(response_message) > 0: text_buff = "".join(response_message) - content_parts.append({"type": "text", "text": text_buff}) self.tts_MessageText = text_buff self.dialogue.put(Message(role="assistant", content=text_buff)) - # 与主流程独立开来合成相关音频 - try: - future = self.executor.submit(self.tts.to_tts, text_buff) - audio_datas = future.result() - if audio_datas: - tool_speak_opus_data = audio_datas - for audio_data in audio_datas: - self.tts.tts_audio_queue.put((SentenceType.MIDDLE, audio_data, text_buff)) - except Exception as e: - self.logger.bind(tag=TAG).warning(f"工具调用前文本音频合成失败: {e}") response_message.clear() tool_call_data = tool_calls_list[0] # 只取第一个工具 - # 构建工具调用的显示文本 - try: - tool_input = json.loads(tool_call_data['arguments']) if tool_call_data['arguments'] else {} - except: - tool_input = {} + # 构建工具调用的显示文本,格式如: get_weather({"location": "北京"}) + tool_input = json.loads(tool_call_data.get("arguments") or "{}") + tool_text = json.dumps([ + { + "type": "tool", + "text": f"{tool_call_data['name']}({json.dumps(tool_input, ensure_ascii=False)})", + } + ] + ) - # 格式化工具调用为简洁文本,如: get_weather({"location": "北京"}) - tool_text = f"{tool_call_data['name']}({json.dumps(tool_input, ensure_ascii=False)})" - content_parts.append({"type": "tool", "text": tool_text}) - - # 上报包含文本、音频和工具调用的内容(使用chatType=3表示工具调用) + # 上报工具调用的内容(使用chatType=3表示工具调用) tool_call_timestamp = int(time.time()) - report_content = json.dumps(content_parts, ensure_ascii=False) - self.report_queue.put((3, report_content, tool_speak_opus_data, tool_call_timestamp)) + self.report_queue.put((3, tool_text, None, tool_call_timestamp)) self.logger.bind(tag=TAG).debug( f"function_name={tool_call_data['name']}, function_id={tool_call_data['id']}, function_arguments={tool_call_data['arguments']}" From 0ef3a60a4c80cda825ef39f847323e4dbb694c25 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Thu, 19 Mar 2026 11:57:40 +0800 Subject: [PATCH 05/19] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=BC=A9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 7a43da1d..113c1abd 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -1083,7 +1083,8 @@ class ConnectionHandler: # 构建工具调用的显示文本,格式如: get_weather({"location": "北京"}) tool_input = json.loads(tool_call_data.get("arguments") or "{}") - tool_text = json.dumps([ + tool_text = json.dumps( + [ { "type": "tool", "text": f"{tool_call_data['name']}({json.dumps(tool_input, ensure_ascii=False)})", From 9512fe642c38a01cc3e3000f57874b0312e54741 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Thu, 19 Mar 2026 16:04:14 +0800 Subject: [PATCH 06/19] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E5=A4=9A=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 66 +++++++++++++++----------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 113c1abd..d9c5e4b0 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -1063,6 +1063,10 @@ class ConnectionHandler: ) if not bHasError and len(tool_calls_list) > 0: + self.logger.bind(tag=TAG).debug( + f"检测到 {len(tool_calls_list)} 个工具调用" + ) + # 更新工具调用统计 if depth == 0: current_turn = len(self.dialogue.dialogue) // 2 @@ -1079,39 +1083,43 @@ class ConnectionHandler: self.dialogue.put(Message(role="assistant", content=text_buff)) response_message.clear() - tool_call_data = tool_calls_list[0] # 只取第一个工具 + # 收集所有工具调用的 Future + futures_with_data = [] + for tool_call_data in tool_calls_list: + self.logger.bind(tag=TAG).debug( + f"function_name={tool_call_data['name']}, function_id={tool_call_data['id']}, function_arguments={tool_call_data['arguments']}" + ) - # 构建工具调用的显示文本,格式如: get_weather({"location": "北京"}) - tool_input = json.loads(tool_call_data.get("arguments") or "{}") - tool_text = json.dumps( - [ - { - "type": "tool", - "text": f"{tool_call_data['name']}({json.dumps(tool_input, ensure_ascii=False)})", - } - ] - ) + # 构建工具调用的显示文本,格式如: get_weather({"location": "北京"}) + tool_input = json.loads(tool_call_data.get("arguments") or "{}") + tool_text = json.dumps( + [ + { + "type": "tool", + "text": f"{tool_call_data['name']}({json.dumps(tool_input, ensure_ascii=False)})", + } + ] + ) - # 上报工具调用的内容(使用chatType=3表示工具调用) - tool_call_timestamp = int(time.time()) - self.report_queue.put((3, tool_text, None, tool_call_timestamp)) + # 上报工具调用的内容(使用chatType=3表示工具调用) + tool_call_timestamp = int(time.time()) + self.report_queue.put((3, tool_text, None, tool_call_timestamp)) - self.logger.bind(tag=TAG).debug( - f"function_name={tool_call_data['name']}, function_id={tool_call_data['id']}, function_arguments={tool_call_data['arguments']}" - ) + future = asyncio.run_coroutine_threadsafe( + self.func_handler.handle_llm_function_call( + self, tool_call_data + ), + self.loop, + ) + futures_with_data.append((future, tool_call_data)) + + # 等待协程结束(实际等待时长为最慢的那个) + tool_results = [] + for future, tool_call_data in futures_with_data: + result = future.result() + tool_results.append((result, tool_call_data)) - # 执行单个工具 - future = asyncio.run_coroutine_threadsafe( - self.func_handler.handle_llm_function_call( - self, tool_call_data - ), - self.loop, - ) - result = future.result() - tool_results = [(result, tool_call_data)] - - # 工具执行完成后,单独上报结果(时间戳+1确保在工具调用之后) - if result and result.result: + # 工具执行完成后,单独上报结果(时间戳+1确保在工具调用之后) tool_result_text = str(result.result) # 格式化为 {"result": ...} From 3aecb99f70431652eed37fba4c0191ed75e19a66 Mon Sep 17 00:00:00 2001 From: zhuoqinglian <1035449612@qq.com> Date: Fri, 20 Mar 2026 16:21:58 +0800 Subject: [PATCH 07/19] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E8=AE=B0=E5=BD=95=E6=92=AD=E6=94=BE=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E9=87=8D=E5=8F=A0=E6=92=AD=E6=94=BE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ChatHistoryDialog.vue | 12 +++++--- main/manager-web/src/store/index.js | 1 - main/manager-web/src/utils/index.js | 30 +++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/main/manager-web/src/components/ChatHistoryDialog.vue b/main/manager-web/src/components/ChatHistoryDialog.vue index f44c4de7..1af1b151 100644 --- a/main/manager-web/src/components/ChatHistoryDialog.vue +++ b/main/manager-web/src/components/ChatHistoryDialog.vue @@ -49,6 +49,7 @@