Skip to content

Commit 15ee9e6

Browse files
authored
refactor(decision): use XML tags to separate reasoning from JSON decisions (#719)
* Separate the AI's thought process from the instruction JSON using XML tags. * Avoid committing encryption key related materials to Git. * Removing adaptive series prompts, awaiting subsequent modifications for compatibility.
1 parent 0f7e7cd commit 15ee9e6

File tree

6 files changed

+52
-1778
lines changed

6 files changed

+52
-1778
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,7 @@ web/.vite/
4646
eslint-*.json
4747

4848
# VS code
49-
.vscode
49+
.vscode
50+
51+
# 密钥
52+
/crypto

decision/engine.go

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ var (
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 提取思维链分析
465474
func 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

Comments
 (0)