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/10] =?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/10] =?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/10] =?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/10] =?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/10] =?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/10] =?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 079c8630d954764c89537a1213036ecdf5d4ca82 Mon Sep 17 00:00:00 2001 From: 3030332422 <3030332422@qq.com> Date: Wed, 25 Mar 2026 10:43:45 +0800 Subject: [PATCH 07/10] =?UTF-8?q?update=EF=BC=9A=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=89=8D=E7=AB=AF=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ChatHistoryDialog.vue | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/main/manager-web/src/components/ChatHistoryDialog.vue b/main/manager-web/src/components/ChatHistoryDialog.vue index 81a2dcd9..9e8b742f 100644 --- a/main/manager-web/src/components/ChatHistoryDialog.vue +++ b/main/manager-web/src/components/ChatHistoryDialog.vue @@ -30,7 +30,20 @@
{{ item.text }}
{{ item.text }}
-
{{ item.text }}
+
+
+
+ {{ getFirstLineText(item.text) }} +
+
+ {{ item.text }} +
+ + + +
+
{{ item.text }}
+
@@ -92,7 +105,8 @@ export default { scrollTimer: null, isFirstLoad: true, playingAudioId: null, - audioElement: null + audioElement: null, + expandedToolResults: {} // 跟踪工具结果的展开状态 }; }, watch: { @@ -182,6 +196,23 @@ export default { // 如果不是 JSON 格式或没有 content 字段,直接返回原内容 return content; }, + // 切换工具结果的展开/折叠状态 + toggleToolResult(messageIndex, itemIndex) { + const key = `${messageIndex}-${itemIndex}`; + this.$set(this.expandedToolResults, key, !this.expandedToolResults[key]); + }, + // 判断工具结果是否处于折叠状态 + isToolResultCollapsed(messageIndex, itemIndex) { + const key = `${messageIndex}-${itemIndex}`; + // 默认折叠(true表示折叠) + return !this.expandedToolResults[key]; + }, + // 获取截断的文本(只显示第一行) + getFirstLineText(text) { + if (!text) return ''; + const firstLine = text.split('\n')[0]; + return firstLine.length < text.length ? firstLine + '...' : text; + }, resetData() { this.sessions = []; this.messages = []; @@ -191,6 +222,7 @@ export default { this.loading = false; this.hasMore = true; this.isFirstLoad = true; + this.expandedToolResults = {}; }, handleClose() { this.dialogVisible = false; @@ -483,7 +515,31 @@ export default { } .tool-message .message-content { - background-color: #e6ebff; + background-color: #f0f0f0; +} + +.tool-result-wrapper { + position: relative; + padding-right: 20px; +} + +.tool-result-collapsed { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tool-toggle-btn { + position: absolute; + right: 0; + top: 0; + cursor: pointer; + color: #1890ff; + font-size: 12px; +} + +.tool-toggle-btn:hover { + color: #40a9ff; } .loading, From f01de027cd0f94dc8d87b97567a05730a4790e6c Mon Sep 17 00:00:00 2001 From: 3030332422 <3030332422@qq.com> Date: Mon, 30 Mar 2026 16:36:30 +0800 Subject: [PATCH 08/10] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=20intent?= =?UTF-8?q?=5Fllm=20=E6=A8=A1=E5=BC=8F=E4=B8=8B=E5=B7=A5=E5=85=B7=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E4=B8=8D=E4=B8=8A=E6=8A=A5=E7=9A=84=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 | 32 ++++----------- .../core/handle/intentHandler.py | 15 +++++++ .../core/handle/reportHandle.py | 40 +++++++++++++++++++ .../xiaozhi-server/core/providers/asr/base.py | 4 +- 4 files changed, 65 insertions(+), 26 deletions(-) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index d9c5e4b0..c8e730c9 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -24,7 +24,7 @@ from core.utils.modules_initialize import ( initialize_tts, initialize_asr, ) -from core.handle.reportHandle import report +from core.handle.reportHandle import report, enqueue_tool_report from core.providers.tts.default import DefaultTTS from concurrent.futures import ThreadPoolExecutor from core.utils.dialogue import Message, Dialogue @@ -1090,20 +1090,9 @@ class ConnectionHandler: 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)})", - } - ] - ) - - # 上报工具调用的内容(使用chatType=3表示工具调用) - tool_call_timestamp = int(time.time()) - self.report_queue.put((3, tool_text, None, tool_call_timestamp)) + enqueue_tool_report(self, tool_call_data['name'], tool_input) future = asyncio.run_coroutine_threadsafe( self.func_handler.handle_llm_function_call( @@ -1111,21 +1100,16 @@ class ConnectionHandler: ), self.loop, ) - futures_with_data.append((future, tool_call_data)) - + futures_with_data.append((future, tool_call_data, tool_input)) + # 等待协程结束(实际等待时长为最慢的那个) tool_results = [] - for future, tool_call_data in futures_with_data: + for future, tool_call_data, tool_input in futures_with_data: result = future.result() tool_results.append((result, tool_call_data)) - # 工具执行完成后,单独上报结果(时间戳+1确保在工具调用之后) - tool_result_text = str(result.result) - - # 格式化为 {"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)) + # 使用公共方法上报工具调用结果 + enqueue_tool_report(self, tool_call_data['name'], tool_input, str(result.result) if result.result else None, report_tool_call=False) # 统一处理工具调用结果 if tool_results: diff --git a/main/xiaozhi-server/core/handle/intentHandler.py b/main/xiaozhi-server/core/handle/intentHandler.py index a9e3ee15..f092dfb2 100644 --- a/main/xiaozhi-server/core/handle/intentHandler.py +++ b/main/xiaozhi-server/core/handle/intentHandler.py @@ -10,6 +10,7 @@ from core.providers.tts.dto.dto import ContentType from core.handle.helloHandle import checkWakeupWords from plugins_func.register import Action, ActionResponse from core.handle.sendAudioHandle import send_stt_message +from core.handle.reportHandle import enqueue_tool_report from core.utils.util import remove_punctuation_and_length from core.providers.tts.dto.dto import TTSMessageDTO, SentenceType @@ -141,6 +142,17 @@ async def process_intent_result( await send_stt_message(conn, original_text) conn.client_abort = False + # 准备工具调用参数 + tool_input = {} + if function_args: + if isinstance(function_args, str): + tool_input = json.loads(function_args) if function_args else {} + elif isinstance(function_args, dict): + tool_input = function_args + + # 上报工具调用 + enqueue_tool_report(conn, function_name, tool_input) + # 使用executor执行函数调用和结果处理 def process_function_call(): conn.dialogue.put(Message(role="user", content=original_text)) @@ -159,7 +171,10 @@ async def process_intent_result( action=Action.ERROR, result=str(e), response=str(e) ) + # 上报工具调用结果 if result: + enqueue_tool_report(conn, function_name, tool_input, str(result.result) if result.result else None, report_tool_call=False) + if result.action == Action.RESPONSE: # 直接回复前端 text = result.response if text is not None: diff --git a/main/xiaozhi-server/core/handle/reportHandle.py b/main/xiaozhi-server/core/handle/reportHandle.py index 50617852..0f5be087 100644 --- a/main/xiaozhi-server/core/handle/reportHandle.py +++ b/main/xiaozhi-server/core/handle/reportHandle.py @@ -10,6 +10,7 @@ TTS上报功能已集成到ConnectionHandler类中。 """ import time +import json import opuslib_next from typing import TYPE_CHECKING @@ -132,6 +133,45 @@ def enqueue_tts_report(conn: "ConnectionHandler", text, opus_data): conn.logger.bind(tag=TAG).error(f"加入TTS上报队列失败: {text}, {e}") +def enqueue_tool_report(conn: "ConnectionHandler", tool_name: str, tool_input: dict, tool_result: str = None, report_tool_call: bool = True): + """将工具调用数据加入上报队列 + + Args: + conn: 连接对象 + tool_name: 工具名称 + tool_input: 工具输入参数 + tool_result: 工具执行结果(可选) + report_tool_call: 是否上报工具调用本身,默认True;仅上报结果时设为False + """ + if not conn.read_config_from_api or conn.need_bind: + return + if conn.chat_history_conf == 0: + return + + try: + timestamp = int(time.time()) + + # 构建工具调用内容 + if report_tool_call: + tool_text = json.dumps( + [ + { + "type": "tool", + "text": f"{tool_name}({json.dumps(tool_input, ensure_ascii=False)})", + } + ] + ) + conn.report_queue.put((3, tool_text, None, timestamp)) + + # 构建工具结果内容 + if tool_result: + result_display = f'{{"result":"{str(tool_result)}"}}' + result_content = json.dumps([{"type": "tool_result", "text": result_display}], ensure_ascii=False) + conn.report_queue.put((3, result_content, None, timestamp + 1)) + except Exception as e: + conn.logger.bind(tag=TAG).error(f"加入工具上报队列失败: {e}") + + def enqueue_asr_report(conn: "ConnectionHandler", text, opus_data): if not conn.read_config_from_api or conn.need_bind or not conn.report_asr_enable: return diff --git a/main/xiaozhi-server/core/providers/asr/base.py b/main/xiaozhi-server/core/providers/asr/base.py index 0b37c04d..986dd275 100644 --- a/main/xiaozhi-server/core/providers/asr/base.py +++ b/main/xiaozhi-server/core/providers/asr/base.py @@ -168,10 +168,10 @@ class ASRProviderBase(ABC): self.stop_ws_connection() if text_len > 0: - # 使用自定义模块进行上报 - await startToChat(conn, enhanced_text) audio_snapshot = asr_audio_task.copy() enqueue_asr_report(conn, enhanced_text, audio_snapshot) + # 使用自定义模块进行上报 + await startToChat(conn, enhanced_text) except Exception as e: logger.bind(tag=TAG).error(f"处理语音停止失败: {e}") import traceback From cc2e30205f82c25ebe84fc1ae55674016c9f81c3 Mon Sep 17 00:00:00 2001 From: 3030332422 <3030332422@qq.com> Date: Tue, 31 Mar 2026 10:03:50 +0800 Subject: [PATCH 09/10] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8Dplay=5Fmusi?= =?UTF-8?q?c=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/plugins_func/functions/play_music.py | 1 - 1 file changed, 1 deletion(-) diff --git a/main/xiaozhi-server/plugins_func/functions/play_music.py b/main/xiaozhi-server/plugins_func/functions/play_music.py index 835c3f50..1195ae54 100644 --- a/main/xiaozhi-server/plugins_func/functions/play_music.py +++ b/main/xiaozhi-server/plugins_func/functions/play_music.py @@ -217,7 +217,6 @@ async def play_local_music(conn: "ConnectionHandler", specific_file=None): conn.logger.bind(tag=TAG).error(f"选定的音乐文件不存在: {music_path}") return text = _get_random_play_prompt(selected_music) - await send_stt_message(conn, text) conn.dialogue.put(Message(role="assistant", content=text)) if conn.intent_type == "intent_llm": From 6f6c788cd5a71f4470cd7135a0c46a5e6f2da35a Mon Sep 17 00:00:00 2001 From: 3030332422 <3030332422@qq.com> Date: Tue, 31 Mar 2026 14:11:31 +0800 Subject: [PATCH 10/10] =?UTF-8?q?update=EF=BC=9A=E4=BF=AE=E6=94=B9ai=5Fage?= =?UTF-8?q?nt=5Fchat=5Fhistory=E8=A1=A8content=E5=AD=97=E6=AE=B5=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=B8=BATEXT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/db/changelog/202603311200.sql | 2 ++ .../main/resources/db/changelog/db.changelog-master.yaml | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 main/manager-api/src/main/resources/db/changelog/202603311200.sql diff --git a/main/manager-api/src/main/resources/db/changelog/202603311200.sql b/main/manager-api/src/main/resources/db/changelog/202603311200.sql new file mode 100644 index 00000000..0e34d0a1 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202603311200.sql @@ -0,0 +1,2 @@ +-- 修改聊天内容字段类型 +ALTER TABLE ai_agent_chat_history MODIFY COLUMN content TEXT COMMENT '聊天内容'; diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index efdf68d4..21105386 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -571,3 +571,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202603111131.sql + - changeSet: + id: 202603311200 + author: cgd + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202603311200.sql