feat(decision): add validateJSONFormat to catch common AI errors

Adds comprehensive JSON validation before parsing to catch common AI output errors:
1. Format validation: Ensures JSON starts with [{ (decision array)
2. Range symbol detection: Rejects ~ symbols (e.g., "leverage: 3~5")
3. Thousands separator detection: Rejects commas in numbers (e.g., "98,000")
Execution order (critical for fullwidth character fix):
1. Extract JSON from response
2. fixMissingQuotes - normalize fullwidth → halfwidth 
3. validateJSONFormat - check for common errors 
4. Parse JSON
This validation layer provides early error detection and clearer error messages
for debugging AI response issues.
Added helper function:
- min(a, b int) int - returns smaller of two integers
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
ZhouYongyou
2025-11-04 23:04:22 +08:00
parent 2f0f026fdb
commit 1ca4b80add
+48 -2
View File
@@ -444,12 +444,17 @@ func extractDecisions(response string) ([]Decision, error) {
jsonContent := strings.TrimSpace(response[arrayStart : arrayEnd+1])
// 🔧 修复常见的JSON格式错误:缺少引号的字段值
// 🔧 修复全角字符和引号问题(必须在验证之前!)
// 修复常见的JSON格式错误:全角字符、缺少引号的字段值等
// 匹配: "reasoning": 内容"} 或 "reasoning": 内容} (没有引号)
// 修复为: "reasoning": "内容"}
// 使用简单的字符串扫描而不是正则表达式
jsonContent = fixMissingQuotes(jsonContent)
// 🔧 验证 JSON 格式(检测常见错误)
if err := validateJSONFormat(jsonContent); err != nil {
return nil, fmt.Errorf("JSON格式验证失败: %w\nJSON内容: %s\n完整响应:\n%s", err, jsonContent, response)
}
// 解析JSON
var decisions []Decision
if err := json.Unmarshal([]byte(jsonContent), &decisions); err != nil {
@@ -478,6 +483,47 @@ func fixMissingQuotes(jsonStr string) string {
return jsonStr
}
// validateJSONFormat 验证 JSON 格式,检测常见错误
func validateJSONFormat(jsonStr string) error {
trimmed := strings.TrimSpace(jsonStr)
// 检查是否是决策对象数组(必须以 [{ 或 [ { 开头)
if !strings.HasPrefix(trimmed, "[{") && !strings.HasPrefix(trimmed, "[ {") {
// 检查是否是纯数字/范围数组(常见错误)
if strings.HasPrefix(trimmed, "[") && !strings.Contains(trimmed[:min(20, len(trimmed))], "{") {
return fmt.Errorf("不是有效的决策数组(必须包含对象 {}),实际内容: %s", trimmed[:min(50, len(trimmed))])
}
return fmt.Errorf("JSON 必须以 [{ 开头(决策对象数组),实际: %s", trimmed[:min(20, len(trimmed))])
}
// 检查是否包含范围符号 ~(LLM 常见错误)
if strings.Contains(jsonStr, "~") {
return fmt.Errorf("JSON 中不可包含范围符号 ~,所有数字必须是精确的单一值")
}
// 检查是否包含千位分隔符(如 98,000)
// 使用简单的模式匹配:数字+逗号+3位数字
for i := 0; i < len(jsonStr)-4; i++ {
if jsonStr[i] >= '0' && jsonStr[i] <= '9' &&
jsonStr[i+1] == ',' &&
jsonStr[i+2] >= '0' && jsonStr[i+2] <= '9' &&
jsonStr[i+3] >= '0' && jsonStr[i+3] <= '9' &&
jsonStr[i+4] >= '0' && jsonStr[i+4] <= '9' {
return fmt.Errorf("JSON 数字不可包含千位分隔符逗号,发现: %s", jsonStr[i:min(i+10, len(jsonStr))])
}
}
return nil
}
// min 返回两个整数中的较小值
func min(a, b int) int {
if a < b {
return a
}
return b
}
// validateDecisions 验证所有决策(需要账户信息和杠杆配置)
func validateDecisions(decisions []Decision, accountEquity float64, btcEthLeverage, altcoinLeverage int) error {
for i, decision := range decisions {