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 @@
+
- {{ extractContentFromString(message.content) }}
+
+
+
+
{{ item.text }}
+
{{ item.text }}
+
{{ item.text }}
+
+
+
+
+ {{ 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 }}
+
@@ -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