- {{ 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/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 @@