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] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B7=A5=E5=85=B7=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=EF=BC=8C=E6=9A=82?= =?UTF-8?q?=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)