diff --git a/mcp_server.py b/mcp_server.py
index 8c2838ce..773d405d 100644
--- a/mcp_server.py
+++ b/mcp_server.py
@@ -2361,6 +2361,36 @@ MEETING_MODEL_CONFIGS = {
},
}
+
+def clean_deepseek_tool_markers(content: str) -> str:
+ """
+ 清理 DeepSeek 模型输出中的工具调用标记
+ DeepSeek 有时会以文本形式输出工具调用,格式如:
+ <|tool▁calls▁begin|><|tool▁call▁begin|>tool_name<|tool▁sep|>{"args": "value"}<|tool▁call▁end|><|tool▁calls▁end|>
+ """
+ import re
+ if not content:
+ return content
+
+ # 清理 DeepSeek 工具调用标记
+ # 匹配 <|tool▁calls▁begin|> ... <|tool▁calls▁end|> 整个块
+ pattern = r'<|tool▁calls▁begin|>.*?<|tool▁calls▁end|>'
+ cleaned = re.sub(pattern, '', content, flags=re.DOTALL)
+
+ # 也清理可能残留的单个标记
+ markers = [
+ '<|tool▁calls▁begin|>',
+ '<|tool▁calls▁end|>',
+ '<|tool▁call▁begin|>',
+ '<|tool▁call▁end|>',
+ '<|tool▁sep|>',
+ ]
+ for marker in markers:
+ cleaned = cleaned.replace(marker, '')
+
+ return cleaned.strip()
+
+
# 每个角色可用的工具列表
ROLE_TOOLS = {
"buffett": ["search_china_news", "search_research_reports", "get_stock_basic_info", "get_stock_financial_index"],
@@ -2663,6 +2693,9 @@ async def stream_role_response(
"content": content
}
+ # 清理 DeepSeek 工具调用标记
+ full_content = clean_deepseek_tool_markers(full_content)
+
# 发送完成事件
yield {
"type": "content_done",
diff --git a/src/views/AgentChat/components/MeetingRoom/MeetingMessageBubble.js b/src/views/AgentChat/components/MeetingRoom/MeetingMessageBubble.js
index 19845976..4d4b22b3 100644
--- a/src/views/AgentChat/components/MeetingRoom/MeetingMessageBubble.js
+++ b/src/views/AgentChat/components/MeetingRoom/MeetingMessageBubble.js
@@ -38,6 +38,32 @@ import {
import { getRoleConfig, MEETING_ROLES } from '../../constants/meetingRoles';
import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts';
+/**
+ * 清理 DeepSeek 模型输出中的工具调用标记
+ * DeepSeek 有时会以文本形式输出工具调用,格式如:
+ * <|tool▁calls▁begin|><|tool▁call▁begin|>tool_name<|tool▁sep|>{"args": "value"}<|tool▁call▁end|><|tool▁calls▁end|>
+ */
+const cleanDeepseekToolMarkers = (content) => {
+ if (!content) return content;
+
+ // 清理 DeepSeek 工具调用标记(匹配整个块)
+ let cleaned = content.replace(/<|tool▁calls▁begin|>[\s\S]*?<|tool▁calls▁end|>/g, '');
+
+ // 清理可能残留的单个标记
+ const markers = [
+ '<|tool▁calls▁begin|>',
+ '<|tool▁calls▁end|>',
+ '<|tool▁call▁begin|>',
+ '<|tool▁call▁end|>',
+ '<|tool▁sep|>',
+ ];
+ markers.forEach((marker) => {
+ cleaned = cleaned.split(marker).join('');
+ });
+
+ return cleaned.trim();
+};
+
/**
* 解析 deepmoney 格式的内容
* 格式: 思考过程回答内容
@@ -48,10 +74,13 @@ import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts';
const parseDeepmoneyContent = (content) => {
if (!content) return { thinking: null, answer: '' };
+ // 先清理 DeepSeek 工具调用标记
+ const cleanedContent = cleanDeepseekToolMarkers(content);
+
// 匹配 ... 标签
- const thinkMatch = content.match(/([\s\S]*?)<\/think>/i);
+ const thinkMatch = cleanedContent.match(/([\s\S]*?)<\/think>/i);
// 匹配 ... 标签
- const answerMatch = content.match(/([\s\S]*?)<\/answer>/i);
+ const answerMatch = cleanedContent.match(/([\s\S]*?)<\/answer>/i);
// 如果有 answer 标签,提取内容
if (answerMatch) {
@@ -64,7 +93,7 @@ const parseDeepmoneyContent = (content) => {
// 如果只有 think 标签但没有 answer 标签,可能正在流式输出中
if (thinkMatch && !answerMatch) {
// 检查 think 后面是否有其他内容
- const afterThink = content.replace(/[\s\S]*?<\/think>/i, '').trim();
+ const afterThink = cleanedContent.replace(/[\s\S]*?<\/think>/i, '').trim();
// 如果 think 后面有内容但不是 answer 标签包裹的,可能是部分输出
if (afterThink && !afterThink.startsWith('')) {
return {
@@ -78,10 +107,10 @@ const parseDeepmoneyContent = (content) => {
};
}
- // 如果没有特殊标签,返回原内容
+ // 如果没有特殊标签,返回清理后的内容
return {
thinking: null,
- answer: content,
+ answer: cleanedContent,
};
};