From ca2c42835783627deb2cb2207cff66517c5e3fbe Mon Sep 17 00:00:00 2001 From: 0xYYBB | ZYY | Bobo <128128010+zhouyongyou@users.noreply.github.com> Date: Thu, 6 Nov 2025 00:08:23 +0800 Subject: [PATCH] fix(decision): add safe fallback when AI outputs only reasoning without JSON (#561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 (Problem) 当 AI 只输出思维链分析没有 JSON 决策时,系统会崩溃并报错: "无法找到JSON数组起始",导致整个交易周期失败,前端显示红色错误。 ## 解决方案 (Solution) 1. 添加安全回退机制 (Safe Fallback) - 当检测不到 JSON 数组时,自动生成保底决策 - Symbol: "ALL", Action: "wait" - Reasoning 包含思维链摘要(最多 240 字符) 2. 统一注释为简体中文 + 英文对照 - 关键修复 (Critical Fix) - 安全回退 (Safe Fallback) - 退而求其次 (Fallback) ## 效果 (Impact) - 修复前:系统崩溃,前端显示红色错误 "获取AI决策失败" - 修复后:系统稳定,自动进入 wait 状态,前端显示绿色成功 - 日志记录:[SafeFallback] 标记方便监控和调试 ## 设计考量 (Design Considerations) - 仅在完全找不到 JSON 时触发(区分于格式错误) - 有 JSON 但格式错误仍然报错(提示需要改进 prompt) - 保留完整思维链摘要供后续分析 - 避免隐藏真正的问题(格式错误应该暴露) ## 测试 (Testing) - ✅ 正常 JSON 输出:解析成功 - ✅ 纯思维链输出:安全回退到 wait - ✅ JSON 格式错误:继续报错(预期行为) - ✅ 编译通过 ## 监控建议 (Monitoring) 可通过日志统计 fallback 频率: ```bash grep "[SafeFallback]" logs/nofx.log | wc -l ``` 如果频率 > 5% 的交易周期,建议检查并改进 prompt 质量。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- decision/engine.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/decision/engine.go b/decision/engine.go index 598658d1..98a56f73 100644 --- a/decision/engine.go +++ b/decision/engine.go @@ -481,15 +481,15 @@ func extractDecisions(response string) ([]Decision, error) { s := removeInvisibleRunes(response) s = strings.TrimSpace(s) - // 🔧 關鍵修復:在正則匹配之前就先修復全角字符! - // 否則正則表達式 \[ 無法匹配全角的 [ + // 🔧 关键修复 (Critical Fix):在正则匹配之前就先修复全角字符! + // 否则正则表达式 \[ 无法匹配全角的 [ s = fixMissingQuotes(s) // 1) 优先从 ```json 代码块中提取 if m := reJSONFence.FindStringSubmatch(s); m != nil && len(m) > 1 { jsonContent := strings.TrimSpace(m[1]) jsonContent = compactArrayOpen(jsonContent) // 把 "[ {" 规整为 "[{" - jsonContent = fixMissingQuotes(jsonContent) // 二次修復(防止 regex 提取後還有全角) + jsonContent = fixMissingQuotes(jsonContent) // 二次修复(防止 regex 提取后还有残留全角) if err := validateJSONFormat(jsonContent); err != nil { return nil, fmt.Errorf("JSON格式验证失败: %w\nJSON内容: %s\n完整响应:\n%s", err, jsonContent, response) } @@ -500,16 +500,32 @@ func extractDecisions(response string) ([]Decision, error) { return decisions, nil } - // 2) 退而求其次:全文寻找首个对象数组 - // 注意:此時 s 已經過 fixMissingQuotes(),全角字符已轉換為半角 + // 2) 退而求其次 (Fallback):全文寻找首个对象数组 + // 注意:此时 s 已经过 fixMissingQuotes(),全角字符已转换为半角 jsonContent := strings.TrimSpace(reJSONArray.FindString(s)) if jsonContent == "" { - return nil, fmt.Errorf("无法找到JSON数组起始(已嘗試修復全角字符)\n原始響應前200字符: %s", s[:min(200, len(s))]) + // 🔧 安全回退 (Safe Fallback):当AI只输出思维链没有JSON时,生成保底决策(避免系统崩溃) + log.Printf("⚠️ [SafeFallback] AI未输出JSON决策,进入安全等待模式 (AI response without JSON, entering safe wait mode)") + + // 提取思维链摘要(最多 240 字符) + cotSummary := s + if len(cotSummary) > 240 { + cotSummary = cotSummary[:240] + "..." + } + + // 生成保底决策:所有币种进入 wait 状态 + fallbackDecision := Decision{ + Symbol: "ALL", + Action: "wait", + Reasoning: fmt.Sprintf("模型未输出结构化JSON决策,进入安全等待;摘要:%s", cotSummary), + } + + return []Decision{fallbackDecision}, nil } - // 🔧 規整格式(此時全角字符已在前面修復過) + // 🔧 规整格式(此时全角字符已在前面修复过) jsonContent = compactArrayOpen(jsonContent) - jsonContent = fixMissingQuotes(jsonContent) // 二次修復(防止 regex 提取後還有殘留全角) + jsonContent = fixMissingQuotes(jsonContent) // 二次修复(防止 regex 提取后还有残留全角) // 🔧 验证 JSON 格式(检测常见错误) if err := validateJSONFormat(jsonContent); err != nil {