From 4313a8cc1ae1a3dfd7c438a9a72bd0aa1367358e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=B7=E5=B0=8F=E5=89=8D?= Date: Sun, 18 Jan 2026 09:10:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0ios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MeAgent/ios/app.xcodeproj/project.pbxproj | 10 +- MeAgent/navigation/Screens.js | 2 + .../Agent/components/MarkdownRenderer.js | 144 +++++++++++++++--- .../screens/Agent/components/WelcomeScreen.js | 32 ---- MeAgent/src/screens/Concepts/ConceptList.js | 5 +- MeAgent/src/screens/Events/EventList.js | 43 ++++++ MeAgent/src/screens/Market/MarketHot.js | 40 ++--- MeAgent/src/screens/Market/TodayStats.js | 96 ++++++------ MeAgent/src/services/api.js | 26 +++- MeAgent/src/services/authService.js | 22 +++ MeAgent/src/store/slices/eventsSlice.js | 2 +- mcp_server.py | 75 ++++++++- 12 files changed, 362 insertions(+), 135 deletions(-) diff --git a/MeAgent/ios/app.xcodeproj/project.pbxproj b/MeAgent/ios/app.xcodeproj/project.pbxproj index 3322cf0e..59461e5a 100644 --- a/MeAgent/ios/app.xcodeproj/project.pbxproj +++ b/MeAgent/ios/app.xcodeproj/project.pbxproj @@ -460,7 +460,10 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -518,7 +521,10 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/MeAgent/navigation/Screens.js b/MeAgent/navigation/Screens.js index 31d0f6d5..a3be405f 100644 --- a/MeAgent/navigation/Screens.js +++ b/MeAgent/navigation/Screens.js @@ -323,6 +323,8 @@ function MarketStack(props) {
diff --git a/MeAgent/src/screens/Agent/components/MarkdownRenderer.js b/MeAgent/src/screens/Agent/components/MarkdownRenderer.js index 1a2a3d63..53de6e8a 100644 --- a/MeAgent/src/screens/Agent/components/MarkdownRenderer.js +++ b/MeAgent/src/screens/Agent/components/MarkdownRenderer.js @@ -3,7 +3,7 @@ * 支持 Markdown 渲染和 ECharts 图表渲染 */ -import React, { memo, useMemo, useState, useCallback } from 'react'; +import React, { memo, useMemo, useState, useCallback, useContext, createContext } from 'react'; import { View, Text, @@ -14,8 +14,12 @@ import { ActivityIndicator, } from 'react-native'; import { WebView } from 'react-native-webview'; +import { useNavigation } from '@react-navigation/native'; import { AgentTheme } from '../../../constants/agentConstants'; +// 股票代码正则:匹配 000001.SZ, 600000.SH, 或纯六位数字 +const STOCK_CODE_REGEX = /\b(\d{6})(?:\.(SZ|SH|sz|sh|BJ|bj))?\b/g; + const { width: SCREEN_WIDTH } = Dimensions.get('window'); const CHART_WIDTH = SCREEN_WIDTH - 48; // 减去边距 const CHART_HEIGHT = 300; @@ -137,7 +141,7 @@ const TableView = memo(({ rows }) => { /** * Markdown 文本渲染器(支持表格) */ -const MarkdownText = memo(({ content }) => { +const MarkdownText = memo(({ content, navigation }) => { const lines = content.split('\n'); const elements = []; let i = 0; @@ -155,7 +159,7 @@ const MarkdownText = memo(({ content }) => { } } - elements.push(); + elements.push(); i++; } @@ -169,7 +173,7 @@ const MarkdownText = memo(({ content }) => { /** * 单行 Markdown 渲染 */ -const MarkdownLine = memo(({ line }) => { +const MarkdownLine = memo(({ line, navigation }) => { // 空行 if (!line.trim()) { return ; @@ -188,7 +192,7 @@ const MarkdownLine = memo(({ line }) => { ]; return ( - {renderInlineStyles(text)} + {renderInlineStyles(text, navigation)} ); } @@ -199,7 +203,7 @@ const MarkdownLine = memo(({ line }) => { return ( - {renderInlineStyles(listMatch[1])} + {renderInlineStyles(listMatch[1], navigation)} ); } @@ -210,7 +214,7 @@ const MarkdownLine = memo(({ line }) => { return ( {orderedListMatch[1]}. - {renderInlineStyles(orderedListMatch[2])} + {renderInlineStyles(orderedListMatch[2], navigation)} ); } @@ -225,7 +229,7 @@ const MarkdownLine = memo(({ line }) => { if (quoteMatch) { return ( - {renderInlineStyles(quoteMatch[1])} + {renderInlineStyles(quoteMatch[1], navigation)} ); } @@ -237,32 +241,113 @@ const MarkdownLine = memo(({ line }) => { // 普通段落 return ( - {renderInlineStyles(line)} + {renderInlineStyles(line, navigation)} ); }); /** - * 渲染行内样式(粗体、斜体、代码、链接) + * 股票代码可点击组件 */ -const renderInlineStyles = (text) => { +const StockCodeLink = memo(({ code, exchange, navigation }) => { + // 确定交易所后缀 + let exchangeSuffix = exchange?.toUpperCase() || ''; + if (!exchangeSuffix) { + // 根据代码前缀推断交易所 + if (code.startsWith('6')) { + exchangeSuffix = 'SH'; + } else if (code.startsWith('0') || code.startsWith('3')) { + exchangeSuffix = 'SZ'; + } else if (code.startsWith('4') || code.startsWith('8')) { + exchangeSuffix = 'BJ'; + } + } + + const handlePress = useCallback(() => { + const stockCode = exchangeSuffix ? `${code}.${exchangeSuffix}` : code; + navigation?.navigate('StockDetail', { + code: stockCode, + name: stockCode, // 名称可以在详情页获取 + }); + }, [code, exchangeSuffix, navigation]); + + const displayCode = exchangeSuffix ? `${code}.${exchangeSuffix}` : code; + + return ( + + {displayCode} + + ); +}); + +/** + * 渲染行内样式(粗体、斜体、代码、链接、股票代码) + */ +const renderInlineStyles = (text, navigation) => { if (!text) return null; + // 先进行基本文本处理 + const processedText = text + .replace(/\*\*(.+?)\*\*/g, (_, m) => m) // 移除粗体标记(简化) + .replace(/\*(.+?)\*/g, (_, m) => m) // 移除斜体标记 + .replace(/`([^`]+)`/g, (_, m) => `[${m}]`); // 标记代码 + + // 检测股票代码并分割文本 + const stockMatches = []; + let match; + const regex = new RegExp(STOCK_CODE_REGEX.source, 'g'); + while ((match = regex.exec(processedText)) !== null) { + stockMatches.push({ + code: match[1], + exchange: match[2], + index: match.index, + length: match[0].length, + fullMatch: match[0], + }); + } + + // 如果没有股票代码,返回普通文本 + if (stockMatches.length === 0) { + return {processedText}; + } + + // 有股票代码,需要分割并渲染 const elements = []; + let lastIndex = 0; let key = 0; - // 为简化实现,这里只做基本渲染 - // 完整实现需要递归解析 - elements.push( - - {text - .replace(/\*\*(.+?)\*\*/g, (_, m) => m) // 移除粗体标记(简化) - .replace(/\*(.+?)\*/g, (_, m) => m) // 移除斜体标记 - .replace(/`([^`]+)`/g, (_, m) => `[${m}]`) // 标记代码 - } - - ); + stockMatches.forEach((stockMatch) => { + // 添加股票代码前的文本 + if (stockMatch.index > lastIndex) { + elements.push( + + {processedText.substring(lastIndex, stockMatch.index)} + + ); + } - return elements; + // 添加可点击的股票代码 + elements.push( + + ); + + lastIndex = stockMatch.index + stockMatch.length; + }); + + // 添加最后剩余的文本 + if (lastIndex < processedText.length) { + elements.push( + + {processedText.substring(lastIndex)} + + ); + } + + return {elements}; }; /** @@ -670,6 +755,7 @@ const MermaidView = memo(({ code }) => { * MarkdownRenderer 主组件 */ const MarkdownRenderer = ({ content }) => { + const navigation = useNavigation(); const parts = useMemo(() => parseMarkdown(content), [content]); return ( @@ -681,7 +767,7 @@ const MarkdownRenderer = ({ content }) => { if (part.type === 'mermaid') { return ; } - return ; + return ; })} ); @@ -795,6 +881,16 @@ const styles = StyleSheet.create({ marginVertical: 16, }, + // 股票代码链接 + stockCodeLink: { + color: '#F59E0B', + fontWeight: '600', + textDecorationLine: 'underline', + backgroundColor: 'rgba(245, 158, 11, 0.1)', + paddingHorizontal: 4, + borderRadius: 4, + }, + // 图表容器 chartContainer: { backgroundColor: 'rgba(99, 102, 241, 0.1)', diff --git a/MeAgent/src/screens/Agent/components/WelcomeScreen.js b/MeAgent/src/screens/Agent/components/WelcomeScreen.js index c2f13105..b045e5f8 100644 --- a/MeAgent/src/screens/Agent/components/WelcomeScreen.js +++ b/MeAgent/src/screens/Agent/components/WelcomeScreen.js @@ -95,38 +95,6 @@ const WelcomeScreen = ({ onQuickQuestion }) => { 在市场的混沌中,找到价值与风险的平衡点 - {/* Bento Grid 功能展示 */} - - - - - - - - - - - {/* 快捷问题 */} diff --git a/MeAgent/src/screens/Concepts/ConceptList.js b/MeAgent/src/screens/Concepts/ConceptList.js index 1efa692e..8fc22d12 100644 --- a/MeAgent/src/screens/Concepts/ConceptList.js +++ b/MeAgent/src/screens/Concepts/ConceptList.js @@ -630,7 +630,7 @@ const ConceptList = () => { const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); const [drillPath, setDrillPath] = useState([]); - const [viewMode, setViewMode] = useState('card'); // 'card' | 'list' + const [viewMode, setViewMode] = useState('list'); // 'card' | 'list' const [sortBy, setSortBy] = useState('change'); // 'change' | 'outbreak' const [leafConcepts, setLeafConcepts] = useState([]); // 列表模式下的叶子概念 const [leafLoading, setLeafLoading] = useState(false); @@ -1154,7 +1154,7 @@ const ConceptList = () => { ) : ( // 父分类卡片列表 - + {currentItems.map((item, index) => ( { ) )} + + handleModeChange(VIEW_MODES.STATS)}> + {({ isPressed }) => ( + viewMode === VIEW_MODES.STATS ? ( + + + + 统计 + + + ) : ( + + + + 统计 + + + ) + )} + ); @@ -479,6 +509,19 @@ const EventList = ({ navigation }) => { const keyExtractor = useCallback((item) => `event-${item.id}`, []); + // 统计视图模式 + if (viewMode === VIEW_MODES.STATS) { + return ( + + + + {renderHeader()} + + + + ); + } + // 题材视图模式 if (viewMode === VIEW_MODES.MAINLINE) { return ( diff --git a/MeAgent/src/screens/Market/MarketHot.js b/MeAgent/src/screens/Market/MarketHot.js index d05acac4..bf11d3a2 100644 --- a/MeAgent/src/screens/Market/MarketHot.js +++ b/MeAgent/src/screens/Market/MarketHot.js @@ -27,10 +27,20 @@ import { Ionicons } from '@expo/vector-icons'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import ztService from '../../services/ztService'; +import tradingDayUtils from '../../utils/tradingDayUtils'; import { gradients } from '../../theme'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); +// 获取初始日期(如果今天不是交易日,返回上一个交易日) +const getInitialDate = () => { + const today = new Date(); + if (tradingDayUtils.isTradingDay(today)) { + return today; + } + return tradingDayUtils.getPreviousTradingDay(today); +}; + // 日期格式化 const formatDisplayDate = (date) => { const d = new Date(date); @@ -73,7 +83,7 @@ const parseContinuousDays = (str) => { const MarketHot = ({ navigation }) => { const insets = useSafeAreaInsets(); - const [currentDate, setCurrentDate] = useState(new Date()); + const [currentDate, setCurrentDate] = useState(getInitialDate); const [ztData, setZtData] = useState(null); const [stats, setStats] = useState(null); const [hotSectors, setHotSectors] = useState([]); @@ -179,24 +189,14 @@ const MarketHot = ({ navigation }) => { - - navigation.navigate('TodayStats')} - p={2} - rounded="full" - bg="rgba(255,255,255,0.1)" - > - - - navigation.navigate('EventCalendar')} - p={2} - rounded="full" - bg="rgba(255,255,255,0.1)" - > - - - + navigation.navigate('EventCalendar')} + p={2} + rounded="full" + bg="rgba(255,255,255,0.1)" + > + + @@ -212,7 +212,7 @@ const MarketHot = ({ navigation }) => { - setCurrentDate(new Date())}> + setCurrentDate(getInitialDate())}> {formatDisplayDate(currentDate)} diff --git a/MeAgent/src/screens/Market/TodayStats.js b/MeAgent/src/screens/Market/TodayStats.js index 955ffcfc..15a7b169 100644 --- a/MeAgent/src/screens/Market/TodayStats.js +++ b/MeAgent/src/screens/Market/TodayStats.js @@ -302,7 +302,7 @@ const TopStockItem = ({ stock, rank }) => ( ); -const TodayStats = ({ navigation }) => { +const TodayStats = ({ navigation, isEmbedded = false }) => { const insets = useSafeAreaInsets(); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); @@ -364,7 +364,7 @@ const TodayStats = ({ navigation }) => { { /> } > - {/* 标题栏 */} - - - - navigation.goBack()} - p={2} - mr={2} - rounded="full" - bg="rgba(255,255,255,0.05)" - > - - - - - - 今日统计 + {/* 标题栏 - 仅在非嵌入模式下显示 */} + {!isEmbedded && ( + + + + navigation.goBack()} + p={2} + mr={2} + rounded="full" + bg="rgba(255,255,255,0.05)" + > + + + + + + 今日统计 + + + + + 实时 + + + + + 事件胜率 · 市场统计 · TOP排行 - - - - 实时 - - - - - 事件胜率 · 市场统计 · TOP排行 - - + + + navigation.openDrawer?.()} + p={2} + rounded="full" + bg="rgba(255,255,255,0.1)" + > + + - navigation.openDrawer?.()} - p={2} - rounded="full" - bg="rgba(255,255,255,0.1)" - > - - - - + + )} {/* 双圆环仪表盘 */} diff --git a/MeAgent/src/services/api.js b/MeAgent/src/services/api.js index aa1ffc31..a5f280c9 100644 --- a/MeAgent/src/services/api.js +++ b/MeAgent/src/services/api.js @@ -3,11 +3,16 @@ * 复用 Web 端的 API 逻辑,适配 React Native */ +import AsyncStorage from '@react-native-async-storage/async-storage'; + export const API_BASE_URL = 'https://api.valuefrontier.cn'; // 静态数据存储地址(腾讯 COS) export const API_BASE = 'https://valuefrontier-1308417363.cos-website.ap-shanghai.myqcloud.com'; +// Token 存储键名(与 authService 保持一致) +const ACCESS_TOKEN_KEY = '@auth_access_token'; + /** * 通用 API 请求函数 * @param {string} url - API 路径 @@ -21,13 +26,24 @@ export const apiRequest = async (url, options = {}) => { console.log(`[API] ${method} ${fullUrl}`); try { + // 获取存储的 access_token + const accessToken = await AsyncStorage.getItem(ACCESS_TOKEN_KEY); + + // 构建请求头 + const headers = { + 'Content-Type': 'application/json', + ...options.headers, + }; + + // 如果有 token,添加到 Authorization header + if (accessToken) { + headers['Authorization'] = `Bearer ${accessToken}`; + } + const response = await fetch(fullUrl, { ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers, - }, - // 重要:携带 Cookie 以支持 Session 认证 + headers, + // 保留 credentials 以支持 Cookie(作为备选认证方式) credentials: 'include', }); diff --git a/MeAgent/src/services/authService.js b/MeAgent/src/services/authService.js index 8f2a1cd5..c92b49b6 100644 --- a/MeAgent/src/services/authService.js +++ b/MeAgent/src/services/authService.js @@ -10,6 +10,7 @@ import { apiRequest } from './api'; const STORAGE_KEYS = { USER_INFO: '@auth_user_info', IS_LOGGED_IN: '@auth_is_logged_in', + ACCESS_TOKEN: '@auth_access_token', }; /** @@ -64,6 +65,13 @@ export const authService = { // 保存用户信息到本地存储 await AsyncStorage.setItem(STORAGE_KEYS.USER_INFO, JSON.stringify(response.user)); await AsyncStorage.setItem(STORAGE_KEYS.IS_LOGGED_IN, 'true'); + + // 保存 access_token(如果后端返回) + if (response.access_token) { + await AsyncStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, response.access_token); + console.log('[AuthService] 已保存 access_token'); + } + console.log('[AuthService] 登录成功,用户:', response.user.username); } @@ -125,11 +133,25 @@ export const authService = { try { await AsyncStorage.removeItem(STORAGE_KEYS.USER_INFO); await AsyncStorage.removeItem(STORAGE_KEYS.IS_LOGGED_IN); + await AsyncStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN); } catch (error) { console.error('[AuthService] 清除本地状态失败:', error); } }, + /** + * 获取存储的 access_token + * @returns {Promise} + */ + getAccessToken: async () => { + try { + return await AsyncStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN); + } catch (error) { + console.error('[AuthService] 读取 token 失败:', error); + return null; + } + }, + /** * 获取本地存储的用户信息 * @returns {Promise} 用户信息 diff --git a/MeAgent/src/store/slices/eventsSlice.js b/MeAgent/src/store/slices/eventsSlice.js index 436af4b7..70fa7495 100644 --- a/MeAgent/src/store/slices/eventsSlice.js +++ b/MeAgent/src/store/slices/eventsSlice.js @@ -34,7 +34,7 @@ const initialState = { popularKeywords: [], // 主线/题材事件数据 mainlineData: [], - mainlineGroupBy: 'lv1', + mainlineGroupBy: 'lv3', // 当前事件详情 currentEvent: null, // 加载状态 diff --git a/mcp_server.py b/mcp_server.py index 983acb50..b23d9ea0 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -2618,7 +2618,21 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 raise assistant_message = response.choices[0].message - logger.info(f"[Agent Stream] LLM 响应: finish_reason={response.choices[0].finish_reason}") + finish_reason = response.choices[0].finish_reason + logger.info(f"[Agent Stream] LLM 响应: finish_reason={finish_reason}") + + # 详细日志:检查 vLLM 返回的内容 + tool_calls_count = len(assistant_message.tool_calls) if assistant_message.tool_calls else 0 + logger.info(f"[Agent Stream] tool_calls 数量: {tool_calls_count}, finish_reason: {finish_reason}") + + if assistant_message.content: + content_preview = assistant_message.content[:500].replace('\n', ' ') + logger.info(f"[Agent Stream] content 预览: {content_preview}") + # 检查是否包含工具调用标签(vLLM 应该已解析,如果仍有标签说明解析失败) + if '' in assistant_message.content: + logger.warning(f"[Agent Stream] ⚠️ vLLM 未解析工具调用!finish_reason={finish_reason}") + logger.warning(f"[Agent Stream] ⚠️ 请检查 vLLM 启动参数: --tool-call-parser minimax_m2") + logger.warning(f"[Agent Stream] ⚠️ 将使用备用解析器处理...") # 获取工具调用(优先使用原生 tool_calls,其次解析文本格式) native_tool_calls = assistant_message.tool_calls or [] @@ -2633,7 +2647,8 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 '```tool_call' in content or '"tool":' in content or 'DSML' in content or # DeepSeek DSML 格式 - '|DSML|' in content # 全角竖线版本 + '|DSML|' in content or # 全角竖线版本 + '' in content # MiniMax 格式 ) if has_tool_markers: logger.info(f"[Agent Stream] 尝试从文本内容解析工具调用") @@ -3023,6 +3038,9 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 yield self._format_sse("summary_chunk", {"content": final_summary}) logger.warning("[Summary] 使用降级方案") + # 过滤掉可能残留的工具调用标签(MiniMax 等模型可能在总结中输出工具调用) + final_summary = self._filter_tool_call_tags(final_summary) + # 发送完整的总结和元数据 yield self._format_sse("summary", { "content": final_summary, @@ -3091,6 +3109,17 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 """格式化 SSE 消息""" return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n" + def _filter_tool_call_tags(self, text: str) -> str: + """过滤掉文本中的工具调用标签(总结阶段不应包含工具调用)""" + import re + # 过滤 MiniMax 格式 + text = re.sub(r'.*?', '', text, flags=re.DOTALL) + # 过滤其他格式 + text = re.sub(r'.*?', '', text, flags=re.DOTALL) + text = re.sub(r'```tool_call.*?```', '', text, flags=re.DOTALL) + text = re.sub(r'<[|\|]DSML[|\|]function_calls>.*?', '', text, flags=re.DOTALL) + return text.strip() + def _parse_text_tool_calls(self, content: str) -> List[Dict[str, Any]]: """ 解析文本格式的工具调用 @@ -3099,6 +3128,7 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 1. value 2. ```tool_call\n{"name": "xxx", "arguments": {...}}\n``` 3. DeepSeek DSML 格式: <|DSML|function_calls> <|DSML|invoke name="xxx"> <|DSML|parameter name="yyy" string="true">value + 4. MiniMax 格式: value 返回: [{"name": "tool_name", "arguments": {...}}, ...] """ @@ -3106,6 +3136,47 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 tool_calls = [] + # 格式0 (优先): MiniMax 格式 + # 002916.SZ + minimax_pattern = r'(.*?)' + minimax_matches = re.findall(minimax_pattern, content, re.DOTALL) + + for minimax_content in minimax_matches: + # 解析 invoke 标签 - 支持带引号和不带引号的 name + invoke_pattern = r'\s]+))>(.*?)' + invoke_matches = re.findall(invoke_pattern, minimax_content, re.DOTALL) + + for name_quoted, name_unquoted, params_str in invoke_matches: + func_name = name_quoted or name_unquoted + if not func_name: + continue + + arguments = {} + # 解析参数: valuevalue + param_pattern = r'\s]+))>(.*?)' + param_matches = re.findall(param_pattern, params_str, re.DOTALL) + + for pname_quoted, pname_unquoted, param_value in param_matches: + param_name = pname_quoted or pname_unquoted + if not param_name: + continue + param_value = param_value.strip() + # 尝试解析 JSON 值,否则作为字符串 + try: + arguments[param_name] = json.loads(param_value) + except: + arguments[param_name] = param_value + + tool_calls.append({ + "name": func_name, + "arguments": arguments + }) + + # 如果 MiniMax 格式解析到了工具调用,直接返回(优先级最高) + if tool_calls: + logger.info(f"[MiniMax Tool Call] 解析到 {len(tool_calls)} 个工具调用: {tool_calls}") + return tool_calls + # 格式1: 标签格式 # 例如: 300274 pattern1 = r'\s*(.*?)\s*'