2121 reArrayHead = regexp .MustCompile (`^\[\s*\{` )
2222 reArrayOpenSpace = regexp .MustCompile (`^\[\s+\{` )
2323 reInvisibleRunes = regexp .MustCompile ("[\u200B \u200C \u200D \uFEFF ]" )
24+
25+ // 新增:XML标签提取(支持思维链中包含任何字符)
26+ reReasoningTag = regexp .MustCompile (`(?s)<reasoning>(.*?)</reasoning>` )
27+ reDecisionTag = regexp .MustCompile (`(?s)<decision>(.*?)</decision>` )
2428)
2529
2630// PositionInfo 持仓信息
@@ -316,15 +320,20 @@ func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage in
316320 sb .WriteString ("6. 开仓金额: 建议 **≥12 USDT** (交易所最小名义价值 10 USDT + 安全边际)\n \n " )
317321
318322 // 3. 输出格式 - 动态生成
319- sb .WriteString ("#输出格式\n \n " )
320- sb .WriteString ("第一步: 思维链(纯文本)\n " )
321- sb .WriteString ("简洁分析你的思考过程\n \n " )
322- sb .WriteString ("第二步: JSON决策数组\n \n " )
323+ sb .WriteString ("# 输出格式 (严格遵守)\n \n " )
324+ sb .WriteString ("**必须使用XML标签 <reasoning> 和 <decision> 标签分隔思维链和决策JSON,避免解析错误**\n \n " )
325+ sb .WriteString ("## 格式要求\n \n " )
326+ sb .WriteString ("<reasoning>\n " )
327+ sb .WriteString ("你的思维链分析...\n " )
328+ sb .WriteString ("- 简洁分析你的思考过程 \n " )
329+ sb .WriteString ("</reasoning>\n \n " )
330+ sb .WriteString ("<decision>\n " )
323331 sb .WriteString ("```json\n [\n " )
324332 sb .WriteString (fmt .Sprintf (" {\" symbol\" : \" BTCUSDT\" , \" action\" : \" open_short\" , \" leverage\" : %d, \" position_size_usd\" : %.0f, \" stop_loss\" : 97000, \" take_profit\" : 91000, \" confidence\" : 85, \" risk_usd\" : 300, \" reasoning\" : \" 下跌趋势+MACD死叉\" },\n " , btcEthLeverage , accountEquity * 5 ))
325333 sb .WriteString (" {\" symbol\" : \" ETHUSDT\" , \" action\" : \" close_long\" , \" reasoning\" : \" 止盈离场\" }\n " )
326- sb .WriteString ("]\n ```\n \n " )
327- sb .WriteString ("字段说明:\n " )
334+ sb .WriteString ("]\n ```\n " )
335+ sb .WriteString ("</decision>\n \n " )
336+ sb .WriteString ("## 字段说明\n \n " )
328337 sb .WriteString ("- `action`: open_long | open_short | close_long | close_short | hold | wait\n " )
329338 sb .WriteString ("- `confidence`: 0-100(开仓建议≥75)\n " )
330339 sb .WriteString ("- 开仓时必填: leverage, position_size_usd, stop_loss, take_profit, confidence, risk_usd, reasoning\n \n " )
@@ -463,15 +472,26 @@ func parseFullDecisionResponse(aiResponse string, accountEquity float64, btcEthL
463472
464473// extractCoTTrace 提取思维链分析
465474func extractCoTTrace (response string ) string {
466- // 查找JSON数组的开始位置
467- jsonStart := strings .Index (response , "[" )
475+ // 方法1: 优先尝试提取 <reasoning> 标签内容
476+ if match := reReasoningTag .FindStringSubmatch (response ); match != nil && len (match ) > 1 {
477+ log .Printf ("✓ 使用 <reasoning> 标签提取思维链" )
478+ return strings .TrimSpace (match [1 ])
479+ }
480+
481+ // 方法2: 如果没有 <reasoning> 标签,但有 <decision> 标签,提取 <decision> 之前的内容
482+ if decisionIdx := strings .Index (response , "<decision>" ); decisionIdx > 0 {
483+ log .Printf ("✓ 提取 <decision> 标签之前的内容作为思维链" )
484+ return strings .TrimSpace (response [:decisionIdx ])
485+ }
468486
487+ // 方法3: 后备方案 - 查找JSON数组的开始位置
488+ jsonStart := strings .Index (response , "[" )
469489 if jsonStart > 0 {
470- // 思维链是JSON数组之前的内容
490+ log . Printf ( "⚠️ 使用旧版格式([ 字符分离)提取思维链" )
471491 return strings .TrimSpace (response [:jsonStart ])
472492 }
473493
474- // 如果找不到JSON ,整个响应都是思维链
494+ // 如果找不到任何标记 ,整个响应都是思维链
475495 return strings .TrimSpace (response )
476496}
477497
@@ -485,8 +505,22 @@ func extractDecisions(response string) ([]Decision, error) {
485505 // 否则正则表达式 \[ 无法匹配全角的 [
486506 s = fixMissingQuotes (s )
487507
508+ // 方法1: 优先尝试从 <decision> 标签中提取
509+ var jsonPart string
510+ if match := reDecisionTag .FindStringSubmatch (s ); match != nil && len (match ) > 1 {
511+ jsonPart = strings .TrimSpace (match [1 ])
512+ log .Printf ("✓ 使用 <decision> 标签提取JSON" )
513+ } else {
514+ // 后备方案:使用整个响应
515+ jsonPart = s
516+ log .Printf ("⚠️ 未找到 <decision> 标签,使用全文搜索JSON" )
517+ }
518+
519+ // 修复 jsonPart 中的全角字符
520+ jsonPart = fixMissingQuotes (jsonPart )
521+
488522 // 1) 优先从 ```json 代码块中提取
489- if m := reJSONFence .FindStringSubmatch (s ); m != nil && len (m ) > 1 {
523+ if m := reJSONFence .FindStringSubmatch (jsonPart ); m != nil && len (m ) > 1 {
490524 jsonContent := strings .TrimSpace (m [1 ])
491525 jsonContent = compactArrayOpen (jsonContent ) // 把 "[ {" 规整为 "[{"
492526 jsonContent = fixMissingQuotes (jsonContent ) // 二次修复(防止 regex 提取后还有残留全角)
@@ -501,14 +535,14 @@ func extractDecisions(response string) ([]Decision, error) {
501535 }
502536
503537 // 2) 退而求其次 (Fallback):全文寻找首个对象数组
504- // 注意:此时 s 已经过 fixMissingQuotes(),全角字符已转换为半角
505- jsonContent := strings .TrimSpace (reJSONArray .FindString (s ))
538+ // 注意:此时 jsonPart 已经过 fixMissingQuotes(),全角字符已转换为半角
539+ jsonContent := strings .TrimSpace (reJSONArray .FindString (jsonPart ))
506540 if jsonContent == "" {
507541 // 🔧 安全回退 (Safe Fallback):当AI只输出思维链没有JSON时,生成保底决策(避免系统崩溃)
508542 log .Printf ("⚠️ [SafeFallback] AI未输出JSON决策,进入安全等待模式 (AI response without JSON, entering safe wait mode)" )
509543
510544 // 提取思维链摘要(最多 240 字符)
511- cotSummary := s
545+ cotSummary := jsonPart
512546 if len (cotSummary ) > 240 {
513547 cotSummary = cotSummary [:240 ] + "..."
514548 }
0 commit comments